기술 문서

HotPDF로 Delphi에서 구조 순서 PDF 텍스트 추출

모든 지오메트릭 텍스트 추출기는 추측하고 있습니다. 페이지가 그리는 글리프를 읽고, 베이스라인과 수평 위치로 정렬한 뒤, 시각적 배치가 사람이 읽을 순서와 일치하길 바랍니다. 단일 컬럼 보고서에서는 그 추측이 맞습니다. 두 컬럼 학술지 기사, 사이드바가 있는 양식, 셀이 컬럼별로 출력된 표에서는 알아차리기 어렵고 하류에서 발견하기 비싼 방식으로 틀립니다. HotPDF는 이에 ExtractLoadedPageStructureText로 답합니다. 지오메트리를 전혀 무시합니다. ISO 32000-1 §14.8.4가 정의한 대로 작성 순서로 문서 구조 트리를 순회한 뒤, marked-content identifier로 페이지 글리프를 재조립합니다. 태그된 PDF에서 이는 휴리스틱이 아니라 생산 애플리케이션이 선언한 순서입니다

페이지에 쓸 만한 구조 트리가 없으면 함수는 False를 돌려주는데, 이는 실패가 아니라 지오메트릭 추출기로 폴백하라는 신호입니다. 두 경로 설계가 알고리즘보다 더 중요합니다. 실제 문서 수수문은 태그된 정부 양식과 스캐너 출력을 같은 폴더에서 받으며, 그중 하나만 처리하는 파이프라인은 파이프라인이 아닙니다

지오메트릭 추출은 읽기 순서를 왜 틀리는가

PDF 콘텐츠 스트림은 읽기 순서를 전혀 실지 않기 때문입니다. 드로잉 연산자의 나열이며, 생산자는 자기 레이아웃 엔진에 맞는 어떤 순서로든 이를 내보일 자유가 있습니다. 워드 프로세서는 보통 흐름 순서로 내보내 지오메트릭 정렬도 멀쩡해 보입니다. 레이아웃 도구, 양식 디자이너, 보고서 생성기는 자주 그렇지 않습니다. 페이지 바닥글이 본문보다 먼저 출력될 수 있고, 표는 컬럼 우선으로 채워질 수 있으며, 두 컬럼 페이지는 조판기가 둘을 함께 풀었기 때문에 양쪽 컬럼의 줄이 섞일 수 있습니다

두 컬럼 PDF 페이지 비교: 베이스라인 정렬 지오메트릭 추출이 컬럼을 엮는 것과 HotPDF 구조 순서 MCID 추출의 대비
베이스라인으로 글리프를 정렬하면 두 컬럼이 엉터리로 섞이는 반면, 구조 트리는 생산자가 선언한 순서를 재생합니다

실패 양상은 조용합니다. 지오메트릭 추출기는 오류를 보고하지 않고, 문장이 두 컬럼에서 엮인 산문을 그저 돌려줍니다. 그 텍스트를 소비하는 무엇이든, 검색 색인이든, 전자송장 필드 매퍼든, 언어 모델에 먹이는 검색 파이프라인이든, 경고 없이 피해를 물려받습니다. HotPDF는 로드된 문서용 지오메트릭 추출기도 함께 제공하며, 태그 없는 파일에는 여전히 올바른 도구입니다. 구조 순서 경로의 요점은 문서가 이미 답을 실어 가고 있을 때는 추측을 멈추는 것입니다

구조 트리가 실제로 저장하는 것

태그된 PDF는 페이지의 두 번째 병렬 기술을 갖고 있습니다. 카탈로그가 /StructTreeRoot를 가리키고, 그 /K 자식들이 구조 요소의 트리를 이룹니다. /Document, /Sect, /P, /Table, /TR, /TD 등입니다. 그 트리의 잎은 marked-content 참조, 즉 페이지 콘텐츠 스트림의 한 구간을 이름 짓는 정수입니다. 콘텐츠 쪽에서 그 구간들은 /MCID를 실은 BDC 연산자로 열리고 EMC로 닫힙니다. 각 구조 요소는 자기가 속한 페이지를 이름 짓는 /Pg 엔트리도 실는데, 구조 트리가 수백 페이지에 걸친 문서에서 페이지별 순회가 가능한 것이 바로 이 때문입니다

