PDFium 컴포넌트는 단일 메서드인 ImportPages를 통해 PDF 병합을 노출합니다. 패턴은 항상 동일합니다. CreateDocument로 빈 대상 문서를 만들고, 각 원본 파일을 열고, ImportPages를 호출하여 페이지를 가로질러 복사하고, 원본을 닫고, 반복합니다. 루프가 끝나면 SaveAs가 결과를 디스크에 씁니다. 특별한 병합 모드나 설정할 구성은 없습니다. 복잡성은 엣지 케이스에 존재하며, 경고 없이 물어뜯는 몇 가지가 있습니다
핵심 루프
두 개의 TPdf 인스턴스면 충분합니다. 하나는 CreateDocument로 비어 있게 생성된 대상 문서를 보관합니다. 다른 하나는 각 원본 파일을 차례대로 엽니다. 다음은 파일 경로 목록을 가져와서 병합된 출력을 단일 경로에 쓰는 프로시저입니다:
procedure MergeFiles(const FileList: TStrings; const OutputPath: string);
var
PdfDest, PdfSrc: TPdf;
InsertAt, I: Integer;
begin
PdfDest := TPdf.Create(nil);
PdfSrc := TPdf.Create(nil);
try
PdfDest.CreateDocument;
InsertAt := 1; // ImportPages uses 1-based destination position
for I := 0 to FileList.Count - 1 do
begin
PdfSrc.FileName := FileList[I];
PdfSrc.Active := True;
if not PdfSrc.Active then
raise Exception.CreateFmt('Cannot open: %s', [FileList[I]]);
PdfDest.ImportPages(
PdfSrc,
'1-' + IntToStr(PdfSrc.PageCount), // full document range
InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;
end;
PdfDest.SaveAs(OutputPath);
finally
PdfSrc.Free;
PdfDest.Free;
end;
end;
첫 번째 읽기에서 이 코드의 두 가지를 간과하기 쉽습니다. 첫 번째는 PDFium이 로드 실패를 보고하는 방식입니다. Active := True는 결코 예외를 발생시키지 않습니다. 파일이 없거나 손상되었거나 암호로 보호된 경우 PDFium은 내부적으로 오류를 포착하고 Active를 False로 남겨둡니다. 10번째 줄의 명시적인 확인이 없으면, 잘못된 파일은 출력에 아무런 표시도 남기지 않은 채 소리 없이 병합에서 빠지게 됩니다. 최종 PDF는 예상보다 페이지 수가 적고 어떤 파일이 원인인지 알 수 없게 됩니다
두 번째는 InsertAt 카운터입니다. ImportPages의 세 번째 인수는 첫 번째 가져온 페이지가 도달하는 대상의 1 기반(1-based) 위치입니다. 1에서 시작하면 그렇지 않았다면 비어 있었을 파일의 시작 부분에 첫 번째 원본 문서를 둡니다. 각 원본 이후 카운터는 PdfSrc.PageCount만큼 진행하므로 다음 페이지 일괄 처리는 마지막 페이지 뒤에 추가됩니다. 카운터를 늘리는 것을 잊어버리면 모든 후속 원본이 1번 위치의 페이지를 덮어쓰게 되어 목록의 마지막 문서 외에는 아무것도 얻지 못하게 됩니다
선택적 페이지 범위
원본에서 모든 페이지를 가져올 필요는 없습니다. 두 번째 인수로 전달되는 범위 문자열은 쉼표와 하이픈의 단순한 형식을 따릅니다: "1-3"은 1에서 3페이지까지 가져오고, "2,4,6"은 3개의 특정 페이지를 선택하고, "1-"은 1페이지부터 문서 끝까지를 의미합니다. 범위는 단일 문자열로 결합할 수 있으므로 "1-3,5,7-"은 4페이지와 6페이지를 건너뜁니다. 여기서 하나의 미묘한 세부 사항이 중요합니다. 숫자는 페이지가 대상에서 어디로 끝나든 관계없이 항상 원본 문서의 1부터 시작하는 페이지를 참조합니다. 200페이지 카탈로그에서 40~50페이지를 가져오려면 범위 문자열은 대상에 이미 있는 것을 기준으로 한 위치가 아니라 "40-50"입니다
// Extract cover plus a three-page executive summary from a long report
PdfSrc.FileName := 'annual-report.pdf';
PdfSrc.Active := True;
if PdfSrc.Active then
begin
// Page 1 is the cover; pages 3-5 are the summary
PdfDest.ImportPages(PdfSrc, '1,3-5', InsertAt);
Inc(InsertAt, 4); // 1 cover + 3 summary pages = 4 pages added
PdfSrc.Active := False;
end;
InsertAt에 대한 증가분을 계산할 때, 원본의 페이지 수가 아니라 실제로 가져온 페이지의 수를 세십시오. '1,3-5'를 전달하면 4개의 페이지를 가져왔으므로 4만큼 진행합니다. PdfSrc.PageCount만큼 진행하면 빈 대상 위치에 틈이 생기고 다음 원본 문서를 의도한 것보다 파일 안쪽 깊숙이 배치하게 됩니다
ImportPages가 보존하는 것과 보존하지 않는 것
ImportPages에 의해 복사된 페이지는 보이는 콘텐츠를 온전하게 전달합니다. 텍스트, 벡터 그래픽, 래스터 이미지, 임베디드 폰트 및 폼 XObject는 모두 페이지 콘텐츠 스트림의 일부로 전송됩니다. 댓글, 하이라이트 및 잉크 스트로크를 포함한 페이지 수준의 주석도 문서 수준이 아닌 페이지 딕셔너리(dictionary) 안에 저장되어 있기 때문에 함께 전달됩니다
문서 수준 메타데이터는 다른 이야기입니다. 원본 Info 딕셔너리의 제목, 저자, 주제 및 키워드 문자열은 뒤에 남습니다. CreateDocument 이후 대상 문서는 빈 메타데이터로 시작하므로, 병합된 출력에 채워진 필드가 필요한 경우 SaveAs를 호출하기 직전에 필드를 PdfDest에 직접 할당해야 합니다. TPdf의 Title, Author, Subject, Keywords 및 Creator 속성은 일반 문자열을 가져와 저장 시 Info 딕셔너리에 씁니다
대화형 폼 필드는 더 복잡합니다. AcroForm 필드 정의는 개별 페이지 스트림 내부가 아니라 문서 수준 딕셔너리에 있습니다. ImportPages가 폼 필드를 포함하는 페이지를 복사할 때, 이러한 필드의 시각적 모양은 페이지 콘텐츠 스트림으로 렌더링되기 때문에 전달되지만, 이를 대화형으로 만드는 필드 위젯은 AcroForm 구조의 일부이며 따라오지 않습니다. 일반적인 병합에서는 원본 문서의 텍스트 필드가 가져올 때의 값을 표시하지만 병합된 파일에서는 편집할 수 없습니다. 필드를 채울 수 있는 상태로 유지해야 하는 경우, 가져오기 전에 각 원본 문서에서 이를 플래튼(flatten) 처리하세요: 이는 콘텐츠 스트림에 현재 값을 굽고 대화형 오버레이를 제거하여 출력에서 손상된 위젯 없이 깔끔한 시각적 결과를 제공합니다
암호화된 원본 파일
암호로 보호된 원본 문서는 암호화되지 않은 문서와 동일한 방식으로 열리며, 먼저 설정할 하나의 추가 속성이 있습니다. Active := True로 설정하기 전에 PdfSrc.Password에 암호를 할당하면 PDFium이 문서를 열 때 이를 사용합니다:
PdfSrc.Password := 'user-password';
PdfSrc.FileName := 'protected.pdf';
PdfSrc.Active := True;
if not PdfSrc.Active then
raise Exception.Create('Wrong password or file cannot be opened');
PdfDest.ImportPages(PdfSrc, '1-' + IntToStr(PdfSrc.PageCount), InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;
잘못된 암호는 파일이 누락되었을 때와 마찬가지로 조용한 Active = False 결과를 유발하므로 명시적인 확인이 동일하게 필요합니다. 암호화는 대상으로 전송되지 않습니다. 보호된 원본에서 가져온 페이지는 대상에 보호되지 않은 콘텐츠로 안착합니다. 병합된 출력에도 암호화가 필요한 경우 SaveAs를 호출하기 전에 PdfDest에서 구성하십시오
결과 저장하기
TPdf의 SaveAs는 파일 경로 또는 TStream을 허용합니다. 대부분의 병합에서는 파일 오버로드가 원하는 것입니다:
PdfDest.SaveAs('merged-output.pdf');
선택 사항인 두 번째 인수는 저장 모드를 제어하는 TSaveOption입니다. 기본값인 saNone은 파일에서 문서를 로드한 경우 점진적 업데이트를 쓰고 새로 만든 경우 완전히 다시 씁니다. CreateDocument로 빌드된 대상은 항상 새로우므로 출력은 컴팩트한 단일 리비전 파일이 됩니다. 세 번째 인수인 TPdfVersion은 특정 버전을 요구하는 다운스트림 소비자가 있을 때 PDF 버전 헤더를 고정할 수 있게 해줍니다. pvUnknown으로 두면 PDFium이 콘텐츠에 따라 선택하도록 둡니다
여기에 표시된 ImportPages 및 SaveAs 메서드는 델파이 및 C++Builder용 PDFium 컴포넌트의 일부입니다