기술 문서

PDFium Component를 사용한 Delphi에서의 PDF 텍스트 추출

PDF 텍스트 추출은 간단해 보이지만, 텍스트 레이어가 누락되었거나 손상되었거나 혹은 논리적인 순서 없이 수십 개의 자잘한 문자 청크로 조각나 배치된 문서를 마주하면 대단히 까다로워집니다. PDFium Component는 두 가지 접근 경로를 제공합니다: 즉 페이지 상의 모든 글리프(glyph)에 인덱스 기반으로 하위 수준 접근을 지원하는 Character[] 배열과, PDF의 태그 구조 트리 또는 휴리스틱 분석을 토대로 단락과 제목 단위를 복원해 구조화된 뷰를 구성해 주는 ReadablePageContent입니다. 각 기능의 장단점이 다르므로 각 방식이 반환하는 상세 데이터 성격을 파악하는 것이 중요합니다

문서 오픈 시의 예외 처리 및 감지되지 않는 에러 주의 사항

TPdfFileName 지정 후 Active := True 설정을 연동하여 파일을 로드합니다. 여기서 한 가지 중요한 세부 특징이 있습니다: Active := True 호출은 결코 예외(exception)를 발생시키지 않는다는 점입니다. 파일이 유실되었거나, 비밀번호가 지정되어 있거나, 혹은 손상된 경우 PDFium 라이브러리가 내부적으로 오류를 차단하며 Active 속성이 조용히 False 상태를 유지합니다. 따라서 텍스트 추출 루틴 진입 전에 다음과 같은 방어 코드가 반드시 수반되어야 합니다:

Pdf := TPdf.Create(nil);
try
  Pdf.FileName := 'report.pdf';
  Pdf.Active := True;
  if not Pdf.Active then
  begin
    ShowMessage('Could not open PDF (damaged or wrong password)');
    Exit;
  end;
  // extraction follows here
finally
  Pdf.Active := False;
  Pdf.Free;
end;

암호 지정 파일의 경우 Active := True 처리 전에 Pdf.Password := '...'를 미리 입력해야 합니다. 암호 검증 실패 시 추가 조작 없이 즉각 Active 처리가 실패하므로, 연결 실패 시 라이브러리를 일단 닫고 올바른 패스워드로 다시 오픈 작업을 재시도해야 합니다

Character[] 배열을 사용한 페이지별 텍스트 추출

가장 하위 레벨의 방법은 페이지 내 모든 단일 문자를 순회 탐색하는 것입니다. Pdf.PageNumber를 지정해 해당 페이지의 텍스트 레이어를 메모리에 얹은 뒤, Character[] 속성을 사용해 CharacterCount 만큼 루프를 수행합니다. 각 문자 엔트리 별로 두 개의 확인 플래그를 체크해 유용하게 쓸 수 있습니다: CharacterGenerated[i]는 렌더러가 자동으로 덧붙인 가상 글리프(예를 들어 줄바꿈 시에 덧붙인 하이픈 기호 등)로 실제 유니코드 값에 대응하지 않음을 의미하며, CharacterMapError[i]는 글리프를 특정 코드 포인트로 변환하지 못했음을 뜻하며 주로 ToUnicode 테이블 정보가 유실된 특수 인코딩 글꼴 문서 등에서 유발됩니다

procedure ExtractAllText(Pdf: TPdf; Output: TStrings);
var
  Page, I: Integer;
  Line: string;
  Ch: WideChar;
begin
  for Page := 1 to Pdf.PageCount do
  begin
    Pdf.PageNumber := Page;
    Line := '';
    for I := 0 to Pdf.CharacterCount - 1 do
    begin
      if Pdf.CharacterGenerated[I] or Pdf.CharacterMapError[I] then
        Continue;
      Ch := Pdf.Character[I];
      if Ch = #13 then
        Ch := #10;   // normalize CR to LF
      Line := Line + Ch;
    end;
    Output.Add(Line);
  end;
end;

