Pdf1.FileName := 'C:\MyDocument.pdf';
Pdf1.Active := True;
ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
ShowMessage('Title: ' + Pdf1.Title);
ShowMessage('Author: ' + Pdf1.Author);
var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
Stream.LoadFromFile('C:\MyDocument.pdf');
Pdf1.LoadDocument(Stream, True);
Pdf1.Active := True;
ShowMessage('PDF loaded from stream');
finally
Stream.Free;
end;
end;
Pdf1.CreateDocument;
Pdf1.Active := True;
Pdf1.AddPage(0, 612, 792);
Pdf1.PageNumber := 1;
Pdf1.AddText('Hello World!', 'Arial', 12, 100, 700, clBlack, $FF, 0.0);
Pdf1.UpdatePage;
Pdf1.SaveAs('C:\NewDocument.pdf');
Pdf1.Active := False;
var
PageText: WString;
RectText: WString;
begin
Pdf1.PageNumber := 1;
PageText := Pdf1.Text;
Memo1.Text := PageText;
RectText := Pdf1.TextInRectangle(100, 100, 500, 200);
ShowMessage('Text in rectangle: ' + RectText);
end;
var
Position: Integer;
begin
Position := Pdf1.FindFirst('Hello', [seCaseSensitive]);
while Position >= 0 do
begin
ShowMessage('Found at character: ' + IntToStr(Position));
Position := Pdf1.FindNext;
end;
end;
Pdf1.PageNumber := 1;
Pdf1.AddText('Document Title', 'Times New Roman', 16, 100, 750, clBlue);
Pdf1.AddText('This is the document content.', 'Arial', 12, 100, 700, clBlack);
Pdf1.UpdatePage;
var
I: Integer;
MetricSize: Single;
FontBytes: TBytes;
begin
Pdf1.PageNumber := 1;
for I := 0 to Pdf1.ObjectCount - 1 do
if Pdf1.ObjectType[I] = otText then
begin
MetricSize := 12.0;
FontBytes := Pdf1.FontData[I];
Memo1.Lines.Add('Object ' + IntToStr(I));
Memo1.Lines.Add('Base name: ' + Pdf1.FontBaseName[I]);
Memo1.Lines.Add('Family: ' + Pdf1.FontFamilyName[I]);
Memo1.Lines.Add('Embedded: ' + BoolToStr(Pdf1.FontIsEmbedded[I], True));
Memo1.Lines.Add('Weight: ' + IntToStr(Pdf1.FontWeight[I]));
Memo1.Lines.Add('Italic angle: ' + IntToStr(Pdf1.FontItalicAngle[I]));
Memo1.Lines.Add('Ascent: ' + FloatToStr(Pdf1.FontAscent[I, MetricSize]));
Memo1.Lines.Add('Descent: ' + FloatToStr(Pdf1.FontDescent[I, MetricSize]));
Memo1.Lines.Add('Font data bytes: ' + IntToStr(Length(FontBytes)));
end;
if Pdf1.CharacterCount > 0 then
begin
Memo1.Lines.Add('First character size: ' + FloatToStr(Pdf1.CharacterFontSize[0]));
Memo1.Lines.Add('First character weight: ' + IntToStr(Pdf1.CharacterFontWeight[0]));
Memo1.Lines.Add('First character angle: ' + FloatToStr(Pdf1.CharacterAngle[0]));
end;
end;
Demo\Delphi\FontProperties, Demo\CBuilder\FontProperties, Demo\Lazarus\FontProperties 프로젝트는 TPdf와 TPdfView 양쪽으로 동일한 메타데이터를 보여줍니다.
Demo\Delphi\ContentExtractionLab, Demo\CBuilder\ContentExtractionLab, Demo\Lazarus\ContentExtractionLab 프로젝트는 콘텐츠 추출을 단일 워크플로로 전환합니다. 이 프로젝트는 PDF를 열거나 만들고, 사용자가 메타데이터, 페이지 텍스트, 페이지 객체, 이미지, 첨부 파일, 링크, 주석, 북마크, 글꼴 / 문자 메트릭을 선택하도록 한 다음 요약과 자세한 TXT 또는 JSON 리포트를 생성합니다. 또한 모두 / 없음 카테고리 선택, 리포트 클립보드 복사 및 소스 PDF 저장 액션을 포함하여 사용자가 생성된 샘플 PDF를 추출 리포트와 함께 보관할 수 있도록 합니다. 이전의 ExtractText, ExtractImages, Attachment, FontProperties 데모는 집중된 예제로 남아 있으며, ExtractText는 Delphi, C++Builder, Lazarus 전체에서 페이지당 텍스트 출력을 생성하는 페이지 범위 및 페이지 구분자 옵션을 지원하고, ContentExtractionLab은 교차 기능 인벤토리 샘플입니다.
var
I: Integer;
FieldInfo: TPdfFormFieldInfo;
begin
Pdf1.FormField[0] := 'John';
for I := 0 to Pdf1.FormFieldCount - 1 do
begin
FieldInfo := Pdf1.FormFieldInfo[I];
ShowMessage(FieldInfo.Name + ': ' + Pdf1.FormField[I]);
end;
end;
// 평탄화 없이는 FormField[i] := value가 /V 항목만 작성합니다 —
// 보이는 외관은 /AP 스트림에서 오며 새로 고쳐지지 않을 수 있습니다.
// GenerateFormAppearances는 모든 위젯에 대해 /AP를 재생성한 다음
// FlattenAllPages가 모든 페이지의 주석과 위젯을 영구적이고 편집할 수 없는
// 콘텐츠로 굽습니다.
Pdf1.FileName := 'C:\Application.pdf';
Pdf1.Active := True;
Pdf1.FormField[0] := 'John Smith';
Pdf1.FormField[1] := 'john@example.com';
Pdf1.GenerateFormAppearances;
if Pdf1.FlattenAllPages then
Pdf1.SaveAs('C:\Application.flat.pdf');
Pdf1.Active := False;
var
I: Integer;
Bitmap: TBitmap;
begin
for I := 0 to Pdf1.BitmapCount - 1 do
begin
Bitmap := Pdf1.Bitmap[I];
try
Bitmap.SaveToFile('C:\ExtractedImage' + IntToStr(I) + '.bmp');
finally
Bitmap.Free;
end;
end;
end;
var
JpegStream: TFileStream;
begin
JpegStream := TFileStream.Create('C:\MyImage.jpg', fmOpenRead or fmShareDenyWrite);
try
Pdf1.AddJpegImage(JpegStream, 100, 500, 200, 150);
finally
JpegStream.Free;
end;
Pdf1.AddPicture(Image1.Picture, 100, 300, 150, 100);
Pdf1.UpdatePage;
end;
var
I: Integer;
Annotation: TPdfAnnotation;
begin
for I := 0 to Pdf1.AnnotationCount - 1 do
begin
Annotation := Pdf1.Annotation[I];
ShowMessage('Annotation ' + IntToStr(I) + ': ' + Annotation.ContentsText);
end;
end;
Pdf1.CreatePath(100, 100, 200, 150, fmAlternate, clYellow, $FF, True, clBlack, $FF, 2.0);
Pdf1.AddPath;
Pdf1.CreatePath(100, 100, fmNone, clBlack, $FF, True, clRed, $FF, 3.0);
Pdf1.LineTo(200, 100);
Pdf1.LineTo(150, 200);
Pdf1.ClosePath;
Pdf1.AddPath;
Pdf1.UpdatePage;
var
I: Integer;
Bookmarks: TBookmarks;
begin
Bookmarks := Pdf1.Bookmarks;
for I := 0 to Length(Bookmarks) - 1 do
ShowMessage(Bookmarks[I].Title + ' -> Page ' + IntToStr(Bookmarks[I].PageNumber));
end;
var
I: Integer;
Destination: TDestination;
begin
for I := 0 to Pdf1.DestinationCount - 1 do
begin
Destination := Pdf1.Destination[I];
if Destination.Name = 'Chapter1' then
begin
Pdf1.PageNumber := Destination.PageNumber;
Break;
end;
end;
end;
var
I: Integer;
AttachmentData: TBytes;
AttachmentName: WString;
FileStream: TFileStream;
begin
for I := 0 to Pdf1.AttachmentCount - 1 do
begin
AttachmentName := Pdf1.AttachmentName[I];
AttachmentData := Pdf1.Attachment[I];
FileStream := TFileStream.Create('C:\Extracted_' + string(AttachmentName), fmCreate);
try
if Length(AttachmentData) > 0 then
FileStream.WriteBuffer(AttachmentData[0], Length(AttachmentData));
finally
FileStream.Free;
end;
end;
if Pdf1.CreateAttachment('MyFile.txt') then
Pdf1.Attachment[Pdf1.AttachmentCount - 1] := TFile.ReadAllBytes('C:\MyFile.txt');
end;
Pdf1.AddPage(Pdf1.PageCount + 1, 612, 792);
Pdf1.AddPage(Pdf1.PageCount + 1, 595, 842);
Pdf1.AddPage(Pdf1.PageCount + 1, 400, 600);
var
SourcePdf: TPdf;
begin
SourcePdf := TPdf.Create(nil);
try
SourcePdf.FileName := 'C:\SourceDocument.pdf';
SourcePdf.Active := True;
Pdf1.ImportPages(SourcePdf, '1', Pdf1.PageCount + 1);
finally
SourcePdf.Free;
end;
end;
if Pdf1.PageCount > 1 then
Pdf1.DeletePage(Pdf1.PageCount);
// 압축된 PDF 문서 만들기
Pdf1.CreateDocument;
Pdf1.Compressed := True; // 모든 스트림이 FlateDecode로 압축되도록 보장 (기본값)
Pdf1.Active := True;
Pdf1.AddPage(0, 595, 842); // A4 크기
Pdf1.AddText('Hello World!', 'Arial', 24, 100, 700, clBlack, $FF, 0.0);
Pdf1.SaveAs('C:\Compressed.pdf');
Pdf1.Active := False;
// 옵션으로 저장
Pdf1.SaveAs('C:\Output.pdf', saIncremental); // 증분 저장
Pdf1.SaveAs('C:\NoSecurity.pdf', saRemoveSecurity); // 보안 제거
// 특정 PDF 버전 대상으로 저장. PDF 버전 검증기는 선택한 대상 이후에
// 도입된 모든 기능을 거부합니다 (JBIG2Decode, MarkInfo, AES 암호화,
// Polygon / Caret / Watermark / Redact 주석 등).
if not Pdf1.SaveAs('C:\Legacy15.pdf', saNone, pv15) then
ShowMessage('Save rejected: document contains features newer than PDF 1.5');
// SaveAsPdfA는 XMP 메타데이터 스트림, sRGB ICC OutputIntent 및 업데이트된
// Catalog를 주입하는 증분 업데이트로 기본 저장을 사후 처리하여 결과가
// PDF/A-1b를 준수하도록 합니다. 사용자 지정 프로필을 제공하지 않으면
// 번들된 sRGB IEC61966-2.1 프로필이 자동으로 사용됩니다.
Pdf1.FileName := 'C:\Report.pdf';
Pdf1.Active := True;
if Pdf1.SaveAsPdfA('C:\Report.pdfa.pdf', pac1b) then
ShowMessage('Saved PDF/A-1b archive');
// 사용자 지정 ICC 프로필이 포함된 PDF/A (예: CMYK 인쇄 파이프라인)
var
Opts: TPdfASaveOptions;
begin
Opts := Default(TPdfASaveOptions);
Opts.Conformance := pac1b;
Opts.IccProfileData := TFile.ReadAllBytes('C:\Profiles\Coated_GRACoL_2006.icc');
Pdf1.SaveAsPdfA('C:\Report.cmyk.pdfa.pdf', Opts);
end;
// 인메모리 PDF/A 출력
var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
Opts := Default(TPdfASaveOptions);
Opts.Conformance := pac1b;
if Pdf1.SaveAsPdfAToStream(Stream, Opts) then
UploadToArchive(Stream);
finally
Stream.Free;
end;
end;
var
Validation: TPdfAValidationResult;
Issue: TPdfAValidationIssue;
begin
Pdf1.FileName := 'C:\Incoming\report.pdf';
Pdf1.Active := True;
// 저비용 헤더 스타일 프로브 — 전체 검증기를 반복하지 않고 준수성을
// 읽습니다. 결과: pacNone, pac1a, pac1b, pac2b, pac3b 또는 pacUnknown.
case Pdf1.PdfAConformance of
pac1b: Status.Caption := 'PDF/A-1b';
pac2b: Status.Caption := 'PDF/A-2b';
pacNone: Status.Caption := 'Not PDF/A';
else
Status.Caption := 'Unknown conformance';
end;
// 문제별 진단이 포함된 전체 검증기
Validation := Pdf1.ValidatePdfA;
Memo1.Lines.Add('Conformance: ' + GetEnumName(TypeInfo(TPdfAConformance),
Ord(Validation.Conformance)));
for Issue in Validation.Issues do
Memo1.Lines.Add('- ' + Issue.Description);
end;
Demo\Delphi\StandardsLab, Demo\CBuilder\StandardsLab, Demo\Lazarus\StandardsLab 프로젝트는 PDF를 로드하거나 만든 후 ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR, ValidatePdfVT를 실행한 다음, 감지된 각 준수 수준과 문제 수를 하나의 그리드에 표시합니다. 저장 버튼은 SaveAsPdfA, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfX, SaveAsPdfR, SaveAsPdfVT를 호출하여 사용자 지정 하니스를 작성하지 않고도 마커 출력 워크플로를 볼 수 있습니다.
FPdfPreflightReport 유닛은 ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR, ValidatePdfVT를 하나의 리포트 객체로 래핑합니다. Demo\Delphi\PreflightReport, Demo\Lazarus\PreflightReport, Demo\CBuilder\PreflightReport는 사용자가 표준을 선택하고, 요약을 생성하며, 상태, 우선순위, 문제 카테고리, 카테고리 수, 다음 액션, 문제 코드, 권장 액션을 검사하고, 미리보기를 텍스트, Markdown, JSON, CSV로 전환하고, TXT, HTML, Markdown, JSON 또는 CSV 리포트를 내보낼 수 있도록 합니다. Delphi GUI 샘플은 통과하지 못한 표준, 우선순위, 문제 코드, 권장 조치를 remediation 체크리스트로 요약한 Action Plan 미리보기도 추가합니다. Demo\Delphi\PreflightReportCli는 입력 / 출력 인수, 선택적 암호, 출력 형식 선택에 plan / actionplan 포함, 표준 필터링, 선택적 단일 파일 attach=output.pdf 리포트 임베딩, failon= CI 게이팅 종료 코드, batch=list.txt 처리, 선택적 재귀 및 안정적인 경로 정렬 처리가 포함된 batchdir=folder 디렉토리 스캐닝, outdir=reports 리포트 라우팅, 중복 입력 파일 이름에 대한 충돌 안전 리포트 이름, 집계 및 표준 상태 합계가 포함된 TXT / JSON / HTML 요약, 항목당 반복되는 실행 설정이 포함된 CSV 행 매니페스트, 스크립트 및 CI 작업을 위한 무인수 샘플 모드와 동일한 리포트 경로를 콘솔 워크플로로 노출합니다. 생성된 리포트는 내장 검증기가 마커 수준 및 선택된 파일 수준 검사를 다루므로 전체 콘텐츠 수준 프리플라이트는 여전히 전용 검증 엔진에 위임될 수 있다고 명시합니다.
// LoadCustomDocument는 PDFium이 검색 가능한 모든 TStream에서 블록을
// 필요에 따라 가져올 수 있도록 합니다. 스트림은 메모리에 복사되지
// 않으므로 멀티 GB PDF, 원격 HTTP 본문 스트림, 데이터베이스 기반
// 소스가 모두 사전 버퍼 없이 실행 가능합니다.
var
FileStream: TFileStream;
begin
// 파일에서 읽기 스트리밍 (인메모리 복사 없음)
FileStream := TFileStream.Create('C:\Huge.pdf',
fmOpenRead or fmShareDenyWrite);
Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
// ^^ 소유권 이전 — Pdf1이 UnloadDocument에서 FileStream을 해제합니다.
Pdf1.Active := True;
ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
end;
// 외부 소유 스트림 (호출자가 UnloadDocument 후 해제)
var
Stream: TFileStream;
begin
Stream := TFileStream.Create('C:\Live.pdf',
fmOpenRead or fmShareDenyNone);
try
Pdf1.LoadCustomDocument(Stream, False);
Pdf1.Active := True;
DoWork;
Pdf1.Active := False;
finally
Stream.Free;
end;
end;
// FPdfAsync는 장기 실행 내보내기 및 렌더 경로에서 작동하는
// UI 취소 의미론을 위한 IPdfCancellationToken /
// IPdfCancellationTokenSource를 노출합니다.
uses FPdfAsync;
var
CancelSource: IPdfCancellationTokenSource;
CancelToken: IPdfCancellationToken;
begin
CancelSource := PdfCancellationTokenSource;
CancelToken := CancelSource.Token;
// ... 사용자가 취소를 클릭함 ...
CancelSource.Cancel;
// 워커는 주기적으로 CancelToken.IsCancelled를 확인하거나, 렌더 경로는
// PDFium의 IFSDK_PAUSE 콜백을 통해 폴링합니다.
if CancelToken.IsCancelled then
Memo1.Lines.Add('Cancelled by user');
end;
// RenderPageProgressive를 사용하면 사용자가 취소를 클릭할 때 장기
// 고해상도 렌더링이 완료될 때까지 호출자를 차단하는 대신 페이지 중간에
// 중단할 수 있습니다.
var
Status: TPdfProgressiveStatus;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.PixelFormat := pf32bit;
Bitmap.SetSize(2480, 3508); // A4 @ 300 DPI
Status := Pdf1.RenderPageProgressive(Bitmap, 0, 0, Bitmap.Width,
Bitmap.Height, CancelToken, ro0, [reAnnotations], clWhite);
case Status of
prsDone: SaveBitmap(Bitmap);
prsCancelled: Memo1.Lines.Add('Render cancelled — partial result available');
prsFailed: Memo1.Lines.Add('PDFium reported FPDF_RENDER_FAILED');
end;
finally
Bitmap.Free;
end;
end;
// TPdfFuture<T>는 백그라운드 스레드에서 워커를 시작하고
// TPdfFutureResult<T> 봉투를 메인 스레드에 다시 게시합니다.
//
// 중요: TPdf는 인스턴스별로 렌더 호출을 직렬화하지만 워커는 여전히
// 디스패치와 문서 수명을 소유합니다. 렌더링이 진행 중인 동안 TPdf를
// 변경하지 마십시오. 일괄 내보내기는 일반적으로 파일당 TPdf 하나를 유지합니다.
type
TRenderResult = record
Bitmap : TBitmap;
PageNo : Integer;
end;
var
Future: TPdfFuture<TRenderResult>;
begin
Future := TPdfFuture<TRenderResult>.Create(
function (const ACancelToken: IPdfCancellationToken): TRenderResult
var
LocalPdf: TPdf;
begin
LocalPdf := TPdf.Create(nil);
try
LocalPdf.FileName := 'C:\Huge.pdf';
LocalPdf.Active := True;
LocalPdf.PageNumber := 1;
Result.Bitmap := LocalPdf.RenderPage(0, 0, 2480, 3508);
Result.PageNo := 1;
finally
LocalPdf.Free;
end;
end,
procedure (const AResult: TPdfFutureResult<TRenderResult>)
begin
case AResult.State of
pfsSuccess: PaintBitmapToView(AResult.Value.Bitmap);
pfsCancelled: Log.Add('Cancelled');
pfsFailed: Log.Add('Worker failed: ' + AResult.ErrorMessage);
end;
end);
end;
// FPdfMatrix는 PDFium의 FS_MATRIX를 래핑하여 호출자가 선언적으로
// 변환을 빌드하고 FPDFPageObj_SetMatrix에 전달할 수 있도록 합니다.
uses FPdfMatrix, FPdfView;
var
M: TPdfMatrix;
RawMatrix: FS_MATRIX;
PageObj: FPDF_PAGEOBJECT;
begin
M := TPdfMatrix.Create;
try
M.Translate(72, 200); // 1인치 오른쪽, 약 2.8인치 위로 이동
M.Scale(0.5, 0.5); // 절반 크기 스탬프
M.Rotate(15); // 15도 반시계 방향 회전
// M.HorizontalFlip;
// M.VerticalFlip;
// M.CentralFlip;
// M.Skew(10, 5);
// M.Multiply(Other); // 다른 행렬과 사후 곱셈
// 모든 PDFium 페이지 객체 (텍스트 / 경로 / 이미지 / form XObject)에 적용
RawMatrix := M.Handle;
FPDFPageObj_SetMatrix(PageObj, RawMatrix);
finally
M.Free;
end;
end;
// ImportPagesByIndex는 소스 PDF에서 명시적 0 기반 인덱스 배열을
// 이 문서로 복사합니다. InsertAt = 0은 1페이지 앞에 삽입하고
// PageCount는 추가합니다.
var
Source: TPdf;
begin
Source := TPdf.Create(nil);
try
Source.FileName := 'C:\Report.pdf';
Source.Active := True;
// Source에서 페이지 0, 2, 4 (1 기반: 페이지 1, 3, 5)를 가져와서
// Pdf1의 끝에 추가합니다.
Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);
// 빈 배열은 모든 소스 페이지를 가져옵니다
// Pdf1.ImportPagesByIndex(Source, [], 0);
finally
Source.Free;
end;
end;
// ImportNPagesToOne은 이 문서의 NumX*NumY 합성 페이지를 가진
// 새로운 TPdf를 반환합니다. OutputWidth/Height는 PDF 사용자 단위입니다
// (1단위 = 1/72인치).
var
Composite: TPdf;
begin
Pdf1.FileName := 'C:\Slides.pdf';
Pdf1.Active := True;
// 4-up A4 가로: 842 x 595 pt, 2열 x 2행
Composite := Pdf1.ImportNPagesToOne(842, 595, 2, 2);
try
if Composite <> nil then
Composite.SaveAs('C:\Slides_4up.pdf');
finally
Composite.Free;
end;
end;
// MovePages는 이동할 페이지의 0 기반 인덱스와 이동이 완료된 후
// 첫 번째로 이동된 페이지의 대상 인덱스를 받습니다. 다른 페이지는
// 이동된 블록 주위로 이동하므로 문서 길이는 동일하게 유지됩니다.
//
// 예: [0,1,2,3,4]로 번호 매겨진 페이지가 있는 5페이지 문서에서
// MovePages([2, 3], 0)은 [2, 3, 0, 1, 4]로 재정렬합니다.
if not Pdf1.MovePages([2, 3], 0) then
ShowMessage('Invalid indices passed to MovePages');
// CreateXObjectFromPage는 다른 TPdf의 모든 페이지에서 재사용 가능한
// Form XObject 핸들을 만듭니다. 해당 핸들을 이 문서의 원하는 만큼 많은
// 페이지에 스탬프하세요 — 각 스탬프는 행렬 API를 통해 위치 지정, 크기 조정,
// 회전할 수 있는 단일 PDFium 페이지 객체입니다.
uses FPdfMatrix, FPdfView;
var
WatermarkSource: TPdf;
XObj: TPdfXObject;
PageObj: FPDF_PAGEOBJECT;
M: TPdfMatrix;
RawMatrix: FS_MATRIX;
I: Integer;
begin
WatermarkSource := TPdf.Create(nil);
try
WatermarkSource.FileName := 'C:\Watermarks\Confidential.pdf';
WatermarkSource.Active := True;
// WatermarkSource의 0페이지를 재사용 가능한 XObject로 래핑
XObj := Pdf1.CreateXObjectFromPage(WatermarkSource, 0);
try
for I := 1 to Pdf1.PageCount do
begin
Pdf1.PageNumber := I;
PageObj := Pdf1.InsertFormObjectFromXObject(XObj);
if PageObj = nil then Continue;
// 페이지에서 워터마크를 중앙에 배치
M := TPdfMatrix.Create;
try
M.Scale(0.5, 0.5);
M.Translate(0.25 * Pdf1.PageWidth, 0.5 * Pdf1.PageHeight);
RawMatrix := M.Handle;
FPDFPageObj_SetMatrix(PageObj, RawMatrix);
finally
M.Free;
end;
Pdf1.UpdatePage;
end;
finally
XObj.Free; // FPDF_XOBJECT 핸들을 닫습니다
end;
Pdf1.SaveAs('C:\Watermarked.pdf');
finally
WatermarkSource.Free;
end;
end;
Demo\Delphi\WatermarkStamp, Demo\Lazarus\WatermarkStamp, Demo\CBuilder\WatermarkStamp 프로젝트는 이 패턴을 실행 가능한 워크플로로 전환합니다. 즉, 대상 PDF와 재사용 가능한 스탬프 PDF를 만들고, CreateXObjectFromPage로 스탬프 페이지를 래핑하며, 모든 대상 페이지에 동일한 Form XObject를 삽입하고, 중앙 워터마크와 오른쪽 상단 스탬프를 위한 TPdfMatrix 변환을 적용하며, 결과를 저장하기 전에 선택적인 페이지 번호 레이블을 추가합니다.
// AddImage(FileName)은 VCL 또는 LCL 그래픽 유닛에 등록된 모든 형식
// (BMP, PngImage를 통한 PNG, JPG 등)을 받습니다.
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);
// AddImage(TBitmap)은 TPicture 중간 단계를 건너뜁니다. 비트맵이 이미
// 렌더링 또는 일괄 생성 작업에서 온 경우 이상적입니다.
var
Bitmap: TBitmap;
begin
Bitmap := Pdf1.RenderPage(0, 0, 595, 842);
try
Pdf1.PageNumber := 2;
Pdf1.AddImage(Bitmap, 50, 50, 250, 350);
Pdf1.UpdatePage;
finally
Bitmap.Free;
end;
end;
Demo\Delphi\ImageToPDF, Demo\CBuilder\ImageToPDF, Demo\Lazarus\ImageToPDF 프로젝트는 이를 완전한 일괄 워크플로로 전환합니다. 여러 이미지 파일을 선택하고, 선택한 항목을 미리 보고, 이미지당 하나의 PDF 페이지를 만들고, 각 이미지를 A4 세로 또는 가로 페이지에 맞게 크기 조정하며, 결과를 저장하고, 생성된 PDF를 엽니다.
var
MatchCount: Integer;
begin
// 뷰의 현재 페이지에서 모든 일치 항목 위에 clYellow 마스크를 그립니다.
// 다른 페이지로 전환하면 강조 표시가 자동으로 지워집니다.
PdfView1.HighlightColor := clYellow;
MatchCount := PdfView1.HighlightSearchText('invoice', False{Case}, True{Word});
Status.Caption := Format('%d match(es) on this page', [MatchCount]);
// 페이지가 변경되기 전에 프로그래밍 방식으로 오버레이 제거
PdfView1.ClearHighlight;
end;
// FitMode는 크기 조정 또는 페이지 변경 중에 긴 문서를 프레임에 맞게 유지합니다.
// Zoom을 직접 설정하면 FitMode가 취소됩니다 (pfmNone으로 되돌아감).
PdfView1.FitMode := pfmFitPage; // 전체 페이지가 뷰포트에
// PdfView1.FitMode := pfmFitWidth; // 페이지 너비가 뷰포트 너비와 일치
// PdfView1.FitMode := pfmActualSize; // 100%
// PageColor는 렌더링된 PDF 페이지를 호스트 컨트롤의 Color와 분리합니다 —
// 일반적인 다크 모드 뷰어 설정은 PDF 페이지를 종이 흰색으로 유지하면서
// 스크롤 영역을 어둡게 유지합니다.
PdfView1.Color := clBlack; // 스크롤 영역
PdfView1.PageColor := clWhite; // PDF 페이지 배경
// 어두운 배경에서 페이지를 부각시키기 위한 선택적 그림자 및 페이지 테두리
PdfView1.PageShadowSize := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;
Demo\Delphi\ViewerInteractionLab, Demo\CBuilder\ViewerInteractionLab, Demo\Lazarus\ViewerInteractionLab 프로젝트는 현대적인 TPdfView 상호작용 표면을 하나의 컴팩트한 폼에 유지합니다. 샘플 PDF를 열거나 만들고, DisplayMode를 전환하고, FitMode를 적용하며, PageColor를 변경하고, 페이지를 회전하며, HighlightSearchText로 현재 페이지 검색 결과를 강조 표시하고, 사용자 텍스트 선택을 활성화 또는 비활성화하며, SelectedText를 검사하고, SelectAll, CopySelectionToClipboard, ClearSelection을 호출하며, 마우스가 페이지 위로 이동할 때 상태 표시줄에서 DeviceToPage 좌표가 업데이트되는 것을 관찰할 수 있습니다.
Demo\Delphi\SearchAndSelect, Demo\CBuilder\SearchAndSelect, Demo\Lazarus\SearchAndSelect 프로젝트는 검색 및 선택 뷰어 워크플로를 독립형 샘플로 전환합니다. PDF를 열거나 만들고, 페이지 번호, 문자 인덱스 및 미리보기 텍스트가 포함된 모든 페이지 검색 결과를 나열하며, 이전 / 다음 탐색 및 더블 클릭 결과 이동을 지원하고, HighlightSearchText로 현재 페이지 일치 항목을 강조 표시하며, 대소문자 구분 및 단어 단위 옵션을 노출하고, AllowUserTextSelection을 전환하며, SelectedText를 미리 보고, 버튼에서 SelectAll, CopySelectionToClipboard, ClearSelection을 구동하며, 사용자가 단일 및 펼침 표시 모드 간에 전환할 수 있도록 합니다.
Demo\Delphi\PrintPreferences, Demo\CBuilder\PrintPreferences, Demo\Lazarus\PrintPreferences 프로젝트는 인쇄 전에 작성자 인쇄 환경설정을 읽습니다. 이들은 PrintCopies, PrintPageRanges, PrintScaling, PrintPaperHandling을 표시하고, 작성자가 제공한 매수와 범위를 인쇄 입력에 복사할 수 있으며, SetPdfPrintPaperHandlingDevMode가 PDF 양면 인쇄 의도를 Windows DEVMODE에 어떻게 매핑하는지 보여줍니다.
Demo\Delphi\PrintPDF, Demo\CBuilder\PrintPDF, Demo\Lazarus\PrintPDF 프로젝트는 표준 인쇄 워크플로를 보여줍니다. PDF 열기, 페이지 미리보기, 플랫폼 인쇄 대화상자에서 페이지 범위 및 매수 선택, 한 부씩 인쇄 모드 준수, TPdf.RenderPage를 통한 각 페이지 렌더링, 취소가 있는 인쇄 진행률 노출. Delphi 샘플은 또한 빠른 로딩을 위해 드롭된 PDF 파일을 받습니다.
Demo\Delphi\MultiPageViewer, Demo\CBuilder\MultiPageViewer, Demo\Lazarus\MultiPageViewer 프로젝트는 일반 페이지 탐색 및 줌 워크플로로 연속, 펼침, 표지 인식 보기를 실행합니다. 매우 큰 PDF에서 첫 페이지 열기 속도와 렌더링 응답성을 테스트하는 데에도 유용합니다. 대용량 파일 테스트에서 Acrobat이나 Foxit이 첫 번째 뷰까지 20-30초가 걸릴 수 있는 2.x GB 문서가 PDFiumPas 뷰어 경로를 통해 거의 즉시 사용 가능해질 수 있습니다.
Demo\Delphi\SplitView, Demo\CBuilder\SplitView, Demo\Lazarus\SplitView 프로젝트는 나란히 PDF 검토를 보여줍니다. C++Builder 및 Lazarus 샘플은 두 개 또는 세 개 창 비교, 활성 뷰 선택, 선택적 동기화된 탐색 명령, 공유 줌 사전 설정, 선택된 뷰 또는 로드된 모든 뷰의 회전에 중점을 둡니다. 첫 번째 사용 가능한 뷰와 후속 페이지 렌더링이 응답성을 유지하도록 하면서 큰 문서를 비교하려면 이러한 샘플을 사용하세요. Acrobat이나 Foxit이 열기까지 20-30초가 걸릴 수 있는 동일한 2.x GB PDF 파일이 PDFiumPas 뷰어 경로에서는 거의 즉시 사용 가능해질 수 있습니다.
Demo\Delphi\SecurityAudit, Demo\CBuilder\SecurityAudit, Demo\Lazarus\SecurityAudit 프로젝트는 PDF를 읽기 전용으로 열고 권한 상태, 임베디드 첨부 파일, 문서 JavaScript 액션, URI 및 Launch 링크 주석, 웹 링크, 서명 요약, XFA 상태, V8 헬퍼 가용성, 지원되지 않는 기능 콜백을 나열합니다. 동일한 요약과 발견 행은 TXT 감사 리포트로 저장 또는 복사하거나 구조화된 JSON으로 저장할 수 있습니다. 이는 호스트 애플리케이션 위험 패널로 의도된 것이며 맬웨어 감지가 아닙니다.
// LinkOptions는 네 가지 PDF 링크 액션 유형의 자동 처리를 제어합니다.
// 기본값은 goto와 URI만 활성화합니다. launch (프로그램 실행) 및
// 임베디드 GotoR (다른 파일로 점프)는 잘못된 클릭이 임의의 코드를
// 실행할 수 없도록 OFF 상태로 유지됩니다.
PdfView1.LinkOptions :=
[loAutoGoto, loAutoOpenURI]; // 안전한 기본값
// OnAnnotationLinkClick은 문서 내 목적지 (페이지 점프, 이름 지정된
// 목적지, 액션 체인)에 대해 발생합니다. 뷰가 링크를 자동으로 따르는 것을
// 방지하려면 Handled = True로 표시하세요.
procedure TForm1.PdfView1AnnotationLinkClick(Sender: TObject;
LinkIndex: Integer; const Action: TPdfAction;
var Handled: Boolean);
begin
if Action.Kind = paUri then
begin
if MessageDlg('Open ' + Action.Uri + '?', mtConfirmation, [mbYes, mbNo], 0)
<> mrYes then
Handled := True; // 자동 처리 억제
end;
end;
// OnWebLinkClick은 PDFium의 웹 링크 스캐너가 발견한 URI 문자열에 대해
// 발생합니다 (텍스트 스트림에서 자동 감지된 URL,
// /A <</S/URI...>> 주석 없이도).
procedure TForm1.PdfView1WebLinkClick(Sender: TObject;
WebLinkIndex: Integer; const Url: WString;
var Handled: Boolean);
begin
Log.Add('Web link clicked: ' + string(Url));
end;
// 포커스가 있는 AcroForm 위젯에서 6가지 새 메서드는 PDFium의 폼 편집
// 히스토리를 구동합니다. 포커스가 있는 위젯이 없거나 문서에 AcroForm이
// 없으면 모두 안전하게 단락 평가합니다.
procedure TForm1.btnSelectAllClick(Sender: TObject);
begin
PdfView1.SelectAllFormText;
EditMenuCopy.Caption := 'Copy: ' + string(PdfView1.GetSelectedFormText);
end;
procedure TForm1.btnUndoClick(Sender: TObject);
begin
btnUndo.Enabled := PdfView1.FormCanUndo;
btnRedo.Enabled := PdfView1.FormCanRedo;
if PdfView1.FormCanUndo then
PdfView1.FormUndo;
end;
procedure TForm1.btnRedoClick(Sender: TObject);
begin
if PdfView1.FormCanRedo then
PdfView1.FormRedo;
end;
// 세 가지 모두 기본값이 True이므로 기존 앱은 동작 변경 없이 업그레이드됩니다.
// 키오스크, 미리보기 창 또는 읽기 전용 임베딩의 경우 TPdfView를
// 서브클래싱하지 않고 False로 전환하세요.
PdfView1.AllowUserPageChange := False; // PgUp/PgDn/Ctrl+Home/End
PdfView1.ChangePageOnMouseScrolling := False; // 휠 위/아래 점프
PdfView1.AllowUserTextSelection := False; // 마우스/키보드 텍스트 선택
// Ctrl+휠은 줌용으로 예약되어 있으며 ChangePageOnMouseScrolling에
// 관계없이 탐색 핸들러가 건너뜁니다.
// TPdfThumbnailView (유닛 FPdfThumbnail)는 스크롤 가능한 사이드 패널에
// 행당 페이지 썸네일 하나를 렌더링합니다.
uses FPdfThumbnail;
procedure TForm1.FormCreate(Sender: TObject);
begin
ThumbView.Pdf := Pdf1;
ThumbView.ThumbnailWidth := 120;
ThumbView.ThumbnailHeight := 160;
ThumbView.SelectionColor := $00CFA85F; // 따뜻한 강조 표시
ThumbView.OnPageClick := ThumbViewPageClick;
end;
procedure TForm1.ThumbViewPageClick(Sender: TObject; PageIndex: Integer);
begin
PdfView1.PageNumber := PageIndex + 1; // PageIndex는 0 기반
end;
procedure TForm1.PdfView1PageNumber변경 (Changed)(Sender: TObject);
begin
ThumbView.CurrentPageIndex := PdfView1.PageNumber - 1;
end;
// Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport,
// Demo\Lazarus\BatchExport에서 사용하는 패턴. 파일당 TPdf 하나,
// UI 취소를 위한 IPdfCancellationToken, 버퍼링된 파일 로드를 위한
// TPdfStreamAdapter.
uses FPdfAsync;
procedure ExportBatch(const Files: TArray<string>;
const OutDir: string; DPI: Integer; Quality: Integer;
const ACancelToken: IPdfCancellationToken);
var
I, J: Integer;
Job: TPdf;
Bmp: TBitmap;
Jpeg: TJPEGImage;
begin
for I := 0 to High(Files) do
begin
if ACancelToken.IsCancelled then Break;
OverallProgress.Position := I;
Job := TPdf.Create(nil);
try
Job.FileName := Files[I];
Job.Active := True;
PageProgress.Max := Job.PageCount;
for J := 1 to Job.PageCount do
begin
if ACancelToken.IsCancelled then Break;
PageProgress.Position := J;
Job.PageNumber := J;
Bmp := Job.RenderPage(
0, 0,
Round(Job.PageWidth * DPI / 72),
Round(Job.PageHeight * DPI / 72));
try
Jpeg := TJPEGImage.Create;
try
Jpeg.CompressionQuality := Quality;
Jpeg.Assign(Bmp);
Jpeg.SaveToFile(Format('%s\%s.p%.3d.jpg',
[OutDir, ExtractFileName(Files[I]), J]));
finally
Jpeg.Free;
end;
finally
Bmp.Free;
end;
end;
finally
Job.Free;
end;
end;
end;
procedure FitCurrentPage;
begin
PdfView1.Zoom := PdfView1.PageZoom[PdfView1.PageNumber];
end;
procedure InspectViewerPoint(X, Y: Integer);
var
PageNo: Integer;
PageX, PageY: Double;
CharIndex: Integer;
Ch: WString;
begin
PageNo := PdfView1.PageNumber;
if not PdfView1.DeviceToPage(X, Y, PageNo, PageX, PageY) then
Exit;
CharIndex := PdfView1.CharacterIndexAtPos(X, Y, 8, 8);
if CharIndex >= 0 then
begin
Ch := PdfView1.Text(CharIndex, 1);
ShowMessage(Format('Page %d, X %.2f, Y %.2f, character %s',
[PageNo, PageX, PageY, string(Ch)]));
end;
end;
function LoadPdfSafely(const FileName: string): Boolean;
begin
Result := False;
if not FileExists(FileName) then
Exit;
try
Pdf1.FileName := FileName;
Pdf1.Active := True;
Result := Pdf1.Active;
except
Result := False;
end;
end;
function ExtractTextSafely(PageNo: Integer): WString;
begin
Result := '';
if not Pdf1.Active then
Exit;
if (PageNo < 1) or (PageNo > Pdf1.PageCount) then
Exit;
Pdf1.PageNumber := PageNo;
Result := Pdf1.Text;
end;
// IsTagged는 저비용 Catalog 프로브 — /StructTreeRoot가 존재할 때 True입니다.
// StructureElements는 트리를 유형, 제목, 대체 텍스트, 실제 텍스트,
// 확장 텍스트, 언어, 수준, 부모 인덱스, 자식 / marked content / 속성 수가
// 포함된 TPdfStructureElement 레코드의 평면 배열로 구체화합니다.
var
Elements: TPdfStructureElements;
I: Integer;
begin
if not Pdf1.IsTagged then
begin
Log.Add('Document is NOT tagged — accessibility tools may struggle.');
Exit;
end;
if Pdf1.Language = '' then
Log.Add('Tagged PDF without /Lang — fails PDF/UA.');
Elements := Pdf1.StructureElements;
for I := 0 to High(Elements) do
Log.Add(StringOfChar(' ', Elements[I].Level * 2)
+ Elements[I].StructType + ' "' + Elements[I].Title + '"');
end;
// SetPdfPrintMode는 PDFium의 FPDF_SetPrintMode에 매핑되는 전역 함수입니다.
// 사용 중인 프린터 드라이버에 맞는 TPdfPrintMode 값을 사용하세요.
SetPdfPrintMode(pmEmf); // GDI EMF (기본값)
// SetPdfPrintMode(pmTextOnly); // 텍스트 전용 렌더링
// SetPdfPrintMode(pmPostScript2); // PostScript Level 2
// SetPdfPrintMode(pmPostScript3); // PostScript Level 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks); // EMF + 이미지 마스크
// SetPdfPrintMode(pmPostScript3Type42); // PostScript 3 + Type 42 글꼴
// PrintCopies, PrintPageRanges, PrintScaling, PrintPaperHandling은
// 뷰어 환경설정 사전 항목입니다 — 이들은 문서 작성자가 PDF 리더의
// 기본값으로 원했던 것을 인코딩합니다.
var
Ranges: TPrintPageRanges;
Range: TPrintPageRange;
begin
ShowMessage('Suggested copies: ' + IntToStr(Pdf1.PrintCopies));
ShowMessage('Print scaling: ' + GetEnumName(TypeInfo(TPrintScaling),
Ord(Pdf1.PrintScaling)));
Ranges := Pdf1.PrintPageRanges; // 1 기반 범위
for Range in Ranges do
Log.Add(Format('Pages %d..%d', [Range.First, Range.Last]));
if SetPdfPrintPaperHandlingDevMode(PrinterDevMode, Pdf1.PrintPaperHandling) then
ApplyPrinterDevMode(PrinterDevMode);
end;
// 문서 수준 JavaScript는 /Names /JavaScript 이름 트리에 저장됩니다.
// JavaScriptAction[i]는 항목 하나를 반환하고, JavaScriptActions는
// 전체 배열을 반환합니다.
var
I: Integer;
Action: TPdfJavaScriptAction;
begin
for I := 0 to Pdf1.JavaScriptActionCount - 1 do
begin
Action := Pdf1.JavaScriptAction[I];
Memo1.Lines.Add('Name: ' + string(Action.Name));
Memo1.Lines.Add('Script: ' + string(Action.Script));
Memo1.Lines.Add('---');
end;
end;
procedure ProcessMultiplePages;
var
I: Integer;
PageText: WString;
begin
for I := 1 to Pdf1.PageCount do
begin
Pdf1.PageNumber := I;
PageText := Pdf1.Text;
ProcessPageText(PageText);
end;
end;
procedure SetOptimalRenderOptions;
begin
PdfView1.Options := [reAnnotations, reLcd];
end;
// 제로 카피 렌더 경로 (v1.21.0부터 기본값): RenderPage는 호출자가 사전
// 할당된 비트맵을 전달할 때 대상 TBitmap의 DIB 버퍼에 직접 쓰므로
// width * height * 4 바이트 복사 한 번을 피합니다.
procedure RenderIntoCallerBitmap(Bitmap: TBitmap);
begin
Bitmap.PixelFormat := pf32bit;
Bitmap.SetSize(2480, 3508); // A4 @ 300 DPI
Pdf1.RenderPage(Bitmap, 0, 0, Bitmap.Width, Bitmap.Height);
end;