PDF 구조 트리 해부: Sect, Table, TR, TD 같은 StructTreeRoot 요소를 HotPDF 페이지 콘텐츠 스트림의 BDC MCID 구간과 연결
트리의 잎은 marked-content 참조이며, 각 요소는 순회가 현재 페이지로 걸러내게 하는 Pg 엔트리를 실습니다

HotPDF는 그 트리를 깊이 상한 128단계로 순회하며 /Pg로 걸러 현재 페이지만 기여하게 합니다. 순회의 출력은 텍스트가 아니라 MCID 값의 순서 목록입니다. 이 페이지에서 marked-content 구간들의 작성 순서입니다. 텍스트 재조립은 그 순서로 글리프를 재생하는 일이 됩니다

MCID는 글리프 추출 중에 기록되지 나중에 찾지 않습니다

이 기능을 값싸게 만드는 구현 세부입니다. HotPDF는 추출하는 모든 글리프에 활성 marked-content 식별자를 THPDFGlyphRecordMCID 필드에 이미 기록합니다. 콘텐츠 스트림 해석기가 각 TjTJ 연산자를 처리하는 순간 어느 BDC 범위가 열려 있는지 알기 때문입니다. 따라서 구조 순서 추출에는 콘텐츠 스트림에 대한 두 번째 패스가 필요 없습니다. 구조 트리에서 MCID 나열을 수집한 뒤, 이미 추출된 글리프를 MCID로 버킷에 나누고 그 나열 순서로 내보냅니다

var
  Pdf: THotPDF;
  PageCount, I, Untagged: Integer;
  PageText, AllText: UnicodeString;
  Report: TStrings;   // 호출자 소유의 진단 싱크
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('accessible-form.pdf');
    AllText := '';
    for I := 0 to PageCount - 1 do
    begin
      if Pdf.ExtractLoadedPageStructureText(I, PageText, Untagged) then
      begin
        // 구조 트리에서 곧장 온 작성 순서
        if Untagged > 0 then
          Report.Add(Format('page %d: %d glyphs outside the structure tree',
            [I, Untagged]));
      end
      else
        // 이 페이지에 쓸 만한 구조 트리 없음: 지오메트릭 폴백
        Pdf.ExtractLoadedPageText(I, PageText);
      AllText := AllText + PageText + #13#10;
    end;
  finally
    Pdf.Free;
  end;
end;

태그 없는 글리프는 세어질 뿐 조용히 버려지지 않습니다

페이지는 부분적으로만 태그될 수 있습니다. 생산자는 장식 선, 페이지 번호, 후반 워터마크를 어떤 BDC 범위 밖에도 추가하며, 그 글리프들은 어느 MCID에도 속하지 않습니다. 버려버리는 것이 깔끔한 구현이지만 틀린 구현입니다. 같은 간극은 생산자가 본문은 태그하고 표를 잊었을 때도 나타나며, 여러분은 표를 눈치채지 못한 채 잃게 되기 때문입니다

HotPDF는 청구되지 않은 글리프를 구조 순서 텍스트 뒤에 지오메트릭 꼬리로 덧붙이고, 그 개수를 UntaggedGlyphCount 출력 매개변수로 보고합니다. 그 수는 행동할 수 있는 품질 신호입니다. 2천 글리프 페이지의 한 줌은 페이지 가구이며 무시할 수 있습니다. 페이지의 40퍼센트가 구조 트리 밖이라는 것은 태그가 장식이며 그 파일에는 지오메트릭 추출기가 더 정직한 답이라는 뜻입니다

