기술 문서

PDFium Delphi를 사용하여 PDF 문서를 여러 파일로 분할

· PDF 프로그래밍

큰 PDF 파일을 더 작은 파일로 나누는 것은 문서 배포, 보관 및 처리에 필수적입니다. PDF 분할 데모 이 데모는 Delphi의 PDFium VCL을 사용하여 다양한 방법으로 PDF 문서를 분할하는 방법을 보여줍니다.

개요

이 포괄적인 데모는 세 가지 분할 모드를 제공합니다. 개별 페이지, 페이지 범위 및 북마크별 분할입니다. 여기에는 진행 상황 추적, 자세한 로깅 및 사용자 정의 가능한 출력 파일 이름 지정 기능이 포함되어 있습니다.

분할 모드

  • 개별 페이지 – 각 페이지에 대해 별도의 PDF 파일을 만듭니다.
  • 페이지 범위 – 사용자 정의 페이지 범위로 분할 (예: 1-5, 6-10)
  • 북마크 기준 – 논리적인 섹션을 위해 북마크 경계에서 분할

PDFium DLL 요구 사항

PDFium VCL 애플리케이션을 실행하기 전에, PDFium DLL 파일이 설치되어 있는지 확인하십시오.

  • pdfium32.dll / pdfium64.dll – 표준 버전 (약 5-6 MB)
  • pdfium32v8.dll / pdfium64v8.dll – V8 JavaScript 엔진 포함 (약 23-27 MB)

설치: 실행 PDFiumVCL\DLLs\CopyDlls.bat 관리자 권한으로 실행하여 DLL 파일을 Windows 시스템 디렉터리에 자동으로 복사합니다.

개별 페이지로 분할

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
procedure TFormMain.ProcessIndividualPages;
var
  I: Integer;
  OutputFile: string;
  OutputDir: string;
  PdfNew: TPdf;
begin
  OutputDir := GetOutputDirectory;
  UpdateProgress('Splitting into individual pages...', 0, Pdf.PageCount);
  
  PdfNew := TPdf.Create(nil);
  try
    for I := 1 to Pdf.PageCount do
    begin
      if FCancelled then
        Break;
        
      // Create new document for this page
      PdfNew.CreateDocument;
      
      // Import single page
      PdfNew.ImportPages(Pdf, IntToStr(I), 1);
      
      // Generate output filename
      OutputFile := GenerateOutputFileName(
        edtFilePattern.Text, Pdf.FileName, I);
      OutputFile := OutputDir + '\' + OutputFile;
      
      // Save the single-page PDF
      if PdfNew.SaveAs(OutputFile) then
      begin
        LogMessage(Format('Created: %s', [ExtractFileName(OutputFile)]));
        Inc(FSplitCount);
      end
      else
        LogMessage(Format('Failed to create: %s', [OutputFile]), LOG_ERROR);
        
      PdfNew.Active := False;
      
      UpdateProgress('Processing...', I, Pdf.PageCount);
    end;
  finally
    PdfNew.Free;
  end;
end;

페이지 범위로 분할

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
procedure TFormMain.ProcessPageRanges;
var
  Ranges: TPageRanges;
  I: Integer;
  OutputFile: string;
  OutputDir: string;
  PageList: string;
  PdfNew: TPdf;
begin
  Ranges := ParsePageRanges(edtPageRanges.Text);
  if Length(Ranges) = 0 then
  begin
    LogMessage('No valid page ranges specified', LOG_ERROR);
    Exit;
  end;
  
  OutputDir := GetOutputDirectory;
  UpdateProgress('Splitting by page ranges...', 0, Length(Ranges));
  
  PdfNew := TPdf.Create(nil);
  try
    for I := 0 to High(Ranges) do
    begin
      if FCancelled then
        Break;
        
      PdfNew.CreateDocument;
      
      // Build page range string
      PageList := Format('%d-%d', [Ranges[I].StartPage, Ranges[I].EndPage]);
      
      // Import the range
      PdfNew.ImportPages(Pdf, PageList, 1);
      
      // Generate output filename
      OutputFile := Format('%s\%s_pages_%d-%d.pdf', [
        OutputDir,
        ChangeFileExt(ExtractFileName(Pdf.FileName), ''),
        Ranges[I].StartPage,
        Ranges[I].EndPage
      ]);
      
      if PdfNew.SaveAs(OutputFile) then
      begin
        LogMessage(Format('Created: %s (pages %s)',
          [ExtractFileName(OutputFile), PageList]));
        Inc(FSplitCount);
      end;
      
      PdfNew.Active := False;
      
      UpdateProgress('Processing...', I + 1, Length(Ranges));
    end;
  finally
    PdfNew.Free;
  end;
