C#로 PdfiumViewer 툴바 기능 확장: 페이지 탐색 및 동적 확대 축소 구현

1. 기본 PDF 뷰어 프레임워크 설계

WinForms 애플리케이션에서 PDF 문서를 표시해야 하는 경우, 오픈소스 라이브러리인 PdfiumViewer는 매우 유용한 선택지입니다. Google의 PDFium 엔진을 기반으로 하여 빠른 렌더링 성능과 안정적인 출력을 제공하지만, 기본적으로 제공되는 툴바는 기능이 제한적입니다. 저장, 인쇄, 확대/축소 외에는 추가 기능이 없어 실무에서는 사용자 경험 측면에서 아쉬움이 있습니다.

다수의 프로젝트에서 PDF 뷰어를 맞춤형으로 개발하면서 얻은 경험을 바탕으로, 이번에는 PdfiumViewer에 맞춤 툴바를 통합하는 방법을 설명하겠습니다. 목표는 **페이지 이동** (처음, 마지막, 이전, 다음, 특정 페이지 입력)과 **확장된 확대/축소 기능** (고정 배율, 가로 맞춤, 화면 맞춤 등)을 구현하는 것입니다.

Visual Studio에서 새로운 Windows Forms 앱을 생성하고, NuGet 패키지 관리자를 통해 PdfiumViewer를 설치합니다. .NET Framework 또는 .NET 6 이상의 WinForms 프로젝트 모두 호환됩니다. 설치 후 빌드 시 pdfium.dll이 출력 디렉터리에 복사되는지 확인하세요. 만약 런타임에 DLL을 찾을 수 없다는 오류가 발생하면, 수동으로 NuGet 캐시 폴더에서 해당 파일을 복사해 넣어야 할 수 있습니다.

폼 디자이너에서 상단에 ToolStrip을 배치하고, 그 아래 PdfiumViewer 컨트롤을 Dock = DockStyle.Fill로 설정하여 남은 공간을 모두 차지하도록 합니다. 이후 툴바에 다음과 같은 요소들을 순서대로 추가합니다:

  • 문서 조작: 인쇄 버튼
  • 확대/축소 영역: 확대, 축소 버튼, 배율 선택 콤보박스
  • 페이지 탐색: 처음 페이지, 이전 페이지, 현재 페이지 입력란, "X / Y" 형식의 페이지 정보 레이블, 다음 페이지, 마지막 페이지
  • 뷰 모드: 전체화면 전환 버튼

각 컨트롤은 의미 있는 이름으로 지정합니다. 예: tsbFirstPage, tsbZoomIn, tscbZoomLevels, tstxtPageInput. UI 구성이 완료되면, 상태 관리를 위한 멤버 변수를 폼 클래스에 선언합니다:

private PdfDocument _currentDocument;
private int _currentPageIndex = 0; // 내부 인덱스 기준 (0부터 시작)
private int _totalPages = 0;
private bool _isFullScreenMode = false;

이제 UI와 데이터 구조가 준비되었으니, 각 기능을 구현해 나갑니다.

2. 페이지 탐색 기능 구현

PdfiumViewer의 핵심은 Renderer 객체이며, 이 객체의 Page 속성을 변경함으로써 현재 표시할 페이지를 제어할 수 있습니다. 중요한 점은 이 속성이 0-based 인덱스를 사용한다는 사실입니다. 따라서 사용자에게는 1페이지라고 보여줘야 하지만, 내부 처리 시에는 0을 사용해야 합니다.

2.1 첫 번째 및 마지막 페이지 이동

첫 페이지로 이동하는 이벤트 핸들러는 다음과 같이 작성합니다:

private void tsbFirstPage_Click(object sender, EventArgs e)
{
    if (_currentDocument == null) return;

    pdfViewer1.Renderer.Page = 0;
    _currentPageIndex = 0;
    UpdatePageInfoDisplay();
}

마지막 페이지로 이동하는 로직도 유사합니다. 단, 전체 페이지 수에서 1을 빼야 올바른 인덱스가 됩니다:

private void tsbLastPage_Click(object sender, EventArgs e)
{
    if (_currentDocument == null) return;

    int lastPageIndex = _currentDocument.PageCount - 1;
    pdfViewer1.Renderer.Page = lastPageIndex;
    _currentPageIndex = lastPageIndex;
    UpdatePageInfoDisplay();
}

2.2 이전 및 다음 페이지 이동

이전/다음 페이지는 현재 인덱스를 기준으로 범위를 검사한 후 이동합니다:

private void tsbPreviousPage_Click(object sender, EventArgs e)
{
    if (_currentDocument == null || _currentPageIndex <= 0) return;

    pdfViewer1.Renderer.Page = --_currentPageIndex;
    UpdatePageInfoDisplay();
}