추출 결과는 PDFium이 사전에 나열한 순서대로 유니코드 코드 포인트들이 정렬된 플랫 문자열입니다. 이 순서는 페이지 드로잉 스트림 상에 배치된 순서를 따르므로, 사용자의 시선 흐름(왼쪽에서 오른쪽)과 항상 정비례하지는 않습니다. 일반적인 오피스 프로그램이 생성한 대다수의 영문/유럽계 문자 문서는 이 방식으로 원활히 처리되나, OCR 변환 시 비대칭 글리프 데이터가 혼입된 스캔 PDF 문서 또는 아랍어 계열처럼 우측에서 좌측으로 읽는 문자들의 경우에는 문자 나열 순서가 꼬일 우려가 있습니다. 이 경우 구조화 분석 모듈인 ReadablePageContent를 사용하는 편이 더 신뢰할 수 있습니다

ReadablePageContent를 사용한 구조화된 텍스트 추출

ReadablePageContent는 한 단계 상위 뷰를 다룹니다: 즉 단락, 헤더, 리스트 목록, 테이블 셀 등의 문서 포맷 형식을 식별할 수 있는 Kind 식별자와 텍스트 조각을 포함하는 Fragments 배열을 구비한 TPdfReadableContent 레코드 데이터를 반환합니다. 문서 구조 트리가 정상 내장된 PDF(Pdf.IsTagged 결과가 True인 기태깅 문서)의 경우, 데이터 소스 출처는 rosStructure로 지정되며 탐색 순서의 논리적 엄격성이 보장됩니다. 구조가 생략된 일반 문서의 경우 PDFium 엔진은 글자 배치 외형의 경계 상자(bounding box) 크기 관계를 수학적으로 역추적해 그룹화하는 휴리스틱 기능(rosHeuristic)을 연동해 작동하나, 100% 완전한 정합성을 담보하지는 못합니다

procedure ExtractStructured(Pdf: TPdf; Output: TStrings);
var
  Page: Integer;
  Content: TPdfReadableContent;
  Fragment: TPdfContentFragment;
begin
  for Page := 1 to Pdf.PageCount do
  begin
    Content := Pdf.ReadablePageContent(Page);
    for Fragment in Content.Fragments do
    begin
      case Fragment.Kind of
        cfHeading   : Output.Add('# ' + Fragment.Text);
        cfParagraph : Output.Add(Fragment.Text);
        cfListItem  : Output.Add('- ' + Fragment.Text);
      else
        Output.Add(Fragment.Text);
      end;
    end;
  end;
end;

만약 Content.Source = rosHeuristic 상태인데 결과 텍스트가 심하게 깨져 보인다면, 이는 해당 원본 문서 자체가 정상적인 텍스트 레이아웃 정보 없이 마구잡이로 구워진 상태임을 의미합니다. 이 단계에서 가장 안전한 해결책은 원본 생성 프로그램 상에서 태깅(tagging) 설정을 켜고 문서를 다시 출력해 로드하거나, 각 문자들의 바이트 좌표계를 읽어 들여 Y값과 X값을 기준으로 위치를 수동 재정렬(sort)하는 사후 가공 처리를 돌리는 방법뿐입니다

CharacterOrigin 및 CharacterRectangle의 활용법

이 두 속성은 페이지 공간 상에서의 문자 위치 좌표계 정보를 반환합니다(단위는 포인트, 원점 기준은 좌측 하단 구석이며 Y축은 위로 올라갈수록 증가). CharacterOrigin[i]는 글리프의 baseline 기준 앵커 좌표이며, CharacterRectangle[i]는 글리프를 둘러싼 전체 경계 사각형 박스입니다. 이 데이터들은 단순 텍스트 추출을 넘어선 지능형 레이아웃 분석 작업의 필수 도구입니다: 예를 들어 컬럼 외곽선 감지, 오차 범위 내의 Y 좌표계 수렴 확인을 통한 문자들의 행 라인 정렬 연산, 혹은 뷰어 상의 텍스트 드래그 선택을 위한 픽셀 좌표 히트 테스트 정보 구성 등입니다. 사용자의 마우스 클릭 지점에 어떤 글자가 위치하는지 찾아내야 한다면, 사각형 루프 연산을 수동 수행하지 않고도 CharacterIndexAtPos(X, Y, ToleranceX, ToleranceY) 호출로 해당 인덱스를 즉시 정확히 조회해 활용할 수 있습니다

배포 환경에서의 DLL 연동 방침

