Technical Article

Splitting PDF Documents into Multiple Files with PDFium Delphi

· PDF Programming

Splitting a large PDF into smaller files is essential for document distribution, archiving, and processing. The Split PDF demo shows how to split PDF documents using various methods with PDFium VCL in Delphi.

Overview

This comprehensive demo offers three splitting modes: individual pages, page ranges, and by bookmarks. It includes progress tracking, detailed logging, and customizable output file naming.

Split Modes

  • Individual Pages – Create a separate PDF for each page
  • Page Ranges – Split by custom page ranges (e.g., 1-5, 6-10)
  • By Bookmarks – Split at bookmark boundaries for logical sections

PDFium DLL Requirements

Before running any PDFium VCL application, ensure the PDFium DLL files are installed:

  • pdfium32.dll / pdfium64.dll – Standard versions (~5-6 MB)
  • pdfium32v8.dll / pdfium64v8.dll – With V8 JavaScript engine (~23-27 MB)

Installation: Run PDFiumVCL\DLLs\CopyDlls.bat as Administrator to automatically copy the DLLs to Windows system directories.

Splitting into Individual Pages

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;

Splitting by Page Ranges

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;

Parsing Page Ranges

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;

Splitting by Bookmarks

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;

Customizable Output Filenames

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;

Progress and Status Updates

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;

Cancellation Support

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;

Completion Summary

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;

Use Cases

  • Extract Chapters – Split books or manuals by chapter using bookmarks
  • Distribute Pages – Give different team members different page ranges
  • Archive By Section – Organize large documents into smaller, manageable files
  • Page Extraction – Extract specific pages for sharing or review

Conclusion

The Split PDF demo demonstrates the flexibility of PDFium VCL for document manipulation. Whether splitting by pages, ranges, or logical sections defined by bookmarks, the process is straightforward and efficient.

Combined with customizable output naming and progress tracking, you can build professional document splitting tools for any workflow.

Download PDFium VCL Component from loslab.com and take control of your PDF documents.