[nest.js] pdfkit

김민재·2025년 2월 11일

nest.js

목록 보기
27/63

공부 이유

PDF 파일을 다운로드 하는 API를 만들어야 했다.

pdfkit란?

Node.js 라이브러리로, 다이렉트로 PDF 파일을 생성하는 기능을 제공한다.

라이브러리 다운로드

  • npm install @types/pdfkit --save-dev
  • npm install pdfkit

print.controller.ts

import { Controller, Get, Param, Res } from '@nestjs/common';
import { PrintService } from './print.service';
import { ApiTags } from '@nestjs/swagger';

@ApiTags('Print')
@Controller('print')
export class PrintController {
    constructor(private readonly printSerivce: PrintService) {}

    // 연차 PDF 형식으로 다운로드 하기
    @Get('pdf/download/:annualLeaveId')
    async downloadPDF(@Param('annualLeaveId') annualLeaveId: number, @Res() res): Promise<void> {
        const buffer = await this.printSerivce.generateAnnualLeavePdf(annualLeaveId);
        res.set({
            'Content-type': 'application/pdf; charset=utf-8',
            'Content-Disposition': 'attachment; filename=annualLeave.pdf',
            'Content-Length': buffer.length,
        });

        res.end(buffer);
    }
}


controller에서는 charset=utf-8을 이용해서 한글로 변환해준다.

print.servic.ts

import { AnnualLeaveService } from './../annual-leave/annualLeave.service';
import { Injectable, NotFoundException } from '@nestjs/common';
import * as path from 'path';
import * as PDFDocument from 'pdfkit'; // pdfkit 직접 불러오기

@Injectable()
export class PrintService {
    constructor(private readonly annualService: AnnualLeaveService) {}

    // 연차 PDF 형식으로 다운로드 하기
    async generateAnnualLeavePdf(annualLeaveId: number): Promise<Buffer> {
        return new Promise(async (resolve, reject) => {
            const doc = new PDFDocument({
                size: 'A4', // 파일 사이즈
                bufferPages: true,
            });

            // 폰트 파일 경로
            const fontPath = path.join(process.cwd(), 'src', 'ponts', '5jKbo-sBL2uJoUIRop32w-wnBo8.ttf');

            if (!fontPath) {
                throw new NotFoundException('폰트 파일을 찾을 수 없습니다.');
            }

            // 연차 정보 조회
            const annualLeave = await this.annualService.showAcceptAnnualLeave(annualLeaveId);

            if (!annualLeave) {
                throw new NotFoundException('수락된 연차 정보가 없습니다.');
            }

            const userName = annualLeave.user.name;
            const department = annualLeave.user.department;

            // PDF에서 한글을 출력하기 위해 폰트를 설정
            doc.font(fontPath) // 폰트 설정
                .fontSize(16) // 폰트 크기 설정
                .text('연차 사용', { align: 'center' }) // 한글 텍스트 출력
                .moveDown()
                .moveDown()
                .moveDown();

            // 사용자 정보 출력
            doc.font(fontPath) // 폰트 설정
                .fontSize(12)
                .text(`사용자: ${userName}`, { align: 'left' })
                .moveDown()
                .text(`부서: ${department}`, { align: 'left' })
                .moveDown();

            // 연차 정보 출력
            const { start_date, end_date, days, record_contents, created_at } = annualLeave;

            doc.font(fontPath) // 폰트 설정
                .fontSize(12)
                .text(`연차 기간: ${start_date} ~ ${end_date}`, { align: 'left' })
                .moveDown()
                .text(`연차 일수: ${days}일`, { align: 'left' })
                .moveDown()
                .text(`기타 사항: ${record_contents || '없음'}`, { align: 'left' })
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .moveDown()
                .text(`신청 날짜: ${created_at}`, { align: 'center' });

            // PDF 데이터를 버퍼로 변환하여 반환
            const buffer: Buffer[] = [];
            doc.on('data', buffer.push.bind(buffer));
            doc.on('end', () => {
                const data = Buffer.concat(buffer);
                resolve(data);
            });

            doc.end();
        });
    }
}


// 폰트 파일 경로

        const fontPath = path.join(process.cwd(), 'src', 'ponts', '5jKbo-sBL2uJoUIRop32w-wnBo8.ttf');

이 부분은 ttf 파일을 다운로드 받고 vscode에서는 인코딩 되기 때문에, 파일을 열지 않고 넣어놓기만 해야한다.

profile
개발 경험치 쌓는 곳

0개의 댓글