PDFlibPas는 Office 자동화 없이 PDF 콘텐츠를 두 가지 편집 가능한 형식으로 변환합니다. ExportPageMarkdown과 ExportDocumentMarkdown은 추론된 제목, 순서 있는/없는 목록, 파이프 표를 담은 시맨틱 Markdown을 반환하고, SaveDOCXToFile과 SaveDOCXToStream은 단락, 제목, 네이티브 목록 번호 매기기, 감지된 표, 폰트 스타일, 페이지 나누기, 배치된 PNG 이미지를 담은 WordprocessingML 패키지를 작성합니다
두 기능 모두 서버에서 Word 설치나 COM 없이 순수 Pascal로 완전히 동작합니다. 바로 이 제약이 이 기능이 데스크톱 도구가 아니라 PDF 라이브러리 안에 존재하는 이유입니다
"PDF에서 Word로"는 왜 정말로 어려운가?
PDF 페이지에는 단락이 들어 있지 않기 때문입니다. 그 안에는 생성기가 내보낸 순서 그대로 좌표에 글리프 런을 배치하는 텍스트 표시 연산자만 있을 뿐, 두 런이 같은 문장에 속한다는 것을 나타낼 의무는커녕 같은 목록 항목에 속한다는 것을 나타낼 의무조차 없습니다. 이 형식은 인쇄된 페이지를 정확히 서술하도록 설계되었고, 그 페이지를 만들어낸 구조를 버림으로써 그 목적을 달성합니다
그래서 모든 변환기는 생성기가 버린 것을 재구성해야 합니다. 줄 묶음은 수직 간격과 기준선 정렬로부터 나옵니다. 단락 경계는 간격 변화와 들여쓰기로부터 나옵니다. 제목은 본문보다 폰트가 크거나 굵고 뒤이어 오는 것과 구분되는 한 줄입니다. 목록은 불릿 문자나 번호 패턴으로 시작하는 일련의 단락입니다. 표는 행과 열에 걸쳐 가장자리가 정렬된 텍스트 블록의 격자입니다. 이 모든 것은 추론이며, 추론이라는 것은 일반적인 조판 관례를 따르는 문서에서는 좋은 결과를, 그렇지 않은 문서에서는 평범한 결과를 낸다는 뜻입니다
태그된 PDF는 예외이며, 그것도 큰 예외입니다. 문서가 구조 트리를 담고 있으면 단락, 제목, 목록, 표의 역할이 추측되는 대신 기록되어 있으며, 이것이 태그된 PDF 접근성 구조에서 다루는 접근성 작업이 변환 품질에서도 성과를 내는 이유입니다. 생성기를 여러분이 통제한다면, 출력에 태그를 붙이는 것은 나중에 그것을 변환해야 할 누군가를 위해 할 수 있는 가장 효과가 큰 단 하나의 일입니다
한 번에 한 페이지씩 하는 Markdown 내보내기
Markdown 경로는 목적지가 텍스트 파이프라인, 즉 문서화 사이트, 검색 색인, 어시스턴트용 검색 코퍼스일 때 선택할 경로입니다. 옵션은 비트 마스크로 되어 있습니다. PDF_MARKDOWN_INCLUDE_PAGE_MARKERS, PDF_MARKDOWN_DETECT_HEADINGS, PDF_MARKDOWN_PRESERVE_STYLES가 있으며, PDF_MARKDOWN_DEFAULT는 이 셋을 모두 결합합니다
var
Pdf: TPDFlib;
Md: WideString;
begin
Pdf := TPDFlib.Create;
try
Pdf.LoadFromFile('handbook.pdf', '');
// 한 페이지를 문자열로
Md := Pdf.ExportPageMarkdown(1, PDF_MARKDOWN_DEFAULT);
// 페이지 범위를 BOM 없는 UTF-8로 디스크에 스트리밍
Pdf.SaveMarkdownToFile('1-40',
PDF_MARKDOWN_DETECT_HEADINGS or PDF_MARKDOWN_PRESERVE_STYLES,
'handbook.md');
finally
Pdf.Free;
end;
end;
페이지 마커는 검색(retrieval) 작업에서 제 몫을 합니다. 자신이 나온 페이지를 함께 담은 텍스트 조각은 정확하게 인용될 수 있고, 그 인용을 따라간 독자는 주장이 실제로 있는 곳에 도달합니다. Markdown이 사람이 읽을 용도라면 끄십시오. 그 경우 원본 레이아웃에서 온 페이지 경계는 그저 잡음일 뿐입니다
스트리밍 진입점은 대형 문서에서 중요합니다. SaveMarkdownToStream과 SaveMarkdownToFile은 한 번에 한 페이지씩 UTF-8을 기록하고 전체 출력을 버퍼링하지 않으므로, 900페이지짜리 매뉴얼이 먼저 메모리 안의 900페이지짜리 문자열이 되는 일은 없습니다. 바이트 순서 표시(BOM)가 없는 것도 의도적입니다. Markdown 파일의 BOM은 놀라울 만큼 많은 정적 사이트 생성기와 diff 도구를 헷갈리게 만듭니다
머신에 Office 없이 만드는 DOCX
DOCX 라이터는 패키지 자체를 만들어냅니다. CRC 검사를 갖춘 순수 Deflate로 작성된 ZIP 항목, WordprocessingML 파트, 그리고 이들을 묶는 관계(relationship)입니다. Word를 호출하는 곳은 어디에도 없으므로, 이 변환은 헤드리스 서버, 서비스 계정 안, 컨테이너 안, 즉 Office 자동화가 라이선스가 없거나 불안정하거나 금지된 모든 곳에서 실행됩니다
var
Pdf: TPDFlib;
Target: TFileStream;
begin
Pdf := TPDFlib.Create;
Target := TFileStream.Create('handbook.docx', fmCreate);
try
Pdf.LoadFromFile('handbook.pdf', '');
Pdf.SaveDOCXToStream('1-40',
PDF_DOCX_INCLUDE_IMAGES or PDF_DOCX_DETECT_HEADINGS or
PDF_DOCX_PRESERVE_STYLES or PDF_DOCX_PRESERVE_PAGE_BREAKS,
Target);
finally
Target.Free;
Pdf.Free;
end;
end;
이미지 데이터는 끝에 모아서 덧붙이는 대신 각 페이지가 처리될 때마다 기록되므로, 최대 메모리 사용량은 문서 전체가 아니라 페이지 하나만큼만 늘어납니다. 명시적인 페이지 순서가 보존되고, 선택되어 있던 PDF 페이지는 작업 후에 복원됩니다. 이는 이 내보내기가 다른 이유로 페이지가 선택되어 있던 더 긴 작업 안의 한 단계일 때 중요합니다
결정론적 패키징이 가져다주는 것은 무엇인가?
바이트 단위의 재현성입니다. 같은 옵션으로 같은 입력을 두 번 변환하면 같은 패키지가 나오며, 이는 출력을 해시하여 변경을 감지하고, 생성된 문서의 두 빌드를 diff하며, 동일한 입력이 다른 산출물을 만들어낼까 걱정하지 않고 적극적으로 캐시할 수 있다는 뜻입니다
Office 자동화는 그것을 보장할 수 없습니다. 타임스탬프, 리비전 식별자, 머신에 종속적인 메타데이터를 함께 심어버리므로, 같은 문서를 두 번 변환해도 해싱을 무력화하는 방식으로 서로 달라집니다. 같은 논리가 재현 가능한 빌드를 위한 결정론적 PDF ID에서 다루는 결정론적 파일 식별자를 이끌어냅니다. 출력이 재현 가능할 때, 검증은 검사가 아니라 비교가 됩니다
출력이 좋은 곳과 그렇지 않은 곳
이 점에 대해서는 사용자에게 솔직해야 합니다. 변환 품질은 변환기보다 입력에 따라 더 크게 달라지기 때문입니다. 태그된 PDF와 깔끔하게 생성된 업무 문서, 즉 청구서, 보고서, 계약서는 잘 변환됩니다. 제목은 제목으로 안착하고, 표는 살아남으며, 목록은 Word에서 올바르게 재번호가 매겨집니다. 2단 학술 레이아웃은 열 구조가 규칙적이라면 받아들일 만하게 변환됩니다. 페이지 나누기에 걸쳐 있는 표는 추론으로 재조립되며 때로는 나뉘기도 합니다. 읽기 순서가 아니라 시각적 효과를 위해 텍스트가 배치된, 화려하게 디자인된 마케팅 자료는 변환이 잘 되지 않으며, 어떤 추론으로도 이를 고칠 수 없습니다
스캔한 문서는 완전히 별개의 경우입니다. 페이지 전체가 하나의 큰 이미지라면 텍스트 객체가 전혀 없으므로, 텍스트 레이어가 존재하기 전까지는 내보낼 것이 없습니다. 텍스트 레이어를 만들어내는 OCR 경로는 선택 사항이 아니라 전제 조건입니다. 대규모 배치를 돌리기 전에, 대표적인 파일 십여 개를 뽑아 출력을 살펴보고, 텍스트 검색과 페이지 요소 열거에서 설명하는 대로 먼저 페이지 요소를 열거해 페이지가 실제로 무엇을 담고 있는지 확인해 보는 것을 고려하십시오
어시스턴트와 검색 파이프라인에는 대개 Markdown 경로가 더 나은 목표입니다. 제목은 청크 경계가 되고, 표는 파이프 표로 읽기 좋은 상태를 유지하며, 페이지 마커는 모든 청크에 인용 가능한 위치를 부여합니다. 사람이 직접 편집하는 용도라면 답은 DOCX입니다. 사용자가 원하는 것은 텍스트 자체가 아니라 그것을 바꿀 수 있는 능력이기 때문입니다
PDFlibPas는 Delphi, C++Builder, Lazarus용 PDF 라이브러리이며 이에 대응하는 DLL과 ActiveX 인터페이스도 갖추고 있어서, 같은 내보내기 호출을 C#, C++, 스크립팅 호스트에서도 사용할 수 있습니다. 전체 문서와 평가판은 PDFlibPas Delphi PDF 라이브러리 페이지에 있습니다