페이지에 쓸 만한 구조 트리가 없거나 태그가 장식일 때 지오메트릭 폴백과 함께하는 HotPDF 구조 텍스트 추출의 판단 흐름
True는 태그 없는 꼬리가 덧붙여진 구조 순서를 뜻하고, False는 실패 대신 페이지를 지오메트릭 추출기로 보냅니다
function ExtractPageBestEffort(Pdf: THotPDF; PageIndex: Integer;
  out AText: UnicodeString; out UsedStructure: Boolean): Boolean;
var
  Untagged, TotalGlyphs: Integer;
  Glyphs: THPDFGlyphArray;
begin
  UsedStructure := False;
  if Pdf.ExtractLoadedPageStructureText(PageIndex, AText, Untagged) then
  begin
    TotalGlyphs := 0;
    if Pdf.ExtractLoadedPageGlyphs(PageIndex, Glyphs) then
      TotalGlyphs := Length(Glyphs);
    // 구조 트리가 페이지 대부분을 주장할 때만 신뢰합니다
    if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
    begin
      UsedStructure := True;
      Result := True;
      Exit;
    end;
  end;
  Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;

함수가 False를 돌려주게 만드는 것

세 사례이며, 하나만 문서의 결함이므로 구분할 가치가 있습니다. 첫째는 평범한 태그 없는 PDF입니다. /StructTreeRoot도 없고 순회할 것도 없으며 False는 단순히 진실입니다. 둘째는 텍스트가 한 번도 태그되지 않은 OCR 레이어에서 오는 스캔 페이지입니다. 셋째가 흥미로운 경우입니다. /MCID 값을 실은 BDC 연산자를 갖지만 페이지에는 /StructParents 엔트리가 없고 구조 트리는 그 식별자들을 결코 참조하지 않는 콘텐츠입니다. marked content는 존재하고 구조 쪽은 존재하지 않으며 복원할 순서도 없습니다. HotPDF는 하나를 발명하는 대신 False를 보고합니다

마지막 사례는 손으로 편집한 파일과, 구조 트리를 만들지 않으면서 optional-content나 artifact 목적으로 marked content를 내보내는 도구의 출력에서 나타납니다. 직접 태그된 PDF를 만들고 있다면 같은 비대칭이 바로 PDF/UA 검증이 확인하는 것이고, 라이터 쪽 대응물은 태그된 페이지 분할 출력을 내는 layout DOM에서 다룹니다

구조 순서가 본전을 뽑는 곳

접근성 감사가 명백한 사례입니다. 문서를 PDF/UA 기준으로 인증한다면 스크린 리더가 읽어 줄 순서가 정확히 구조 순서이므로, 그것을 추출하는 것이 스크린 리더 없이 검토하는 방법입니다. 데이터 캡처가 더 큰 상업 사례입니다. 태그된 정부 양식, 규제 공시, 전자송장 첨부파일은 필드 레이블과 값을 선언된 순서로 실으며, 그 순서로 읽으면 다중 컬럼 레이아웃에서 지오메트릭 추출이 만들어 내는 매핑 버그 한 부류 전체를 제거합니다

가장 최신 소비자는 언어 모델을 위한 검색입니다. 임베딩용 문서 청킹은 텍스트 순서만큼만 좋으며, 두 컬럼을 엮는 청크는 한 번도 존재하지 않았던 문장을 만들어 냅니다. 구조 순서 추출은 이에 대해 가장 값싼 해결책입니다. 태그된 문서에서 올바른 순서는 이미 파일 안에 있고 읽기만 하면 되기 때문입니다

HotPDF는 Delphi와 C++Builder용 네이티브 VCL 컴포넌트이므로, 구조 트리 순회와 글리프 재생은 모두 외부 렌더러 없이 로드된 문서를 상대로 인프로세스에서 돕니다. 로드 문서 추출 계열의 전체 API 세부는 HotPDF Delphi PDF component 제품 페이지에 있습니다