Back-End/Java

Java, Apache PDFBox를 이용하여 PDF 다루기

개발자 DalBy 2024. 5. 14. 16:56
반응형

Java, Apache PDFBox를 이용하여 PDF 다루기

Apache PDFBox 라이브러리를 이용하여 Java로 PDF 파일을 수정, 병합, 생성하는 API 가이드 포스팅을 시작 하겠습니다.

 

먼저 Maven 또는 Gradle 의존성을 추가 해 줍니다. 필자가 사용한 버전은 2.0.31 입니다. 3.x 버전의 경우, 2.x버전 때의 사용법이 다르게 변경되어 다른 부분이 많습니다. 이점 참고 부탁드리겠습니다!

 

Maven

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.31</version>
</dependency>

 

Gradle

implementation group: 'org.apache.pdfbox', name: 'pdfbox', version: '2.0.31'

 

 

설정이 완료되었다면, 먼저 PDF 생성부터 알아보도록 하겠습니다. 기본적으로 PDFBox는 PDF파일을 직접 그릴 때 위에서 아래로 그리게 되어 있습니다. 즉 아래 좌표를 먼저 그렸다가, 위의 좌표를 그리면 의도치 않은 결과와 다양한 에러가 발생 할 수 있습니다. 

 

PDDocument doc = new PDDocument();

 

PDDocument class를 선언하게 되면 해당 PDF 문서를 메모리에 할당합니다. 꼭 작업 완료시 doc.close() 종료 메소드를 호출 해야 합니다. 

 

InputStream fontStream = new FileInputStream(new File(ROOT_FONT_PATH));
PDFont font = PDType0Font.load(doc, fontStream);

한글 입력이나, 추가적인 폰트 추가시 InputStream class를 이용하여 처리합니다. PDFont의 매개변수로는 글꼴 TTF를 type0 글꼴로 로드, 처리 할 PDF문서 객체를 보내줍니다. load(doc, 나눔스퀘어.ttf)

 

PDPage page = new PDPage();

신규 PDF 페이지를 추가할 때 PDPage class를 이용하여 처리합니다. PDPage class는 PDF 파일의 n장 E 또는 n번째 라고 생각하시면 됩니다. 만약 해당 PDF page에 content를 넣고 싶다면 아래와 같이 진행하면 됩니다.

 

PDPageContentStream contentStream = new PDPageContentStream(doc, page);

contentStream.beginText();

contentStream.setFont(font, 12);
contentStream.newLineAtOffset(25, 500);
contentStream.showText("신규 PDF 입니다.");

contentStream.endText();
contentStream.close();
doc.addPage(page);

PDPageContentStream class에 content를 추가할 PDF 문서와 해당 page를 매개변수로 할당 해 줍니다.

beginText()를 호출하여 text를 입력할 것이다라는 알리고 작업이 완료되었다면 contentStream.endText(); 호출하여 text 입력을 종료 합니다.  그 후 스트림을 닫고, content를 추가한 생성한 페이지를 PDF문서에 추가합니다. newLineAtOffset() 메소드는 x, y 좌표를 표현합니다. showText() 메소드는 입력한 text를 표현합니다. 

 

※ 만약 PDF 문서에 이미지를 추가하고 싶을 때는

PDImageXObject pdfImage = PDImageXObject.createFromFile("imagePath.png", doc);

PDPageContentStream contentStream = new PDPageContentStream(doc, page);
contentStream.drawImage(pdImage, x, y);

PDImageXObject class를 이용하여 처리하면 됩니다. contentStream.drawImage(pdImage, x, y); x좌표와 y좌표에 이미지를 그립니다.

 

doc.save(ROOT_PATH+"test.pdf");
doc.close();

모든 PDF 생성 작업이 완료 되었다면 PDF 문서를 저장합니다.

 

 

연결된 코드는 다음과 같습니다.

public int pdfCreate() throws Exception{
    // PDF를 생성한다.
    PDDocument doc = new PDDocument();

    // 폰트 설정
    InputStream fontStream = new FileInputStream(new File(ROOT_FONT_PATH));
    PDFont font = PDType0Font.load(doc, fontStream);

    // 페이지 생성
    PDPage page = new PDPage();

    // 페이지 그리기 (content) pdf와 page를 매개변수로 넘겨준다.
    PDPageContentStream contentStream = new PDPageContentStream(doc, page);

    // text 작성 시작
    contentStream.beginText();

    // 해당 text font 설정
    contentStream.setFont(font, 12);

    // x, y 좌표로 시작
    contentStream.newLineAtOffset(25, 500);
    contentStream.showText("신규 PDF 입니다.");

    // text 작성 종료
    contentStream.endText();

    // content 스트림 종료
    contentStream.close();

    // PDF를 doc에 Add 한다.
    doc.addPage(page);

    // 해당 경로에 PDF 파일을 저장한다.
    doc.save(ROOT_PATH+"test.pdf");

    // 파일 스트림 닫기
    doc.close();

    return 1;
}

 

 

