fix(planting): draw signature directly on page instead of using form field

The PDF signature field is only 92x51 points, which causes signatures to
appear too small or invisible. Changed to use drawImage() directly on
the page at the field's position with a larger size (150x80 max).

🤖 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 2026-01-03 23:28:18 -08:00
parent 5ad21ee097
commit 083c0fd540
1 changed files with 43 additions and 48 deletions

View File

@ -389,64 +389,59 @@ export class PdfGeneratorService {
this.logger.log('Using form fields mode');
await this.fillFormFields(pdfDoc, data, customFont);
// 5. 如果有签名,嵌入签名图片到 signature 按钮字段
// 5. 如果有签名,嵌入签名图片
// 直接使用坐标方式在页面上绘制签名,不受表单字段尺寸限制
if (signature) {
const signatureImage = await pdfDoc.embedPng(signature.signatureImagePng);
// 获取签名字段位置作为参考
let signatureX = 380; // 默认 x 坐标
let signatureY = 100; // 默认 y 坐标
try {
const form = pdfDoc.getForm();
const signatureButton = form.getButton(FORM_FIELDS.SIGNATURE);
// 获取按钮的 widget 和尺寸
const widgets = signatureButton.acroField.getWidgets();
if (widgets.length > 0) {
const widget = widgets[0];
const rect = widget.getRectangle();
const { width: fieldWidth, height: fieldHeight } = rect;
// 计算图片缩放尺寸(保持宽高比,适应字段大小)
const imgDims = signatureImage.scale(1);
const scale = Math.min(fieldWidth / imgDims.width, fieldHeight / imgDims.height);
const scaledWidth = imgDims.width * scale;
const scaledHeight = imgDims.height * scale;
// 计算居中位置
const x = (fieldWidth - scaledWidth) / 2;
const y = (fieldHeight - scaledHeight) / 2;
// 创建外观流内容
const appearanceStream = `q ${scaledWidth} 0 0 ${scaledHeight} ${x} ${y} cm /Img Do Q`;
// 创建 XObject Form 作为外观
const context = pdfDoc.context;
const imageXObjectName = 'Img';
const Resources = context.obj({
XObject: { [imageXObjectName]: signatureImage.ref },
});
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 {
throw new Error('No widgets found for signature button');
const rect = widgets[0].getRectangle();
// 使用字段左下角作为签名位置参考
signatureX = rect.x;
signatureY = rect.y;
this.logger.log(`Signature field position: x=${rect.x}, y=${rect.y}, field size: ${rect.width}x${rect.height}`);
}
} catch (error) {
// 如果没有签名按钮字段,回退到坐标方式
this.logger.log(`Signature button field error: ${error.message}, using coordinates`);
this.embedSignatureByCoordinates(pdfDoc, signatureImage);
} catch {
this.logger.log('No signature button field found, using default coordinates');
}
// 直接在页面上绘制签名图片
const pages = pdfDoc.getPages();
const lastPage = pages[pages.length - 1];
// 计算签名图片的尺寸(保持宽高比,目标尺寸更大)
const maxWidth = 150;
const maxHeight = 80;
const { width, height } = signatureImage.scale(1);
let scaledWidth = width;
let scaledHeight = height;
if (width > maxWidth) {
scaledWidth = maxWidth;
scaledHeight = (height * maxWidth) / width;
}
if (scaledHeight > maxHeight) {
scaledHeight = maxHeight;
scaledWidth = (width * maxHeight) / height;
}
// 在签名位置绘制图片
lastPage.drawImage(signatureImage, {
x: signatureX,
y: signatureY,
width: scaledWidth,
height: scaledHeight,
});
this.logger.log(`Signature drawn at (${signatureX}, ${signatureY}), size: ${scaledWidth}x${scaledHeight}`);
}
// 6. 最后统一扁平化所有表单字段