Delphi 업무용 애플리케이션에 PDF 뷰어 패널을 내장하기 위해 렌더링 루프나 스크롤 엔진, 줌 연산 모델 등을 직접 구현할 필요는 없습니다. losLab PDF Library는 단 한 번의 호출로 문서를 열고 텍스트 선택, 화면 이동(panning), 영역 확대(marquee zoom), 스냅샷, 지정 영역 하이라이트, 전체 텍스트 검색 결과 하이라이트, 주석 상호 작용 및 인쇄 기능 등을 빌트인으로 제공하는 VCL 컨트롤인 TPDFlibViewer를 제공합니다. 본 기사에서는 빈 폼(Form)을 완벽히 작동하는 PDF 뷰어 화면으로 탈바꿈하는 5가지 단계(컨트롤 배치, 즉시 사용 가능한 액션 연결, 검색 하이라이트 추가, 주석 클릭 이벤트 처리 및 인쇄 제어)에 대해 알아봅니다
렌더링 루프를 직접 작성하는 대신 뷰어 컨트롤을 사용하는 이유는 무엇입니까?
계층화(layering) 설계의 이점이 첫 번째 이유입니다. 페이지를 비트맵으로 렌더링하는 것은 전체 작업량의 약 20% 수준인 쉬운 작업에 속합니다; 나머지 80%는 렌더링된 데이터를 감싸고 조율하는 부가 기능들입니다: 즉 제한된 메모리 한도 내에서의 페이지 비트맵 캐시 관리, 규격이 상이한 다중 페이지 배치용 스크롤 레이아웃, 화면 크기 조절 시 배율을 재계산하는 줌 모드, 특정 배율 및 회전 상태에서도 마우스 클릭 좌표를 페이지 내부 좌표계로 역산해 주는 히트 테스트(hit-testing), 그리고 깜빡임 없는 페인팅(repaint) 스케줄링 등입니다. TPDFlibViewer는 이 모든 처리를 자체 제어하며, 픽셀 드로잉 연산은 멀티 엔진 렌더링 기사에서 설명하는 핵심 엔진에 위임합니다. 이에 따라 컨트롤 레이어와 렌더링 레이어가 독립적으로 분리되어 유지보수가 용이해집니다. 결과적으로 개발자의 폼 코드는 렌더러와 직접 씨름할 필요 없이 표준적인 스크롤 가능 Windows 컨트롤과만 통신하게 됩니다
Delphi 애플리케이션에 PDF 뷰어를 어떻게 임베드합니까?
TPDFlibViewer.LoadFromFile은 컨트롤 내부의 고유한 TPDFlib 인스턴스를 거쳐 문서를 오픈하며 성공 시 1을 반환합니다. 이에 따라 뷰어를 탑재하는 최소한의 코드는 생성자 호출, Parent 지정 및 파일 오픈(load) 연산 한 줄로 완결됩니다. 애플리케이션의 다른 영역에서 이미 TPDFlib 인스턴스를 유지 및 관리 중인 경우, AttachLibrary를 호출하면 인스턴스 소유권을 개발자 측에 둔 채 지정된 문서를 즉시 화면에 출력해 줍니다
uses PDFlibViewer;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FViewer := TPDFlibViewer.Create(Self);
FViewer.Parent := Self;
FViewer.Align := alClient;
if FViewer.LoadFromFile('quarterly-report.pdf', '') = 1 then
FViewer.ViewerMode := vmHand; // pan by dragging; a plain click still follows links
end;
ViewerMode 속성은 마우스 조작 동작을 제어하며 다음 5가지 값을 취할 수 있습니다: vmSelect는 기본값인 텍스트 드래그 선택 모드이고, vmHand는 마우스 드래그로 지면을 스크롤하는 모드로 링크 요소 클릭 조작은 여전히 지원합니다, vmZoom은 마우스로 지정한 드래그 박스 영역을 화면 가득 맞춤 확대하는 모드이며, vmSnapshot은 지정한 드래그 박스 영역 이미지를 클립보드로 복사하는 모드(OnSnapshot 이벤트 및 SnapshotToClipboard 속성과 연계 작동)이고, vmHighlight는 지속적으로 표시되는 반투명 하이라이트 영역을 칠해 주는 모드입니다. 하이라이트 툴의 좌표계 처리 방식에 주목하십시오: 하이라이트 영역 정보는 화면 픽셀 기준이 아닌 페이지 고유 좌표 공간 상에 영구 기록되므로, 줌 배율 변경, 지면 회전, 혹은 페이지 전환이 일어나더라도 AddHighlightRegion으로 마크된 영역들이 틀어짐 없이 완벽히 영구 유지됩니다. 심지어 지면이 회전된 상태에서 마우스 드래그 방향이 페이지 축의 반대 방향으로 가더라도 역회전 매핑을 거쳐 네거티브 너비/높이 값을 컨트롤이 내부적으로 자동 정규화해 주므로 안심하고 코딩할 수 있습니다
Delphi에서 글루 코드 없이 PDF 툴바를 어떻게 연결합니까?
PDFlibViewerActions 유닛은 번거로운 툴바 동기화 코딩을 완전히 배제해 줍니다. 이 유닛은 페이지 탐색용 액션(TPDFlibViewerFirstPage, TPDFlibViewerPriorPage, TPDFlibViewerNextPage, TPDFlibViewerLastPage), 배율 제어용 액션(TPDFlibViewerZoomIn, TPDFlibViewerZoomOut, TPDFlibViewerFitWidth, TPDFlibViewerFitPage, TPDFlibViewerActualSize), 화면 회전용 액션(TPDFlibViewerRotateClockwise, TPDFlibViewerRotateAntiClockwise), 인쇄용, 파일 오픈 및 다이얼로그 저장용, 선택 텍스트 복사용, 그리고 마우스 작동 툴 스위치 액션(TPDFlibViewerSelectMode, TPDFlibViewerHandMode, TPDFlibViewerMarqueeZoomMode, TPDFlibViewerSnapshotMode, TPDFlibViewerHighlightMode) 등 즉시 연동 사용이 가능한 약 20여 종의 TAction 파생 컴포넌트들을 제공합니다. 각 액션은 Viewer 속성을 통해 타겟 뷰어 컨트롤을 지정하며 자체적으로 상태를 업데이트합니다: 즉 파일이 닫혀 있으면 액션이 비활성화되고, 탐색 액션은 현재 페이지 위치를 추적하며, 텍스트 선택 복사 액션은 드래그 영역 존재 여부를 실시간 추적하고, 마우스 모드 제어 액션들도 활성화된 모드 상태와 체크(Checked) 상태를 유기적으로 자동 동기화합니다. 디자인 타임에 Action List 편집기의 losLab PDF 카테고리에서 비주얼 드롭할 수 있으며, 런타임 코드 생성 시에는 단 3줄의 구현으로 완성됩니다
uses System.Actions, Vcl.ActnList, PDFlibViewer, PDFlibViewerActions;
procedure TMainForm.BuildToolbar;
var
NextPage: TPDFlibViewerNextPage;
FitWidth: TPDFlibViewerFitWidth;
HandMode: TPDFlibViewerHandMode;
begin
NextPage := TPDFlibViewerNextPage.Create(Self);
NextPage.ActionList := ActionList1;
NextPage.Viewer := FViewer;
btnNext.Action := NextPage;
FitWidth := TPDFlibViewerFitWidth.Create(Self);
FitWidth.ActionList := ActionList1;
FitWidth.Viewer := FViewer;
btnFitWidth.Action := FitWidth;
HandMode := TPDFlibViewerHandMode.Create(Self);
HandMode.ActionList := ActionList1;
HandMode.Viewer := FViewer;
btnHand.Action := HandMode;
end;
자체 액션 등록 논리를 구현할 때 주의할 유닛 바인딩 함정이 있습니다. RegisterActions 함수는 Vcl.ActnList가 아닌 System.Actions에 선언되어 있어, uses 절에 Vcl.ActnList만 등록해 둔 경우 대단히 난해한 컴파일 오류를 냅니다: 즉 [TAction1, TAction2, ...] 형태의 클래스 배열 리터럴이 셋(Set)으로 오해되어 파싱되며 액션 클래스 상의 타입 오류를 지시하는 E2010 Incompatible types: 'Integer' and 'class of ...' 에러가 다량 뿜어집니다. 해결책은 uses 절에 유닛을 추가하는 것뿐이나 컴파일러 오류 메시지는 전혀 엉뚱한 부분을 지시하므로 기억해 두는 것이 좋습니다. 배율 조절 툴바 연동 시 TPDFlibViewer.EffectiveZoomPercent 속성은 가로폭 및 페이지 맞춤 모드 시 역산된 비례 비율을 포함하여 실제 렌더링에 반영된 실질 배율 수치를 반환하므로, 배율 단계별 가감 처리를 수동 누적 방식이 아닌 정확한 현재 뷰 배율 수치를 기준으로 깔끔하게 연동해 낼 수 있습니다
검색어 하이라이트 표시 및 그 이면에 숨겨진 좌표계 전환 처리
HighlightSearchHits는 문서 전체에서 특정 쿼리 문자열을 검색하고 매칭된 글자 영역 전체에 반투명 하이라이트 바를 덧칠하며 총 매칭 수를 반환합니다; 이와 함께 ClearSearchHighlights, SearchHighlightCount 및 SearchHighlightColor 등의 제어 속성이 유기적으로 엮여 있습니다. 매칭 좌표 사각형 영역이 페이지 내부 좌표에 안착되고 매 드로잉 시마다 재투영 처리되므로, 스크롤바 이동, 줌 조절 또는 지면 회전 시에도 하이라이트 마크가 텍스트 위에 한 치의 오차도 없이 고정되어 따라다닙니다. 패널 컴포넌트인 TPDFlibSearchPanel을 함께 사용하는 경우, RunSearch 연산이 기본 켜져 있는 HighlightInView 속성을 연동해 연결된 뷰어 화면 상에 하이라이트 처리를 자동 연동해 칠해 줍니다
procedure TMainForm.btnSearchClick(Sender: TObject);
var
Hits: Integer;
begin
FViewer.SearchHighlightColor := clYellow;
Hits := FViewer.HighlightSearchHits(edtQuery.Text, False); // case-insensitive
StatusBar1.SimpleText := Format('%d matches', [Hits]);
end;
원시 검색 결과 분석 데이터를 활용해 하이라이트 기능을 수동 구현하기보다 뷰어 내장 API를 활용하는 편이 훨씬 유리한 이유는 좌표계 정규화 작업 때문입니다. 텍스트 검색 엔진은 라이브러리의 Origin 설정값에 따라 각기 다른 방향의 매칭 영역 사각형 좌표계를 반환합니다: Origin이 0 또는 3인 경우 Y축은 오리지널 PDF 사양처럼 아래에서 위로 증가하며, Origin이 2 또는 3인 경우 X축은 지면 우측 끝을 기준으로 역산해 측정됩니다. 반면 뷰어 오버레이 레이어는 위에서 아래로, 좌측 기준으로 정렬된 페이지 공간을 기준으로 드로잉을 처리하므로, 각 매칭 사각형 좌표계를 저장 전에 대칭 플립 보정해 주어야 합니다(Y = PageHeight - Bottom, 필요시 X = PageWidth - Right) — 기본 원점을 기준으로 반환된 상단 좌표를 무보정 대입하면 하이라이트 마크가 엉뚱하게 수직 대칭 반사된 위치에 오 드로잉되는 결함이 생깁니다. TPDFlibViewer는 검색된 결과를 저장할 때 활성화된 Origin 정보를 기준으로 이 좌표 정규화 과정을 자동 처리하며, 렌더링 시에는 회전 방향도 연동 대입하므로 어떠한 상황에서도 마커가 텍스트 위에 정확하게 안착합니다. 반투명 처리의 경우 VCL Canvas 레벨에서 알파 블렌딩을 내장 지원하지 않으므로 Windows 표준 AlphaBlend API를 연동해 작동합니다. 상세한 검색 모듈 정보 및 기하학 구조 분석에 대해서는 텍스트 검색 및 페이지 요소 탐색 기사에서 설명합니다
주석 클릭 제어, 힌트 창 노출 및 첨부 파일 추출
TPDFlibViewer는 문서 내의 주석(annotation) 데이터를 단순 그림이 아닌 상호 작용이 가능한 개체로 취급합니다. 링크가 아닌 주석 요소를 클릭하면 페이지 번호, 1부터 시작하는 주석 인덱스 및 주석 Subtype 문자열과 함께 OnAnnotClick 이벤트가 구동됩니다; AnnotAtPagePoint 호출을 쓰면 동일한 히트 테스트 판정을 코드 레벨에서 직접 활용할 수 있으며 단순 팝업 주석 등은 자동 배제해 주므로 실질적으로 가시화된 주석 개체만 필터링하기에 적합합니다. 주석 위에 마우스 호버 시 AnnotHints 속성을 켜두면 주석의 텍스트 본문이 표준 VCL ShowHint 속성을 연동해 툴팁 힌트 창으로 자동 노출됩니다. 파일 첨부 주석(FileAttachment)의 경우 SaveAnnotAttachmentToFile을 호출하면 파일 내부에 패키징된 첨부 데이터를 즉시 디스크 파일로 저장해 주며 성공 시 1을 반환하여, 사용자의 클릭 조작만으로 첨부 파일을 손쉽게 다운로드받게 하는 핵심 워크플로를 단 몇 줄로 구현해 낼 수 있습니다
procedure TMainForm.ViewerAnnotClick(Sender: TObject; APage, AnnotIndex: Integer;
const Subtype: WideString);
begin
if Subtype = 'FileAttachment' then
if FViewer.SaveAnnotAttachmentToFile(APage, AnnotIndex,
'C:\Temp\attached-invoice.xml') = 1 then
StatusBar1.SimpleText := 'Attachment saved';
end;
인쇄 기능 및 아키텍처 제약 사항
인쇄 기능 구현에도 추가 수동 연동이 필요하지 않습니다. PrintDoc을 호출하면 프린터 종류 선택, 페이지 출력 범위 지정, 매수 지정 등이 적용된 표준 Windows 인쇄 다이얼로그 화면을 노출하고 라이브러리 인쇄 엔진으로 작업을 이송하며, PrintDoc(False)를 사용하면 UI 출력 없이 즉시 시스템 기본 프린터로 문서 전체를 바로 발송합니다. PrintDocRange 호출을 쓰면 프린터 장치명 지정, 출력 시작/종료 페이지 지정, 매수 및 순차 인쇄 여부 등을 프로그래밍 방식으로 완전 자동화해 제어할 수 있으므로, 동일 뷰어 모듈을 활용해 배치성 실서버 자동 인쇄 서비스를 구축하기에 유용합니다
설계 고려 사항으로 두 가지 제약 환경을 전합니다. 첫째, TPDFlibViewer는 Windows VCL 컨트롤입니다; 뷰어 레이어 구성 요소는 NOVCL 빌드 대상에서 생략되므로 VCL 폼을 사용하지 않는 콘솔 백그라운드 서비스 등의 경우에는 하위의 TPDFlib 실 드로잉 API를 수동 호출해 처리해야 합니다. 둘째, 화면 없는 가상 창(headless) 뷰 구성을 위해 CaptureRegion 함수는 Parent 지정이 없고 윈도우 핸들이 활성화되지 않은 백그라운드 상태의 컨트롤 상에서도 드로잉 결과물 및 하이라이트 오버레이를 비트맵 객체로 정상 반환해 줍니다. 윈도우 핸들 생성을 강제하는 VCL 호출 처리를 배인팅 루틴에서 배제해 구현했기 때문입니다. 덕분에 자동화 검사 빌드 등에서 지면을 픽셀 단위로 비교 검증하는 처리를 가볍게 연동할 수 있으나, 마우스 드래그처럼 메시지 루프 처리가 꼭 수반되어야 하는 상호 작용 기능들의 경우에는 여전히 실 윈도우 뷰 세션을 연결해야 작동합니다. 성능 최적화를 위해 뷰어는 SetCacheLimit으로 메모리 캐시 상한 크기를 조정할 수 있으며, 렌더링된 지면들을 세션 간에 로컬 디스크 파일로 영구 보존할 수도 있습니다; 뷰어 디스크 캐시 및 모니터별 DPI 설정 기사에서 상세히 다룹니다
뷰어 빌더는 5가지로 완결됩니다: 화면에 뷰어를 올리고 LoadFromFile을 연동하며, PDFlibViewerActions에 등록된 액션들을 툴바 버튼과 연결하고, 검색 연동 시 HighlightSearchHits를 연동해 화면 마킹 처리를 장착하고, 주석 조작 처리를 위해 OnAnnotClick을 핸들링하며, 인쇄용 PrintDoc을 외부에 제공하는 것입니다. 본 기사에 예시된 뷰어 컨트롤, 액션 클래스 및 드로잉 코어 구성 요소 등은 Delphi용 losLab PDF Library의 표준 패키지에 함께 포함되어 제공됩니다