테스트 결과는 다음과 같습니다.

테스트 결과
테스트 결과

 

 

 

그 다음 PDF 파일을 수정하고 싶을 때 PDF 파일을 읽어와 수정 할 수 있습니다.

 

PDDocument doc = PDDocument.load(new File(ROOT_PATH+"newPDF.pdf"));

PDDocument.load() static 메소드를 이용하면 해당 PDF 파일을 읽을 수 있습니다. ( 대용량인 경우 메모리 사용량 주의 )

 

int pageCount = doc.getNumberOfPages();

 

해당 PDF 파일의 page count를 얻을 수 있습니다. 이것을 활용하여 PDF 파일의 page index를 구하고 삭제하거나 추가하거나 수정할 수 있습니다. 

 

 

content 추가 및 수정 메소드는 다음과 같습니다. 

PDDocument doc = PDDocument.load(new File(ROOT_PATH+"newPDF.pdf"));
PDPage page = new PDPage();

List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
    for(int i = 0; i < 5; i++){
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("text", "안녕하세요 " + i);
        map.put("x", 30 * i);
        map.put("y", 100 * i);
        list.add(map);
    }

pdfPageContent(doc, page, list);
doc.addPage(page);


public void pdfPageContent(PDDocument doc, PDPage page, List<Map<String, Object>> params) throws Exception{

    // 폰트 설정
    InputStream fontStream = new FileInputStream(new File(ROOT_FONT_PATH));
    PDFont font = PDType0Font.load(doc, fontStream);

    // 페이지 그리기 (content) pdf와 page를 매개변수로 넘겨준다.
    PDPageContentStream contentStream = new PDPageContentStream(doc, page);

    // text 작성 시작
    contentStream.beginText();

    // 해당 text font 설정
    contentStream.setFont(font, 12);

    // x, y 좌표로 시작
    for(Map map : params) {
        int x = (int)map.get("x");
        int y = (int)map.get("y");
        String text = (String)map.get("text");

        contentStream.newLineAtOffset(x, y);
        contentStream.showText(text);

    }

    // text 작성 종료
    contentStream.endText();

    // content 스트림 종료
    contentStream.close();
}

 

 

해당 page를 삭제하는 예시는 다음과 같습니다.

// 페이지 삭제
for(int i = 0; i < pageCount; i++){
    if(i == 2 && pageCount > 1){
        doc.removePage(0);
        doc.removePage(1);
        }
    }

removePage() 메소드를 활용하여 매개변수에 해당 PDF의 index 값을 넣고 호출 합니다.

 

 

PDF 파일간의 병합은 더욱 간단합니다.

public int pdfMerge() throws Exception {
    File f1 = new File(ROOT_PATH+"test.pdf");
    File f2 = new File(ROOT_PATH+"test2.pdf");

    PDFMergerUtility merger = new PDFMergerUtility();
    merger.setDestinationFileName(ROOT_PATH+"merge.pdf");
    merger.addSource(f1);
    merger.addSource(f2);
    merger.mergeDocuments();

    return 1;
}

병합할 PDF 파일 2개를 불러와서 PDFMergerUtility class를 이용하여 해당 PDF source를 병합합니다. PDF 파일은 addSource() 메소드에 순차적으로 진행 됩니다. mergeDocuments() 메소드를 호출하면 병합 작업 한 PDF 파일을 setDestinationFileName() 메소드를 호출한 파일 경로에 저장 됩니다. 

 

결과는 다음과 같습니다.

PDF 병합 테스트
PDF 병합 테스트

 

 

 

마지막으로 실제 업무에서 PDF파일 약 500개 정도를(약 1GB ~ 3GB 용량) 병합하여 하나로 압축하는 작업을 진행했었는데, 파일이 많을 경우 파일 스트림의 오픈 시간이 길어진다. 파일 스트림을 장시간 열고 있으면 에러가 발생할 수 있으니, 전체를 한번에 병합하여 처리하는 것 보다 병합본을 계속 읽어 하나씩 병합하는 방법으로 처리하는 것이 에러가 발생하지 않고 속도도 빠르며 안전하다.

 



반응형