打印与导出
Viewer 的打印和导出都建立在同一个前提上:页面已经通过 Viewer 渲染出来。
这一层不重新设计模板。它消费当前 Schema、数据、渲染页尺寸和页面 DOM。
浏览器打印
最短路径是直接调用 print():
await viewer.print()不传 driverId 时,Viewer 会走浏览器打印路径。即使你已经注册了自定义打印驱动,默认也仍然是浏览器打印。
有 Host 时,Viewer 会先给当前 Host 加一层打印隔离样式,再调用 host.print()。打印结束或抛错后,这层隔离状态会被清理。
纸张尺寸策略
pageSizeMode 用来回答一个问题:纸张尺寸听谁的。
await viewer.print({ pageSizeMode: 'driver' })
await viewer.print({ pageSizeMode: 'fixed' })当前只支持两个值:
driver:连续纸会让打印机驱动决定纸张;固定页仍会使用 Schema 解析出的纸张尺寸。fixed:按 Schema 或已渲染页面尺寸生成固定纸张策略。
连续纸比较特殊。如果连续纸请求 fixed,Viewer 需要已经有渲染页尺寸。否则会发出 PRINT_RENDER_METRICS_MISSING 诊断。
自定义打印驱动
需要接本地服务、远程网关或专用设备时,注册一个打印驱动。
viewer.registerPrintDriver({
id: 'thermal-printer',
defaults: {
pageSizeMode: 'fixed',
},
async print(context) {
console.log(context.printPolicy)
console.log(context.renderedPages)
console.log(context.container)
},
})
await viewer.print({
driverId: 'thermal-printer',
})驱动可以提供 defaults.pageSizeMode。调用 viewer.print() 时,如果你没有显式传 pageSizeMode,Viewer 会使用驱动默认值。
context 里最常用的是:
schema和dataprintPolicyrenderedPagescontaineronPhase、onProgress、onDiagnostic
驱动适合做协议适配。布局、分页和尺寸策略应该交给 Viewer 先算好。
托管打印 SDK
如果你使用官方高层打印器,不需要自己注册 PrintDriver。打印器会创建一个托管 Viewer,渲染完成后再把页面交给对应驱动。
模板使用官方内置物料时,在 setupViewer 里注册:
import { registerBuiltinViewerMaterials } from '@easyink/builtin/all'
import { createEasyInkPrinter } from '@easyink/print-integration-easyink-printer'
const printer = createEasyInkPrinter({
serviceUrl: 'http://localhost:18080',
viewer: 'iframe',
setupViewer(viewer) {
registerBuiltinViewerMaterials((type, binding, extension) => {
viewer.registerMaterial(type, binding, extension)
})
},
})
await printer.print({ schema, data })setupViewer 会在 viewer.open() 之前执行。也就是说,内置物料、企业物料和你自己的物料都可以在这里通过 viewer.registerMaterial() 注册。
HiPrint 和 LODOP 的高层打印器也使用同一个入口:
import { registerBuiltinViewerMaterials } from '@easyink/builtin/all'
import { createHiPrintPrinter } from '@easyink/print-integration-hiprint'
import { createLodopPrinter } from '@easyink/print-integration-lodop'
function setupViewer(viewer) {
registerBuiltinViewerMaterials((type, binding, extension) => {
viewer.registerMaterial(type, binding, extension)
})
}
const hiPrintPrinter = createHiPrintPrinter({
client: hiPrintClient,
viewer: 'iframe',
setupViewer,
})
const lodopPrinter = createLodopPrinter({
client: lodopClient,
viewer: 'iframe',
setupViewer,
})打印包不会自动引入 @easyink/builtin。如果你的模板只使用自定义物料,就把自定义注册逻辑放进同一个 setupViewer 里。
打印任务回调
自定义驱动可以复用 Viewer 传进来的任务回调。
await viewer.print({
driverId: 'thermal-printer',
onPhase(event) {
console.log(event.phase, event.message)
},
onProgress(event) {
console.log(event.current, event.total)
},
onDiagnostic(event) {
console.warn(event.code, event.message)
},
throwOnError: true,
})throwOnError: true 会让打印驱动错误、打印策略错误或浏览器打印错误继续向外抛出。默认情况下,Viewer 会发诊断并结束这次打印调用。
导出器注册
导出使用 registerExporter()。
viewer.registerExporter({
id: 'pdf-exporter',
format: 'pdf',
async export(context) {
console.log(context.renderedPages)
console.log(context.container)
return new Blob(['ok'], { type: 'application/pdf' })
},
})然后调用 exportDocument():
const blob = await viewer.exportDocument({
format: 'pdf',
entry: 'preview',
})Viewer 会按 format 找到第一个匹配的导出器。如果不传 format,会使用当前实例注册的第一个导出器。
没有匹配导出器时,会发出 NO_EXPORTER 诊断。传了 throwOnError: true 时,同一个错误会继续抛出。
导出任务回调
导出器可以有 prepare(),也可以在 export() 里上报进度和诊断。
const blob = await viewer.exportDocument({
format: 'pdf',
entry: 'api',
onPhase(event) {
console.log(event.phase)
},
onProgress(event) {
console.log(event.current, event.total)
},
onDiagnostic(event) {
console.warn(event.code, event.message)
},
throwOnError: true,
})当前 Viewer 会在导出过程中发出这些阶段:
- 有
prepare()时:preparing - 调用
export()前:exporting - 导出成功后:
completed
如果导出器抛错,Viewer 会发出 EXPORTER_ERROR。throwOnError: true 时会继续抛出原始错误。
字符串快捷调用
exportDocument() 也可以直接传格式字符串。
const blob = await viewer.exportDocument('pdf')这等价于:
const blob = await viewer.exportDocument({
format: 'pdf',
entry: 'api',
})如果你的导出来自预览按钮、保存菜单或程序调用,可以显式传 entry。当前类型支持 save-menu、preview 和 api。
打印策略解析
写驱动时,你通常会想知道 Viewer 最终准备怎么打印。
import { resolvePrintPolicy } from '@easyink/viewer'
const policy = resolvePrintPolicy({
schema: documentSchema,
options: { pageSizeMode: 'fixed' },
renderedPages: viewer.renderedPages,
})策略对象里常用字段是:
pageModepageSizeModesheetSizeorientationpageBreakBehavioroffset
sheetSize.source 会告诉你尺寸来自 schema 还是已渲染页面。
官方 PDF 与图片导出
Viewer 本身只定义导出器接口,不内置 PDF 或图片编码。要快速导出文件,可以接入官方 DOM 导出插件。
先注册 PDF 和 PNG 插件:
import { createExportRuntime } from '@easyink/export-runtime'
import { createDomImageExportPlugin } from '@easyink/export-plugin-dom-image'
import { createDomPdfExportPlugin } from '@easyink/export-plugin-dom-pdf'
import { toMillimeters } from '@easyink/print-core'
const exportRuntime = createExportRuntime()
exportRuntime.registerPlugin(createDomPdfExportPlugin())
exportRuntime.registerPlugin(createDomImageExportPlugin())然后把 Viewer 页面交给它们:
function resolvePageSizes(context) {
return context.renderedPages?.map(page => ({
widthMm: toMillimeters(page.width, page.unit),
heightMm: toMillimeters(page.height, page.unit),
}))
}
function resolveDomPages(context) {
return Array.from(
context.container?.querySelectorAll<HTMLElement>('.ei-viewer-page') ?? [],
)
}
viewer.registerExporter({
id: 'pdf-export',
format: 'pdf',
async export(context) {
return exportRuntime.exportDocument({
format: 'pdf',
input: {
pages: resolveDomPages(context),
pageSizes: resolvePageSizes(context),
},
throwOnError: true,
onProgress: context.onProgress,
})
},
})
viewer.registerExporter({
id: 'png-export',
format: 'png',
async export(context) {
return exportRuntime.exportDocument({
format: 'png',
input: {
pages: resolveDomPages(context),
pageIndex: 0,
pageSizes: resolvePageSizes(context),
},
throwOnError: true,
onProgress: context.onProgress,
})
},
})现在可以直接调用 Viewer:
const pdfBlob = await viewer.exportDocument({ format: 'pdf', throwOnError: true })
const pngBlob = await viewer.exportDocument({ format: 'png', throwOnError: true })png 导出里的 pageIndex 从 0 开始。如果你的预览界面有“当前页”状态,把它换成当前页索引就行。
JPEG 需要一个单独的插件实例:
exportRuntime.registerPlugin(createDomImageExportPlugin({
id: 'jpeg-export-runtime',
format: 'jpeg',
type: 'image/jpeg',
}))然后注册 jpeg 导出器,调用 exportDocument({ format: 'jpeg' })。input.quality 可以控制 JPEG 质量,范围是 0 到 1。
如果你想一次拿到多页图片,可以直接使用图片插件的纯函数:
import { renderPagesToImageBlobs } from '@easyink/export-plugin-dom-image'
const imageBlobs = await renderPagesToImageBlobs({
pages: resolveDomPages(context),
pageSizes: resolvePageSizes(context),
type: 'image/png',
})这里会返回 Blob[]。你可以逐个下载,也可以在业务层打包成 zip。
如果你要把 Viewer 接到打印设备,继续看 自定义打印驱动。如果你要自己实现一种新导出格式,继续看 自定义导出插件。
关于打印与导出,目前知道这些就够用了。排查错误时可以继续看 诊断系统。