end;

페이지 범위 파싱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
function TFormMain.ParsePageRanges(const RangeStr: string): TPageRanges;
var
  Parts: TStringList;
  I: Integer;
  Part: string;
  DashPos: Integer;
  StartPage, EndPage: Integer;
  Range: TPageRange;
begin
  SetLength(Result, 0);
  
  if Trim(RangeStr) = '' then
    Exit;
    
  Parts := TStringList.Create;
  try
    Parts.Delimiter := ',';
    Parts.DelimitedText := RangeStr;
    
    for I := 0 to Parts.Count - 1 do
    begin
      Part := Trim(Parts[I]);
      if Part = '' then
        Continue;
        
      DashPos := Pos('-', Part);
      if DashPos > 0 then
      begin
        // Range: "1-5"
        StartPage := StrToIntDef(Trim(Copy(Part, 1, DashPos - 1)), 0);
        EndPage := StrToIntDef(Trim(Copy(Part, DashPos + 1, Length(Part))), 0);
      end
      else
      begin
        // Single page: "3"
        StartPage := StrToIntDef(Part, 0);
        EndPage := StartPage;
      end;
      
      if (StartPage > 0) and (EndPage >= StartPage) and
         (EndPage <= Pdf.PageCount) then
      begin
        Range.StartPage := StartPage;
        Range.EndPage := EndPage;
        SetLength(Result, Length(Result) + 1);
        Result[High(Result)] := Range;
      end;
    end;
  finally
    Parts.Free;
  end;
end;

북마크로 분할

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
procedure TFormMain.ProcessBookmarks;
var
  Bookmarks: TBookmarks;
  I: Integer;
  StartPage, EndPage: Integer;
  OutputFile: string;
  OutputDir: string;
  BookmarkTitle: string;
  PdfNew: TPdf;
