PDFlibPas(losLab PDF Library)는 Delphi에서 PDF 블록 템플릿을 채웁니다. 블록은 템플릿 페이지 위의 이름 붙은 사각형으로 텍스트, 이미지, PDF 타입이 있으며, FillTextBlock, FillImageBlock, FillPDFBlock이 가변 데이터를 그 안에 그립니다. 블록은 PDFlibBlock:Text, PDFlibBlock:Image, PDFlibBlock:PDF 페이지 어노테이션이나 /PieceInfo 아래의 page-piece 딕셔너리에서 발견됩니다
쓰임새는 지루하지만 대량인 부류입니다. 디자이너가 수료증, 행사 티켓, 독촉장 레이아웃을 한 번 잡아 두면, 프로그램이 만 건의 레코드마다 이름, 좌석 번호, 서명 이미지, 미리 렌더링한 바코드 페이지를 고정 슬롯에 찍어 넣습니다. 폼 필드로도 일부는 되지만, AcroForm 필드는 자체 appearance 스트림과 자체 규칙을 가진 인터랙티브 객체라 결국 평탄화하며 끝나는 경우가 많습니다. 블록은 더 단순합니다. 사각형에 스타일 힌트 몇 개를 얹은 것으로, 한 번 소비되고 버려집니다
PDFlibPas에서 PDF 블록은 정확히 무엇일까
PDFlibPas 블록은 /Subtype이 PDFlibBlock:Text, PDFlibBlock:Image, PDFlibBlock:PDF 중 하나이고 /Rect가 플레이스홀더 영역이며 /Contents 문자열이 name;key=value;key=value를 담는 페이지 어노테이션입니다. 첫 세그먼트가 블록 이름이고 그 뒤는 전부 속성 목록입니다. TPDFlib.AddBlock은 이 어노테이션을 /F 4(Print)로, appearance 스트림 없이 기록하므로, 채워지지 않은 블록은 화면이나 종이에서 픽셀 하나도 차지하지 않습니다
읽는 쪽에는 두 번째 원천이 있습니다. 블록 어노테이션을 열거한 뒤 PDFlibPas는 페이지 딕셔너리 경로인 /PieceInfo << /PDFlib << /Blocks ... >> >>와 그 변형인 /PieceInfo << /PDFlib << /Private << /Blocks ... >> >> >>로 대체합니다. page-piece 딕셔너리는 표준 PDF로, ISO 32000-1 §14.5가 애플리케이션이 페이지에 붙인 비공개 데이터를 저장하는 장소로 정의하며, 외부 템플릿 저작 도구가 만든 블록 정의는 보통 여기에 살습니다. /Blocks 컨테이너는 블록 이름을 키로 하는 딕셔너리일 수도, 단순 배열일 수도 있습니다. 배열 형태에서는 블록이 자체 /Name을 갖고 있지 않으면 PDFlibPas가 항목 이름을 Block1, Block2 식으로 붙입니다
page-piece 블록 안에서는 /Subtype(또는 /Type) 값이 Image면 이미지 블록, PDF나 Page면 PDF 블록에 대응되고, 그 외는 텍스트로 취급됩니다. /Type, /Subtype, /Name, /Rect, /Custom을 제외한 모든 키는 속성이 되고, 중첩된 /Custom 딕셔너리의 키도 함께 평탄화되므로, /Custom << /department (Finance) >> 항목은 GetBlockProperty(Page, Index, 'department')로 읽힙니다. 블록 인덱스는 페이지마다 1부터이며 어노테이션 블록이 먼저, page-piece 블록이 그 뒤입니다
AddBlock으로 블록 템플릿 만들기
TPDFlib.AddBlock(BlockType, BlockName, Left, Top, Width, Height, Properties)는 현재 선택된 페이지에 블록을 추가하고 성공하면 1을 반환합니다. 빈 이름, 0 또는 음수 너비/높이, 'text', 'image', 'pdf' 이외의 BlockType(대소문자 무시)에는 0을 반환합니다. Properties는 세미콜론으로 이름 뒤에 붙으므로, 저장된 문자열과 같은 key=value;key=value 구문을 씁니다
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
// 정적 아트워크: 테두리, 로고, 고정 문구 ...
Lib.DrawText(72, 200, 'This certifies that');
Lib.AddBlock('text', 'recipient', 72, 230, 450, 44,
'fontname=Times-Bold;fontsize=30;fillcolor={cmyk 0 0.55 1 0.1};alignment=center');
Lib.AddBlock('text', 'course', 72, 290, 450, 24,
'fontname=Helvetica;fontsize=14;alignment=center;fitmethod=word');
Lib.AddBlock('image', 'signature', 360, 600, 160, 60, '');
Lib.AddBlock('pdf', 'seal', 72, 580, 100, 100, '');
if Lib.GetBlockCount(1) <> 4 then
raise Exception.Create('Template blocks missing');
Lib.SaveToFile('certificate-template.pdf');
finally
Lib.Free;
end;
end;
속성 파서는 의도적으로 느슨합니다. 키는 대소문자를 구분하지 않고 대조하고, 값은 트림하되 나머지는 그대로 반환하며({top left}로 쓴 값은 중괄호가 붙은 채 돌아옵니다), 없는 키는 빈 문자열을 반환합니다. 유일한 hard limit은 구분자입니다. 세미콜론이 세그먼트를 끝내므로 값 안에 세미콜론을 넣을 수 없습니다
FillTextBlock는 폰트, 색, 정렬을 어떻게 적용할까
TPDFlib.FillTextBlock(Page, BlockIndex, Text, Options)는 블록 속성을 읽어 드로잉 상태에 적용하고, FitTextBox로 텍스트를 블록 사각형에 맞춘 뒤, finally 블록에서 호출자의 폰트, 텍스트 크기, 텍스트 색, 정렬을 복원합니다. 복원이 병합 루프에서 중요한 이유는, 빨간색 30pt 제목 블록의 스타일이 같은 페이지의 다음 DrawText 호출로 새어 나가면 안 되기 때문입니다
fontname(또는font): 먼저 그 이름으로 문서에 이미 적재된 폰트를 재사용하고, 다음으로Helvetica-Bold,Times-Roman,Courier같은 Standard 14 이름을 매핑하며, 마지막으로 그 이름으로AddTrueTypeFont를 시도합니다fontsize(또는size): 피터에 넘기는 텍스트 크기로, 텍스트가 안 맞으면 이 값을 줄입니다fillcolor(또는textcolor,color):#RRGGBB,{rgb r g b},{cmyk c m y k},{gray g}, 숫자 3개나 4개의 나열, 그리고 black, white, red, green, blue, gray 이름을 받습니다. 1보다 큰 RGB 성분은 0–255로 읽습니다alignment,align,position은 함께 읽힙니다. left, right, center(또는 centre), justify는 수평 정렬을, top과 bottom은 수직 위치를 정하며, 기본값은 수직 중앙입니다fitmethod:word나nofitsplit은 긴 단어가 잘리지 않게 하고,scale이나expand는 크기가 박스를 채우도록 자라는 것을 허용합니다
인쇄소가 주목하는 이유는 CMYK 경로입니다. {cmyk 0 0.55 1 0.1}은 SetTextColorCMYK를 호출하므로, 가변 텍스트가 DeviceCMYK로 방출되어 RGB 왕복 없이 인쇄기에서 브랜드 색을 맞힙니다. FillTextBlock은 텍스트가 그려졌으면 1을 반환합니다. 빈 Text, 없는 블록, 텍스트 블록이 아닌 블록에는 0을 반환하며, 이 경우 모두 블록은 그 자리에 남습니다
이미지와 PDF 블록 채우기, 그리고 Options 비트
FillImageBlock(Page, BlockIndex, ImageID, Options)는 이전에 추가한 이미지를 그리고, FillPDFBlock(Page, BlockIndex, SourceDocumentID, SourcePage, Options)은 다른 적재된 문서의 페이지를 Form XObject로 임포트해 그리는데, 도장, QR 코드 페이지, 레터헤드 같은 벡터 콘텐츠가 끝까지 벡터로 유지됩니다. 둘 다 원본 가로세로 비를 유지하며 결과를 블록 사각형 안에서 중앙에 놓습니다. 세 채우기 함수는 같은 Options 비트를 공유합니다. 비트 1(값 1)은 성공한 채우기 뒤에 블록을 제거합니다. 어노테이션을 지우거나 /Blocks 컨테이너에서 항목을 빼며, 비트 2(값 2)는 이미지와 PDF 콘텐츠를 맞추는 대신 정확한 사각형까지 늘립니다
function FillCertificate(const TemplateFile, SealFile, SignatureFile,
OutFile: WideString; Values: TStrings): Boolean;
var
Lib: TPDFlib;
SealID, SignatureID, I: Integer;
BlockName, BlockType: WideString;
begin
Result := False;
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(SealFile, '') <> 1 then Exit;
SealID := Lib.SelectedDocument; // 실제 문서 ID
if Lib.LoadFromFile(TemplateFile, '') <> 1 then Exit;
// 이제 템플릿이 선택된 문서입니다
SignatureID := Lib.AddImageFromFile(SignatureFile, 0);
// 뒤에서 앞으로 순회: 비트 1이 각 블록을 제거해 뒤쪽 번호를 다시 매깁니다
for I := Lib.GetBlockCount(1) downto 1 do
begin
BlockName := Lib.GetBlockName(1, I);
BlockType := Lib.GetBlockType(1, I);
if BlockType = 'text' then
Lib.FillTextBlock(1, I, Values.Values[BlockName], 1)
else if (BlockType = 'image') and (SignatureID <> 0) then
Lib.FillImageBlock(1, I, SignatureID, 1)
else if BlockType = 'pdf' then
Lib.FillPDFBlock(1, I, SealID, 1, 1);
end;
Result := Lib.SaveToFile(OutFile) = 1;
finally
Lib.Free;
end;
end;
그 루프에는 틀리기 쉬운 디테일이 둘 있습니다. 루프가 최고 인덱스에서 내려오는 이유는 블록 2를 제거하면 옛 블록 3이 블록 2가 되기 때문이고, 비트 1을 켠 채 위로 올라가 세면 블록을 하나 걸러 건너뜁니다. 그리고 FillPDFBlock의 원본 문서는 템플릿을 적재하기 전에 먼저 적재해 ID를 붙잡아 둡니다. 이미지 ID는 선택된 문서에 속하므로, AddImageFromFile은 템플릿이 선택된 동안 실행되어야 하기 때문입니다. 레코드마다 도장이나 레터헤드가 바뀌는 배치 작업이라면, PDFlibPas의 Form XObject 페이지 스티칭과 조립형 워터마크 템플릿이 같은 캡처 후 그리기 메커니즘을 페이지 전체에 씁니다
블록 좌표가 뒤집혀 나오는 이유
코드가 PDFlibPas가 다루는 두 좌표계를 섞으면 블록 사각형이 뒤집혀 나옵니다. 어노테이션 /Rect는 PDF 사용자 공간에 저장되어 페이지 아래에서 잰 값이지만(ISO 32000-1 §12.5.2), TPDFlib 드로잉 API는 위에서 잽니다. AddBlock은 위 기준 입력을 받아 아래 모서리를 PageHeight - Top - Height로 저장하고, GetBlockRect와 세 채우기 함수는 사각형을 읽어 PageHeight - URy로 되돌려 놓습니다
실전에서 이 말은 이렇습니다. 슬롯 지오메트리가 필요할 때, 예를 들어 디버그 프레임을 그리거나 블록 옆에 캡션을 놓을 때는 GetBlockRect(Page, BlockIndex, Left, Top, Width, Height)를 쓰고, 일반 어노테이션 호출로 /Rect를 읽어 DrawText에 곧장 먹이지 마십시오. 792pt Letter 페이지의 /Rect [40 700 240 730] page-piece 블록은 Top = 62를 보고하지 700을 보고하지 않습니다. 이로 막는 버그는 클래식한 것으로, 채워진 값이 전부 아래 여백 근처에 뒤집혀 떨어지는데 아무 에러도 나지 않는 사례입니다
교훈이 되는 함정들
블록 코드에서 가장 교훈적인 버그는 한 글자 실수였습니다. 블록 어노테이션은 서브타입에 접두어 검사로 인식되는데, 'PDFlibBlock:'은 12글자입니다. 초기 버전은 Copy(Subtype, 1, 13)을 그 12글자 리터럴과 비교했으니 절대 같을 수 없었고, GetBlockCount는 모든 템플릿에서 0을 돌려주고 모든 채우기가 "no such block"을 보고했습니다. 아무것도 크래시하지 않았고 로그도 없었습니다. 교훈은 모든 템플릿 엔진에 일반화됩니다. 테스트에서 알려진 템플릿의 블록 수를 단언하십시오. 조용한 제로 매치 열거는 빈 페이지와 똑같이 보이니까요
두 번째 함정은 문서 ID입니다. TPDFlib.LoadFromFile은 방금 적재한 문서의 ID가 아니라 상태 코드, 성공 1, 실패 0을 반환합니다. 그 1을 FillPDFBlock에 SourceDocumentID로 넘기면 실패합니다. 1은 유효한 문서 ID가 아니기 때문입니다. 성공적인 적재 직후 SelectedDocument를 호출해 실제 ID를 얻고, 원본을 나중에 적재했다면 SelectDocument로 템플릿으로 돌아가십시오
더 작은 것 하나: 빈 문자열로 FillTextBlock을 호출하면 아무것도 하지 않고 블록을 유지합니다. 두 번째 주소 줄이 없는 경우 같은 선택 필드가 있는 메일 머지라면 보통 원하는 동작이지만, 채워지지 않은 어노테이션이 출력 파일에 남습니다. 출력이 깨끗해야 한다면 DeleteAnnotation으로 명시적으로 지우거나 비트 1과 함께 공백 한 칸을 채우십시오. 모든 블록 호출은 선택된 페이지를 저장하고 복원하므로, 페이지 3의 채우기가 다음 드로잉 호출의 목적지를 바꾸지 않습니다
블록 어노테이션의 상호 운용성 한계
블록 어노테이션은 사적 관례이지 표준 PDF 기능이 아닙니다. PDFlibBlock:Text와 그 형제들은 ISO 32000-1 §12.5.6.1에 나열된 어노테이션 타입이 아니므로, 뷰어는 미확인 어노테이션으로 취급합니다. Invisible 플래그가 꺼져 있고 appearance 스트림이 없으므로 표준 준수 뷰어는 아무것도 그리지 않지만, 어떤 뷰어는 여전히 코멘트 패널에 나열할 수 있고, 다른 PDF 도구는 그 의미를 이해하지도 보존하지도 않습니다
- 시스템을 떠나는 것은 무엇이든 비트 1을 켠 채 채우십시오. 최종 파일에 평범한 페이지 콘텐츠만 남습니다
- PDF/A는 PDF 사양 밖의 어노테이션 타입을 허용하지 않으므로(ISO 19005-1 §6.5.2), 보관용 출력에는 채워지지 않은 블록 어노테이션이 남으면 안 됩니다
- page-piece 블록은 템플릿에 두는 편이 안전합니다.
/PieceInfo는 표준이고 소비자는 자기 소유가 아닌 데이터를 무시하기 때문입니다 - 블록은 폼 필드가 아닙니다. 인터랙티브성이 없고, 파일에 필드 값이 없으며, FDF나 XFDF로 내보내지도 않습니다. 편집 가능해야 하는 데이터는 PDFlibPas의 FDF, XFDF, XFA 폼 데이터 교환을 보십시오
인쇄 워크플로에서 템플릿이 가변 필드를 표준화되고 검증기가 검사 가능한 방식으로 선언해야 한다면 블록은 잘못된 도구입니다. 그럴 때 쓰라고 PDF/VCR-1이 있는 것이고, PDFlibPas의 PDF/VCR-1 템플릿 플레이스홀더 매니페스트가 그 플레이스홀더들이 어노테이션이 아니라 구조 트리에 사는 방법을 다룹니다
FAQ
PDFlibPas는 다른 도구가 만든 블록 템플릿을 채울 수 있나요?
네. 도구가 블록을 /PieceInfo /PDFlib /Blocks나 /PieceInfo /PDFlib /Private /Blocks 아래에 딕셔너리 또는 배열 형태로, 숫자 4개의 /Rect와 함께 저장한다면 가능합니다. GetBlockCount는 PDFlibBlock:* 어노테이션 뒤에 이들을 세고, 커스텀 키는 GetBlockProperty로 노출됩니다
FillTextBlock은 호출 전에 선택한 폰트를 바꾸나요?
아니요. FillTextBlock은 선택된 폰트, 텍스트 크기, 텍스트 색, 정렬을 저장하고, 블록의 fontname, fontsize, fillcolor, alignment 속성을 적용한 뒤, 맞추기가 실패했더라도 원래 상태를 복원합니다
벡터 로고나 바코드 페이지를 블록에 넣으려면 어떻게 하나요?
원본 PDF를 적재하고 SelectedDocument를 그 ID로 저장한 뒤, 필요하면 템플릿 문서를 다시 선택하고 FillPDFBlock(Page, BlockIndex, SourceID, SourcePage, 1)을 호출합니다. 페이지는 Form XObject로 임포트되므로 벡터로 유지되며, 비트 2를 더하지 않는 한 가로세로 비를 보존한 채 블록에 맞춰집니다
블록 템플릿, Form XObject 페이지 임포트, 가변 데이터 도구함의 나머지는 Delphi용 losLab PDF Library인 PDFlibPas에 들어 있으며, Delphi와 C++Builder 프로젝트를 위한 PDF 생성, 편집, 인쇄 생산을 담당합니다