Tempo Di Valse

[Windows] PDFium 를 이용한 PDF to PNG 본문

개발/ETC

[Windows] PDFium 를 이용한 PDF to PNG

TempoDiValse 2022. 2. 4. 18:35

우선, PNG 파일을 만들기 전에 윈도우에 깔려 있어야 하는 라이브러리들이 있다.

 

- libpng
- zlib

 

그지같은 설명서에서는 PDF 파일 여는 것만 있지 이걸 어떻게 사용하라는 안내도 없고, 헤더파일 열자니 이건 무슨말인 지 모르겠고 해서 소스로 파악 해서 간추려 보았다.

소스는 일전에 빌드파일 만들어놓았던 pdfium 폴더에 있는 sample/pdfium_test.cc 를 참조하였다.

#include <iostream>
#include <vector>
#include <stdlib.h>

#include "fpdfview.h"
#include "cpp/fpdf_scopers.h"

#include <png.h>

// PNG 복사 버퍼 구조체
struct Buffer {
    explicit Buffer(std::vector<uint8_t> *o) : buffer(o) {}
    std::vector<uint8_t> *buffer;
}

void copyToBuffer(png_structp png_ptr, png_bytep data, png_size_t size){
    Buffer *b = static_cast<Buffer*>(png_get_io_ptr(png_ptr));

    size_t old = b->buffer->size();
    b->buffer->resize(old + size);

    memcpy(&(*b->buffer)[old], data, size);
}

void unknown(png_structp p){}

int main(){
    const char *path = PDF_FILE_PATH;
    int indexToLoad = 0; // 열고 싶은 페이지 인덱스 (0 번부터 1페이지이다)

    // PDFium 을 초기화한다. 이걸 해야 사용할 수 있음
    FPDF_InitLibrary();

    // PDF 파일을 연다
    FPDF_DOCUMENT doc = FPDF_LoadDocument(path, NULL);

    if(!doc){
        // 에러를 코드로 알려준다.
        unsigned long code = FPDF_GetLastError();
    }

    // 페이지를 로드한다.
    FPDF_PAGE page = FPDF_LoadPage(doc, indexToLoad);
    int originW = FPDF_GetPageWidth(page);
    int originH = FPDF_GetPageHeight(page);

    // 비트맵을 만든다. PNG 파일을 만들려면 FPDFBitmap_Create 의 마지막 숫자를 1로 놓는다.
    // 마지막 인자 값은 Alpha 가 있는 지 없는 지 확인하는 것이다.
    ScopedFPDFBitmap panel(FPDFBitmap_Create(originW, originH, 1));

    // PNG 의 뒷 배경을 흰색으로 먼저 채운다. 안채우면 테두리에 뭔가 검정색이 보인다.
    FPDFBitmap_FillRect(panel.get(), 0, 0, originW, originH, 0xFFFFFFFF);

    // 페이지를 렌더한다. 배경위에 올리는 것이다.
    FPDF_RenderPageBitmap(panel.get(), page, 0, 0, originW, originH, 0, FPDF_ANNOT);

    // 다 올렸으면 페이지를 종료한다. 페이지가 사용되면 항상 종료하도록 한다.
    FPDF_ClosePage(page);

    // 렌더가 제대로 되었으면 width 랑 height 가 originW/H 와 같은 값이 나올 것이다.
    int width = FPDFBitmap_GetWidth(panel.get());
    int height = FPDFBitmap_GetHeight(panel.get());

    // Stride 라는 것으로 렌더된 비트맵의 한 줄 사이즈를 가져오는 듯 하다.
    // height * rowBytes 를 하게 되면 해당 PNG 가 그려진다.
    int rowBytes = FPDFBitmap_GetStride(panel.get());

    // 렌더된 비트맵을 버퍼로부터 가져와 담는다
    // 버퍼가 void * 형태라서 형변환을 시켜주었다.
    uint8_t *bitmap = static_Cast<uint8_t*>(FPDFBitmap_GetBuffer(panel.get());

    FPDF_CloseDocument(doc);
    FPDF_DestroyLibrary();

    // ------------- 여기까지가 PDFium 에서 페이지를 불러와 렌더하는 로직이었다.

    std::vector<uint8_t> output;
    Buffer ob(&output);

    // PNG 를 사용하려 한다.
    png_struct* png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

    // PNG 파일 메타데이터를 다루는 객체인 것 같다.
    png_info* info_ptr = png_create_info_struct(png_ptr);
    if(!info_ptr){
        //제대로 안만들어지면 PNG 만드는 일을 무효화 한다.
        png_destroy_write_struct(&png_ptr, NULL);
    }

    // 이미지 압축 퍼센트를 얘기하는 것 같은데 아직 만져보진 않았다.
    // Z_DEFAULT_COMPRESSION 은 zlib 에 있는 상수 값이다.
    png_set_compression_level(png_ptr, Z_DEFAULT_COMPRESSION);

    // 무슨 역할 인 지는 모름
    if(setjmp(png_jmpbuf(png_ptr))) return;

    // PNG 를 만들 때 콜백함수를 입력하는 것 같다.
    // unknown 부분은 Flush 메소드 부분이라는데 PDFium 소스에서도 따로 사용하진 않았다.
    png_set_write_fn(png_ptr, &ob, copyToBuffer, unknown);
    png_set_IHDR(png_ptr, info_ptr, width, height, 8, 
            PNG_COLOR_TYPE_RGB_ALPHA, // 타입을 ALPHA 로 안해놓으면 깨진 이미지가 나온다
            PNG_INTERLACE_NONE, 
            PNG_COMPRESSION_TYPE_DEFAULT, 
            PNG_FILTER_TYPE_DEFAULT);

    // 기본적인 메타데이터를 작성했다.
    png_write_info(png_ptr, info_ptr);

    // PNG 로 비트맵 버퍼를 작성한다.
    for(int y=0; y < originH; y++){
        png_write_row(png_ptr, &bitmap[y * rowBytes])
    }

    png_write_end(png_ptr, info_ptr);
    png_destroy_write_struct(&png_ptr, &info_ptr);

    // -------- 여기까지 PNG 로 작성하는 로직이었다.

    FILE *fp = fopen("export.png", "wb");
    fwrite(&output[0], 1, output.size(), fp);
    fclose();
}

나는 클래스화 하여 개발을 한 상태이고, 이 소스는 일부분을 오려다가 붙인거라 다른 곳에 복붙하면 틀림없이 오류발생할 가능성이 100% 있을 것이기 때문에 가져다가 사용하는 것이라면 하나하나 전부 입력하면서 개발을 해야 좋을 것이다.

반응형
Comments