Я пытаюсь использовать метод машинописного текста, который я видел в этом посте о пакете "pdf-lib": https://github.com/Hopding/pdf-lib/issues/362 Итак, я создал службу с гнездом:
import { Injectable } from '@nestjs/common';
import * as fs from "fs";
import fetch from 'node-fetch';
import {
asPDFName,
degrees,
drawImage,
drawText,
PDFArray,
PDFContentStream,
PDFDict,
PDFDocument,
PDFFont,
PDFHexString,
PDFImage,
PDFName,
PDFNumber,
PDFOperator,
PDFOperatorNames as Ops,
popGraphicsState,
pushGraphicsState,
rgb,
rotateDegrees,
StandardFonts,
translate,
} from 'pdf-lib';
@Injectable()
export class AppService {
generatepdf(){
const getAcroForm = (pdfDoc: PDFDocument) =>
pdfDoc.catalog.lookupMaybe(PDFName.of('AcroForm'), PDFDict);
const getAcroFields = (pdfDoc: PDFDocument): PDFDict[] => {
const acroForm = getAcroForm(pdfDoc);
if (!acroForm) return [];
const fieldRefs = acroForm.lookupMaybe(PDFName.of('Fields'), PDFArray);
if (!fieldRefs) return [];
const fields = new Array(fieldRefs.size());
for (let idx = 0, len = fieldRefs.size(); idx < len; idx++) {
fields[idx] = fieldRefs.lookup(idx);
}
return fields;
};
const findAcroFieldByName = (pdfDoc: PDFDocument, name: string) => {
const acroFields = getAcroFields(pdfDoc);
return acroFields.find((acroField) => {
const fieldName = acroField.get(PDFName.of('T'));
return fieldName instanceof PDFName && fieldName.value() === name;
});
};
const imageAppearanceStream = (
image: PDFImage,
rotation: number,
width: number,
height: number,
) => {
const dict = image.doc.context.obj({
Type: 'XObject',
Subtype: 'Form',
FormType: 1,
BBox: [0, 0, width, height],
Resources: { XObject: { Image: image.ref } },
});
const operators = [
rotateDegrees(rotation),
translate(0, rotation % 90 === 0 ? -width : 0),
...drawImage('Image', {
x: 0,
y: 0,
width: height,
height: width,
rotate: degrees(0),
xSkew: degrees(0),
ySkew: degrees(0),
}),
];
const stream = PDFContentStream.of(dict, operators, false);
return image.doc.context.register(stream);
};
const fillAcroTextField = (acroField: PDFDict, text: string, font: PDFFont) => {
const rect = acroField.lookup(PDFName.of('Rect'), PDFArray);
const width =
rect.lookup(2, PDFNumber).value() - rect.lookup(0, PDFNumber).value();
const height =
rect.lookup(3, PDFNumber).value() - rect.lookup(1, PDFNumber).value();
const MK = acroField.lookupMaybe(PDFName.of('MK'), PDFDict);
const R = MK && MK.lookupMaybe(PDFName.of('R'), PDFNumber);
const rotation = R ? R.value() : 0;
const N = singleLineAppearanceStream(font, text, rotation, width, height);
acroField.set(PDFName.of('AP'), acroField.context.obj({ N }));
acroField.set(PDFName.of('Ff'), PDFNumber.of(1 /* Read Only */));
acroField.set(PDFName.of('V'), PDFHexString.fromText(text));
};
const beginMarkedContent = (tag: string) =>
PDFOperator.of(Ops.BeginMarkedContent, [asPDFName(tag)]);
const endMarkedContent = () => PDFOperator.of(Ops.EndMarkedContent);
const singleLineAppearanceStream = (
font: PDFFont,
text: string,
rotation: number,
width: number,
height: number,
) => {
const rotationCorrectedHeight = rotation % 90 === 0 ? width : height;
const size = font.sizeAtHeight(rotationCorrectedHeight - 8);
const encodedText = font.encodeText(text);
const x = 0;
const y = rotationCorrectedHeight - size;
return textFieldAppearanceStream(
font,
size,
encodedText,
rotation,
x,
y,
width,
height,
);
};
const textFieldAppearanceStream = (
font: PDFFont,
size: number,
encodedText: PDFHexString,
rotation: number,
x: number,
y: number,
width: number,
height: number,
) => {
const dict = font.doc.context.obj({
Type: 'XObject',
Subtype: 'Form',
FormType: 1,
BBox: [0, 0, width, height],
Resources: { Font: { F0: font.ref } },
});
const operators = [
rotateDegrees(rotation),
translate(0, rotation % 90 === 0 ? -width : 0),
beginMarkedContent('Tx'),
pushGraphicsState(),
...drawText(encodedText, {
color: rgb(0, 0, 0),
font: 'F0',
size,
rotate: degrees(0),
xSkew: degrees(0),
ySkew: degrees(0),
x,
y,
}),
popGraphicsState(),
endMarkedContent(),
];
const stream = PDFContentStream.of(dict, operators);
return font.doc.context.register(stream);
};
(async () => {
const ticketTemplateBytes = await fetch(
'https://vivere.s3.amazonaws.com/b2936be1256f34a05dcfe14541ea0ab02f82607ab8282f149d4a157dddaad2da/e2dc9810897d9d40260d25548e5b3633d29d45fa80baedd136800d3a2bee1e84.pdf',
).then((res) => res.arrayBuffer());
const pdfDoc = await PDFDocument.load(ticketTemplateBytes);
// Fill Form ---------------------------------------------
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
const fillInField = (fieldName: string, text: string) => {
const field = findAcroFieldByName(pdfDoc, fieldName);
if (field) fillAcroTextField(field, text, helveticaFont);
};
const lockField = (acroField: any) => {
const fieldType = acroField.lookup(PDFName.of('FT'));
if (fieldType === PDFName.of('Tx')) {
acroField.set(PDFName.of('Ff'), PDFNumber.of(1 << 0 /* Read Only */));
}
};
fillInField('STARTDATE', '03/01/2020');
fillInField('STARTTIME', '1:18 PM');
fillInField('ADDRESS', '123 Yolk Drive');
fillInField('FULLNAME', 'Humpty Dumpty');
fillInField('SERIALNO', '876-ABC-5');
fillInField('PRICE', '$2500');
const acroFields = getAcroFields(pdfDoc);
acroFields.forEach((field) => lockField(field));
const pdfBytes = await pdfDoc.save();
fs.writeFileSync('bmr.pdf', pdfBytes);
})();
}
}
и контроллер:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get("pdf")
getHello() {
return this.appService.generatepdf();
}
}
Таким образом, код возвращает нужный мне pdf, но он не заполняет формы. Кто-нибудь знает почему? Я не вижу, что здесь не так, потому что нет ошибки. Спасибо.