@hankchen/canvas-browser-renderer 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -9,6 +9,10 @@ import { RenderBackend, Border, TextDrawOptions, Canvas, TextLayoutPolicies } fr
9
9
  * 图片源经 setImage 预载回写(远程物化在工单 04),未注册的 src 只画盒不绘制;
10
10
  * - drawText:布局层只给纯对齐锚点,基线差由本端以 TextMetrics 消化(ADR-0003 同款——
11
11
  * PHP 的 GD 基线魔数 round(fontSize*0.1) 不移植);字体经 fonts.ts 的判定与注册缝。
12
+ *
13
+ * 可选能力口(image-fit 工单 05):imageIntrinsicSize 供源图内在尺寸,渲染契约臂
14
+ * 据此算 contain 收缩矩形(cover.ts containRect 是其图层无关几何镜像)——后端绘制
15
+ * 面零改动,五原语契约冻结(ADR 0023)。
12
16
  */
13
17
 
14
18
  /** 可绘制图片源:浏览器图片元素/位图,或结构等价的测试替身(须给得出宽高) */
@@ -64,6 +68,17 @@ declare class Canvas2DBackend implements RenderBackend, ViewportAwareBackend {
64
68
  drawRect(x: number, y: number, width: number, height: number, bgColor: string | null, border: Border): void;
65
69
  /** cover 裁切至 width×height 后放置于 (x, y);宽或高 ≤0 跳过(PHP drawImage 同门) */
66
70
  drawImage(src: string, x: number, y: number, width: number, height: number): void;
71
+ /**
72
+ * 源图内在尺寸度量(渲染契约可选能力口,image-fit 工单 05):contain 计划按
73
+ * 此收缩目标矩形,查询键与 drawImage 同一 src 引用、同一次物化回写的 images
74
+ * 表(预览与导出 forkWith 共享,零参数穿透)。尺寸取 naturalWidth/Height
75
+ * 优先(与 drawImage 同门);未注册/非法尺寸返回 null,渲染臂按 cover 降级
76
+ * (PHP getimagesize 失败同门)。查询口非绘制原语,五原语契约冻结不动
77
+ */
78
+ imageIntrinsicSize(src: string): {
79
+ width: number;
80
+ height: number;
81
+ } | null;
67
82
  /**
68
83
  * 单行文本,(x, y) 为对齐语义锚点:水平偏移 = 真实文本宽度(measureText,与
69
84
  * 布局启发式度量无关)× 对齐语义;垂直基线 = 锚点 + 字体 metrics 偏移
package/dist/index.js CHANGED
@@ -128,6 +128,21 @@ var Canvas2DBackend = class _Canvas2DBackend {
128
128
  height
129
129
  );
130
130
  }
131
+ /**
132
+ * 源图内在尺寸度量(渲染契约可选能力口,image-fit 工单 05):contain 计划按
133
+ * 此收缩目标矩形,查询键与 drawImage 同一 src 引用、同一次物化回写的 images
134
+ * 表(预览与导出 forkWith 共享,零参数穿透)。尺寸取 naturalWidth/Height
135
+ * 优先(与 drawImage 同门);未注册/非法尺寸返回 null,渲染臂按 cover 降级
136
+ * (PHP getimagesize 失败同门)。查询口非绘制原语,五原语契约冻结不动
137
+ */
138
+ imageIntrinsicSize(src) {
139
+ const image = this.images.get(src);
140
+ if (!image) return null;
141
+ const width = naturalWidth(image);
142
+ const height = naturalHeight(image);
143
+ if (width <= 0 || height <= 0) return null;
144
+ return { width, height };
145
+ }
131
146
  /**
132
147
  * 单行文本,(x, y) 为对齐语义锚点:水平偏移 = 真实文本宽度(measureText,与
133
148
  * 布局启发式度量无关)× 对齐语义;垂直基线 = 锚点 + 字体 metrics 偏移
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cover.ts","../src/fonts.ts","../src/canvas2d-backend.ts","../src/qr.ts","../src/materializer.ts","../src/sha256.ts","../src/markers.ts","../src/exportPng.ts","../src/png.ts","../src/index.ts"],"sourcesContent":["/**\n * cover 缩放裁切几何:等比缩放至铺满目标盒,溢出居中裁掉(PHP intervention cover /\n * go-canvas image-renderer cover 同款)。明确放弃逐像素复刻 GD/浏览器 rasterizer\n * (spec Out of Scope:布局级一致、像素不求等)——本模块只算\"源图上裁哪个窗\",\n * 缩放插值交给 drawImage 的原生光栅化。纯几何,无 DOM 类型(红线 2)。\n */\n\nexport interface CropWindow {\n sx: number\n sy: number\n sw: number\n sh: number\n}\n\n/**\n * 计算源图 srcW×srcH 铺满 dstW×dstH 的居中裁切窗。调用方保证 dstW/dstH 为正;\n * 裁切窗与居中偏移整型化向零截断;极端宽高比下截断可得 0 宽窗:钳到 ≥1,\n * 宁可纵横比略让也不输出空图\n */\nexport function coverCrop(srcW: number, srcH: number, dstW: number, dstH: number): CropWindow {\n const scale = Math.max(dstW / srcW, dstH / srcH)\n let cropW = Math.trunc(dstW / scale)\n let cropH = Math.trunc(dstH / scale)\n if (cropW < 1) cropW = 1\n if (cropH < 1) cropH = 1\n const sx = Math.trunc((srcW - cropW) / 2)\n const sy = Math.trunc((srcH - cropH) / 2)\n return { sx, sy, sw: cropW, sh: cropH }\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * 字体契约的渲染端消化:内置默认字体判定/简写 + URL 字体的 FontFace 物化。\n * 语义镜像 PHP ImageRenderer::drawText(`$fontFile !== '' && !is_numeric($fontFile)`\n * 才走字体文件,否则 v4 内置默认字体)与 go-canvas typography.IsBuiltinFont——\n * 空串/纯数字字体 id 是旧库 GD 内置字体的血统,浏览器端对应内置默认字体 =\n * 系统无衬线族。本文件是渲染包内唯一触 DOM 运行时 API 的地方(FontFace /\n * document.fonts;Canvas2D 类型在 canvas2d-backend.ts 局部引入),包 tsconfig\n * 仍无 DOM lib(红线 2)。\n */\n\n/** 内置默认字体判定:空串/纯数字字体引用(PHP is_numeric 同门:旧库 GD 内置字体 id) */\nexport function isBuiltinFontRef(font: string): boolean {\n if (font === '') return true\n // 纯数字:可选符号/小数/指数(is_numeric 的实用子集——旧库只用 '1'-'5' 字体 id)\n return /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/.test(font.trim())\n}\n\n/** 内置默认字体的 ctx.font 简写:系统无衬线族按字号取用 */\nexport function builtinFontShorthand(fontSize: number): string {\n return `${fontSize}px sans-serif`\n}\n\n/**\n * font-family 的 CSS 值(工单 11 textarea overlay 用):内置默认字体 = 系统无衬线\n * 族;URL = 注册族名(与 loadCanvasFont 同一 fnv-1a 派生,注册后即命中)尾部附\n * sans-serif 兜底——未注册/未加载时回落系统无衬线,与 canvas 侧未注册 URL 落内置\n * 默认简写的行为同门。纯字符串函数,无 DOM。\n */\nexport function canvasFontCssFamily(font: string): string {\n if (isBuiltinFontRef(font)) return 'sans-serif'\n return `${fontFamilyFor(font)}, sans-serif`\n}\n\n/** ctx.font 可用的注册族名:由字体 URL 确定性派生(fnv-1a 32 位),CSS 安全 */\nfunction fontFamilyFor(font: string): string {\n let hash = 0x811c9dc5\n for (let i = 0; i < font.length; i++) {\n hash ^= font.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193)\n }\n return `canvas-font-${(hash >>> 0).toString(36)}`\n}\n\n// URL → 注册完成态的缓存:同一字体只注册一次(PHP/Go 侧解析缓存的对位物)\nconst fontRegistry = new Map<string, Promise<string>>()\n\n/**\n * URL 字体物化:经 FontFace 注册进 document.fonts,resolve 为 ctx.font 可用的族名。\n * 加载失败(离线/404/解析失败)时 reject——调用方决定降级(预览哲学:占位 +\n * 内置默认字体兜底,工单 04 补失败态标识)\n */\nexport function loadCanvasFont(font: string): Promise<string> {\n const cached = fontRegistry.get(font)\n if (cached) return cached\n\n const family = fontFamilyFor(font)\n const loading = (async () => {\n // CSS 字符串内的引号转义(graph 引用来自宿主数据,不假设 URL 干净)\n const source = `url(\"${font.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\")`\n const face = new FontFace(family, source)\n await face.load()\n // lib.dom 的 FontFaceSet 声明缺 add(运行时存在),按 Set 结构收窄\n ;(document.fonts as unknown as Set<FontFace>).add(face)\n return family\n })()\n // 失败不缓存:下一次渲染可重试(临时网络故障自愈)\n loading.catch(() => fontRegistry.delete(font))\n fontRegistry.set(font, loading)\n return loading\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * Canvas2D 五原语后端。按包红线整体不开 DOM lib,仅本文件局部引入\n * CanvasRenderingContext2D 类型(AGENTS.md 红线 2)。\n *\n * 内容绘制语义(工单 03):\n * - drawImage:cover 等比缩放居中裁切(cover.ts),宽或高 ≤0 跳过(PHP 同门);\n * 图片源经 setImage 预载回写(远程物化在工单 04),未注册的 src 只画盒不绘制;\n * - drawText:布局层只给纯对齐锚点,基线差由本端以 TextMetrics 消化(ADR-0003 同款——\n * PHP 的 GD 基线魔数 round(fontSize*0.1) 不移植);字体经 fonts.ts 的判定与注册缝。\n */\nimport type { Border, BorderSide, RenderBackend, TextDrawOptions } from '@hankchen/canvas'\n\nimport { coverCrop } from './cover'\nimport { builtinFontShorthand, isBuiltinFontRef } from './fonts'\n\n/** 可绘制图片源:浏览器图片元素/位图,或结构等价的测试替身(须给得出宽高) */\nexport interface DrawableImage {\n readonly width: number\n readonly height: number\n}\n\n/**\n * 预览视口变换(工单 05):begin 清屏后施加到 ctx 的 dpr×zoom×相机变换。\n * x/y 由编辑器会话给出(已对齐物理像素),本端只做与 dpr 的乘法合成。\n */\nexport interface PreviewViewportTransform {\n dpr: number\n zoom: number\n x: number\n y: number\n}\n\n/** 视口感知后端能力:编辑器会话在内容重绘前经此同步预览视口(可选能力口) */\nexport interface ViewportAwareBackend {\n setViewportTransform(transform: PreviewViewportTransform | null): void\n}\n\n/**\n * 把预览视口变换施加到 2D 上下文(内容层 begin 与宿主覆盖层共用同一几何,\n * 保证两层在缩放/平移下不错位)。调用方需先自行复位变换并清屏。\n */\nexport function applyViewportTransform(\n ctx: CanvasRenderingContext2D,\n transform: PreviewViewportTransform,\n): void {\n const scale = transform.dpr * transform.zoom\n // 场景整数坐标经 transform 承担缩放与相机(绘制代码零改动);平移已由\n // 会话侧对齐物理像素,此处直接乘 dpr×zoom 落到设备像素网格。\n ctx.setTransform(scale, 0, 0, scale, -transform.x * scale, -transform.y * scale)\n}\n\nexport class Canvas2DBackend implements RenderBackend, ViewportAwareBackend {\n private readonly ctx: CanvasRenderingContext2D\n\n /** 物化回写面:src → 已加载的图片源(工单 04 的物化器落点,渲染前由宿主/物化器填充) */\n private readonly images = new Map<string, DrawableImage>()\n\n /** 字体引用 → 已注册族名(loadCanvasFont 的回写面;未注册 URL 回落内置默认) */\n private readonly fontFamilies = new Map<string, string>()\n\n /** 预览视口(工单 05):null = 恒等变换(如导出走 begin(w,h) 全幅语义) */\n private viewportTransform: PreviewViewportTransform | null = null\n\n constructor(ctx: CanvasRenderingContext2D) {\n this.ctx = ctx\n }\n\n /** 预载回写:注册 src 对应的图片源后 drawImage 才会绘制 */\n setImage(src: string, image: DrawableImage): void {\n this.images.set(src, image)\n }\n\n /** 字体物化回写:注册字体引用对应的 ctx.font 族名 */\n setFontFamily(font: string, family: string): void {\n this.fontFamilies.set(font, family)\n }\n\n /**\n * 派生同资源的导出后端(工单 13):把本后端已物化的图片/字体注册复制到新\n * 渲染面——导出画布不经预览视口(恒等变换全幅渲染),但资源沿用编辑面的\n * 物化结果,不重新装载。\n */\n forkWith(ctx: CanvasRenderingContext2D): Canvas2DBackend {\n const fork = new Canvas2DBackend(ctx)\n for (const [src, image] of this.images) fork.images.set(src, image)\n for (const [font, family] of this.fontFamilies) fork.fontFamilies.set(font, family)\n return fork\n }\n\n setViewportTransform(transform: PreviewViewportTransform | null): void {\n this.viewportTransform = transform\n }\n\n begin(_width: number, _height: number): void {\n // 新建渲染面语义:复用同一面时清残留变换与上帧像素。物理缓冲 =\n // css × dpr(工单 05 起与画布整数像素语义分离),按缓冲实尺寸清。\n this.ctx.setTransform(1, 0, 0, 1, 0, 0)\n this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height)\n\n if (this.viewportTransform) applyViewportTransform(this.ctx, this.viewportTransform)\n }\n\n end(): void {\n // 预览就地绘制,无产物返回;导出(工单 13)经画布 toBlob 走\n }\n\n /**\n * 镜像 PHP ImageRenderer::drawRect:背景填充盒(null/空串跳过)+\n * 四边边框沿盒边描线(线宽居中于路径,与 intervention drawLine 同语义)。\n */\n drawRect(\n x: number,\n y: number,\n width: number,\n height: number,\n bgColor: string | null,\n border: Border,\n ): void {\n const ctx = this.ctx\n if (bgColor !== null && bgColor !== '') {\n ctx.fillStyle = bgColor\n ctx.fillRect(x, y, width, height)\n }\n\n if (border.top) this.strokeLine(x, y, x + width, y, border.top)\n if (border.bottom) this.strokeLine(x, y + height, x + width, y + height, border.bottom)\n if (border.left) this.strokeLine(x, y, x, y + height, border.left)\n if (border.right) this.strokeLine(x + width, y, x + width, y + height, border.right)\n }\n\n /** cover 裁切至 width×height 后放置于 (x, y);宽或高 ≤0 跳过(PHP drawImage 同门) */\n drawImage(src: string, x: number, y: number, width: number, height: number): void {\n if (width <= 0 || height <= 0) return\n\n const image = this.images.get(src)\n if (!image) return // 未物化/未加载:只画盒的占位语义(工单 04 补失败态)\n\n const srcW = naturalWidth(image)\n const srcH = naturalHeight(image)\n if (srcW <= 0 || srcH <= 0) return\n\n const crop = coverCrop(srcW, srcH, width, height)\n this.ctx.drawImage(\n image as CanvasImageSource,\n crop.sx,\n crop.sy,\n crop.sw,\n crop.sh,\n x,\n y,\n width,\n height,\n )\n }\n\n /**\n * 单行文本,(x, y) 为对齐语义锚点:水平偏移 = 真实文本宽度(measureText,与\n * 布局启发式度量无关)× 对齐语义;垂直基线 = 锚点 + 字体 metrics 偏移\n * (top: +ascent / center: +(ascent-descent)/2 / bottom: -descent,未知取值归 top)。\n * 空行零副作用(PHP 同款空串守卫,模板仍对空行分派)。角度为度,正值视觉逆时针\n * (GD angle 惯例;canvas y 轴向下,rotate 取负)。\n */\n drawText(line: string, x: number, y: number, options: TextDrawOptions): void {\n if (line === '') return\n\n const ctx = this.ctx\n ctx.font = this.fontShorthand(options.font, options.fontSize)\n ctx.fillStyle = options.fontColor\n\n const metrics = ctx.measureText(line)\n const ascent = metrics.fontBoundingBoxAscent ?? metrics.actualBoundingBoxAscent\n const descent = metrics.fontBoundingBoxDescent ?? metrics.actualBoundingBoxDescent\n\n // 水平:笔起点相对锚点的偏移(left: 0 / center: -宽一半 / right: -宽,未知归 left)\n let penX = 0\n if (options.horizontalAlign === 'center') penX = -metrics.width / 2\n else if (options.horizontalAlign === 'right') penX = -metrics.width\n\n // 垂直:基线相对锚点的偏移,由字体 metrics 消化基线差\n let baselineY = ascent\n if (options.verticalAlign === 'center') baselineY = (ascent - descent) / 2\n else if (options.verticalAlign === 'bottom') baselineY = -descent\n\n if (options.angle === 0) {\n ctx.fillText(line, x + penX, y + baselineY)\n return\n }\n\n ctx.save()\n ctx.translate(x, y)\n ctx.rotate((-options.angle * Math.PI) / 180)\n ctx.fillText(line, penX, baselineY)\n ctx.restore()\n }\n\n /** ctx.font 简写:内置引用(空串/纯数字)或未注册 URL → 系统无衬线默认 */\n private fontShorthand(font: string, fontSize: number): string {\n if (isBuiltinFontRef(font)) return builtinFontShorthand(fontSize)\n const family = this.fontFamilies.get(font)\n return family ? `${fontSize}px \"${family}\"` : builtinFontShorthand(fontSize)\n }\n\n private strokeLine(x1: number, y1: number, x2: number, y2: number, side: BorderSide): void {\n const ctx = this.ctx\n ctx.beginPath()\n ctx.moveTo(x1, y1)\n ctx.lineTo(x2, y2)\n ctx.strokeStyle = side.color\n ctx.lineWidth = side.width\n ctx.stroke()\n }\n}\n\n/** 图片源尺寸:优先 HTMLImageElement 的固有尺寸(SVG 等内容尺寸可能不同于布局尺寸) */\nfunction naturalWidth(image: DrawableImage): number {\n const natural = (image as Partial<HTMLImageElement>).naturalWidth\n return typeof natural === 'number' && natural > 0 ? natural : image.width\n}\n\nfunction naturalHeight(image: DrawableImage): number {\n const natural = (image as Partial<HTMLImageElement>).naturalHeight\n return typeof natural === 'number' && natural > 0 ? natural : image.height\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * QR 物化的生成端:固定选项与 php-canvas-next ResourceResolver::materializeQrCode\n * 逐项对齐(endroid/qr-code:Encoding UTF-8、ErrorCorrectionLevel::High、margin 0、\n * 黑白双色、RoundBlockSizeMode::None)。与 PHP 的差异点:PHP 按图层宽度就地生成\n * 位图(缓存键含宽度),浏览器端按固定 scale 生成一次、由渲染原语 drawImage 缩放\n * 到盒宽——同内容跨图层(不同宽)只生成一次。\n *\n * qrcode 包运行时是 CJS 同构包(Node 走 pngjs、浏览器走 canvas),类型为具名导出。\n * 本文件只产 dataURL 与模块矩阵,不触 DOM 运行时 API(Image 装载归 materializer)。\n */\nimport { create as createQrCode, toDataURL as qrToDataURL } from 'qrcode'\n\n/** 固定选项(契约):纠错 High、无静区、黑白;scale 只影响产物清晰度(终图随盒缩放) */\nexport const QR_FIXED_OPTIONS = {\n errorCorrectionLevel: 'H',\n margin: 0,\n scale: 4,\n color: { dark: '#000000ff', light: '#ffffffff' },\n} as const\n\n/** QR 符号的模块矩阵视图(测试取样缝:点位断言不经过 PNG 解码) */\nexport interface QrModuleMatrix {\n readonly size: number\n isDark(row: number, col: number): boolean\n}\n\n/** 同步生成模块矩阵(固定选项);UTF-8 串按字节模式编码(endroid Encoding('UTF-8') 同语义) */\nexport function qrModuleMatrix(value: string): QrModuleMatrix {\n const qr = createQrCode(value, { errorCorrectionLevel: QR_FIXED_OPTIONS.errorCorrectionLevel })\n const { size, data } = qr.modules\n return {\n size,\n isDark: (row, col) => data[row * size + col] === 1,\n }\n}\n\n/** 生成 PNG dataURL(固定选项);交给 Image 装载后由渲染原语缩放到盒宽 */\nexport function qrDataUrl(value: string): Promise<string> {\n // QR_FIXED_OPTIONS 的形状即 toDataURL 的渲染选项(errorCorrectionLevel/margin/scale/color)\n return qrToDataURL(value, QR_FIXED_OPTIONS)\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * 物化状态机(工单 04):渲染前把图层的资源引用(图片 / 字体 URL)与 QR 内容\n * 准备成可绘资源,状态 pending → done/failed。\n *\n * 红线(图层 setter 禁 I/O 的编辑器延伸):物化状态只落本状态机的资源切片,永不写\n * graph——切片形状即未来内核 store ui 分支的 `ui.resources`(工单 05+ 建 store 时\n * 由订阅桥接整体替换),物化完成只触发重绘、不产生文档 patch。\n *\n * 缓存键 = sha256(完整引用)(图片/字体取 URL 原串,QR 内容加 `qr:` 命名空间),\n * 修正 PHP ResourceResolver 以 URL basename 作键的同名碰撞旧债。同引用跨图层\n * 去重:状态已有 pending/done 的键不再调度,failed 留待下次 materialize 重试\n * (临时网络故障自愈,与 fonts.ts 的失败不缓存同哲学)。\n *\n * 触发集合:非空引用全部装载。与 PHP 的差异点:PHP 对非 URL 引用\"本地路径直接\n * 使用\"(ResourceResolver 跳过下载),浏览器无文件系统对位——相对路径就是同源\n * URL,仍需经 Image 装载(工单 03 预载先例);跨域语义(crossOrigin/代理)只作用\n * 于绝对 http(s) 引用。\n *\n * 跨域:默认图片装载强制 crossOrigin='anonymous'——无 CORS 头的源在物化阶段即\n * 失败(进 failed 态、画失败标识),不留到导出 toBlob 才抛 SecurityError;宿主经\n * `imageProxy` 注入改写(对应 PHP DownloaderInterface 注入语义),错误消息保持\n * 原始引用以便三端对照 PHP `could not get remote file(<url>)`。\n *\n * 触发:宿主在文档变更后调 materialize(doc)(store 建立后由 doc patch 订阅驱动)。\n * 导出前的 pending 排空闸归工单 13。\n */\nimport { forEachLayerBox, qrImageSrc } from '@hankchen/canvas'\nimport type { Canvas as CanvasDoc, Layer } from '@hankchen/canvas'\n\nimport { type Canvas2DBackend, type DrawableImage } from './canvas2d-backend'\nimport { isBuiltinFontRef, loadCanvasFont } from './fonts'\nimport { qrDataUrl } from './qr'\nimport { sha256Hex } from './sha256'\n\nexport type ResourceStatus = 'pending' | 'done' | 'failed'\n\n/** 单条资源状态;error 仅 failed 时携带(加载器的错误消息) */\nexport interface ResourceEntry {\n readonly status: ResourceStatus\n readonly error?: string\n}\n\n/** 资源状态切片:key = 资源缓存键。整体替换语义,直接对接未来 store 的 ui 分支 */\nexport type ResourceState = Readonly<Record<string, ResourceEntry>>\n\n/** 缓存键:sha256(完整引用)——不继承 PHP basename 旧债(同名不同 URL 不碰撞);派生经键 memo(同引用不重复散列) */\nexport const imageResourceKey = (src: string): string => memoSha256Hex(src)\nexport const fontResourceKey = (font: string): string => memoSha256Hex(font)\n/** QR 内容加命名空间后散列:内容恰为 URL 的 QR 与图片键域隔离 */\nexport const qrResourceKey = (value: string): string => memoSha256Hex(`qr:${value}`)\n\n/**\n * 跨域代理注入点(对应 PHP DownloaderInterface):把 http(s) 资源 URL 改写为代理\n * URL。data:/相对路径不经代理(本就同源/内联)。未配置时原样通过。\n */\nexport function withImageProxy(\n url: string,\n proxy: ((url: string) => string) | undefined,\n): string {\n if (!proxy || !/^https?:\\/\\//i.test(url)) return url\n return proxy(url)\n}\n\n/** 三类资源的装载器(注入缝):测试注桩,宿主可替换缓存/代理策略 */\nexport interface ResourceLoaders {\n /** 图片 URL → 可绘制源(默认:Image + crossOrigin=anonymous + 可选代理) */\n readonly loadImage: (url: string) => Promise<DrawableImage>\n /** 字体 URL → ctx.font 可用的族名(默认:loadCanvasFont 的 FontFace 注册) */\n readonly loadFont: (font: string) => Promise<string>\n /** QR 内容 → 可绘制源(默认:固定选项生成 PNG dataURL 后经 Image 装载) */\n readonly loadQr: (value: string) => Promise<DrawableImage>\n}\n\nexport interface MaterializerOptions {\n /** 覆盖默认浏览器装载器(Node 测试注桩 / 宿主自定义) */\n readonly loaders?: ResourceLoaders\n /** 跨域代理注入点:默认图片装载器把 http(s) 引用改写为代理 URL */\n readonly imageProxy?: (url: string) => string\n /** 同时在途的装载上限(默认 6) */\n readonly concurrency?: number\n}\n\ninterface ResourceRef {\n readonly key: string\n readonly kind: 'image' | 'font' | 'qr'\n readonly ref: string\n}\n\n/**\n * 图层 → 资源引用的唯一分派(物化收集与覆盖层标识共用):\n * 图片取非空 src、字体取非内置引用(空串/纯数字 = 渲染端内置默认)、QR 取非空内容。\n * 无资源引用的图层返回 null。\n */\nexport function resourceRefOf(layer: Layer): ResourceRef | null {\n if (layer.type === 'ImageLayer' && layer.src !== null) {\n return { key: imageResourceKey(layer.src), kind: 'image', ref: layer.src }\n }\n if (layer.type === 'TextLayer' && !isBuiltinFontRef(layer.font)) {\n return { key: fontResourceKey(layer.font), kind: 'font', ref: layer.font }\n }\n if (layer.type === 'QrCodeLayer' && layer.value !== '') {\n return { key: qrResourceKey(layer.value), kind: 'qr', ref: layer.value }\n }\n return null\n}\n\nconst DEFAULT_CONCURRENCY = 6\n\n/**\n * 键 memo 原语(canvas-web-render-perf 工单 02):同输入串不重复散列,容量有界\n * (FIFO 逐最旧)。逐键 doc 变更驱动 materialize 全树重扫,data URL 大图的 sha256\n * 是逐键开销链的大头——文本编辑不碰资源,键派生必须 O(1) 走缓存。产物与底层函数\n * 逐字节一致(resource-keys 测试 oracle 复核)。\n */\nexport function createMemoizedKeyHash(\n load: (input: string) => string,\n capacity = 512,\n): (input: string) => string {\n const cache = new Map<string, string>()\n return (input: string): string => {\n const hit = cache.get(input)\n if (hit !== undefined) return hit\n const key = load(input)\n if (cache.size >= capacity) {\n const oldest = cache.keys().next().value\n if (oldest !== undefined) cache.delete(oldest)\n }\n cache.set(input, key)\n return key\n }\n}\n\nconst memoSha256Hex = createMemoizedKeyHash(sha256Hex)\n\nexport class Materializer {\n private readonly backend: Canvas2DBackend\n private readonly loaders: ResourceLoaders\n private readonly maxConcurrent: number\n\n private readonly entries = new Map<string, ResourceEntry>()\n private readonly listeners = new Set<(state: ResourceState) => void>()\n private readonly settleResolvers: Array<(state: ResourceState) => void> = []\n private readonly queue: ResourceRef[] = []\n private active = 0\n\n constructor(backend: Canvas2DBackend, options: MaterializerOptions = {}) {\n this.backend = backend\n this.loaders = options.loaders ?? createBrowserLoaders({ imageProxy: options.imageProxy })\n this.maxConcurrent = Math.max(options.concurrency ?? DEFAULT_CONCURRENCY, 1)\n }\n\n /** 资源状态快照(ui.resources 切片;每次变更整体替换,不可变) */\n get state(): ResourceState {\n return Object.fromEntries(this.entries)\n }\n\n /** 在途装载条数(playground 提示/将来导出闸消费) */\n get pendingCount(): number {\n let count = 0\n for (const entry of this.entries.values()) {\n if (entry.status === 'pending') count++\n }\n return count\n }\n\n /**\n * 扫描文档收集资源引用(含表格下钻,复用渲染模板的同一遍历),调度未决资源。\n * 幂等:pending/done 的键跳过(同 URL 跨图层只物化一次),failed 重新调度。\n * 快照语义(工单 15):文档不再引用的键当场剪除——打开新文档/删层后旧资源\n * 条目不残留(覆盖层标识与宿主\"失败 N 项\"读数只对当前文档有意义)。\n */\n materialize(doc: CanvasDoc): void {\n const referenced = new Map<string, ResourceRef>()\n // 画布背景图(canvas-background 工单 04):图层树之外的第二收集源——\n // 渲染端 paintBackground 递原始引用给 drawImage,未装载即静默不画,\n // 导出排空闸也据此等待背景图就绪。背景色非资源,零条目。\n const backgroundImage = doc.background?.image\n if (backgroundImage) {\n const key = imageResourceKey(backgroundImage)\n referenced.set(key, { key, kind: 'image', ref: backgroundImage })\n }\n forEachLayerBox(doc, (layer) => {\n const ref = resourceRefOf(layer)\n if (ref === null || referenced.has(ref.key)) return\n referenced.set(ref.key, ref)\n })\n\n let pruned = false\n for (const key of [...this.entries.keys()]) {\n if (!referenced.has(key)) {\n this.entries.delete(key)\n pruned = true\n }\n }\n\n // 已决跳过(failed 可重试)\n const collected = new Map<string, ResourceRef>()\n for (const ref of referenced.values()) {\n const existing = this.entries.get(ref.key)\n if (!existing || existing.status === 'failed') collected.set(ref.key, ref)\n }\n\n if (collected.size === 0) {\n if (pruned) this.notify()\n return\n }\n\n for (const ref of collected.values()) {\n this.entries.set(ref.key, { status: 'pending' })\n this.queue.push(ref)\n }\n this.notify()\n this.pump()\n }\n\n /** 订阅状态变更(整体快照推送);返回退订函数 */\n subscribe(listener: (state: ResourceState) => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /**\n * 全量物化排空闸(工单 13,PNG 导出前置):在途装载全部落定后 resolve,\n * 携带终态快照(failed 不算在途——预览语义按占位出图,宿主可据快照提示)。\n * 闸在 pending 首次归零时收口:其后新调度的装载归下一次 await 覆盖。\n */\n whenSettled(): Promise<ResourceState> {\n if (this.pendingCount === 0) return Promise.resolve(this.state)\n return new Promise((resolve) => {\n this.settleResolvers.push(resolve)\n })\n }\n\n private notify(): void {\n const state = this.state\n for (const listener of this.listeners) listener(state)\n if (this.pendingCount === 0 && this.settleResolvers.length > 0) {\n const resolvers = this.settleResolvers.splice(0)\n for (const resolve of resolvers) resolve(state)\n }\n }\n\n /** 并发上限内的队列泵:完成一个补位一个 */\n private pump(): void {\n while (this.active < this.maxConcurrent && this.queue.length > 0) {\n const ref = this.queue.shift()!\n this.active++\n void this.load(ref).finally(() => {\n this.active--\n this.pump()\n })\n }\n }\n\n private async load(ref: ResourceRef): Promise<void> {\n let entry: ResourceEntry\n try {\n if (ref.kind === 'font') {\n this.backend.setFontFamily(ref.ref, await this.loaders.loadFont(ref.ref))\n } else if (ref.kind === 'qr') {\n this.backend.setImage(qrImageSrc(ref.ref), await this.loaders.loadQr(ref.ref))\n } else {\n this.backend.setImage(ref.ref, await this.loaders.loadImage(ref.ref))\n }\n entry = { status: 'done' }\n } catch (error) {\n entry = {\n status: 'failed',\n error: error instanceof Error ? error.message : String(error),\n }\n }\n // 装载在途时文档可能已切走(键被剪除):死键不回写(不复活僵尸条目、\n // 不推空通知);回写面已就绪,重开同一文档时重装载依赖浏览器 HTTP 缓存\n if (this.entries.has(ref.key)) {\n this.entries.set(ref.key, entry)\n this.notify()\n }\n }\n}\n\n/** 默认浏览器装载器:图片经 Image + crossOrigin(代理注入点),字体经 FontFace */\nexport function createBrowserLoaders(\n options: { imageProxy?: (url: string) => string } = {},\n): ResourceLoaders {\n return {\n // 错误消息用原始引用(三端对照 PHP 消息格式),代理 URL 只用于装载\n loadImage: (url) => loadImageElement(withImageProxy(url, options.imageProxy), url),\n loadFont: (font) => loadCanvasFont(font),\n loadQr: async (value) => loadImageElement(await qrDataUrl(value), `qr:${value}`),\n }\n}\n\n/**\n * 图片装载:crossOrigin='anonymous' 物化期即暴露跨域失败(污染画布的 toBlob\n * SecurityError 不留到导出)。失败消息用调用方的原始引用(PHP `could not get\n * remote file(<url>)` 同格式,方便三端对照),不掺代理改写后的装载地址。\n */\nfunction loadImageElement(src: string, ref: string): Promise<DrawableImage> {\n return new Promise((resolve, reject) => {\n const image = new Image()\n image.crossOrigin = 'anonymous'\n image.onerror = () => reject(new Error(`could not get remote file(${ref})`))\n image.onload = () => resolve(image)\n image.src = src\n })\n}\n","/**\n * 同步 SHA-256(FIPS 180-4):资源缓存键的散列原语。\n *\n * 为什么手写:物化状态机需要同步派生缓存键(渲染/键比较路径不能 await),\n * Web Crypto 的 subtle.digest 只有异步口;而编辑器物化散列不值得为此引依赖。\n * 测试以 node:crypto 为独立 oracle 逐值锁定。\n *\n * TextEncoder 是宿主全局(浏览器 evergreen 与 Node 皆有),本包 tsconfig 无任何\n * lib 注入,这里给最小结构声明以自持类型(不依赖别的文件碰巧拖进的全局)。\n */\n\ninterface TextEncoderLike {\n encode(input: string): Uint8Array\n}\n\ndeclare const TextEncoder: new () => TextEncoderLike\n\nconst K = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,\n])\n\nfunction rotr(x: number, n: number): number {\n return ((x >>> n) | (x << (32 - n)))\n}\n\n/** UTF-8 串的 SHA-256 摘要,64 位小写十六进制 */\nexport function sha256Hex(input: string): string {\n const data = new TextEncoder().encode(input)\n const bitLength = data.length * 8\n\n // 填充:消息 + 0x80 + 零 + 64 位大端长度,总长 512 位倍数\n const paddedLength = (((data.length + 8) >> 6) + 1) << 6\n const words = new Uint32Array(paddedLength / 4)\n for (let i = 0; i < data.length; i++) {\n words[i >>> 2] = words[i >>> 2]! | (data[i]! << ((3 - (i & 3)) * 8))\n }\n words[data.length >>> 2] =\n words[data.length >>> 2]! | (0x80 << ((3 - (data.length & 3)) * 8))\n words[words.length - 1] = bitLength >>> 0\n words[words.length - 2] = Math.floor(bitLength / 0x100000000)\n\n let h0 = 0x6a09e667\n let h1 = 0xbb67ae85\n let h2 = 0x3c6ef372\n let h3 = 0xa54ff53a\n let h4 = 0x510e527f\n let h5 = 0x9b05688c\n let h6 = 0x1f83d9ab\n let h7 = 0x5be0cd19\n\n const w = new Uint32Array(64)\n for (let block = 0; block < words.length; block += 16) {\n for (let t = 0; t < 16; t++) w[t] = words[block + t]!\n for (let t = 16; t < 64; t++) {\n const s0 = rotr(w[t - 15]!, 7) ^ rotr(w[t - 15]!, 18) ^ (w[t - 15]! >>> 3)\n const s1 = rotr(w[t - 2]!, 17) ^ rotr(w[t - 2]!, 19) ^ (w[t - 2]! >>> 10)\n w[t] = (w[t - 16]! + s0 + w[t - 7]! + s1) | 0\n }\n\n let a = h0\n let b = h1\n let c = h2\n let d = h3\n let e = h4\n let f = h5\n let g = h6\n let h = h7\n for (let t = 0; t < 64; t++) {\n const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)\n const ch = (e & f) ^ (~e & g)\n const temp1 = (h + S1 + ch + K[t]! + w[t]!) | 0\n const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)\n const maj = (a & b) ^ (a & c) ^ (b & c)\n const temp2 = (S0 + maj) | 0\n h = g\n g = f\n f = e\n e = (d + temp1) | 0\n d = c\n c = b\n b = a\n a = (temp1 + temp2) | 0\n }\n\n h0 = (h0 + a) | 0\n h1 = (h1 + b) | 0\n h2 = (h2 + c) | 0\n h3 = (h3 + d) | 0\n h4 = (h4 + e) | 0\n h5 = (h5 + f) | 0\n h6 = (h6 + g) | 0\n h7 = (h7 + h) | 0\n }\n\n return [h0, h1, h2, h3, h4, h5, h6, h7]\n .map((word) => (word >>> 0).toString(16).padStart(8, '0'))\n .join('')\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * 物化状态标识(覆盖层):pending → 灰叉(对角交叉占位)、failed → 红叉 + 红框\n * (失败态标识)。研究文档 §2.9 的落点是把标识画在覆盖层而非内容层——内容层保持\n * PHP 镜像语义(物化完成前只有背景/边框盒)。工单 05 的 gizmo 覆盖层建立后由\n * 编辑器管线接管调用,本函数只依赖文档与资源状态切片,编辑器/playground 通用。\n */\nimport { forEachLayerBox } from '@hankchen/canvas'\nimport type { Canvas as CanvasDoc } from '@hankchen/canvas'\n\nimport { resourceRefOf } from './materializer'\nimport type { ResourceState } from './materializer'\n\nconst PENDING_STROKE = 'rgba(100, 116, 139, 0.6)'\nconst FAILED_STROKE = '#ef4444'\nconst FAILED_LINE_WIDTH = 2\n\n/** 在覆盖层上按资源状态画标识:failed 红叉+红框、pending 灰叉,done/无状态零笔画 */\nexport function drawResourceMarkers(\n ctx: CanvasRenderingContext2D,\n doc: CanvasDoc,\n state: ResourceState,\n): void {\n forEachLayerBox(doc, (layer, box) => {\n const ref = resourceRefOf(layer)\n if (ref === null) return\n\n const entry = state[ref.key]\n if (!entry || entry.status === 'done') return\n\n if (entry.status === 'failed') {\n ctx.strokeStyle = FAILED_STROKE\n ctx.lineWidth = FAILED_LINE_WIDTH\n crossAt(ctx, box.x, box.y, box.width, box.height)\n // 红框内缩 1px,避免与图层自身边框叠线\n ctx.strokeRect(box.x + 1, box.y + 1, box.width - 2, box.height - 2)\n } else {\n ctx.strokeStyle = PENDING_STROKE\n ctx.lineWidth = 1\n crossAt(ctx, box.x, box.y, box.width, box.height)\n }\n })\n}\n\n/** 对角交叉:beginPath + 两条对角线 + stroke(笔画按调用序可录断言) */\nfunction crossAt(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n height: number,\n): void {\n ctx.beginPath()\n ctx.moveTo(x, y)\n ctx.lineTo(x + width, y + height)\n ctx.moveTo(x + width, y)\n ctx.lineTo(x, y + height)\n ctx.stroke()\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * 预览 PNG 导出(工单 13,ADR 0004):浏览器 Canvas2D 的导出只作**预览/快速\n * 分享**,终图由服务端渲染端依据 graph JSON 权威产出——出图带预览语义标注\n * (PNG tEXt 元数据;界面提示与文件名后缀归宿主),不得当作生产输出。\n *\n * 全幅语义:离屏画布 = 文档像素(可选 scale 倍率),恒等变换渲染(不经预览\n * 视口);资源沿用编辑面后端的物化结果(forkWith 复制注册,不重新装载)。\n * 调用方须先等待物化排空(Materializer.whenSettled),慢资源才有完整呈现。\n */\nimport { renderCanvas, type Canvas, type TextLayoutPolicies } from '@hankchen/canvas'\n\nimport { type Canvas2DBackend } from './canvas2d-backend'\nimport { insertPngTextChunk } from './png'\n\nexport interface PreviewPngOptions {\n /** 输出倍率(相对文档像素;缺省 1 = 文档像素 1:1) */\n readonly scale?: number\n /** 文本布局策略:与编辑会话同一注入值,导出与预览的断行/盒高才一致 */\n readonly textPolicies?: TextLayoutPolicies\n}\n\nexport interface PreviewPngResult {\n /** PNG blob(已注入预览语义 tEXt) */\n readonly blob: Blob\n /** 输出像素尺寸 */\n readonly width: number\n readonly height: number\n}\n\n/** 预览语义标注(tEXt 域只收 Latin-1,用 ASCII;中文标注由界面/文件名承担) */\nexport const PREVIEW_TEXT_KEYWORD = 'CanvasNext'\nexport const PREVIEW_TEXT_VALUE = 'preview render, not the final image (final = server-side render of the graph JSON)'\n\n/**\n * 渲染当前文档为预览 PNG:离屏 canvas 全幅渲染 → toBlob → 注入 tEXt 标注。\n * 抛错时文档与编辑面不受影响(导出面独立,fork 不回写编辑面后端)。\n */\nexport async function exportPreviewPng(\n doc: Canvas,\n sourceBackend: Canvas2DBackend,\n options: PreviewPngOptions = {},\n): Promise<PreviewPngResult> {\n const scale = options.scale ?? 1\n const width = Math.max(1, Math.round(doc.width * scale))\n const height = Math.max(1, Math.round(doc.height * scale))\n const canvas = document.createElement('canvas')\n canvas.width = width\n canvas.height = height\n const ctx = canvas.getContext('2d')\n if (!ctx) throw new Error('无法创建导出画布(2D 上下文不可用)')\n\n renderCanvas(doc, sourceBackend.forkWith(ctx), options.textPolicies)\n const blob = await toPngBlob(canvas)\n const bytes = await blob.arrayBuffer()\n const annotated = insertPngTextChunk(\n new Uint8Array(bytes),\n PREVIEW_TEXT_KEYWORD,\n PREVIEW_TEXT_VALUE,\n )\n return { blob: new Blob([annotated], { type: 'image/png' }), width, height }\n}\n\nfunction toPngBlob(canvas: HTMLCanvasElement): Promise<Blob> {\n return new Promise((resolve, reject) => {\n canvas.toBlob((blob) => {\n if (blob) resolve(blob)\n else reject(new Error('PNG 导出失败(toBlob 返回空)'))\n }, 'image/png')\n })\n}\n","/**\n * PNG 字节级工具(工单 13):tEXt 文本块注入——预览导出的文件语义标注\n * (「预览图,非终图」,ADR 0004)写进 PNG 元数据,随文件走、机器可读;\n * 界面与文件名标注归宿主。纯字节函数、无 DOM(Node 可测)。\n *\n * 块结构:[长度 u32 BE][类型 4B][数据][CRC32 u32 BE];CRC 覆盖类型+数据\n * (IEEE 802.3 多项式,初始/终值取反)。tEXt 数据 = keyword \\0 text,\n * 两段均为 Latin-1(本包只用 ASCII 常量,规范约束不走 UTF-8 通道)。\n */\n\n/** PNG 文件签名(8 字节魔数) */\nexport const PNG_SIGNATURE = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n\nlet crcTable: Uint32Array | null = null\n\nfunction getCrcTable(): Uint32Array {\n if (crcTable) return crcTable\n const table = new Uint32Array(256)\n for (let n = 0; n < 256; n += 1) {\n let c = n\n for (let k = 0; k < 8; k += 1) {\n c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1\n }\n table[n] = c >>> 0\n }\n crcTable = table\n return table\n}\n\n/** PNG 规范的 CRC32(type+data 域校验用) */\nexport function crc32(bytes: Uint8Array): number {\n const table = getCrcTable()\n let crc = 0xffffffff\n for (let i = 0; i < bytes.length; i += 1) {\n crc = table[(crc ^ bytes[i]!) & 0xff]! ^ (crc >>> 8)\n }\n return (crc ^ 0xffffffff) >>> 0\n}\n\n/** 解析出的 PNG 块(类型 + 数据;解析时逐块校验长度域与 CRC 域) */\nexport interface PngChunk {\n readonly type: string\n readonly data: Uint8Array\n}\n\n/** 解析 PNG 块序列:签名/长度/CRC 任一不符即抛错(注入前的完整性门) */\nexport function parsePngChunks(png: Uint8Array): PngChunk[] {\n for (let i = 0; i < PNG_SIGNATURE.length; i += 1) {\n if (png[i] !== PNG_SIGNATURE[i]) throw new Error('不是 PNG 字节流(签名不符)')\n }\n const view = new DataView(png.buffer, png.byteOffset, png.byteLength)\n const chunks: PngChunk[] = []\n let offset = PNG_SIGNATURE.length\n while (offset + 12 <= png.length) {\n const length = view.getUint32(offset)\n const type = String.fromCharCode(png[offset + 4]!, png[offset + 5]!, png[offset + 6]!, png[offset + 7]!)\n const dataStart = offset + 8\n const dataEnd = dataStart + length\n if (dataEnd + 4 > png.length) throw new Error(`PNG 块 ${type} 长度越界`)\n const data = png.subarray(dataStart, dataEnd)\n const expected = view.getUint32(dataEnd)\n const actual = crc32(png.subarray(offset + 4, dataEnd))\n if (expected !== actual) throw new Error(`PNG 块 ${type} CRC 校验失败`)\n chunks.push({ type, data })\n offset = dataEnd + 4\n if (type === 'IEND') break\n }\n return chunks\n}\n\n/** 单个 Latin-1 字符串 → 字节(charCode & 0xff;本包输入恒为 ASCII 常量) */\nfunction latin1Bytes(text: string): Uint8Array<ArrayBuffer> {\n const out = new Uint8Array(text.length)\n for (let i = 0; i < text.length; i += 1) out[i] = text.charCodeAt(i) & 0xff\n return out\n}\n\nfunction encodeChunk(type: string, data: Uint8Array): Uint8Array<ArrayBuffer> {\n const out = new Uint8Array(12 + data.length)\n const view = new DataView(out.buffer)\n view.setUint32(0, data.length)\n for (let i = 0; i < 4; i += 1) out[4 + i] = type.charCodeAt(i)\n out.set(data, 8)\n view.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)))\n return out\n}\n\n/**\n * 在 IHDR 之后注入 tEXt 块(keyword\\0text),返回新字节流(不改写入参)。\n * 块序 tEXt 紧随 IHDR:任何合法 PNG 解析器都能在图像数据前读到标注。\n * keyword 违反 tEXt 规范(1–79 字节)或缺 IHDR 时抛错。\n */\nexport function insertPngTextChunk(png: Uint8Array, keyword: string, text: string): Uint8Array<ArrayBuffer> {\n const keywordLength = keyword.length\n if (keywordLength < 1 || keywordLength > 79) throw new Error(`tEXt keyword 长度越界(1–79):${keywordLength}`)\n if (!parsePngChunks(png).some((chunk) => chunk.type === 'IHDR')) throw new Error('PNG 缺少 IHDR 块')\n\n const data = new Uint8Array([...latin1Bytes(keyword), 0, ...latin1Bytes(text)])\n const textChunk = encodeChunk('tEXt', data)\n\n // IHDR 块 = 签名后第一个块,注入点在其后(IHDR 定长 25 字节由 parse 定位,不硬编码)\n const view = new DataView(png.buffer, png.byteOffset, png.byteLength)\n const ihdrLength = view.getUint32(PNG_SIGNATURE.length)\n const insertAt = PNG_SIGNATURE.length + 12 + ihdrLength\n\n const out = new Uint8Array(png.length + textChunk.length)\n out.set(png.subarray(0, insertAt), 0)\n out.set(textChunk, insertAt)\n out.set(png.subarray(insertAt), insertAt + textChunk.length)\n return out\n}\n","/**\n * @hankchen/canvas-browser-renderer 公共出口。\n *\n * Canvas2D 五原语后端(begin → 按 priority 序分派 → end)+ 物化状态机(工单 04)。\n * 本包整体无 DOM lib 编译,Canvas2D/FontFace/Image 类型只在用到它的文件里局部\n * `/// <reference lib=\"dom\" />`。\n */\nexport const PACKAGE_NAME = '@hankchen/canvas-browser-renderer' as const\n\nexport {\n Canvas2DBackend,\n applyViewportTransform,\n type DrawableImage,\n type PreviewViewportTransform,\n type ViewportAwareBackend,\n} from './canvas2d-backend'\nexport { coverCrop, type CropWindow } from './cover'\nexport {\n builtinFontShorthand,\n canvasFontCssFamily,\n isBuiltinFontRef,\n loadCanvasFont,\n} from './fonts'\nexport { QR_FIXED_OPTIONS, qrDataUrl, qrModuleMatrix, type QrModuleMatrix } from './qr'\nexport {\n Materializer,\n createBrowserLoaders,\n fontResourceKey,\n imageResourceKey,\n qrResourceKey,\n withImageProxy,\n type MaterializerOptions,\n type ResourceEntry,\n type ResourceLoaders,\n type ResourceState,\n type ResourceStatus,\n} from './materializer'\nexport { drawResourceMarkers } from './markers'\nexport { sha256Hex } from './sha256'\nexport {\n PREVIEW_TEXT_KEYWORD,\n PREVIEW_TEXT_VALUE,\n exportPreviewPng,\n type PreviewPngOptions,\n type PreviewPngResult,\n} from './exportPng'\n// png.ts 的字节级工具(crc32/parsePngChunks 等)不进包公共出口:仅 exportPng 与\n// Node 测试(tests/png.test.ts 直连模块)消费,导出面保持最小。\n"],"mappings":"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","names":["forEachLayerBox","forEachLayerBox"]}
1
+ {"version":3,"sources":["../src/cover.ts","../src/fonts.ts","../src/canvas2d-backend.ts","../src/qr.ts","../src/materializer.ts","../src/sha256.ts","../src/markers.ts","../src/exportPng.ts","../src/png.ts","../src/index.ts"],"sourcesContent":["/**\n * cover 缩放裁切几何:等比缩放至铺满目标盒,溢出居中裁掉(PHP intervention cover /\n * go-canvas image-renderer cover 同款)。明确放弃逐像素复刻 GD/浏览器 rasterizer\n * (spec Out of Scope:布局级一致、像素不求等)——本模块只算\"源图上裁哪个窗\",\n * 缩放插值交给 drawImage 的原生光栅化。纯几何,无 DOM 类型(红线 2)。\n */\n\nexport interface CropWindow {\n sx: number\n sy: number\n sw: number\n sh: number\n}\n\n/**\n * 计算源图 srcW×srcH 铺满 dstW×dstH 的居中裁切窗。调用方保证 dstW/dstH 为正;\n * 裁切窗与居中偏移整型化向零截断;极端宽高比下截断可得 0 宽窗:钳到 ≥1,\n * 宁可纵横比略让也不输出空图\n */\nexport function coverCrop(srcW: number, srcH: number, dstW: number, dstH: number): CropWindow {\n const scale = Math.max(dstW / srcW, dstH / srcH)\n let cropW = Math.trunc(dstW / scale)\n let cropH = Math.trunc(dstH / scale)\n if (cropW < 1) cropW = 1\n if (cropH < 1) cropH = 1\n const sx = Math.trunc((srcW - cropW) / 2)\n const sy = Math.trunc((srcH - cropH) / 2)\n return { sx, sy, sw: cropW, sh: cropH }\n}\n\n/** contain 收缩矩形(相对目标盒原点) */\nexport interface FitRect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/**\n * contain 收缩矩形(ADR 0023,image-fit 工单 05):源图 srcW×srcH 完整落入\n * dstW×dstH 的居中等比矩形——两维同一缩放系数取 min(长边贴目标盒),与\n * coverCrop 同门(只算几何、缩放插值交给 drawImage;偏移向零截断)。调用方\n * 保证 dstW/dstH 为正;极端比例截断得 0 钳到 ≥1(coverCrop 房规,宁可纵横比\n * 略让也不输出空矩形)——注意与核心 containRect(PHP getContainRect 同门)的\n * 差别:核心不钳制、截断得 0 由渲染臂 drawImage ≤0 防护只画盒。本函数无图层\n * 上下文(恒居中),对齐 + 边距放置归核心渲染契约的图层感知 containRect;\n * 运行时 contain 计划在核心渲染臂,本函数供本包测试断言用\n */\nexport function containRect(srcW: number, srcH: number, dstW: number, dstH: number): FitRect {\n const scale = Math.min(dstW / srcW, dstH / srcH)\n let width = Math.trunc(srcW * scale)\n let height = Math.trunc(srcH * scale)\n if (width < 1) width = 1\n if (height < 1) height = 1\n return {\n x: Math.trunc((dstW - width) / 2),\n y: Math.trunc((dstH - height) / 2),\n width,\n height,\n }\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * 字体契约的渲染端消化:内置默认字体判定/简写 + URL 字体的 FontFace 物化。\n * 语义镜像 PHP ImageRenderer::drawText(`$fontFile !== '' && !is_numeric($fontFile)`\n * 才走字体文件,否则 v4 内置默认字体)与 go-canvas typography.IsBuiltinFont——\n * 空串/纯数字字体 id 是旧库 GD 内置字体的血统,浏览器端对应内置默认字体 =\n * 系统无衬线族。本文件是渲染包内唯一触 DOM 运行时 API 的地方(FontFace /\n * document.fonts;Canvas2D 类型在 canvas2d-backend.ts 局部引入),包 tsconfig\n * 仍无 DOM lib(红线 2)。\n */\n\n/** 内置默认字体判定:空串/纯数字字体引用(PHP is_numeric 同门:旧库 GD 内置字体 id) */\nexport function isBuiltinFontRef(font: string): boolean {\n if (font === '') return true\n // 纯数字:可选符号/小数/指数(is_numeric 的实用子集——旧库只用 '1'-'5' 字体 id)\n return /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/.test(font.trim())\n}\n\n/** 内置默认字体的 ctx.font 简写:系统无衬线族按字号取用 */\nexport function builtinFontShorthand(fontSize: number): string {\n return `${fontSize}px sans-serif`\n}\n\n/**\n * font-family 的 CSS 值(工单 11 textarea overlay 用):内置默认字体 = 系统无衬线\n * 族;URL = 注册族名(与 loadCanvasFont 同一 fnv-1a 派生,注册后即命中)尾部附\n * sans-serif 兜底——未注册/未加载时回落系统无衬线,与 canvas 侧未注册 URL 落内置\n * 默认简写的行为同门。纯字符串函数,无 DOM。\n */\nexport function canvasFontCssFamily(font: string): string {\n if (isBuiltinFontRef(font)) return 'sans-serif'\n return `${fontFamilyFor(font)}, sans-serif`\n}\n\n/** ctx.font 可用的注册族名:由字体 URL 确定性派生(fnv-1a 32 位),CSS 安全 */\nfunction fontFamilyFor(font: string): string {\n let hash = 0x811c9dc5\n for (let i = 0; i < font.length; i++) {\n hash ^= font.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193)\n }\n return `canvas-font-${(hash >>> 0).toString(36)}`\n}\n\n// URL → 注册完成态的缓存:同一字体只注册一次(PHP/Go 侧解析缓存的对位物)\nconst fontRegistry = new Map<string, Promise<string>>()\n\n/**\n * URL 字体物化:经 FontFace 注册进 document.fonts,resolve 为 ctx.font 可用的族名。\n * 加载失败(离线/404/解析失败)时 reject——调用方决定降级(预览哲学:占位 +\n * 内置默认字体兜底,工单 04 补失败态标识)\n */\nexport function loadCanvasFont(font: string): Promise<string> {\n const cached = fontRegistry.get(font)\n if (cached) return cached\n\n const family = fontFamilyFor(font)\n const loading = (async () => {\n // CSS 字符串内的引号转义(graph 引用来自宿主数据,不假设 URL 干净)\n const source = `url(\"${font.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\")`\n const face = new FontFace(family, source)\n await face.load()\n // lib.dom 的 FontFaceSet 声明缺 add(运行时存在),按 Set 结构收窄\n ;(document.fonts as unknown as Set<FontFace>).add(face)\n return family\n })()\n // 失败不缓存:下一次渲染可重试(临时网络故障自愈)\n loading.catch(() => fontRegistry.delete(font))\n fontRegistry.set(font, loading)\n return loading\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * Canvas2D 五原语后端。按包红线整体不开 DOM lib,仅本文件局部引入\n * CanvasRenderingContext2D 类型(AGENTS.md 红线 2)。\n *\n * 内容绘制语义(工单 03):\n * - drawImage:cover 等比缩放居中裁切(cover.ts),宽或高 ≤0 跳过(PHP 同门);\n * 图片源经 setImage 预载回写(远程物化在工单 04),未注册的 src 只画盒不绘制;\n * - drawText:布局层只给纯对齐锚点,基线差由本端以 TextMetrics 消化(ADR-0003 同款——\n * PHP 的 GD 基线魔数 round(fontSize*0.1) 不移植);字体经 fonts.ts 的判定与注册缝。\n *\n * 可选能力口(image-fit 工单 05):imageIntrinsicSize 供源图内在尺寸,渲染契约臂\n * 据此算 contain 收缩矩形(cover.ts containRect 是其图层无关几何镜像)——后端绘制\n * 面零改动,五原语契约冻结(ADR 0023)。\n */\nimport type { Border, BorderSide, RenderBackend, TextDrawOptions } from '@hankchen/canvas'\n\nimport { coverCrop } from './cover'\nimport { builtinFontShorthand, isBuiltinFontRef } from './fonts'\n\n/** 可绘制图片源:浏览器图片元素/位图,或结构等价的测试替身(须给得出宽高) */\nexport interface DrawableImage {\n readonly width: number\n readonly height: number\n}\n\n/**\n * 预览视口变换(工单 05):begin 清屏后施加到 ctx 的 dpr×zoom×相机变换。\n * x/y 由编辑器会话给出(已对齐物理像素),本端只做与 dpr 的乘法合成。\n */\nexport interface PreviewViewportTransform {\n dpr: number\n zoom: number\n x: number\n y: number\n}\n\n/** 视口感知后端能力:编辑器会话在内容重绘前经此同步预览视口(可选能力口) */\nexport interface ViewportAwareBackend {\n setViewportTransform(transform: PreviewViewportTransform | null): void\n}\n\n/**\n * 把预览视口变换施加到 2D 上下文(内容层 begin 与宿主覆盖层共用同一几何,\n * 保证两层在缩放/平移下不错位)。调用方需先自行复位变换并清屏。\n */\nexport function applyViewportTransform(\n ctx: CanvasRenderingContext2D,\n transform: PreviewViewportTransform,\n): void {\n const scale = transform.dpr * transform.zoom\n // 场景整数坐标经 transform 承担缩放与相机(绘制代码零改动);平移已由\n // 会话侧对齐物理像素,此处直接乘 dpr×zoom 落到设备像素网格。\n ctx.setTransform(scale, 0, 0, scale, -transform.x * scale, -transform.y * scale)\n}\n\nexport class Canvas2DBackend implements RenderBackend, ViewportAwareBackend {\n private readonly ctx: CanvasRenderingContext2D\n\n /** 物化回写面:src → 已加载的图片源(工单 04 的物化器落点,渲染前由宿主/物化器填充) */\n private readonly images = new Map<string, DrawableImage>()\n\n /** 字体引用 → 已注册族名(loadCanvasFont 的回写面;未注册 URL 回落内置默认) */\n private readonly fontFamilies = new Map<string, string>()\n\n /** 预览视口(工单 05):null = 恒等变换(如导出走 begin(w,h) 全幅语义) */\n private viewportTransform: PreviewViewportTransform | null = null\n\n constructor(ctx: CanvasRenderingContext2D) {\n this.ctx = ctx\n }\n\n /** 预载回写:注册 src 对应的图片源后 drawImage 才会绘制 */\n setImage(src: string, image: DrawableImage): void {\n this.images.set(src, image)\n }\n\n /** 字体物化回写:注册字体引用对应的 ctx.font 族名 */\n setFontFamily(font: string, family: string): void {\n this.fontFamilies.set(font, family)\n }\n\n /**\n * 派生同资源的导出后端(工单 13):把本后端已物化的图片/字体注册复制到新\n * 渲染面——导出画布不经预览视口(恒等变换全幅渲染),但资源沿用编辑面的\n * 物化结果,不重新装载。\n */\n forkWith(ctx: CanvasRenderingContext2D): Canvas2DBackend {\n const fork = new Canvas2DBackend(ctx)\n for (const [src, image] of this.images) fork.images.set(src, image)\n for (const [font, family] of this.fontFamilies) fork.fontFamilies.set(font, family)\n return fork\n }\n\n setViewportTransform(transform: PreviewViewportTransform | null): void {\n this.viewportTransform = transform\n }\n\n begin(_width: number, _height: number): void {\n // 新建渲染面语义:复用同一面时清残留变换与上帧像素。物理缓冲 =\n // css × dpr(工单 05 起与画布整数像素语义分离),按缓冲实尺寸清。\n this.ctx.setTransform(1, 0, 0, 1, 0, 0)\n this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height)\n\n if (this.viewportTransform) applyViewportTransform(this.ctx, this.viewportTransform)\n }\n\n end(): void {\n // 预览就地绘制,无产物返回;导出(工单 13)经画布 toBlob 走\n }\n\n /**\n * 镜像 PHP ImageRenderer::drawRect:背景填充盒(null/空串跳过)+\n * 四边边框沿盒边描线(线宽居中于路径,与 intervention drawLine 同语义)。\n */\n drawRect(\n x: number,\n y: number,\n width: number,\n height: number,\n bgColor: string | null,\n border: Border,\n ): void {\n const ctx = this.ctx\n if (bgColor !== null && bgColor !== '') {\n ctx.fillStyle = bgColor\n ctx.fillRect(x, y, width, height)\n }\n\n if (border.top) this.strokeLine(x, y, x + width, y, border.top)\n if (border.bottom) this.strokeLine(x, y + height, x + width, y + height, border.bottom)\n if (border.left) this.strokeLine(x, y, x, y + height, border.left)\n if (border.right) this.strokeLine(x + width, y, x + width, y + height, border.right)\n }\n\n /** cover 裁切至 width×height 后放置于 (x, y);宽或高 ≤0 跳过(PHP drawImage 同门) */\n drawImage(src: string, x: number, y: number, width: number, height: number): void {\n if (width <= 0 || height <= 0) return\n\n const image = this.images.get(src)\n if (!image) return // 未物化/未加载:只画盒的占位语义(工单 04 补失败态)\n\n const srcW = naturalWidth(image)\n const srcH = naturalHeight(image)\n if (srcW <= 0 || srcH <= 0) return\n\n const crop = coverCrop(srcW, srcH, width, height)\n this.ctx.drawImage(\n image as CanvasImageSource,\n crop.sx,\n crop.sy,\n crop.sw,\n crop.sh,\n x,\n y,\n width,\n height,\n )\n }\n\n /**\n * 源图内在尺寸度量(渲染契约可选能力口,image-fit 工单 05):contain 计划按\n * 此收缩目标矩形,查询键与 drawImage 同一 src 引用、同一次物化回写的 images\n * 表(预览与导出 forkWith 共享,零参数穿透)。尺寸取 naturalWidth/Height\n * 优先(与 drawImage 同门);未注册/非法尺寸返回 null,渲染臂按 cover 降级\n * (PHP getimagesize 失败同门)。查询口非绘制原语,五原语契约冻结不动\n */\n imageIntrinsicSize(src: string): { width: number; height: number } | null {\n const image = this.images.get(src)\n if (!image) return null\n\n const width = naturalWidth(image)\n const height = naturalHeight(image)\n if (width <= 0 || height <= 0) return null\n return { width, height }\n }\n\n /**\n * 单行文本,(x, y) 为对齐语义锚点:水平偏移 = 真实文本宽度(measureText,与\n * 布局启发式度量无关)× 对齐语义;垂直基线 = 锚点 + 字体 metrics 偏移\n * (top: +ascent / center: +(ascent-descent)/2 / bottom: -descent,未知取值归 top)。\n * 空行零副作用(PHP 同款空串守卫,模板仍对空行分派)。角度为度,正值视觉逆时针\n * (GD angle 惯例;canvas y 轴向下,rotate 取负)。\n */\n drawText(line: string, x: number, y: number, options: TextDrawOptions): void {\n if (line === '') return\n\n const ctx = this.ctx\n ctx.font = this.fontShorthand(options.font, options.fontSize)\n ctx.fillStyle = options.fontColor\n\n const metrics = ctx.measureText(line)\n const ascent = metrics.fontBoundingBoxAscent ?? metrics.actualBoundingBoxAscent\n const descent = metrics.fontBoundingBoxDescent ?? metrics.actualBoundingBoxDescent\n\n // 水平:笔起点相对锚点的偏移(left: 0 / center: -宽一半 / right: -宽,未知归 left)\n let penX = 0\n if (options.horizontalAlign === 'center') penX = -metrics.width / 2\n else if (options.horizontalAlign === 'right') penX = -metrics.width\n\n // 垂直:基线相对锚点的偏移,由字体 metrics 消化基线差\n let baselineY = ascent\n if (options.verticalAlign === 'center') baselineY = (ascent - descent) / 2\n else if (options.verticalAlign === 'bottom') baselineY = -descent\n\n if (options.angle === 0) {\n ctx.fillText(line, x + penX, y + baselineY)\n return\n }\n\n ctx.save()\n ctx.translate(x, y)\n ctx.rotate((-options.angle * Math.PI) / 180)\n ctx.fillText(line, penX, baselineY)\n ctx.restore()\n }\n\n /** ctx.font 简写:内置引用(空串/纯数字)或未注册 URL → 系统无衬线默认 */\n private fontShorthand(font: string, fontSize: number): string {\n if (isBuiltinFontRef(font)) return builtinFontShorthand(fontSize)\n const family = this.fontFamilies.get(font)\n return family ? `${fontSize}px \"${family}\"` : builtinFontShorthand(fontSize)\n }\n\n private strokeLine(x1: number, y1: number, x2: number, y2: number, side: BorderSide): void {\n const ctx = this.ctx\n ctx.beginPath()\n ctx.moveTo(x1, y1)\n ctx.lineTo(x2, y2)\n ctx.strokeStyle = side.color\n ctx.lineWidth = side.width\n ctx.stroke()\n }\n}\n\n/** 图片源尺寸:优先 HTMLImageElement 的固有尺寸(SVG 等内容尺寸可能不同于布局尺寸) */\nfunction naturalWidth(image: DrawableImage): number {\n const natural = (image as Partial<HTMLImageElement>).naturalWidth\n return typeof natural === 'number' && natural > 0 ? natural : image.width\n}\n\nfunction naturalHeight(image: DrawableImage): number {\n const natural = (image as Partial<HTMLImageElement>).naturalHeight\n return typeof natural === 'number' && natural > 0 ? natural : image.height\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * QR 物化的生成端:固定选项与 php-canvas-next ResourceResolver::materializeQrCode\n * 逐项对齐(endroid/qr-code:Encoding UTF-8、ErrorCorrectionLevel::High、margin 0、\n * 黑白双色、RoundBlockSizeMode::None)。与 PHP 的差异点:PHP 按图层宽度就地生成\n * 位图(缓存键含宽度),浏览器端按固定 scale 生成一次、由渲染原语 drawImage 缩放\n * 到盒宽——同内容跨图层(不同宽)只生成一次。\n *\n * qrcode 包运行时是 CJS 同构包(Node 走 pngjs、浏览器走 canvas),类型为具名导出。\n * 本文件只产 dataURL 与模块矩阵,不触 DOM 运行时 API(Image 装载归 materializer)。\n */\nimport { create as createQrCode, toDataURL as qrToDataURL } from 'qrcode'\n\n/** 固定选项(契约):纠错 High、无静区、黑白;scale 只影响产物清晰度(终图随盒缩放) */\nexport const QR_FIXED_OPTIONS = {\n errorCorrectionLevel: 'H',\n margin: 0,\n scale: 4,\n color: { dark: '#000000ff', light: '#ffffffff' },\n} as const\n\n/** QR 符号的模块矩阵视图(测试取样缝:点位断言不经过 PNG 解码) */\nexport interface QrModuleMatrix {\n readonly size: number\n isDark(row: number, col: number): boolean\n}\n\n/** 同步生成模块矩阵(固定选项);UTF-8 串按字节模式编码(endroid Encoding('UTF-8') 同语义) */\nexport function qrModuleMatrix(value: string): QrModuleMatrix {\n const qr = createQrCode(value, { errorCorrectionLevel: QR_FIXED_OPTIONS.errorCorrectionLevel })\n const { size, data } = qr.modules\n return {\n size,\n isDark: (row, col) => data[row * size + col] === 1,\n }\n}\n\n/** 生成 PNG dataURL(固定选项);交给 Image 装载后由渲染原语缩放到盒宽 */\nexport function qrDataUrl(value: string): Promise<string> {\n // QR_FIXED_OPTIONS 的形状即 toDataURL 的渲染选项(errorCorrectionLevel/margin/scale/color)\n return qrToDataURL(value, QR_FIXED_OPTIONS)\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * 物化状态机(工单 04):渲染前把图层的资源引用(图片 / 字体 URL)与 QR 内容\n * 准备成可绘资源,状态 pending → done/failed。\n *\n * 红线(图层 setter 禁 I/O 的编辑器延伸):物化状态只落本状态机的资源切片,永不写\n * graph——切片形状即未来内核 store ui 分支的 `ui.resources`(工单 05+ 建 store 时\n * 由订阅桥接整体替换),物化完成只触发重绘、不产生文档 patch。\n *\n * 缓存键 = sha256(完整引用)(图片/字体取 URL 原串,QR 内容加 `qr:` 命名空间),\n * 修正 PHP ResourceResolver 以 URL basename 作键的同名碰撞旧债。同引用跨图层\n * 去重:状态已有 pending/done 的键不再调度,failed 留待下次 materialize 重试\n * (临时网络故障自愈,与 fonts.ts 的失败不缓存同哲学)。\n *\n * 触发集合:非空引用全部装载。与 PHP 的差异点:PHP 对非 URL 引用\"本地路径直接\n * 使用\"(ResourceResolver 跳过下载),浏览器无文件系统对位——相对路径就是同源\n * URL,仍需经 Image 装载(工单 03 预载先例);跨域语义(crossOrigin/代理)只作用\n * 于绝对 http(s) 引用。\n *\n * 跨域:默认图片装载强制 crossOrigin='anonymous'——无 CORS 头的源在物化阶段即\n * 失败(进 failed 态、画失败标识),不留到导出 toBlob 才抛 SecurityError;宿主经\n * `imageProxy` 注入改写(对应 PHP DownloaderInterface 注入语义),错误消息保持\n * 原始引用以便三端对照 PHP `could not get remote file(<url>)`。\n *\n * 触发:宿主在文档变更后调 materialize(doc)(store 建立后由 doc patch 订阅驱动)。\n * 导出前的 pending 排空闸归工单 13。\n */\nimport { forEachLayerBox, qrImageSrc } from '@hankchen/canvas'\nimport type { Canvas as CanvasDoc, Layer } from '@hankchen/canvas'\n\nimport { type Canvas2DBackend, type DrawableImage } from './canvas2d-backend'\nimport { isBuiltinFontRef, loadCanvasFont } from './fonts'\nimport { qrDataUrl } from './qr'\nimport { sha256Hex } from './sha256'\n\nexport type ResourceStatus = 'pending' | 'done' | 'failed'\n\n/** 单条资源状态;error 仅 failed 时携带(加载器的错误消息) */\nexport interface ResourceEntry {\n readonly status: ResourceStatus\n readonly error?: string\n}\n\n/** 资源状态切片:key = 资源缓存键。整体替换语义,直接对接未来 store 的 ui 分支 */\nexport type ResourceState = Readonly<Record<string, ResourceEntry>>\n\n/** 缓存键:sha256(完整引用)——不继承 PHP basename 旧债(同名不同 URL 不碰撞);派生经键 memo(同引用不重复散列) */\nexport const imageResourceKey = (src: string): string => memoSha256Hex(src)\nexport const fontResourceKey = (font: string): string => memoSha256Hex(font)\n/** QR 内容加命名空间后散列:内容恰为 URL 的 QR 与图片键域隔离 */\nexport const qrResourceKey = (value: string): string => memoSha256Hex(`qr:${value}`)\n\n/**\n * 跨域代理注入点(对应 PHP DownloaderInterface):把 http(s) 资源 URL 改写为代理\n * URL。data:/相对路径不经代理(本就同源/内联)。未配置时原样通过。\n */\nexport function withImageProxy(\n url: string,\n proxy: ((url: string) => string) | undefined,\n): string {\n if (!proxy || !/^https?:\\/\\//i.test(url)) return url\n return proxy(url)\n}\n\n/** 三类资源的装载器(注入缝):测试注桩,宿主可替换缓存/代理策略 */\nexport interface ResourceLoaders {\n /** 图片 URL → 可绘制源(默认:Image + crossOrigin=anonymous + 可选代理) */\n readonly loadImage: (url: string) => Promise<DrawableImage>\n /** 字体 URL → ctx.font 可用的族名(默认:loadCanvasFont 的 FontFace 注册) */\n readonly loadFont: (font: string) => Promise<string>\n /** QR 内容 → 可绘制源(默认:固定选项生成 PNG dataURL 后经 Image 装载) */\n readonly loadQr: (value: string) => Promise<DrawableImage>\n}\n\nexport interface MaterializerOptions {\n /** 覆盖默认浏览器装载器(Node 测试注桩 / 宿主自定义) */\n readonly loaders?: ResourceLoaders\n /** 跨域代理注入点:默认图片装载器把 http(s) 引用改写为代理 URL */\n readonly imageProxy?: (url: string) => string\n /** 同时在途的装载上限(默认 6) */\n readonly concurrency?: number\n}\n\ninterface ResourceRef {\n readonly key: string\n readonly kind: 'image' | 'font' | 'qr'\n readonly ref: string\n}\n\n/**\n * 图层 → 资源引用的唯一分派(物化收集与覆盖层标识共用):\n * 图片取非空 src、字体取非内置引用(空串/纯数字 = 渲染端内置默认)、QR 取非空内容。\n * 无资源引用的图层返回 null。\n */\nexport function resourceRefOf(layer: Layer): ResourceRef | null {\n if (layer.type === 'ImageLayer' && layer.src !== null) {\n return { key: imageResourceKey(layer.src), kind: 'image', ref: layer.src }\n }\n if (layer.type === 'TextLayer' && !isBuiltinFontRef(layer.font)) {\n return { key: fontResourceKey(layer.font), kind: 'font', ref: layer.font }\n }\n if (layer.type === 'QrCodeLayer' && layer.value !== '') {\n return { key: qrResourceKey(layer.value), kind: 'qr', ref: layer.value }\n }\n return null\n}\n\nconst DEFAULT_CONCURRENCY = 6\n\n/**\n * 键 memo 原语(canvas-web-render-perf 工单 02):同输入串不重复散列,容量有界\n * (FIFO 逐最旧)。逐键 doc 变更驱动 materialize 全树重扫,data URL 大图的 sha256\n * 是逐键开销链的大头——文本编辑不碰资源,键派生必须 O(1) 走缓存。产物与底层函数\n * 逐字节一致(resource-keys 测试 oracle 复核)。\n */\nexport function createMemoizedKeyHash(\n load: (input: string) => string,\n capacity = 512,\n): (input: string) => string {\n const cache = new Map<string, string>()\n return (input: string): string => {\n const hit = cache.get(input)\n if (hit !== undefined) return hit\n const key = load(input)\n if (cache.size >= capacity) {\n const oldest = cache.keys().next().value\n if (oldest !== undefined) cache.delete(oldest)\n }\n cache.set(input, key)\n return key\n }\n}\n\nconst memoSha256Hex = createMemoizedKeyHash(sha256Hex)\n\nexport class Materializer {\n private readonly backend: Canvas2DBackend\n private readonly loaders: ResourceLoaders\n private readonly maxConcurrent: number\n\n private readonly entries = new Map<string, ResourceEntry>()\n private readonly listeners = new Set<(state: ResourceState) => void>()\n private readonly settleResolvers: Array<(state: ResourceState) => void> = []\n private readonly queue: ResourceRef[] = []\n private active = 0\n\n constructor(backend: Canvas2DBackend, options: MaterializerOptions = {}) {\n this.backend = backend\n this.loaders = options.loaders ?? createBrowserLoaders({ imageProxy: options.imageProxy })\n this.maxConcurrent = Math.max(options.concurrency ?? DEFAULT_CONCURRENCY, 1)\n }\n\n /** 资源状态快照(ui.resources 切片;每次变更整体替换,不可变) */\n get state(): ResourceState {\n return Object.fromEntries(this.entries)\n }\n\n /** 在途装载条数(playground 提示/将来导出闸消费) */\n get pendingCount(): number {\n let count = 0\n for (const entry of this.entries.values()) {\n if (entry.status === 'pending') count++\n }\n return count\n }\n\n /**\n * 扫描文档收集资源引用(含表格下钻,复用渲染模板的同一遍历),调度未决资源。\n * 幂等:pending/done 的键跳过(同 URL 跨图层只物化一次),failed 重新调度。\n * 快照语义(工单 15):文档不再引用的键当场剪除——打开新文档/删层后旧资源\n * 条目不残留(覆盖层标识与宿主\"失败 N 项\"读数只对当前文档有意义)。\n */\n materialize(doc: CanvasDoc): void {\n const referenced = new Map<string, ResourceRef>()\n // 画布背景图(canvas-background 工单 04):图层树之外的第二收集源——\n // 渲染端 paintBackground 递原始引用给 drawImage,未装载即静默不画,\n // 导出排空闸也据此等待背景图就绪。背景色非资源,零条目。\n const backgroundImage = doc.background?.image\n if (backgroundImage) {\n const key = imageResourceKey(backgroundImage)\n referenced.set(key, { key, kind: 'image', ref: backgroundImage })\n }\n forEachLayerBox(doc, (layer) => {\n const ref = resourceRefOf(layer)\n if (ref === null || referenced.has(ref.key)) return\n referenced.set(ref.key, ref)\n })\n\n let pruned = false\n for (const key of [...this.entries.keys()]) {\n if (!referenced.has(key)) {\n this.entries.delete(key)\n pruned = true\n }\n }\n\n // 已决跳过(failed 可重试)\n const collected = new Map<string, ResourceRef>()\n for (const ref of referenced.values()) {\n const existing = this.entries.get(ref.key)\n if (!existing || existing.status === 'failed') collected.set(ref.key, ref)\n }\n\n if (collected.size === 0) {\n if (pruned) this.notify()\n return\n }\n\n for (const ref of collected.values()) {\n this.entries.set(ref.key, { status: 'pending' })\n this.queue.push(ref)\n }\n this.notify()\n this.pump()\n }\n\n /** 订阅状态变更(整体快照推送);返回退订函数 */\n subscribe(listener: (state: ResourceState) => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /**\n * 全量物化排空闸(工单 13,PNG 导出前置):在途装载全部落定后 resolve,\n * 携带终态快照(failed 不算在途——预览语义按占位出图,宿主可据快照提示)。\n * 闸在 pending 首次归零时收口:其后新调度的装载归下一次 await 覆盖。\n */\n whenSettled(): Promise<ResourceState> {\n if (this.pendingCount === 0) return Promise.resolve(this.state)\n return new Promise((resolve) => {\n this.settleResolvers.push(resolve)\n })\n }\n\n private notify(): void {\n const state = this.state\n for (const listener of this.listeners) listener(state)\n if (this.pendingCount === 0 && this.settleResolvers.length > 0) {\n const resolvers = this.settleResolvers.splice(0)\n for (const resolve of resolvers) resolve(state)\n }\n }\n\n /** 并发上限内的队列泵:完成一个补位一个 */\n private pump(): void {\n while (this.active < this.maxConcurrent && this.queue.length > 0) {\n const ref = this.queue.shift()!\n this.active++\n void this.load(ref).finally(() => {\n this.active--\n this.pump()\n })\n }\n }\n\n private async load(ref: ResourceRef): Promise<void> {\n let entry: ResourceEntry\n try {\n if (ref.kind === 'font') {\n this.backend.setFontFamily(ref.ref, await this.loaders.loadFont(ref.ref))\n } else if (ref.kind === 'qr') {\n this.backend.setImage(qrImageSrc(ref.ref), await this.loaders.loadQr(ref.ref))\n } else {\n this.backend.setImage(ref.ref, await this.loaders.loadImage(ref.ref))\n }\n entry = { status: 'done' }\n } catch (error) {\n entry = {\n status: 'failed',\n error: error instanceof Error ? error.message : String(error),\n }\n }\n // 装载在途时文档可能已切走(键被剪除):死键不回写(不复活僵尸条目、\n // 不推空通知);回写面已就绪,重开同一文档时重装载依赖浏览器 HTTP 缓存\n if (this.entries.has(ref.key)) {\n this.entries.set(ref.key, entry)\n this.notify()\n }\n }\n}\n\n/** 默认浏览器装载器:图片经 Image + crossOrigin(代理注入点),字体经 FontFace */\nexport function createBrowserLoaders(\n options: { imageProxy?: (url: string) => string } = {},\n): ResourceLoaders {\n return {\n // 错误消息用原始引用(三端对照 PHP 消息格式),代理 URL 只用于装载\n loadImage: (url) => loadImageElement(withImageProxy(url, options.imageProxy), url),\n loadFont: (font) => loadCanvasFont(font),\n loadQr: async (value) => loadImageElement(await qrDataUrl(value), `qr:${value}`),\n }\n}\n\n/**\n * 图片装载:crossOrigin='anonymous' 物化期即暴露跨域失败(污染画布的 toBlob\n * SecurityError 不留到导出)。失败消息用调用方的原始引用(PHP `could not get\n * remote file(<url>)` 同格式,方便三端对照),不掺代理改写后的装载地址。\n */\nfunction loadImageElement(src: string, ref: string): Promise<DrawableImage> {\n return new Promise((resolve, reject) => {\n const image = new Image()\n image.crossOrigin = 'anonymous'\n image.onerror = () => reject(new Error(`could not get remote file(${ref})`))\n image.onload = () => resolve(image)\n image.src = src\n })\n}\n","/**\n * 同步 SHA-256(FIPS 180-4):资源缓存键的散列原语。\n *\n * 为什么手写:物化状态机需要同步派生缓存键(渲染/键比较路径不能 await),\n * Web Crypto 的 subtle.digest 只有异步口;而编辑器物化散列不值得为此引依赖。\n * 测试以 node:crypto 为独立 oracle 逐值锁定。\n *\n * TextEncoder 是宿主全局(浏览器 evergreen 与 Node 皆有),本包 tsconfig 无任何\n * lib 注入,这里给最小结构声明以自持类型(不依赖别的文件碰巧拖进的全局)。\n */\n\ninterface TextEncoderLike {\n encode(input: string): Uint8Array\n}\n\ndeclare const TextEncoder: new () => TextEncoderLike\n\nconst K = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,\n])\n\nfunction rotr(x: number, n: number): number {\n return ((x >>> n) | (x << (32 - n)))\n}\n\n/** UTF-8 串的 SHA-256 摘要,64 位小写十六进制 */\nexport function sha256Hex(input: string): string {\n const data = new TextEncoder().encode(input)\n const bitLength = data.length * 8\n\n // 填充:消息 + 0x80 + 零 + 64 位大端长度,总长 512 位倍数\n const paddedLength = (((data.length + 8) >> 6) + 1) << 6\n const words = new Uint32Array(paddedLength / 4)\n for (let i = 0; i < data.length; i++) {\n words[i >>> 2] = words[i >>> 2]! | (data[i]! << ((3 - (i & 3)) * 8))\n }\n words[data.length >>> 2] =\n words[data.length >>> 2]! | (0x80 << ((3 - (data.length & 3)) * 8))\n words[words.length - 1] = bitLength >>> 0\n words[words.length - 2] = Math.floor(bitLength / 0x100000000)\n\n let h0 = 0x6a09e667\n let h1 = 0xbb67ae85\n let h2 = 0x3c6ef372\n let h3 = 0xa54ff53a\n let h4 = 0x510e527f\n let h5 = 0x9b05688c\n let h6 = 0x1f83d9ab\n let h7 = 0x5be0cd19\n\n const w = new Uint32Array(64)\n for (let block = 0; block < words.length; block += 16) {\n for (let t = 0; t < 16; t++) w[t] = words[block + t]!\n for (let t = 16; t < 64; t++) {\n const s0 = rotr(w[t - 15]!, 7) ^ rotr(w[t - 15]!, 18) ^ (w[t - 15]! >>> 3)\n const s1 = rotr(w[t - 2]!, 17) ^ rotr(w[t - 2]!, 19) ^ (w[t - 2]! >>> 10)\n w[t] = (w[t - 16]! + s0 + w[t - 7]! + s1) | 0\n }\n\n let a = h0\n let b = h1\n let c = h2\n let d = h3\n let e = h4\n let f = h5\n let g = h6\n let h = h7\n for (let t = 0; t < 64; t++) {\n const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)\n const ch = (e & f) ^ (~e & g)\n const temp1 = (h + S1 + ch + K[t]! + w[t]!) | 0\n const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)\n const maj = (a & b) ^ (a & c) ^ (b & c)\n const temp2 = (S0 + maj) | 0\n h = g\n g = f\n f = e\n e = (d + temp1) | 0\n d = c\n c = b\n b = a\n a = (temp1 + temp2) | 0\n }\n\n h0 = (h0 + a) | 0\n h1 = (h1 + b) | 0\n h2 = (h2 + c) | 0\n h3 = (h3 + d) | 0\n h4 = (h4 + e) | 0\n h5 = (h5 + f) | 0\n h6 = (h6 + g) | 0\n h7 = (h7 + h) | 0\n }\n\n return [h0, h1, h2, h3, h4, h5, h6, h7]\n .map((word) => (word >>> 0).toString(16).padStart(8, '0'))\n .join('')\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * 物化状态标识(覆盖层):pending → 灰叉(对角交叉占位)、failed → 红叉 + 红框\n * (失败态标识)。研究文档 §2.9 的落点是把标识画在覆盖层而非内容层——内容层保持\n * PHP 镜像语义(物化完成前只有背景/边框盒)。工单 05 的 gizmo 覆盖层建立后由\n * 编辑器管线接管调用,本函数只依赖文档与资源状态切片,编辑器/playground 通用。\n */\nimport { forEachLayerBox } from '@hankchen/canvas'\nimport type { Canvas as CanvasDoc } from '@hankchen/canvas'\n\nimport { resourceRefOf } from './materializer'\nimport type { ResourceState } from './materializer'\n\nconst PENDING_STROKE = 'rgba(100, 116, 139, 0.6)'\nconst FAILED_STROKE = '#ef4444'\nconst FAILED_LINE_WIDTH = 2\n\n/** 在覆盖层上按资源状态画标识:failed 红叉+红框、pending 灰叉,done/无状态零笔画 */\nexport function drawResourceMarkers(\n ctx: CanvasRenderingContext2D,\n doc: CanvasDoc,\n state: ResourceState,\n): void {\n forEachLayerBox(doc, (layer, box) => {\n const ref = resourceRefOf(layer)\n if (ref === null) return\n\n const entry = state[ref.key]\n if (!entry || entry.status === 'done') return\n\n if (entry.status === 'failed') {\n ctx.strokeStyle = FAILED_STROKE\n ctx.lineWidth = FAILED_LINE_WIDTH\n crossAt(ctx, box.x, box.y, box.width, box.height)\n // 红框内缩 1px,避免与图层自身边框叠线\n ctx.strokeRect(box.x + 1, box.y + 1, box.width - 2, box.height - 2)\n } else {\n ctx.strokeStyle = PENDING_STROKE\n ctx.lineWidth = 1\n crossAt(ctx, box.x, box.y, box.width, box.height)\n }\n })\n}\n\n/** 对角交叉:beginPath + 两条对角线 + stroke(笔画按调用序可录断言) */\nfunction crossAt(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n height: number,\n): void {\n ctx.beginPath()\n ctx.moveTo(x, y)\n ctx.lineTo(x + width, y + height)\n ctx.moveTo(x + width, y)\n ctx.lineTo(x, y + height)\n ctx.stroke()\n}\n","/// <reference lib=\"dom\" />\n\n/**\n * 预览 PNG 导出(工单 13,ADR 0004):浏览器 Canvas2D 的导出只作**预览/快速\n * 分享**,终图由服务端渲染端依据 graph JSON 权威产出——出图带预览语义标注\n * (PNG tEXt 元数据;界面提示与文件名后缀归宿主),不得当作生产输出。\n *\n * 全幅语义:离屏画布 = 文档像素(可选 scale 倍率),恒等变换渲染(不经预览\n * 视口);资源沿用编辑面后端的物化结果(forkWith 复制注册,不重新装载)。\n * 调用方须先等待物化排空(Materializer.whenSettled),慢资源才有完整呈现。\n */\nimport { renderCanvas, type Canvas, type TextLayoutPolicies } from '@hankchen/canvas'\n\nimport { type Canvas2DBackend } from './canvas2d-backend'\nimport { insertPngTextChunk } from './png'\n\nexport interface PreviewPngOptions {\n /** 输出倍率(相对文档像素;缺省 1 = 文档像素 1:1) */\n readonly scale?: number\n /** 文本布局策略:与编辑会话同一注入值,导出与预览的断行/盒高才一致 */\n readonly textPolicies?: TextLayoutPolicies\n}\n\nexport interface PreviewPngResult {\n /** PNG blob(已注入预览语义 tEXt) */\n readonly blob: Blob\n /** 输出像素尺寸 */\n readonly width: number\n readonly height: number\n}\n\n/** 预览语义标注(tEXt 域只收 Latin-1,用 ASCII;中文标注由界面/文件名承担) */\nexport const PREVIEW_TEXT_KEYWORD = 'CanvasNext'\nexport const PREVIEW_TEXT_VALUE = 'preview render, not the final image (final = server-side render of the graph JSON)'\n\n/**\n * 渲染当前文档为预览 PNG:离屏 canvas 全幅渲染 → toBlob → 注入 tEXt 标注。\n * 抛错时文档与编辑面不受影响(导出面独立,fork 不回写编辑面后端)。\n */\nexport async function exportPreviewPng(\n doc: Canvas,\n sourceBackend: Canvas2DBackend,\n options: PreviewPngOptions = {},\n): Promise<PreviewPngResult> {\n const scale = options.scale ?? 1\n const width = Math.max(1, Math.round(doc.width * scale))\n const height = Math.max(1, Math.round(doc.height * scale))\n const canvas = document.createElement('canvas')\n canvas.width = width\n canvas.height = height\n const ctx = canvas.getContext('2d')\n if (!ctx) throw new Error('无法创建导出画布(2D 上下文不可用)')\n\n renderCanvas(doc, sourceBackend.forkWith(ctx), options.textPolicies)\n const blob = await toPngBlob(canvas)\n const bytes = await blob.arrayBuffer()\n const annotated = insertPngTextChunk(\n new Uint8Array(bytes),\n PREVIEW_TEXT_KEYWORD,\n PREVIEW_TEXT_VALUE,\n )\n return { blob: new Blob([annotated], { type: 'image/png' }), width, height }\n}\n\nfunction toPngBlob(canvas: HTMLCanvasElement): Promise<Blob> {\n return new Promise((resolve, reject) => {\n canvas.toBlob((blob) => {\n if (blob) resolve(blob)\n else reject(new Error('PNG 导出失败(toBlob 返回空)'))\n }, 'image/png')\n })\n}\n","/**\n * PNG 字节级工具(工单 13):tEXt 文本块注入——预览导出的文件语义标注\n * (「预览图,非终图」,ADR 0004)写进 PNG 元数据,随文件走、机器可读;\n * 界面与文件名标注归宿主。纯字节函数、无 DOM(Node 可测)。\n *\n * 块结构:[长度 u32 BE][类型 4B][数据][CRC32 u32 BE];CRC 覆盖类型+数据\n * (IEEE 802.3 多项式,初始/终值取反)。tEXt 数据 = keyword \\0 text,\n * 两段均为 Latin-1(本包只用 ASCII 常量,规范约束不走 UTF-8 通道)。\n */\n\n/** PNG 文件签名(8 字节魔数) */\nexport const PNG_SIGNATURE = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n\nlet crcTable: Uint32Array | null = null\n\nfunction getCrcTable(): Uint32Array {\n if (crcTable) return crcTable\n const table = new Uint32Array(256)\n for (let n = 0; n < 256; n += 1) {\n let c = n\n for (let k = 0; k < 8; k += 1) {\n c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1\n }\n table[n] = c >>> 0\n }\n crcTable = table\n return table\n}\n\n/** PNG 规范的 CRC32(type+data 域校验用) */\nexport function crc32(bytes: Uint8Array): number {\n const table = getCrcTable()\n let crc = 0xffffffff\n for (let i = 0; i < bytes.length; i += 1) {\n crc = table[(crc ^ bytes[i]!) & 0xff]! ^ (crc >>> 8)\n }\n return (crc ^ 0xffffffff) >>> 0\n}\n\n/** 解析出的 PNG 块(类型 + 数据;解析时逐块校验长度域与 CRC 域) */\nexport interface PngChunk {\n readonly type: string\n readonly data: Uint8Array\n}\n\n/** 解析 PNG 块序列:签名/长度/CRC 任一不符即抛错(注入前的完整性门) */\nexport function parsePngChunks(png: Uint8Array): PngChunk[] {\n for (let i = 0; i < PNG_SIGNATURE.length; i += 1) {\n if (png[i] !== PNG_SIGNATURE[i]) throw new Error('不是 PNG 字节流(签名不符)')\n }\n const view = new DataView(png.buffer, png.byteOffset, png.byteLength)\n const chunks: PngChunk[] = []\n let offset = PNG_SIGNATURE.length\n while (offset + 12 <= png.length) {\n const length = view.getUint32(offset)\n const type = String.fromCharCode(png[offset + 4]!, png[offset + 5]!, png[offset + 6]!, png[offset + 7]!)\n const dataStart = offset + 8\n const dataEnd = dataStart + length\n if (dataEnd + 4 > png.length) throw new Error(`PNG 块 ${type} 长度越界`)\n const data = png.subarray(dataStart, dataEnd)\n const expected = view.getUint32(dataEnd)\n const actual = crc32(png.subarray(offset + 4, dataEnd))\n if (expected !== actual) throw new Error(`PNG 块 ${type} CRC 校验失败`)\n chunks.push({ type, data })\n offset = dataEnd + 4\n if (type === 'IEND') break\n }\n return chunks\n}\n\n/** 单个 Latin-1 字符串 → 字节(charCode & 0xff;本包输入恒为 ASCII 常量) */\nfunction latin1Bytes(text: string): Uint8Array<ArrayBuffer> {\n const out = new Uint8Array(text.length)\n for (let i = 0; i < text.length; i += 1) out[i] = text.charCodeAt(i) & 0xff\n return out\n}\n\nfunction encodeChunk(type: string, data: Uint8Array): Uint8Array<ArrayBuffer> {\n const out = new Uint8Array(12 + data.length)\n const view = new DataView(out.buffer)\n view.setUint32(0, data.length)\n for (let i = 0; i < 4; i += 1) out[4 + i] = type.charCodeAt(i)\n out.set(data, 8)\n view.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)))\n return out\n}\n\n/**\n * 在 IHDR 之后注入 tEXt 块(keyword\\0text),返回新字节流(不改写入参)。\n * 块序 tEXt 紧随 IHDR:任何合法 PNG 解析器都能在图像数据前读到标注。\n * keyword 违反 tEXt 规范(1–79 字节)或缺 IHDR 时抛错。\n */\nexport function insertPngTextChunk(png: Uint8Array, keyword: string, text: string): Uint8Array<ArrayBuffer> {\n const keywordLength = keyword.length\n if (keywordLength < 1 || keywordLength > 79) throw new Error(`tEXt keyword 长度越界(1–79):${keywordLength}`)\n if (!parsePngChunks(png).some((chunk) => chunk.type === 'IHDR')) throw new Error('PNG 缺少 IHDR 块')\n\n const data = new Uint8Array([...latin1Bytes(keyword), 0, ...latin1Bytes(text)])\n const textChunk = encodeChunk('tEXt', data)\n\n // IHDR 块 = 签名后第一个块,注入点在其后(IHDR 定长 25 字节由 parse 定位,不硬编码)\n const view = new DataView(png.buffer, png.byteOffset, png.byteLength)\n const ihdrLength = view.getUint32(PNG_SIGNATURE.length)\n const insertAt = PNG_SIGNATURE.length + 12 + ihdrLength\n\n const out = new Uint8Array(png.length + textChunk.length)\n out.set(png.subarray(0, insertAt), 0)\n out.set(textChunk, insertAt)\n out.set(png.subarray(insertAt), insertAt + textChunk.length)\n return out\n}\n","/**\n * @hankchen/canvas-browser-renderer 公共出口。\n *\n * Canvas2D 五原语后端(begin → 按 priority 序分派 → end)+ 物化状态机(工单 04)。\n * 本包整体无 DOM lib 编译,Canvas2D/FontFace/Image 类型只在用到它的文件里局部\n * `/// <reference lib=\"dom\" />`。\n */\nexport const PACKAGE_NAME = '@hankchen/canvas-browser-renderer' as const\n\nexport {\n Canvas2DBackend,\n applyViewportTransform,\n type DrawableImage,\n type PreviewViewportTransform,\n type ViewportAwareBackend,\n} from './canvas2d-backend'\nexport { coverCrop, type CropWindow } from './cover'\nexport {\n builtinFontShorthand,\n canvasFontCssFamily,\n isBuiltinFontRef,\n loadCanvasFont,\n} from './fonts'\nexport { QR_FIXED_OPTIONS, qrDataUrl, qrModuleMatrix, type QrModuleMatrix } from './qr'\nexport {\n Materializer,\n createBrowserLoaders,\n fontResourceKey,\n imageResourceKey,\n qrResourceKey,\n withImageProxy,\n type MaterializerOptions,\n type ResourceEntry,\n type ResourceLoaders,\n type ResourceState,\n type ResourceStatus,\n} from './materializer'\nexport { drawResourceMarkers } from './markers'\nexport { sha256Hex } from './sha256'\nexport {\n PREVIEW_TEXT_KEYWORD,\n PREVIEW_TEXT_VALUE,\n exportPreviewPng,\n type PreviewPngOptions,\n type PreviewPngResult,\n} from './exportPng'\n// png.ts 的字节级工具(crc32/parsePngChunks 等)不进包公共出口:仅 exportPng 与\n// Node 测试(tests/png.test.ts 直连模块)消费,导出面保持最小。\n"],"mappings":"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","names":["forEachLayerBox","forEachLayerBox"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hankchen/canvas-browser-renderer",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "description": "Canvas2D 五原语渲染后端(矩形/图片 cover/文本基线消化、字体与 QR 物化)",
6
6
  "type": "module",
@@ -28,7 +28,7 @@
28
28
  "directory": "packages/canvas-browser-renderer"
29
29
  },
30
30
  "dependencies": {
31
- "@hankchen/canvas": "^0.2.0",
31
+ "@hankchen/canvas": "^0.3.0",
32
32
  "qrcode": "^1.5.4"
33
33
  },
34
34
  "devDependencies": {