begin
  Bookmarks := Pdf.Bookmarks;
  
  if Length(Bookmarks) = 0 then
  begin
    LogMessage('No bookmarks found in document', LOG_WARNING);
    Exit;
  end;
  
  OutputDir := GetOutputDirectory;
  UpdateProgress('Splitting by bookmarks...', 0, Length(Bookmarks));
  
  PdfNew := TPdf.Create(nil);
  try
    for I := 0 to High(Bookmarks) do
    begin
      if FCancelled then
        Break;
        
      StartPage := Bookmarks[I].PageNumber;
      
      // End page is start of next bookmark or end of document
      if I < High(Bookmarks) then
        EndPage := Bookmarks[I + 1].PageNumber - 1
      else
        EndPage := Pdf.PageCount;
        
      if (StartPage > 0) and (EndPage >= StartPage) then
      begin
        PdfNew.CreateDocument;
        
        PdfNew.ImportPages(Pdf,
          Format('%d-%d', [StartPage, EndPage]), 1);
          
        // Clean bookmark title for filename
        BookmarkTitle := Bookmarks[I].Title;
        BookmarkTitle := StringReplace(BookmarkTitle, '/', '_', [rfReplaceAll]);
        BookmarkTitle := StringReplace(BookmarkTitle, '\', '_', [rfReplaceAll]);
        BookmarkTitle := StringReplace(BookmarkTitle, ':', '_', [rfReplaceAll]);
        
        OutputFile := Format('%s\%02d_%s.pdf', [
          OutputDir, I + 1, BookmarkTitle]);
          
        if PdfNew.SaveAs(OutputFile) then
        begin
          LogMessage(Format('Created: %s (pages %d-%d)',
            [ExtractFileName(OutputFile), StartPage, EndPage]));
          Inc(FSplitCount);
        end;
        
        PdfNew.Active := False;
      end;
      
      UpdateProgress('Processing...', I + 1, Length(Bookmarks));
    end;
  finally
    PdfNew.Free;
  end;
end;

사용자 정의 가능한 출력 파일 이름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function TFormMain.GenerateOutputFileName(
  const Pattern, SourceFile: string;
  PageNum: Integer): string;
var
  BaseName, Ext: string;
begin
  BaseName := ChangeFileExt(ExtractFileName(SourceFile), '');
  Ext := ExtractFileExt(SourceFile);
  
  Result := StringReplace(Pattern, '{filename}', BaseName,
    [rfReplaceAll, rfIgnoreCase]);
  Result := StringReplace(Result, '{page}', IntToStr(PageNum),
    [rfReplaceAll, rfIgnoreCase]);
  Result := StringReplace(Result, '{page:000}', Format('%.3d', [PageNum]),
    [rfReplaceAll, rfIgnoreCase]);
    
  // Ensure .pdf extension
  if not EndsText('.pdf', Result) then
    Result := Result + '.pdf';
end;

진행 상황 및 상태 업데이트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
procedure TFormMain.UpdateProgress(const Status: string;
  Current, Total: Integer);
begin
  lblStatus.Caption := Status;
  
  if Total > 0 then
  begin
    prgProgress.Max := Total;
    prgProgress.Position := Current;
    lblProgress.Caption := Format('%d of %d pages', [Current, Total]);
  end
  else
  begin
    prgProgress.Position := 0;
    lblProgress.Caption := 'Initializing...';
  end;
  
  Application.ProcessMessages;
end;
 
procedure TFormMain.LogMessage(const Msg: string;
  const Level: string = 'INFO');
var
  TimeStamp, LogLine: string;
begin
  TimeStamp := FormatDateTime('hh:nn:ss', Now);
  LogLine := Format('[%s] %s: %s', [TimeStamp, Level, Msg]);
  
  mmoLog.Lines.Add(LogLine);
  mmoLog.Perform(WM_VSCROLL, SB_BOTTOM, 0);
  Application.ProcessMessages;
end;

취소 기능 지원

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
procedure TFormMain.btnCancelClick(Sender: TObject);
begin
  FCancelled := True;
  LogMessage('Cancellation requested...', LOG_WARNING);
end;
 
procedure TFormMain.SetProcessingState(Processing: Boolean);
begin
  FProcessing := Processing;
  
  btnBrowse.Enabled := not Processing;
  edtPdfFile.Enabled := not Processing;
  grpOptions.Enabled := not Processing;
  btnSplit.Enabled := not Processing;
  btnCancel.Enabled := Processing;
end;

완료 요약

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
procedure TFormMain.ShowCompletionSummary;
var
  ElapsedTime: TDateTime;
  ElapsedStr: string;
begin
  ElapsedTime := Now - FStartTime;
  ElapsedStr := FormatDateTime('nn:ss', ElapsedTime);
  
  if FCancelled then
    LogMessage(Format('Operation cancelled. Created %d files in %s',
      [FSplitCount, ElapsedStr]), LOG_WARNING)
  else
    LogMessage(Format('Split completed. Created %d files in %s',
      [FSplitCount, ElapsedStr]), LOG_SUCCESS);
end;

사용 사례

  • 챕터 추출 – 북마크를 사용하여 책이나 매뉴얼을 챕터별로 분할
  • 페이지 분배 – 서로 다른 팀 구성원에게 서로 다른 페이지 범위를 할당
  • 섹션별 보관 – 큰 문서를 더 작고 관리하기 쉬운 파일로 구성
  • 페이지 추출 특정 페이지를 추출하여 공유하거나 검토합니다.

결론

Split PDF 데모는 PDFium VCL의 문서 조작 유연성을 보여줍니다. 페이지, 범위 또는 북마크로 정의된 논리적 섹션으로 분할하든, 프로세스는 간단하고 효율적입니다.

사용자 정의 가능한 출력 이름 지정 및 진행률 추적과 결합하여 모든 워크플로우에 대한 전문적인 문서 분할 도구를 만들 수 있습니다.

다운로드 PDFium VCL 컴포넌트 loslab.com에서 PDF 문서를 관리하세요.