PDFium Component는 모든 PDF 해석 작업을 컴파일 타겟 운영체제 플랫폼에 맞춰 네이티브 DLL 파일인 pdfium32.dll 또는 pdfium64.dll로 위임해 연동합니다. 컴포넌트는 해당 DLL 파일들을 Windows 시스템 디렉터리로 자동으로 찾아 복사해 주는 CopyDlls.bat 스크립트를 내장하고 있습니다. 개발 기기 환경에서 이 배치 파일을 관리자 권한으로 한 번 구동해 주면 세팅이 유지되며, 실서버나 패키지 배포 시에는 DLL 파일들을 개발 완료된 프로그램의 실행 파일(EXE)과 동일한 경로 상에 두어 배포하면 됩니다. JS 지원 V8 빌드(pdfium32v8.dll, pdfium64v8.dll)는 대단히 무거우므로 문서 내의 JS 실행 동작을 꼭 수행해야 하는 경우에만 선별 탑재하십시오. 일반적인 텍스트 추출 작업에는 표준 빌드 버전을 배포하는 편이 깔끔합니다

실행 도중 대상 DLL 파일이 식별되지 않는 경우에도 컴포넌트는 로드 실패 오류를 내부 차단하므로, 파일 유실 때와 동일하게 Active := True가 아무 반응 없이 조용히 실패합니다. 배포 전 배포용 테스트 가상 컴퓨터 환경 등에서 연동 파일들이 누락되지 않았는지 꼼꼼히 확인해 볼 필요가 있습니다

레이아웃 분석을 위해 Character[]와 FontSize[] 함께 활용하기

단순 텍스트의 나열을 넘어, 문자 수준 제어 API는 각 글리프의 실 렌더링 포인트 수치를 가리키는 FontSize[i]를 추가로 제공합니다. CharacterOrigin[i]CharacterRectangle[i] 정보를 이 폰트 크기 데이터와 크로스체크하면, 구조 트리 데이터가 생략된 문서 내에서도 본문 텍스트와 제목 텍스트를 논리적으로 선별해 낼 수 있습니다. 문장 라인의 평균 폰트 크기가 임계치 수준 위로 껑충 뛰는 구간은 제목 라인으로 분류할 수 있습니다. 동일한 논리로 이미지 영역 하단의 작은 텍스트는 캡션 정보로, 페이지 맨 바닥의 초소형 텍스트는 각주(footnotes) 정보로 추출하는 로직을 손쉽게 구현할 수 있습니다. 이 과정에서 리얼 드로잉을 돌릴 필요는 없으며, 세 가지 속성 모두 Active := True 상태일 때 메모리에 빌드되어 제공되는 텍스트 레이어를 고속으로 조회해 처리합니다

한 가지 미세한 조정 사항: FontSize[i]는 페이지의 CTM(현재 변환 행렬) 사양이 최종 반영된 크기를 지시하므로, 페이지 전체 배율이 기가공되어 저장된 문서의 경우 그에 비례해 가공된 결과 크기가 노출될 수 있습니다. 전체 문서 상에서 페이지 별 레이아웃 규격이 상이한 경우에는 임계 대조 검토 전에 각 페이지의 MediaBox 세로 좌표계를 기준으로 비례식을 세워 정규화 처리를 연동하는 것이 좋습니다

추출 결과물 파일 출력 처리

Delphi의 TStringList는 XE 버전부터 UTF-8 입출력을 안전하게 지원합니다. 파일 수신 시스템과의 범용성을 감안해 BOM 표식이 없는 깔끔한 UTF-8 출력을 희망한다면 WriteBOM := False 설정을 적용하십시오:

var
  Lines: TStringList;
begin
  Lines := TStringList.Create;
  try
    ExtractAllText(Pdf, Lines);
    Lines.WriteBOM := False;
    Lines.SaveToFile('output.txt', TEncoding.UTF8);
  finally
    Lines.Free;
  end;
end;

메모리 점유가 우려되는 기가바이트 단위의 초대형 문서를 추출할 때는, 추출 문자열 전체를 문자열 리스트 메모리에 미리 축적하기보다는 페이지 루프 내에서 TEncoding.UTF8 설정을 건 TStreamWriter를 활용해 텍스트를 디스크 파일로 실시간 기록해 나가는 구조를 취하십시오

여기에 소개된 Character[], CharacterCount, CharacterOrigin[], CharacterRectangle[], ReadablePageContent, 및 CharacterIndexAtPos 등의 핵심 API 구성 요소는 Delphi 및 C++Builder용 PDFium Component의 내장 표준 사양으로 제공됩니다