private void tsbNextPage_Click(object sender, EventArgs e)
{
    if (_currentDocument == null || _currentPageIndex >= _currentDocument.PageCount - 1) return;

    pdfViewer1.Renderer.Page = ++_currentPageIndex;
    UpdatePageInfoDisplay();
}

2.3 페이지 번호 직접 입력

특정 페이지로 이동하려면 입력 값을 검증하고 변환해야 합니다. KeyPress 이벤트에서 엔터 키를 감지합니다:

private void tstxtPageInput_KeyPress(object sender, KeyPressEventArgs e)
{
    if (e.KeyChar != (char)Keys.Enter || _currentDocument == null) return;

    if (int.TryParse(tstxtPageInput.Text, out int requestedPage))
    {
        // 1-based 입력을 0-based 인덱스로 변환
        int targetIndex = requestedPage - 1;
        if (targetIndex >= 0 && targetIndex < _currentDocument.PageCount)
        {
            pdfViewer1.Renderer.Page = targetIndex;
            _currentPageIndex = targetIndex;
            UpdatePageInfoDisplay();
        }
        else
        {
            MessageBox.Show($"유효한 페이지 번호를 입력하세요 (1~{_currentDocument.PageCount})", "오류", 
                            MessageBoxButtons.OK, MessageBoxIcon.Warning);
            tstxtPageInput.Focus();
        }
    }
    else
    {
        MessageBox.Show("숫자만 입력 가능합니다.", "입력 오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
        tstxtPageInput.SelectAll();
    }
}

3. 고급 확대/축소 기능 개발

기본 확대/축소 외에도 다양한 보기 모드를 지원하면 편의성이 크게 향상됩니다.

3.1 고정 배율 설정

툴바의 콤보박스에 미리 정의된 배율을 추가합니다:

private void InitializeZoomComboBox()
{
    var zoomLevels = new[] { "50%", "75%", "100%", "125%", "150%", "200%", "400%" };
    tscbZoomLevels.Items.AddRange(zoomLevels);
    tscbZoomLevels.SelectedIndex = 2; // 기본값 100%
}

선택 시 실제 확대 적용:

private void tscbZoomLevels_SelectedIndexChanged(object sender, EventArgs e)
{
    if (_currentDocument == null) return;

    string selectedText = tscbZoomLevels.SelectedItem.ToString();
    float zoomFactor = float.Parse(selectedText.Trim('%')) / 100f;
    pdfViewer1.Zoom = zoomFactor;
}

3.2 보기 모드 기반 확대

가로 너비에 맞추거나 전체 페이지를 화면에 표시하는 기능을 추가합니다:

private void tsbFitWidth_Click(object sender, EventArgs e)
{
    if (_currentDocument != null)
        pdfViewer1.BringIntoView(PdfViewerBringIntoViewOptions.FitWidth);
}

private void tsbFitVisible_Click(object sender, EventArgs e)
{
    if (_currentDocument != null)
        pdfViewer1.BringIntoView(PdfViewerBringIntoViewOptions.FitBest);
}

3.3 확대/축소 버튼

간편하게 +/- 버튼으로 배율 조절:

private void tsbZoomIn_Click(object sender, EventArgs e)
{
    pdfViewer1.Zoom += 0.25f;
    UpdateZoomSelection(); // 콤보박스 선택 상태 동기화
}

private void tsbZoomOut_Click(object sender, EventArgs e)
{
    if (pdfViewer1.Zoom > 0.25f)
        pdfViewer1.Zoom -= 0.25f;
    UpdateZoomSelection();
}

private void UpdateZoomSelection()
{
    string currentZoom = $"{(int)(pdfViewer1.Zoom * 100)}%";
    int index = tscbZoomLevels.FindStringExact(currentZoom);
    if (index != -1)
        tscbZoomLevels.SelectedIndex = index;
}

4. 상태 동기화 및 UI 갱신

현재 페이지나 배율이 변경될 때마다 UI를 업데이트하는 보조 메서드가 필요합니다:

private void UpdatePageInfoDisplay()
{
    if (_currentDocument != null)
    {
        tslPageInfo.Text = $"({_currentPageIndex + 1} / {_currentDocument.PageCount})";
        tstxtPageInput.Text = (_currentPageIndex + 1).ToString();
    }
}

PDF 문서를 열거나 닫을 때 상태를 초기화하는 로직도 함께 구현하면 완성도가 높아집니다.

태그: PdfiumViewer C# WinForms PDF Viewer Custom Toolbar

6월 20일 16:42에 게시됨