fix(pdf-generator): 使用自定义外观流嵌入签名图片

- 恢复使用 widget.setNormalAppearance() 方式
- 创建 XObject Form 作为外观流
- 签名图片按字段尺寸等比例缩放并居中
- 不依赖页面索引,直接设置到widget上

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-26 07:58:44 -08:00
parent 91d3e65289
commit e9b2917561
1 changed files with 29 additions and 22 deletions

View File

@ -383,41 +383,48 @@ export class PdfGeneratorService {
const form = pdfDoc.getForm(); const form = pdfDoc.getForm();
const signatureButton = form.getButton(FORM_FIELDS.SIGNATURE); const signatureButton = form.getButton(FORM_FIELDS.SIGNATURE);
// 获取按钮的 widget 和位置 // 获取按钮的 widget 和尺寸
const widgets = signatureButton.acroField.getWidgets(); const widgets = signatureButton.acroField.getWidgets();
if (widgets.length > 0) { if (widgets.length > 0) {
const widget = widgets[0]; const widget = widgets[0];
const rect = widget.getRectangle(); const rect = widget.getRectangle();
const { width: fieldWidth, height: fieldHeight } = rect;
// 获取签名图片原始尺寸 // 计算图片缩放尺寸(保持宽高比,适应字段大小)
const imgDims = signatureImage.scale(1); const imgDims = signatureImage.scale(1);
const scale = Math.min(fieldWidth / imgDims.width, fieldHeight / imgDims.height);
// 按字段尺寸等比例缩放签名图片
// 计算宽度和高度的缩放比例,取较小值以确保签名完全在字段内
const scaleX = rect.width / imgDims.width;
const scaleY = rect.height / imgDims.height;
const scale = Math.min(scaleX, scaleY);
const scaledWidth = imgDims.width * scale; const scaledWidth = imgDims.width * scale;
const scaledHeight = imgDims.height * scale; const scaledHeight = imgDims.height * scale;
// 在字段内居中放置签名 // 计算居中位置
const offsetX = (rect.width - scaledWidth) / 2; const x = (fieldWidth - scaledWidth) / 2;
const offsetY = (rect.height - scaledHeight) / 2; const y = (fieldHeight - scaledHeight) / 2;
// 获取按钮所在的页面 // 创建外观流内容
const pages = pdfDoc.getPages(); const appearanceStream = `q ${scaledWidth} 0 0 ${scaledHeight} ${x} ${y} cm /Img Do Q`;
const page = pages[5]; // 第6页签名区域
// 在按钮位置绘制缩放后的签名图片(居中) // 创建 XObject Form 作为外观
page.drawImage(signatureImage, { const context = pdfDoc.context;
x: rect.x + offsetX, const imageXObjectName = 'Img';
y: rect.y + offsetY,
width: scaledWidth, const Resources = context.obj({
height: scaledHeight, XObject: { [imageXObjectName]: signatureImage.ref },
}); });
this.logger.log(`Signature embedded at (${rect.x + offsetX}, ${rect.y + offsetY}) with size ${scaledWidth}x${scaledHeight}, field size: ${rect.width}x${rect.height}`); const appearanceStreamRef = context.register(
context.stream(appearanceStream, {
Type: 'XObject',
Subtype: 'Form',
FormType: 1,
BBox: [0, 0, fieldWidth, fieldHeight],
Resources,
}),
);
// 设置外观到 widget
widget.setNormalAppearance(appearanceStreamRef);
this.logger.log(`Signature embedded using custom appearance stream, field size: ${fieldWidth}x${fieldHeight}`);
} else { } else {
throw new Error('No widgets found for signature button'); throw new Error('No widgets found for signature button');
} }