@hankchen/canvas-image-renderer 0.0.0-stage → 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/backend.ts","../src/color.ts","../src/decode.ts","../src/exif.ts","../src/typography.ts","../src/url.ts","../src/text.ts","../src/resolver.ts","../src/qr.ts","../src/index.ts"],"sourcesContent":["/**\n * 位图渲染后端(对位 go-canvas/image-renderer/backend.go 的 Renderer):以\n * @napi-rs/canvas 新建透明画布为渲染面,实现核心 RenderBackend 五原语接入\n * 渲染模板;承载图像登记缝(setImage)、异步预备缝(preload)、EXIF 混合口径\n * 映射与 PNG 编码(end → encodeSync('png') 透明底产物,对位 go *image.NRGBA)。\n *\n * 失败即抛纪律(核心 render.ts 文件头契约):任一原语失败即抛——渲染面整体\n * 丢弃、end() 不达、失败后实例不复用(建面态残留,重建实例再渲染)。唯一例外\n * 是 drawImage 的 src 查不到/解码失败:核心契约的缺省约定为静默不画(JS 坏图\n * 现状 parity;对位 go 位图后端此二处报错中止——TS 侧有意从宽,两端口径见\n * drawImage/imageIntrinsicSize docblock)。\n *\n * 与 go 的结构性差异(实现形态,非语义):napi 的 Image 同步只出尺寸、可绘制\n * 位图须经 loadImage/decode() 异步解码,而核心 drawImage 是同步原语——故后端\n * 增设 preload(src) 异步预备缝,渲染前把本次要画的 src 装配进解码缓存;同步\n * 原语只消费缓存(未装配/装配失败一律静默不画)。默认 resolver(本包\n * createDefaultResolver)物化时对访问到的每个 src 自动预热,完整管线不受影响。\n *\n * 非并发安全(对位 go):渲染面与解码缓存按实例持有,并发渲染请各自新建实例;\n * 实例可跨多次渲染复用,begin 即重置。\n */\nimport { createCanvas, type Canvas as NapiCanvas, type Image as NapiImage, type SKRSContext2D } from '@napi-rs/canvas'\nimport type { BlitRect, Border, RenderBackend, TextDrawOptions } from '@hankchen/canvas'\n\nimport { cssColor, parseColor } from './color'\nimport { decodeHeaderSize, decodeDrawableImage, loadSourceBytes } from './decode'\nimport { canonicalSourceRect, exifOrientation, rawDimensions } from './exif'\nimport { registerFontData } from './typography'\nimport { drawTextLine } from './text'\n\n/** 解码缓存项:位图 + 该字节的 EXIF orientation(映射缝的同步输入) */\ninterface DecodedEntry {\n readonly image: NapiImage\n readonly orientation: number\n}\n\nexport class ImageRendererBackend implements RenderBackend {\n private surface: NapiCanvas | null = null\n private ctx: SKRSContext2D | null = null\n\n /** 图像登记表(核心 setImage 缝):键 = wire src 引用(`qr:` 前缀/远程 URL/任意键) */\n private readonly registry = new Map<string, Uint8Array>()\n\n /** 解码缓存:键 = src 引用;null = 装配失败标记(静默不画) */\n private readonly decoded = new Map<string, DecodedEntry | null>()\n\n /** begin 取渲染面;未建面即调用绘制原语属用法错误(模板保证先 begin) */\n private canvasCtx(): SKRSContext2D {\n if (this.ctx === null || this.surface === null) {\n throw new Error('位图渲染面未初始化:应经渲染模板 Begin 建面')\n }\n return this.ctx\n }\n\n /**\n * 渲染前异步预备:解析 src(登记表/data URL/本地路径)并解码出可绘制位图进\n * 缓存。幂等;返回是否可绘制。默认 resolver 物化时逐 src 调用——手工装配\n * 后端时须自行预热,否则 drawImage 静默不画(见文件头「结构性差异」注)\n */\n async preload(src: string): Promise<boolean> {\n if (this.decoded.has(src)) {\n return this.decoded.get(src) !== null\n }\n const bytes = loadSourceBytes(src, this.registry)\n if (bytes === null) {\n this.decoded.set(src, null)\n return false\n }\n const image = await decodeDrawableImage(bytes)\n if (image === null) {\n this.decoded.set(src, null)\n return false\n }\n this.decoded.set(src, { image, orientation: exifOrientation(bytes) })\n return true\n }\n\n /** 创建渲染面(width/height ≤0 报错;全新透明画布,对位 image.NewNRGBA) */\n begin(width: number, height: number): void {\n if (width <= 0 || height <= 0) {\n throw new Error(`渲染面尺寸非法: ${width}x${height}`)\n }\n this.surface = createCanvas(width, height)\n this.ctx = this.surface.getContext('2d')\n }\n\n /** 收尾:encodeSync('png') 透明底产物;未建面返回 null(对位 go 字面 nil) */\n end(): Uint8Array | null {\n if (this.surface === null) {\n return null\n }\n return new Uint8Array(this.surface.encodeSync('png'))\n }\n\n /**\n * 矩形盒:背景色 + 四边边框。背景为 null 或空串跳过填充(PHP 同款守卫);\n * 边框 = 四边各一条线宽=边框宽的直线(非描边矩形),按盒内嵌绘制,逐边可\n * null/零宽不计,序 top/bottom/left/right(PHP 绘制序,后画覆盖先画;\n * canvas source-over 合成 = go draw.Over 同语义)\n */\n drawRect(x: number, y: number, width: number, height: number, bgColor: string | null, border: Border): void {\n const ctx = this.canvasCtx()\n\n if (bgColor !== null && bgColor !== '') {\n ctx.fillStyle = cssColor(parseColor(bgColor))\n ctx.fillRect(x, y, width, height)\n }\n\n // 边框带公式与 go borderBands 逐条同式(盒内嵌;bottom/right 的 max 防越界收缩)\n const bands: Array<{ band: [number, number, number, number]; color: string }> = []\n if (border.top !== null && border.top.width > 0) {\n bands.push({ band: [x, y, width, border.top.width], color: border.top.color })\n }\n if (border.bottom !== null && border.bottom.width > 0) {\n const bandY = Math.max(y, y + height - border.bottom.width)\n bands.push({ band: [x, bandY, width, y + height - bandY], color: border.bottom.color })\n }\n if (border.left !== null && border.left.width > 0) {\n bands.push({ band: [x, y, border.left.width, height], color: border.left.color })\n }\n if (border.right !== null && border.right.width > 0) {\n const bandX = Math.max(x, x + width - border.right.width)\n bands.push({ band: [bandX, y, x + width - bandX, height], color: border.right.color })\n }\n for (const { band, color } of bands) {\n // 逐边颜色独立解析,解析失败 wrap 后上抛中止(对位 go fillEdge)\n let fill: string\n try {\n fill = cssColor(parseColor(color))\n } catch (error) {\n throw new Error(`解析边框色: ${(error as Error).message}`)\n }\n ctx.fillStyle = fill\n ctx.fillRect(band[0], band[1], band[2], band[3])\n }\n }\n\n /**\n * 图片双矩形 blit(ADR 0024):把 src 的 srcRect 子矩形拉伸搬运到 dstRect。\n * srcRect 是 raw(内在坐标)空间窗口——napi 位图是 canonical(EXIF 自动转正)\n * 空间,非整图窗口经 canonicalSourceRect 映射进 canonical(04 号决议像素面\n * 口径的落地机制,与 go「先裁后转」逐像素等价);整图哨兵(零宽高)直取\n * canonical 整图(raw 全图转正 = canonical 全图,恒等免映射)。\n * dstRect 尺寸 ≤0 防护先于读取跳过;src 未装配/装配失败/映射窗与位图不相交\n * 一律静默不画(缺省约定,见文件头)\n */\n drawImage(src: string, srcRect: BlitRect, dstRect: BlitRect): void {\n const ctx = this.canvasCtx()\n if (dstRect.width <= 0 || dstRect.height <= 0) {\n return\n }\n\n const entry = this.decoded.get(src)\n if (entry === undefined || entry === null) {\n return\n }\n const { image, orientation } = entry\n\n // canonical 位图界内的源窗;哨兵 → 整图(按解码实际尺寸,ADR 0024 接口契约)\n let window: BlitRect\n if (srcRect.width <= 0 || srcRect.height <= 0) {\n window = { x: 0, y: 0, width: image.width, height: image.height }\n } else {\n const raw = rawDimensions(image.width, image.height, orientation)\n window = canonicalSourceRect(srcRect, orientation, raw.width, raw.height)\n }\n\n // 与解码图求交(对位 go region.Intersect;不相交静默不画)\n const x0 = Math.max(0, Math.ceil(window.x))\n const y0 = Math.max(0, Math.ceil(window.y))\n const x1 = Math.min(image.width, window.x + window.width)\n const y1 = Math.min(image.height, window.y + window.height)\n if (x1 <= x0 || y1 <= y0) {\n return\n }\n\n ctx.drawImage(image, x0, y0, x1 - x0, y1 - y0, dstRect.x, dstRect.y, dstRect.width, dstRect.height)\n }\n\n /**\n * 单行文本(对位 go text.go DrawText):原语层永远真实字体绘制——字体装配/\n * TTC 拒收/内置默认字体判定/对齐锚点与基线差/角度旋转见 text.ts\n * (drawTextLine)。空行零副作用;字色解析失败中止\n */\n drawText(line: string, x: number, y: number, options: TextDrawOptions): void {\n const ctx = this.canvasCtx()\n drawTextLine(ctx, line, x, y, options)\n }\n\n /**\n * 源图内在尺寸度量(ADR 0023/0024 查询口):返 raw(未转正)尺寸——对位\n * go image.DecodeConfig / PHP getimagesize 的内在坐标系。查不到/解码失败返\n * null → 核心统一退化 fill 形态。\n * 两路在「raw 出参」契约上收敛(napi 同步/异步口径差异的实测收敛,见\n * decode.ts decodeHeaderSize docblock):\n * - preload 缓存命中:位图是 canonical(异步解码已转正),按 DecodedEntry\n * 已存的 orientation ≥5 互换换回 raw(评审 P0-3:缓存臂不重读磁盘重算\n * orientation——字节不可得时兜底 1 会产出 canonical 错误空间尺寸;不产出\n * 空间错误的尺寸,弃用既有已存数据更是错上加错);\n * - 未装配 src:同步头部探测直出 raw 头尺寸(同步口不施加转正),原样返回\n */\n imageIntrinsicSize(src: string): { width: number; height: number } | null {\n const cached = this.decoded.get(src)\n if (cached !== undefined && cached !== null) {\n return rawDimensions(cached.image.width, cached.image.height, cached.orientation)\n }\n const bytes = loadSourceBytes(src, this.registry)\n if (bytes === null) {\n return null\n }\n return decodeHeaderSize(bytes)\n }\n\n /**\n * 图像登记缝(核心 setImage 契约):物化器按键登记资源字节,键即 drawImage\n * 收到的 src 引用。字节拷贝入表(所有权归登记表,防外部原地改写);同键重\n * 登记使解码缓存失效(下次 preload 重装配)\n */\n setImage(key: string, data: Uint8Array): void {\n this.registry.set(key, new Uint8Array(data))\n this.decoded.delete(key)\n }\n\n /**\n * 字体物化缝(resolver 的远程字体下载字节按键登记;核心 ResourceResolver\n * 契约「注册缝归后端包契约」的本包落点):TTC 拒收与 GlobalFonts 注册在\n * 此处完成(物化阶段 fail-fast,渲染不带病启动),drawText 以 wire font\n * 引用(URL 原文)直取。幂等(同字节同别名)\n */\n registerFont(ref: string, data: Uint8Array): void {\n registerFontData(ref, data)\n }\n}\n","/**\n * 颜色解析(对位 go-canvas/image-renderer/color.go 同式复制,注明出处;\n * go 侧先例:color/exif/qr 小件在两 module 复制,TS 侧核心包零依赖红线\n * 禁止收纳,故落本包)。wire 面与图层选项只产出 CSS 十六进制写法(PHP\n * intervention 还接受颜色名与 rgb() 函数串,本项目不使用),故仅支持 hex\n * 形态,其余报错中止渲染(原语失败即抛纪律)。\n */\n\n/** 直 alpha 颜色分量(0–255,无预乘;对位 go color.NRGBA) */\nexport interface NrgbaColor {\n r: number\n g: number\n b: number\n a: number\n}\n\n/**\n * 解析十六进制颜色为直 alpha 分量。支持 #rgb / #rgba / #rrggbb / #rrggbbaa,\n * 前导 # 可省略;3/4 位逐位翻倍后走同一累积路径;6 位 alpha=255。\n * 非法长度/非法十六进制位即抛(错误消息对位 go color.go 文案)\n */\nexport function parseColor(s: string): NrgbaColor {\n const h = s.startsWith('#') ? s.slice(1) : s\n let hex: string\n switch (h.length) {\n case 3:\n case 4: {\n // 逐位翻倍:#f00 → ff0000\n hex = Array.from(h, (ch) => ch + ch).join('')\n break\n }\n case 6:\n case 8:\n hex = h\n break\n default:\n throw new Error(`无法解析颜色 ${JSON.stringify(s)}: 支持 #rgb/#rgba/#rrggbb/#rrggbbaa`)\n }\n\n const c = [0, 0, 0, 0]\n for (let i = 0; i < hex.length; i += 1) {\n c[i / 2 | 0] = c[i / 2 | 0]! * 16 + hexDigit(hex[i]!, s)\n }\n if (hex.length === 6) {\n c[3] = 255\n }\n return { r: c[0]!, g: c[1]!, b: c[2]!, a: c[3]! }\n}\n\nfunction hexDigit(ch: string, source: string): number {\n const code = ch.charCodeAt(0)\n if (code >= 0x30 && code <= 0x39) return code - 0x30 // 0-9\n if (code >= 0x61 && code <= 0x66) return code - 0x61 + 10 // a-f\n if (code >= 0x41 && code <= 0x46) return code - 0x41 + 10 // A-F\n throw new Error(`无法解析颜色 ${JSON.stringify(source)}: 非法十六进制位 ${JSON.stringify(ch)}`)\n}\n\n/**\n * napi canvas fillStyle 字符串(css rgba() 函数式,alpha 归一到 0–1)。\n * canvas 2D 默认 source-over 合成 = go draw.Over 同语义(后画覆盖先画)\n */\nexport function cssColor(c: NrgbaColor): string {\n return `rgba(${c.r},${c.g},${c.b},${c.a / 255})`\n}\n","/**\n * 图片字节装载与解码(对位 go decode.go + blitSource 共享前段的读源半边):\n * 三源依次查——登记表(setImage 键,`qr:` 前缀与物化资源同门)→ data URL →\n * 本地路径。远程 URL 不进原语同步路径(网络 I/O 归 ResourceResolver 物化,\n * 未物化的远程引用按查不到处置)。\n */\nimport { Image, loadImage, type Image as NapiImage } from '@napi-rs/canvas'\nimport { readFileSync } from 'node:fs'\n\n/** 登记面最小结构(ImageRendererBackend 的 setImage 登记表同构,解耦复用) */\nexport interface ByteRegistry {\n get(key: string): Uint8Array | undefined\n}\n\n/**\n * 按引用装载图片字节:登记表命中优先;data URL 仅支持 base64 形态;其余按\n * 本地路径读盘(文件视为渲染会话内不可变,对位 go os.ReadFile)。查不到返回\n * null——由调用方按各自契约处置(drawImage 静默不画 / imageIntrinsicSize\n * 返 null 退化 fill)\n */\nexport function loadSourceBytes(src: string, registry?: ByteRegistry): Uint8Array | null {\n const hit = registry?.get(src)\n if (hit) {\n return hit\n }\n if (src.startsWith('data:')) {\n const comma = src.indexOf(',')\n if (comma < 0 || !src.slice(0, comma).endsWith(';base64')) {\n return null\n }\n return new Uint8Array(Buffer.from(src.slice(comma + 1), 'base64'))\n }\n // 带 scheme 的远程引用静默不装(Windows 盘符路径非本仓宿主面);本地路径读盘\n if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\\/\\//.test(src)) {\n return null\n }\n try {\n return new Uint8Array(readFileSync(src))\n } catch {\n return null\n }\n}\n\n/**\n * 同步头部尺寸探测:napi Image 的 width/height 在 src 赋值后同步可得(位图\n * raster 才需 await decode)。损坏/空字节 width 归 0 → null。\n *\n * 实测钉死(1.0.13,精确钉版 + exif 测试互检):同步 width/height 是 **raw**\n * (EXIF 转正前)的头尺寸——转正发生在异步解码的 raster 阶段(async loadImage\n * 出 canonical 位图)。故本探测结果直返即查询面要的 raw 内在尺寸,无需再按\n * orientation 换算(见 backend.imageIntrinsicSize 两路收敛注释)\n */\nexport function decodeHeaderSize(bytes: Uint8Array): { width: number; height: number } | null {\n const img = new Image()\n img.src = Buffer.from(bytes)\n if (img.width <= 0 || img.height <= 0) {\n return null\n }\n return { width: img.width, height: img.height }\n}\n\n/**\n * 异步解码出可绘制位图(napi 实测 1.0.13:同步只出尺寸,raster 须经\n * loadImage/decode() 才可 drawImage——后端 preload 缝的实现底座)。\n * 解码失败返回 null(调用方标记该 src 静默不画)\n */\nexport async function decodeDrawableImage(bytes: Uint8Array): Promise<NapiImage | null> {\n try {\n return await loadImage(Buffer.from(bytes))\n } catch {\n return null\n }\n}\n","/**\n * EXIF orientation:JPEG 的 APP1(Exif) 段携带拍摄方向。node 侧混合口径(04 号\n * 决议)——像素面吃 napi 解码层自动转正(canonical 空间),本模块只承担两件\n * 同步职责:① 从字节流读 orientation(drawImage 的 raw→canonical 窗口映射与\n * imageIntrinsicSize 的 raw 尺寸互换都需要它,二者皆为同步原语);② 把 raw\n * (内在)坐标窗口映射进 canonical 位图坐标(canonicalSourceRect)。\n *\n * 解析器对位 go-canvas/image-renderer/exif.go 同式复制(注明出处;go 侧先例:\n * color/exif/qr 小件在两 module 复制)——只找 IFD0 的 Orientation 标签(0x0112),\n * 其余结构一概不碰。工票原案「用 exifr 读 orientation」在同步原语契约下不可行\n * (exifr 全异步、核心 imageIntrinsicSize/drawImage 为同步口),改用本同式解析器:\n * 与 go 逐字节同语义、测试与 go exif_test 同手法互检,「≥5 互换返 raw」语义不变。\n */\n\n/**\n * 从图片字节流解析 EXIF orientation(1–8)。非 JPEG、无 EXIF、结构截断/非法一律\n * 归 1(无需转正)——与 go exifOrientation 同语义\n */\nexport function exifOrientation(data: Uint8Array): number {\n if (data.length < 4 || data[0] !== 0xff || data[1] !== 0xd8) {\n return 1 // 非 JPEG(PNG/WebP 等不携带该 EXIF 结构)\n }\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength)\n let pos = 2\n while (pos + 4 <= data.length && data[pos] === 0xff) {\n const marker = data[pos + 1]!\n if (marker === 0xff) {\n pos += 1 // 标记前的填充字节,步进一位重新判读\n continue\n }\n if (marker === 0xd8 || marker === 0x01 || marker === 0xd9) {\n pos += 2 // SOI/TEM/EOI 无段载荷\n continue\n }\n const segLen = view.getUint16(pos + 2)\n if (segLen < 2 || pos + 2 + segLen > data.length) {\n return 1 // 截断或非法段长\n }\n if (marker === 0xda) {\n return 1 // SOS:熵编码开始,EXIF 只会更靠前\n }\n if (marker === 0xe1 && segLen >= 8 && isExifHeader(data, pos + 4)) {\n return parseTIFFOrientation(data.subarray(pos + 10, pos + 2 + segLen))\n }\n pos += 2 + segLen\n }\n return 1\n}\n\nfunction isExifHeader(data: Uint8Array, offset: number): boolean {\n // \"Exif\\0\\0\"\n return (\n data[offset] === 0x45 &&\n data[offset + 1] === 0x78 &&\n data[offset + 2] === 0x69 &&\n data[offset + 3] === 0x66 &&\n data[offset + 4] === 0x00 &&\n data[offset + 5] === 0x00\n )\n}\n\n/** 从 TIFF 头起解析 IFD0 的 Orientation 值(SHORT/LONG 均兼容) */\nfunction parseTIFFOrientation(tiff: Uint8Array): number {\n if (tiff.length < 8) {\n return 1\n }\n const le = tiff[0] === 0x49 && tiff[1] === 0x49 // \"II\"\n const be = tiff[0] === 0x4d && tiff[1] === 0x4d // \"MM\"\n if (!le && !be) {\n return 1\n }\n const view = new DataView(tiff.buffer, tiff.byteOffset, tiff.byteLength)\n const u16 = (o: number) => view.getUint16(o, le)\n const u32 = (o: number) => view.getUint32(o, le)\n\n if (u16(2) !== 42) {\n return 1\n }\n const ifd0 = u32(4)\n if (ifd0 < 8 || ifd0 + 2 > tiff.length) {\n return 1\n }\n const entries = u16(ifd0)\n for (let i = 0; i < entries; i += 1) {\n const e = ifd0 + 2 + i * 12\n if (e + 12 > tiff.length) {\n return 1\n }\n if (u16(e) !== 0x0112) {\n continue // Orientation\n }\n let v: number\n switch (u16(e + 2)) {\n // 字段类型:3 SHORT / 4 LONG,值内联于条目\n case 3:\n v = u16(e + 8)\n break\n case 4:\n v = u32(e + 8)\n break\n default:\n return 1\n }\n return v >= 1 && v <= 8 ? v : 1\n }\n return 1\n}\n\n/** blit 矩形(对位核心包 BlitRect;此处收窄为本包内用形,避免循环依赖负担) */\nexport interface SourceRect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/**\n * raw(内在坐标)窗口 → canonical(napi 转正后位图)窗口。\n *\n * 04 号决议像素面口径的落地机制:go 是「先裁后转」(裁窗在内在坐标系、转正后置),\n * napi 是「解码即转正」——orientation 是逐像素双射,裁窗与转正可交换,把 raw 窗口\n * 经转正映射搬进 canonical 位图即得同一块像素(任意窗口精确等价,居中 cover 只是\n * 最常见入参)。映射公式由 go applyOrientation 的 canonical(dx,dy)=raw(sx,sy)\n * 八向公式反解(互检见 tests/exif.test.ts 的手算字面量与 go 期望栅格):\n * - 2–4 镜像族:维度不变,原点按镜像轴翻折;\n * - 5–8 旋转族:宽高互换,原点按转置轴翻折。\n *\n * orientation 由 exifOrientation 保证在 1–8;sw/sh 为 raw 尺寸\n */\nexport function canonicalSourceRect(\n raw: SourceRect,\n orientation: number,\n sw: number,\n sh: number,\n): SourceRect {\n const { x, y, width, height } = raw\n switch (orientation) {\n case 2: // 水平镜像\n return { x: sw - x - width, y, width, height }\n case 3: // 旋转 180°\n return { x: sw - x - width, y: sh - y - height, width, height }\n case 4: // 垂直镜像\n return { x, y: sh - y - height, width, height }\n case 5: // 转置(主对角镜像)\n return { x: y, y: x, width: height, height: width }\n case 6: // 旋转 90° 顺时针\n return { x: sh - y - height, y: x, width: height, height: width }\n case 7: // 反转置(反对角镜像)\n return { x: sh - y - height, y: sw - x - width, width: height, height: width }\n case 8: // 旋转 90° 逆时针\n return { x: y, y: sw - x - width, width: height, height: width }\n default: // 1 或未知:无 EXIF 语义,直通\n return { x, y, width, height }\n }\n}\n\n/**\n * canonical 位图尺寸对应的 raw 尺寸(imageIntrinsicSize 查询面):orientation ≥5\n * (90° 族)时 napi 解出的 w/h 已互换,再换回来即 raw 内在尺寸——对位 go\n * image.DecodeConfig(内在坐标系未转正)与 PHP getimagesize\n */\nexport function rawDimensions(canonicalWidth: number, canonicalHeight: number, orientation: number): {\n width: number\n height: number\n} {\n return orientation >= 5\n ? { width: canonicalHeight, height: canonicalWidth }\n : { width: canonicalWidth, height: canonicalHeight }\n}\n","/**\n * 字体装配与真实度量(对位 go-canvas/image-renderer/typography 包——本包唯一\n * 字体加载入口,绘制端 text.ts 与度量端共用同一装配配置,保证度量与绘制口径\n * 同源)。\n *\n * TTC fail-fast(04/05 号决议):go image 侧对 TTC 取首个 face(静默),node\n * 两后端统一拒收——魔数嗅探 `ttcf` 即抛,响亮报错优于 napi「能吃但静默取默认\n * 面」的多面 CJK 集合风险;对 go 是有意收紧,不是镜像。\n *\n * napi 装配机制:GlobalFonts 仅提供 registerFromPath(1.0.13 无 buffer 注册口),\n * 下载字节须落盘后按路径注册;family 别名用字节内容派生(sha256 前缀),同字节\n * 重注册幂等、同路径重注册不再冲突。解析结果(ref → 别名)进程内缓存——字体\n * 文件视为进程生命周期内不可变(对位 go parsedFonts sync.Map)。\n *\n * 度量器(08 号决议 v1 单组件,对位 go OpenTypeMeasurerFactory):离屏\n * measuring canvas + ctx.measureText 返真实宽度;加载失败(缺文件/解析失败/\n * 远程字体尚未物化)降级核心启发式度量,布局不中断;经核心\n * TextLayoutPolicies.measurerFactory 显式注入,缺省不注入(启发式对齐版,\n * 开箱行级与 PHP/Go 一致)。\n */\nimport { createCanvas, GlobalFonts, type SKRSContext2D } from '@napi-rs/canvas'\nimport { createHash } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\n\nimport { createHeuristicMeasurer, type TextMeasurer, type TextMeasurerFactory } from '@hankchen/canvas'\n\nimport { isNumeric } from './url'\n\n/** 内置默认字体判定:空串/纯数字字体 id(对位 go typography.IsBuiltinFont)。\n * 空串与纯数字同归默认字面——判定与 resolver 跳过字体物化共用 isNumeric 同一口径\n * (src/url.ts,防漂移)。度量与绘制共用本判定(对位 go measurer.go docblock) */\nexport function isBuiltinFont(font: string): boolean {\n return font === '' || isNumeric(font)\n}\n\n/** TTC/OTC 集合魔数(fail-fast 拒收面;go image 取首 face 的有意收紧见文件头) */\nfunction assertNotTtc(data: Uint8Array, ref: string): void {\n if (data.length >= 4 && data[0] === 0x74 && data[1] === 0x74 && data[2] === 0x63 && data[3] === 0x66) {\n throw new Error(`字体 ${ref} 为 TTC/OTC 集合: 本渲染端 fail-fast 拒收(魔数 ttcf;多面 CJK 集合静默取默认面是最危险的静默错误)`)\n }\n}\n\n/** 字节内容派生稳定别名(同字节同别名:重注册幂等、napi 侧不冲突) */\nfunction fontAlias(data: Uint8Array): string {\n return `ncv-${createHash('sha256').update(data).digest('hex').slice(0, 24)}`\n}\n\n/** ref → napi family 别名(进程内缓存;字体文件视为不可变) */\nconst registeredFonts = new Map<string, string>()\n\n/** 下载字体字节的落盘目录(napi 仅提供 registerFromPath,字节面须先成文件) */\nfunction fontCacheDir(): string {\n const dir = join(tmpdir(), 'js-canvas-image-renderer-fonts')\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n return dir\n}\n\n/**\n * 按路径装配字体:读字节 → TTC 嗅探拒收 → 内容派生别名 → registerFromPath。\n * 失败即抛(绘制端:中止渲染,对位 go sessionFace 失败通道;度量端捕获后降级\n * 启发式)。返回 CSS family 简写用的别名\n */\nfunction loadFontFamilyByPath(path: string, ref: string): string {\n const cached = registeredFonts.get(ref)\n if (cached !== undefined) {\n return cached\n }\n let data: Uint8Array\n try {\n data = new Uint8Array(readFileSync(path))\n } catch (error) {\n throw new Error(`读取字体 ${ref}: ${(error as Error).message}`)\n }\n return registerFontBytes(data, ref)\n}\n\n/** 按字节装配字体(远程字体物化缝):TTC 嗅探 → 落盘 → registerFromPath */\nfunction registerFontBytes(data: Uint8Array, ref: string): string {\n assertNotTtc(data, ref)\n const alias = fontAlias(data)\n if (GlobalFonts.has(alias)) {\n registeredFonts.set(ref, alias)\n return alias\n }\n const file = join(fontCacheDir(), `${alias}.font`)\n if (!existsSync(file)) {\n writeFileSync(file, data)\n }\n try {\n GlobalFonts.registerFromPath(file, alias)\n } catch (error) {\n throw new Error(`解析字体 ${ref}: ${(error as Error).message}`)\n }\n if (!GlobalFonts.has(alias)) {\n throw new Error(`解析字体 ${ref}: 注册后未命中别名(非 sfnt 字体文件?)`)\n }\n registeredFonts.set(ref, alias)\n return alias\n}\n\n/**\n * 远程字体物化缝:resolver 把下载字节按键(URL 原文)登记,drawText 以 wire\n * font 引用直取。字节面在此处完成 TTC 拒收与注册(物化阶段 fail-fast,渲染\n * 不带病启动);幂等(同字节同别名)\n */\nexport function registerFontData(ref: string, data: Uint8Array): void {\n registerFontBytes(data, ref)\n}\n\n/**\n * 字体引用 → CSS family 别名(内置默认 → 'sans-serif',即渲染端默认字体族;\n * 对位 go BuiltinFace 的「内置语义」位)。装配失败即抛——绘制端原样上抛中止,\n * 度量端经 createNapiMeasurerFactory 捕获降级\n */\nexport function loadFontFamily(font: string): string {\n if (isBuiltinFont(font)) {\n return 'sans-serif'\n }\n return loadFontFamilyByPath(font, font)\n}\n\n/** 离屏 measuring canvas(懒建;进程级共享,度量端单线程串行使用) */\nlet measuringCtx: SKRSContext2D | null = null\n\nfunction ensureMeasuringCtx(): SKRSContext2D {\n if (measuringCtx === null) {\n measuringCtx = createCanvas(16, 16).getContext('2d')\n }\n return measuringCtx\n}\n\n/**\n * napi 真实字体度量器工厂(08 号决议 v1 单组件;对位 go OpenTypeMeasurerFactory):\n * 每次工厂调用新建独立度量器(go 三组件「工厂每次新建」先例),度量前按图层\n * 字体与字号重设 ctx.font(同「用前设置」纪律)。\n *\n * 基线换算实测钉死(1.0.13):TextMetrics.fontBoundingBoxAscent/Descent 为\n * 「基线距字盒顶/底」正值口径,与 go x/image Metrics 的 Ascent / Descent(正值\n * 约定)同号同义——绘制端基线差消化用这对字段(见 text.ts);而\n * alphabeticBaseline/ideographicBaseline 实测是「相对基线自身」的偏移量\n * (alphabetic 恒 0、ideographic 为 CJK 基线相对 alphabetic 的偏移),不是\n * 「距盒顶」口径,不作基线锚点用。宽度即 metrics.width 像素浮点(与落笔同一\n * 度量路径,对位 go MeasureString 与 DrawString 同路径)。\n *\n * 加载失败(缺文件/解析失败/远程字体尚未物化)与非正字号降级核心启发式度量,\n * 布局不中断(对位 go 工厂降级臂);TTC 视为加载失败同降级(拒收只在绘制端\n * fail-fast——度量中断会破坏整个布局,两侧差异是刻意的)\n */\nexport const createNapiMeasurerFactory: TextMeasurerFactory = (font, fontSize) => {\n let family: string\n try {\n if (!Number.isFinite(fontSize) || fontSize <= 0) {\n return createHeuristicMeasurer(fontSize)\n }\n family = loadFontFamily(font)\n } catch {\n return createHeuristicMeasurer(fontSize)\n }\n const ctx = ensureMeasuringCtx()\n // 别名(ncv-hex)与 sans-serif 均为 CSS 安全裸字面,不加引号——带引号的\n // \"sans-serif\" 会按字面族名解析、回退到不可控的默认字体(实测 1.0.13)\n const shorthand = `${fontSize}px ${family}`\n return {\n measure(text: string): number {\n ctx.font = shorthand\n return ctx.measureText(text).width\n },\n } satisfies TextMeasurer\n}\n","/**\n * URL 判定小件(对位 go resolver/resolver.go isRemoteURL/IsNumeric 的渲染端消费面;\n * 同式复制先例 = pdf-renderer src/url.ts,两后端包互不 import 的红线内各自持有)。\n * 与 resolver 默认实现(resolver.ts)、字体装配(typography.ts isBuiltinFont)\n * 共用同一口径,防多处判定漂移。\n */\n\n/**\n * isRemoteURL 是否远程 URL:协议无关,凡可解析出 scheme 与 host 即视为远程\n * (对齐 PHP FILTER_VALIDATE_URL 的「须带协议与主机」;支持的协议面由注入的\n * fetch/Downloader 决定),其余一律按本地路径直接使用\n */\nexport function isRemoteURL(s: string): boolean {\n try {\n const url = new URL(s)\n return url.protocol !== '' && url.host !== ''\n } catch {\n return false\n }\n}\n\n/**\n * isNumeric 纯数字判定:近似 PHP is_numeric(整型/浮点字面量)。纯数字字体 id 是\n * 旧库 GD 内置字体编号,语义 = 「无字体文件」→渲染端内置默认字体;本函数是 resolver\n * 跳过字体物化与 typography.isBuiltinFont 回退默认字体的**同一口径**(对位 go\n * resolver.IsNumeric 导出防漂移的分工)\n */\nexport function isNumeric(s: string): boolean {\n if (s === '') return false\n return Number.isFinite(Number(s))\n}\n","/**\n * 文本绘制原语(对位 go-canvas/image-renderer/text.go):单行、对齐锚点落笔、\n * 角度旋转。(x, y) 为对齐语义锚点——水平按真实文本宽度(ctx.measureText.width,\n * 与落笔同一度量路径,对位 go MeasureString)对齐;垂直用字盒基线差把基线落到\n * 锚点语义对应位置;布局层只返回纯对齐锚点,字体基线差统一在渲染端消化\n * (08 号决议分层纪律:原语层永远真实字体绘制)。\n *\n * 基线换算(关键缝,实测钉死见 typography.ts docblock):canvas 的\n * fontBoundingBoxAscent/Descent 与 go x/image Metrics 的 Ascent/Descent 同号\n * 同义(正值、基线距字盒顶/底),go 的三条基线公式逐条平移——\n * top(含未知值缺省臂): baseline = y + ascent\n * center: baseline = y + (ascent - descent) / 2\n * bottom: baseline = y - descent\n *\n * 角度:单位度,正值逆时针(GD 惯例)。路径对位 go drawRotatedText——离屏\n * 渲染后绕锚点旋转移到渲染面;canvas transform 矩阵与 go f64.Aff3 同式\n * ([[cos,sin],[-sin,cos]],y 轴向下的视觉逆时针)。\n */\nimport { createCanvas, type SKRSContext2D } from '@napi-rs/canvas'\nimport type { TextDrawOptions } from '@hankchen/canvas'\n\nimport { cssColor, parseColor } from './color'\nimport { loadFontFamily } from './typography'\n\n/** napi measureText 返回面(d.ts 内部接口未导出,经方法返回类型取) */\ntype TextMetricsLike = ReturnType<SKRSContext2D['measureText']>\n\n/** 基线相对锚点的垂直偏移(go DrawText verticalAlign 三臂同式平移) */\nfunction baselineOffset(metrics: TextMetricsLike, verticalAlign: string): number {\n const ascent = metrics.fontBoundingBoxAscent ?? metrics.actualBoundingBoxAscent\n const descent = metrics.fontBoundingBoxDescent ?? metrics.actualBoundingBoxDescent\n switch (verticalAlign) {\n case 'center':\n return (ascent - descent) / 2\n case 'bottom':\n return -descent\n default:\n return ascent // top 与未知值缺省臂(对位 go switch default)\n }\n}\n\n/** 笔起点相对锚点的水平偏移(go DrawText horizontalAlign 三臂同式平移) */\nfunction penOffset(width: number, horizontalAlign: string): number {\n switch (horizontalAlign) {\n case 'center':\n return -width / 2\n case 'right':\n return -width\n default:\n return 0 // left 与未知值缺省臂\n }\n}\n\n/**\n * 在渲染面上绘制单行文本。空行零副作用(PHP 同款空串守卫);字色解析失败\n * wrap「解析字色」上抛中止;字体装配失败(缺文件/TTC 拒收)上抛中止\n */\nexport function drawTextLine(ctx: SKRSContext2D, line: string, x: number, y: number, options: TextDrawOptions): void {\n if (line === '') {\n return\n }\n let fill: string\n try {\n fill = cssColor(parseColor(options.fontColor))\n } catch (error) {\n throw new Error(`解析字色: ${(error as Error).message}`)\n }\n\n const family = loadFontFamily(options.font)\n // 裸字面(别名 ncv-hex / sans-serif 均 CSS 安全);带引号会按字面族名解析、\n // 回退到不可控的默认字体(实测 1.0.13)\n const shorthand = `${options.fontSize}px ${family}`\n ctx.font = shorthand\n ctx.textAlign = 'left' // 对齐偏移自算,免疫共享 ctx 的残留状态\n ctx.textBaseline = 'alphabetic'\n\n const metrics = ctx.measureText(line)\n const width = metrics.width\n const penX = penOffset(width, options.horizontalAlign)\n const baselineY = baselineOffset(metrics, options.verticalAlign)\n\n if (options.angle === 0) {\n ctx.fillStyle = fill\n ctx.fillText(line, x + penX, y + baselineY)\n return\n }\n\n drawRotatedText(ctx, line, fill, shorthand, metrics, x, y, options.angle, penX, baselineY)\n}\n\n/**\n * 角度支持(对位 go drawRotatedText):文本先离屏渲染,再绕锚点 (x, y) 旋转\n * 合成到渲染面。离屏盒宽 = 文本宽向上取整、高 = 字盒高(ascent+descent),\n * 四周留白 margin = 2 + ceil(盒高/16)——字形可越出 em box,留白随字面高度比例\n * 放宽、下限 2px 兜底小字号(go 同式)。\n * 锚点在离屏中的位置 = 离屏笔起点 - (笔偏移, 基线偏移);仿射矩阵按\n * 「源坐标 → 目标坐标」:dest = R(θ)·(src - 锚点离屏) + 锚点渲染面,经\n * translate(锚点) + transform(R, -锚点离屏) 合成(与 go f64.Aff3 展开式逐项等价)\n */\nfunction drawRotatedText(\n ctx: SKRSContext2D,\n line: string,\n fill: string,\n shorthand: string,\n metrics: TextMetricsLike,\n x: number,\n y: number,\n angle: number,\n penX: number,\n baselineY: number,\n): void {\n const ascent = metrics.fontBoundingBoxAscent ?? metrics.actualBoundingBoxAscent\n const descent = metrics.fontBoundingBoxDescent ?? metrics.actualBoundingBoxDescent\n const boxHeight = ascent + descent\n\n const margin = 2 + Math.ceil(boxHeight / 16)\n const offscreen = createCanvas(Math.ceil(metrics.width) + 2 * margin, Math.ceil(boxHeight) + 2 * margin)\n const offCtx = offscreen.getContext('2d')\n offCtx.font = shorthand\n offCtx.fillStyle = fill\n offCtx.textAlign = 'left'\n offCtx.textBaseline = 'alphabetic'\n offCtx.fillText(line, margin, margin + ascent)\n\n const ax = margin - penX\n const ay = margin + ascent - baselineY\n const rad = (angle * Math.PI) / 180\n const cos = Math.cos(rad)\n const sin = Math.sin(rad)\n\n ctx.save()\n ctx.translate(x, y)\n // canvas transform(a,b,c,d,e,f):x' = a·x + c·y + e, y' = b·x + d·y + f;\n // (a,b,c,d) = (cos,-sin,sin,cos) 即视觉逆时针旋转阵(y 轴向下坐标系)\n ctx.transform(cos, -sin, sin, cos, -ax, -ay)\n ctx.drawImage(offscreen, 0, 0)\n ctx.restore()\n}\n","/**\n * 默认 ResourceResolver(对位 go NewDefaultResolver + resolver/resolver.go 默认\n * 实现):HTTP 下载(核心 FetchLike 结构面,Node ≥18 全局 fetch 即契合)+ 缓存\n * (进程内内存 + 可选落盘目录,缓存键同式:图片/字体 = sha256(完整URL) hex +\n * URL path 扩展名,QR = sha256(text_width) + .png)+ QR 物化前置注入(后置选项\n * 可覆写)。物化走画布树收集资源(背景图/ImageLayer src/TextLayer font/\n * QrCodeLayer value),字节经核心 setImage 缝以 wire src 同键登记——readonly\n * 领域树零拷贝,物化结果不进 wire(decode-encode 往返恒等不触碰,对位 go\n * 「resolver 回写图层 SetResolvedSrc」在 readonly 约束下的等价语义)。\n *\n * 字体物化:远程 URL 字体下载后经本包字体缝 registerFont 登记(字体非图像,\n * 不经 setImage——核心 resolver.ts 契约明文);空串/纯数字/本地路径跳过(与\n * isBuiltinFont 同一口径,对位 go materializeFont 跳过条件)。本地路径字体由\n * drawText 直读。\n *\n * preload 预热接线:napi 位图异步解码的装配前提(backend.preload 缝,见\n * backend.ts 文件头)——遍历中对访问到的每个图像 src(含本地路径与 QR 键)\n * 预热,未命中/解码失败静默(渲染面按各自契约降级:drawImage 不画、\n * imageIntrinsicSize 走同步探测)。手写 setImage 的宿主不经此接线,需自行预热。\n *\n * 渲染期取消(go ctx 检查点)v1 不做(06 号决议 E1 记雾区)。\n */\nimport { createHash } from 'node:crypto'\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport {\n qrImageSrc,\n ResourceMaterializeError,\n layerWidth,\n type Canvas,\n type FetchLike,\n type ImageRegisteringBackend,\n type Layer,\n type QRMaterializer,\n type ResourceResolver,\n} from '@hankchen/canvas'\n\nimport { QrCodeMaterializer } from './qr'\nimport { isNumeric, isRemoteURL } from './url'\n\n/** 本包后端渲染缝的最小结构面:核心登记缝 + 字体登记 + 预热(均后端自决实现) */\nexport interface MaterializationBackend extends ImageRegisteringBackend {\n /** 字体物化缝(ImageRendererBackend 实现;远程 URL 字体字节按键登记) */\n registerFont?(ref: string, data: Uint8Array): void\n /** 渲染前异步预热缝(ImageRendererBackend 实现;位图装配前提) */\n preload?(src: string): Promise<boolean>\n}\n\nexport interface DefaultResolverOptions {\n /** fetch 注入缝(代理/鉴权/测试桩);缺省全局 fetch */\n fetchLike?: FetchLike\n /** 落盘缓存根目录;缺省仅内存缓存。子目录 img_layers/text_layers/qr_layers(go 同名) */\n cacheRoot?: string\n /** QR 物化缝;缺省接 QrCodeMaterializer(前置注入),显式 null = 不接线(遇\n * 二维码图层报 qr_materializer_required),传自定义实现则覆写默认 */\n qrMaterializer?: QRMaterializer | null\n}\n\n/** 缓存子目录(对位 go resolver:img_layers / text_layers / qr_layers) */\nconst SUB_IMAGES = 'img_layers'\nconst SUB_FONTS = 'text_layers'\nconst SUB_QR = 'qr_layers'\n\n/**\n * 构造带二维码物化的默认解析器(对位 go NewDefaultResolver):QR 缝固定前置\n * 注入,options 后置可覆写;fetch 缺省全局 fetch,缓存缺省仅内存。同一 resolver\n * 实例的内存缓存跨多次 resolve 共享(命中即跳过,不下载不覆写;对位 go 命中\n * 跳过语义,无失效机制)\n */\nexport function createDefaultResolver(options: DefaultResolverOptions = {}): ResourceResolver {\n const fetchLike = options.fetchLike ?? ((url: string) => globalThis.fetch(url))\n const qr = options.qrMaterializer === undefined ? new QrCodeMaterializer() : options.qrMaterializer\n const memory = new Map<string, Uint8Array>()\n const cacheRoot = options.cacheRoot\n\n async function cachedRemoteBytes(sub: string, rawUrl: string): Promise<Uint8Array> {\n const key = `${sub}:${cacheKey(rawUrl)}`\n const hit = memory.get(key)\n if (hit !== undefined) {\n return hit\n }\n\n const file = cacheRoot === undefined ? null : join(cacheRoot, sub, cacheKey(rawUrl))\n if (file !== null) {\n try {\n const bytes = new Uint8Array(readFileSync(file))\n memory.set(key, bytes)\n return bytes\n } catch {\n // 磁盘未命中走下载\n }\n }\n\n const content = await download(rawUrl, fetchLike)\n if (file !== null) {\n try {\n mkdirSync(join(cacheRoot!, sub), { recursive: true })\n writeFileSync(file, content)\n } catch (error) {\n throw new ResourceMaterializeError(\n 'resource_save_failed',\n `remote file(${rawUrl}) save to cache path failed: ${(error as Error).message}`,\n )\n }\n }\n memory.set(key, content)\n return content\n }\n\n /**\n * QR 字节缓存(对位 go materializeQrCode:缓存键 = 内容+宽度 hash,命中不再\n * 生成)。落盘失败归 qr_generate_failed(对位 go ErrQRGenerateFailed 的\n * save 臂;与图片/字体的 resource_save_failed 分工同门)\n */\n async function cachedQrBytes(text: string, width: number): Promise<Uint8Array> {\n const key = `qr:${qrCacheKey(text, width)}`\n const hit = memory.get(key)\n if (hit !== undefined) {\n return hit\n }\n\n const file = cacheRoot === undefined ? null : join(cacheRoot, SUB_QR, qrCacheKey(text, width))\n if (file !== null) {\n try {\n const bytes = new Uint8Array(readFileSync(file))\n memory.set(key, bytes)\n return bytes\n } catch {\n // 磁盘未命中走生成\n }\n }\n\n const bytes = await qr!.materialize(text, width)\n if (file !== null) {\n try {\n mkdirSync(join(cacheRoot!, SUB_QR), { recursive: true })\n writeFileSync(file, bytes)\n } catch (error) {\n throw new ResourceMaterializeError('qr_generate_failed', `qr code save to cache path failed: ${(error as Error).message}`)\n }\n }\n memory.set(key, bytes)\n return bytes\n }\n\n async function registerImage(backend: MaterializationBackend, key: string, bytes: Uint8Array): Promise<void> {\n if (typeof backend.setImage !== 'function') {\n // 物化结果无处登记属用法错误(不设稳定 code;对位 go 的编译期接口保证)\n throw new Error('资源物化需要后端实现 setImage 登记缝(ImageRendererBackend 即契合)')\n }\n backend.setImage(key, bytes)\n await preloadQuietly(backend, key)\n }\n\n /** 预热失败静默(渲染面按各自契约降级,物化阶段不因位图装配失败中止) */\n async function preloadQuietly(backend: MaterializationBackend, src: string): Promise<void> {\n if (typeof backend.preload === 'function') {\n await backend.preload(src)\n }\n }\n\n async function resolve(canvas: Canvas, backend: ImageRegisteringBackend): Promise<void> {\n const materialization = backend as MaterializationBackend\n\n if (canvas.background !== null && canvas.background.image !== null) {\n const image = canvas.background.image\n if (isRemoteURL(image)) {\n await registerImage(materialization, image, await cachedRemoteBytes(SUB_IMAGES, image))\n } else {\n await preloadQuietly(materialization, image)\n }\n }\n\n for (const layer of canvas.layers) {\n await resolveLayer(layer, materialization)\n }\n }\n\n async function resolveLayer(layer: Layer, backend: MaterializationBackend): Promise<void> {\n switch (layer.type) {\n case 'TableLayer':\n for (const row of layer.rows) {\n await resolveLayer(row, backend)\n }\n break\n case 'TableRowLayer':\n for (const cell of layer.cells) {\n await resolveLayer(cell, backend)\n }\n break\n case 'TableCellLayer':\n if (layer.content !== null) {\n await resolveLayer(layer.content, backend)\n }\n break\n case 'ImageLayer': {\n const src = layer.src\n if (src === null) {\n break\n }\n if (isRemoteURL(src)) {\n await registerImage(backend, src, await cachedRemoteBytes(SUB_IMAGES, src))\n } else {\n // 本地路径/data URL 由 drawImage 直读,只做位图预热\n await preloadQuietly(backend, src)\n }\n break\n }\n case 'TextLayer': {\n const font = layer.font\n // 空串/纯数字 = 渲染端内置默认字体语义(IsBuiltinFont 同口径);非 URL 本地路径直读\n if (font === '' || isNumeric(font) || !isRemoteURL(font)) {\n break\n }\n const bytes = await cachedRemoteBytes(SUB_FONTS, font)\n if (typeof backend.registerFont !== 'function') {\n throw new Error('远程字体物化需要后端实现 registerFont 缝(ImageRendererBackend 即契合)')\n }\n // 注册/TTC 拒收归后端缝实现(字体非图像不经 setImage;resolver 只交字节)\n backend.registerFont(font, bytes)\n break\n }\n case 'QrCodeLayer': {\n const value = layer.value\n if (value === '') {\n break\n }\n if (qr === null) {\n throw new ResourceMaterializeError(\n 'qr_materializer_required',\n '二维码图层物化需要注入 QRMaterializer(ADR-0002:实现在渲染后端包)',\n )\n }\n // 宽度 = 图层宽(对位 go materializeQrCode 的 l.Width())\n await registerImage(backend, qrImageSrc(value), await cachedQrBytes(value, layerWidth(layer)))\n break\n }\n }\n }\n\n return { resolve }\n}\n\n/** 下载:非 2xx/空响应体均失败(对位 go HTTPDownloader 失败面),消息含 URL */\nasync function download(rawUrl: string, fetchLike: FetchLike): Promise<Uint8Array> {\n let response: { ok: boolean; status: number; arrayBuffer: () => Promise<ArrayBuffer> }\n try {\n response = await fetchLike(rawUrl)\n } catch (error) {\n throw new ResourceMaterializeError('resource_download_failed', `could not get remote file(${rawUrl}): ${(error as Error).message}`)\n }\n if (!response.ok) {\n throw new ResourceMaterializeError('resource_download_failed', `could not get remote file(${rawUrl}): HTTP ${response.status}`)\n }\n const buffer = await response.arrayBuffer()\n if (buffer.byteLength === 0) {\n throw new ResourceMaterializeError('resource_download_failed', `could not get remote file(${rawUrl}): 空响应体`)\n }\n return new Uint8Array(buffer)\n}\n\n/**\n * 图片/字体缓存键 = sha256(完整 URL) 十六进制 + URL path 原扩展名(同式复制自\n * go resolver.go cacheKey):有意偏离 PHP 的 basename 键——不同 URL 同名不互覆、\n * 键对查询串敏感;URL 解析失败扩展名缺省\n */\nfunction cacheKey(rawUrl: string): string {\n const sum = createHash('sha256').update(rawUrl).digest('hex')\n let ext = ''\n try {\n const path = new URL(rawUrl).pathname\n ext = path.match(/(\\.[a-zA-Z0-9]+)$/)?.[1] ?? ''\n } catch {\n // 扩展名缺省\n }\n return sum + ext\n}\n\n/** 二维码缓存键 = sha256(内容_宽度) 十六进制 + \".png\"(同式复制自 go qrCacheKey) */\nfunction qrCacheKey(text: string, width: number): string {\n return createHash('sha256').update(`${text}_${width}`).digest('hex') + '.png'\n}\n","/**\n * 二维码物化的默认实现(对位 go-canvas/image-renderer/qr.go 的 QRMaterializer;\n * 同式复制先例、编码器实现不同源——node 侧用 soldair/node-qrcode,参数面与 go\n * (yeqown 适配 endroid 用法)逐项对位,产物字节面语义一致):\n * - 纠错 = QR 'H' 30%(go ErrorCorrectionHighest;node-qrcode 库默认非 H,须显式覆写);\n * - size = max(width, 1)(对齐 endroid size=max(图层宽,1));\n * - margin = 0(node-qrcode 库默认 4 模块静区,显式归零,对位 WithBorderWidth(0));\n * - 纯黑前景/纯白背景(不透明,对位 color.Black/White);\n * - 显式 PNG 输出。\n * - 编码模式复刻 bacon chooseMode 三分(pickEncMode 同式复制自 go qr.go):\n * 纯数字→Numeric、QR 字母数字集→Alphanumeric、其余→Byte,**无 Kanji 分支**\n * ——node-qrcode 自动模式会对纯中日文升级 Kanji(与 go EncModeAuto 同病),\n * 必须以显式 segment 模式压制,保证与 go/endroid 矩阵一致。\n *\n * 参数映射注(块宽语义):go WithQRWidth(uint8) = 整数块像素宽,出图 = 模块数×块宽;\n * node-qrcode 的 `scale` 即同语义(每模块像素数),故 blockWidth = min(max(size/模块数,\n * 1), 255) 向零截断后直接作 scale——出图绝对尺寸与 go 同式(维度×块宽),对目标宽\n * 向下量化、目标宽小于模块数时块宽钳 1 出图反大于目标宽;差异由渲染原语内切正方形\n * cover 缩放铺放吸收(ADR 0015 同门),只影响缓存 PNG 绝对尺寸。255 上限对位 go\n * uint8 形参界(node 无此硬界,同式钳保持产物尺寸面一致)。\n */\nimport QRCode, { type QRCodeSegment } from 'qrcode'\nimport { Buffer } from 'node:buffer'\nimport { ResourceMaterializeError, type QRMaterializer } from '@hankchen/canvas'\n\n/** 二维码物化默认实现(固定选项;核心 QRMaterializer 契约的实现)。命名 QrCode\n * 对齐领域命名 QrCodeLayer 与 pdf 包同类(评审 P1-6 两包统一;核心接口名\n * QRMaterializer 是 go 对位名,不动) */\nexport class QrCodeMaterializer implements QRMaterializer {\n async materialize(text: string, width: number): Promise<Uint8Array> {\n const size = Math.max(width, 1) // 对齐 endroid size=max(图层宽,1)\n const segment = segmentFor(text, pickEncMode(text))\n\n try {\n // 模块数决定块宽量化(create 与 toBuffer 同输入同矩阵)\n const code = QRCode.create([segment], { errorCorrectionLevel: 'H' })\n const blockWidth = Math.min(Math.max(Math.trunc(size / code.modules.size), 1), 255)\n const png = await QRCode.toBuffer([segment], {\n errorCorrectionLevel: 'H',\n margin: 0,\n scale: blockWidth,\n color: { dark: '#000000ff', light: '#ffffffff' },\n })\n return new Uint8Array(png)\n } catch (error) {\n throw new ResourceMaterializeError('qr_generate_failed', `二维码生成失败(${text}): ${(error as Error).message}`)\n }\n }\n}\n\n/** 编码模式判定结果(node-qrcode segment 模式名;无 Kanji 分支) */\ntype QrSegmentMode = 'numeric' | 'alphanumeric' | 'byte'\n\n/**\n * 复刻 bacon Encoder::chooseMode 在 UTF-8 提示下的模式选择(同式复制自 go qr.go\n * pickEncMode):纯数字→Numeric、QR 字母数字集→Alphanumeric、其余→Byte,无\n * Kanji 分支。与 node-qrcode 自动模式的差别即在此:后者对纯中日文内容升级\n * Kanji 码,两端矩阵不一致\n */\nfunction pickEncMode(text: string): QrSegmentMode {\n if (text === '') {\n return 'byte' // bacon chooseMode 空串守卫,忠实复刻(实际到不了:resolver 已拦空内容)\n }\n if (isAllDigits(text)) {\n return 'numeric'\n }\n if (isAllAlphaNum(text)) {\n return 'alphanumeric'\n }\n return 'byte'\n}\n\n/** segment 构造(判别联合按分支收窄,显式模式压制库的 Kanji 自动升级)。\n * byte 分支传 UTF-8 字节面(@types 的 ByteSegment 只收字节序列;运行时对\n * string 与 UTF-8 Buffer 同码) */\nfunction segmentFor(text: string, mode: QrSegmentMode): QRCodeSegment {\n switch (mode) {\n case 'numeric':\n return { data: text, mode: 'numeric' }\n case 'alphanumeric':\n return { data: text, mode: 'alphanumeric' }\n default:\n return { data: Buffer.from(text, 'utf8'), mode: 'byte' }\n }\n}\n\n/** 纯 ASCII 数字(PHP ctype_digit 语义,空串由调用方先行分流) */\nfunction isAllDigits(text: string): boolean {\n for (const ch of text) {\n if (ch < '0' || ch > '9') {\n return false\n }\n }\n return true\n}\n\n/** QR 字母数字集(PHP/bacon 与 node-qrcode 的字符表一致:0-9 A-Z 空格 $ % * + - . / :) */\nfunction isAllAlphaNum(text: string): boolean {\n for (const ch of text) {\n if ((ch < '0' || ch > '9') && (ch < 'A' || ch > 'Z')) {\n switch (ch) {\n case ' ':\n case '$':\n case '%':\n case '*':\n case '+':\n case '-':\n case '.':\n case '/':\n case ':':\n break\n default:\n return false\n }\n }\n }\n return true\n}\n","/**\n * @hankchen/canvas-image-renderer 公共出口:位图渲染后端(五原语 + setImage/\n * registerFont/preload 装配缝)、EXIF 混合口径映射件、napi 度量增强件(08 号\n * 决议,显式注入)、QR 物化与默认 resolver(对位 go image-renderer module 的\n * 导出面)。渲染契约与物化契约类型来自 @hankchen/canvas,不在此重复导出。\n */\nexport const PACKAGE_NAME = '@hankchen/canvas-image-renderer' as const\n\nexport { ImageRendererBackend } from './backend'\nexport {\n createDefaultResolver,\n type DefaultResolverOptions,\n type MaterializationBackend,\n} from './resolver'\nexport { QrCodeMaterializer } from './qr'\nexport { createNapiMeasurerFactory, isBuiltinFont, loadFontFamily, registerFontData } from './typography'\nexport { drawTextLine } from './text'\nexport { parseColor, cssColor, type NrgbaColor } from './color'\nexport { canonicalSourceRect, exifOrientation, rawDimensions, type SourceRect } from './exif'\nexport { decodeDrawableImage, decodeHeaderSize, loadSourceBytes, type ByteRegistry } from './decode'\n"],"mappings":"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","names":["createCanvas","readFileSync","readFileSync","createCanvas","createCanvas","createCanvas","createHash","mkdirSync","readFileSync","writeFileSync","join","ResourceMaterializeError","Buffer","join","readFileSync","mkdirSync","writeFileSync","ResourceMaterializeError","bytes","createHash"]}
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@hankchen/canvas-image-renderer",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "description": "graph → 透明底 PNG 的 Node 渲染后端:@napi-rs/canvas 五原语实现、EXIF 混合口径、资源物化默认接线(对位 go-canvas/image-renderer)",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "sideEffects": false,
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "registry": "https://registry.npmjs.org/"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/HankChenCH/js-canvas.git",
28
+ "directory": "packages/image-renderer"
29
+ },
30
+ "dependencies": {
31
+ "@napi-rs/canvas": "1.0.13",
32
+ "qrcode": "1.5.4",
33
+ "@hankchen/canvas": "2.2.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^26.6.2",
37
+ "@types/qrcode": "^1.5.5",
38
+ "tsup": "^8.5.0",
39
+ "vitest": "^5.0.1"
40
+ },
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "test": "vitest run",
44
+ "typecheck": "tsc --noEmit -p tsconfig.json",
45
+ "visualcheck": "node scripts/visualcheck.mjs"
46
+ }
6
47
  }