기술 문서

Delphi PDF 바코드: QR, PDF417, DataMatrix

배송 라벨이나 인보이스에 인쇄된 바코드의 임무는 하나, 스캐너가 첫 번째 스캔에서 읽어내는 것입니다. 그 한 번을 통과할지 여부는 소포가 하역장에 도착하기 훨씬 전에 결정됩니다. 심볼이 페이지에 어떤 방식으로 올라갔는지가 그것을 결정합니다. Delphi 리포팅 파이프라인에서 가장 흔한 실수는 바코드를 다른 곳에서 비트맵으로 렌더링한 다음 그 이미지를 PDF에 배치하는 것입니다. 화면의 특정 확대 배율에서는 멀쩡해 보이지만 그 밖의 모든 곳에서 품질이 떨어집니다

다른 방법은 심볼을 벡터 콘텐츠로, 페이지에 직접 그리는 것입니다. PDF Library for Delphi는 바로 이를 위한 드로잉 호출 계열을 제공하며, 2D 매트릭스 심볼인 QR, PDF417, DataMatrix와 Code128 및 GS1-128을 아우르는 선형 계열, 그리고 우편 자동화용 USPS Intelligent Mail을 지원합니다. 벡터를 택하는 근거는 미적인 것이 아닙니다. 바가 스캐너가 기대하는 위치에 정확히 놓이느냐의 문제입니다

왜 벡터가 배치된 비트맵보다 나은가

바코드는 바와 공백의 패턴이며, 2차원에서는 어두운 모듈과 밝은 모듈의 격자입니다. 디코더는 그 너비들의 비율을 측정하는 방식으로 동작합니다. 이 비율을 왜곡하는 모든 것은 심볼의 오류 예산을 갉아먹는 잡음입니다. 래스터화된 바코드 이미지는 고정된 픽셀을 담고 있습니다. 도트가 이미지 격자로 정확히 나누어떨어지지 않는 프린터로 PDF를 렌더링하면 래스터라이저는 리샘플링을 해야 하고, 날카로워야 할 모듈 경계가 장치 픽셀 두 개에 걸쳐 퍼집니다. 좁은 바가 굵어지고 인접한 공백이 얇아지면서, 디코더가 의존하는 너비 비율이 어긋나기 시작합니다

벡터 콘텐츠로 그리면 같은 심볼이 PDF 사용자 공간 좌표로 기술된 채워진 사각형들의 집합이 됩니다. 맞서 싸워야 할 고정 픽셀 격자가 없습니다. 인쇄 시점에 장치는 실제로 보유한 해상도로 각 사각형을 렌더링하므로, 어떤 배율과 어떤 인쇄 크기에서도 모든 모듈 경계가 하드웨어가 허용하는 만큼 선명합니다. 팔레트 라벨용으로 벡터 심볼을 키우거나 소포용으로 줄여도 기하 구조는 정확하게 유지됩니다. 이 정밀도가 첫 스캔 판독률을 높게 유지하며, 그것이야말로 페이지에 바코드를 넣는 목적 전부입니다

프린터 리샘플링으로 흐려진 비트맵 바코드와, 어떤 인쇄 크기에서도 모듈 경계를 선명하게 유지하는 벡터 사각형을 비교한 PDF Library for Delphi 다이어그램
리샘플링된 비트맵 바는 인코딩된 너비 비율에서 벗어납니다

QR 코드와 네 가지 정정 수준

QR은 두 축을 동시에 읽는 2D 매트릭스 심볼이며, 그래서 작은 정사각형 안에 많은 데이터를 담습니다. 손상 내성은 Reed-Solomon 오류 정정에서 나오며, 네 가지 수준으로 제공됩니다. 수준 L은 코드워드의 약 7퍼센트를, M은 약 15퍼센트, Q는 약 25퍼센트, H는 약 30퍼센트를 복구합니다. 높은 정정 수준이 공짜는 아닙니다. 복구용 코드워드가 모듈 용량을 차지하므로, 데이터 양이 고정되어 있다면 수준이 높을수록 더 조밀하거나 물리적으로 더 큰 심볼이 필요해집니다

이 맞바꿈은 심볼이 놓일 환경에 대한 질문입니다. 화면으로만 스캔될 깨끗한 디지털 문서라면 L에 두고 작게 유지해도 됩니다. 인쇄되고, 다뤄지고, 긁히고, 어쩌면 테이프로 일부가 가려질 라벨이라면 Q나 H가 적절합니다. 더 이상 온전하지 않은 심볼에서 디코더가 페이로드를 재구성할 수 있게 해 주는 것이 바로 그 여분의 중복이기 때문입니다. DrawQRCode는 위치와 함께 그려질 너비 및 높이를 정하는 SymbolSize, 그리고 데이터 모드를 선택하는 EncodeOptions 값(0은 자동, 그 밖에 숫자, 영숫자, ISO-8859-1, UTF-8 변형)과 방향을 지정하는 DrawOptions 값을 받습니다

모듈 비용이 늘어나는 대신 코드워드의 약 7, 15, 25, 30퍼센트를 복구하는 QR 오류 정정 수준 L, M, Q, H를 보여주는 PDF Library for Delphi 다이어그램
Reed-Solomon 수준이 높을수록 더 많은 손상 코드워드를 복구하지만 모듈 용량을 소모합니다
var
  Pdf: TPDFlib;
begin
  Pdf := TPDFlib.Create(nil);
  try
    Pdf.NewDocument;
    Pdf.SetPageSize('A4');
    Pdf.SetMeasurementUnits(1);   // 1 = 밀리미터
    Pdf.NewPage;

    // 30 mm 정사각형 QR, 자동 인코딩, 일반 방향
    Pdf.DrawQRCode(20, 20, 30, 'https://www.loslab.com/', 0, 0);

    Pdf.SaveToFile('Label_QR.pdf');
  finally
    Pdf.Free;
  end;
end;

정정 수준 자체는 요청한 심볼 안에 데이터가 들어가도록 인코더가 선택합니다. 가혹한 환경을 위해 높은 수준을 보장받아야 한다면, 인코더가 크기를 맞추려고 수준을 낮추는 대신 중복에 쓸 모듈 예산을 갖도록 심볼을 넉넉하게 잡으십시오

신분증과 배송 라벨을 위한 PDF417

PDF417은 적층형 선형 심볼입니다. 각 행이 짧은 선형 바코드이고 그 행들이 쌓여 블록을 이루는데, 그래서 넓은 데이터 띠를 직사각형 공간에 넣어야 하는 운전면허증, 탑승권, 운송사 배송 라벨에 등장합니다. 오류 정정은 0에서 8까지의 척도로 동작합니다. 한 단계마다 정정 코드워드 수가 대략 두 배가 되므로, 수준 5는 수준 1보다 훨씬 많은 중복을 담되 페이지에 더 많은 코드워드를 씁니다

PDF417 블록의 형태는 조절할 수 있고, 라벨에는 채워야 할 고정 면적이 있으므로 이 점이 중요합니다. DrawPDF417SymbolEx는 기본 호출이 제공하지 않는 제어를 노출합니다. FixedColumnsFixedRows는 데이터 열 수와 행 수를 고정하며, 0은 인코더에 맡긴다는 뜻입니다. ErrorLevel은 자동을 뜻하는 -1 또는 0에서 8까지의 명시값을 받습니다. ModuleSize는 현재 측정 단위로 표현한 가장 좁은 요소의 너비이고, HeightWidthRatio는 각 모듈의 높이를 너비에 대한 비율로 정합니다. 이것이 가진 공간에 맞춰 블록을 낮고 넓게 또는 높고 좁게 만드는 방법입니다

// 데이터 열 10개 고정, 행 자동, 오류 수준 5,
// 모듈 너비 0.30 mm, 행 높이는 모듈 너비의 세 배
Pdf.DrawPDF417SymbolEx(20, 60, 'PDF417 PAYLOAD 0123456789',
  0,        // Options: 0 = 일반 방향
  10,       // FixedColumns
  0,        // FixedRows: 0 = 자동
  5,        // ErrorLevel: 0에서 8까지
  0.30,     // ModuleSize, 현재 측정 단위 기준
  3.0);     // HeightWidthRatio

라벨 템플릿에서 흔히 쓰는 조절 수단은 열을 고정하는 것입니다. 열 수가 일정하면 블록의 너비를 예측할 수 있어, 문서마다 인코딩되는 페이로드의 길이가 달라져도 주변 레이아웃이 밀리지 않으며, 인코더는 그 차이를 아래쪽으로 행을 추가해 흡수합니다

작은 마킹을 위한 DataMatrix

DataMatrix는 마킹이 작아야 할 때 꺼내 드는 심볼입니다. 현대적인 Reed-Solomon 방식인 ECC 200을 사용하는 조밀한 2D 격자이며, 같은 데이터를 담은 QR 심볼이라면 부담스러울 크기에서도 판독 가능한 상태를 유지합니다. 그래서 부품 직접 마킹, 소형 전자 부품, 조밀한 물류 라벨의 표준 선택지가 되었습니다

DrawDataMatrixSymbol은 도트 피치를 정하는 ModuleSize, ASCII를 뜻하는 1을 넣는 Encoding, 그리고 자동을 뜻하는 0이거나 10x10부터 132x132까지의 표준 정사각형 및 직사각형 치수 중 하나인 SymbolSize를 받습니다. Options 매개변수는 방향과 여백 영역 너비를 결합하며, 100에서 400을 더하면 1에서 4모듈 폭의 흰 테두리가 지정됩니다. 여백 영역은 장식이 아닙니다. 디코더는 심볼의 파인더 패턴을 찾기 위해 그 여백이 필요하고, 다른 잉크에 바짝 붙은 심볼은 포착에 실패하는 심볼입니다

// 크기 자동, ASCII DataMatrix, 0.5 mm 모듈, 일반 방향,
// 1모듈 여백 영역 포함(Options 0 + 100)
Pdf.DrawDataMatrixSymbol(20, 110, 0.5, 'DMX-SN-4408812',
  1,        // Encoding: 1 = ASCII
  0,        // SymbolSize: 0 = 자동
  100);     // Options: 일반 + 1모듈 여백 영역

1D 바코드가 여전히 지배하는 영역

2차원 심볼이 주목을 받지만, 선형 바코드는 여전히 소매와 물류의 넓은 영역을 차지하고 있으며, 그 이유는 한 번의 스윕으로 읽는 레이저 스캐너의 설치 기반입니다. Code128은 영숫자 데이터의 주력이고, 그 효율은 세 가지 문자 집합에서 나옵니다. 집합 A는 제어 문자와 대문자를, 집합 B는 인쇄 가능한 ASCII 전체 범위를 담당하며, 숫자에 중요한 것은 집합 C입니다. 부분집합 C는 두 자리 숫자를 하나의 심볼 문자로 인코딩하므로, 연속된 숫자 데이터는 집합 A나 B에서 필요한 심볼 문자의 절반만 씁니다. 긴 숫자 바코드를 배치하는 가장 조밀한 방법이며, PDF Library for Delphi의 Code128 구현은 이를 위해 B 집합과 C 집합을 자동으로 조합합니다

예전에 EAN-128으로 불리던 표준인 GS1-128은 Code128 위에 Application Identifier를 실어 확장합니다. 이 대괄호 접두사는 뒤따르는 숫자가 일련번호인지, 배치 코드인지, 유효기한인지를 수신 시스템에 알려줍니다. 이 구조는 심볼이 GS1으로 인코딩되었음을 표시하고 가변 길이 필드를 구분하는 특수 비데이터 문자 FNC1으로 표기됩니다. PDF Library for Delphi에서는 Code128 유형과 함께 DrawBarcode를 사용하고, 각 application identifier가 시작되는 자리에 리터럴 [FNC1] 마커를 데이터 문자열에 넣어 GS1-128 심볼을 그립니다

GS1-128 데이터 문자열을 FNC1 마커, 일련번호용 application identifier 21, 변형용 application identifier 20으로 분해해 보여주는 PDF Library for Delphi 다이어그램
FNC1 마커는 심볼을 GS1으로 표시하고 각 가변 길이 필드를 닫습니다
var
  W: Double;
begin
  // Code128, FNC1 마커가 붙으면 GS1-128 심볼이 됨.
  // AI 21(일련번호) = ABC123, AI 20(변형) = 13
  Pdf.DrawBarcode(20, 150, 60, 18, '[FNC1]21ABC123[FNC1]2013',
    3,        // Barcode: 3 = Code128
    0);       // Options: 0 = 기본 드로잉

  // 레이아웃 전에 0.30 mm 좁은 바 기준으로 렌더링 너비를 측정함
  W := Pdf.GetBarcodeWidth(0.30, '[FNC1]21ABC123[FNC1]2013', 3);
end;

우편의 경우 OneCode라고도 불리는 USPS Intelligent Mail은 우편 자동화를 위해 라우팅과 추적 데이터를 하나의 높이 변조 바코드에 인코딩합니다. DrawIntelligentMailBarcode는 바 너비, 전체 바 높이, 트래커 높이, 공백 너비의 기하 정보를 명시적으로 받으며, 데이터는 숫자만으로 된 20, 25, 29 또는 31자리 문자열로 전달합니다. 바 높이와 트래커 높이를 명시적으로 두는 이유는, 각 바가 전체 바인지 어센더인지 디센더인지에 심볼이 정보를 싣고 있고 우편 판독기가 그 높이들이 규격대로 유지되는 데 의존하기 때문입니다

페이지에 그리기와 레이아웃을 위한 측정

여기서 보인 모든 호출은 현재 선택된 페이지의 콘텐츠에, 텍스트와 이미지를 받는 것과 같은 표면에 그립니다. 따라서 바코드는 별도 자산으로 가져오는 것이 아니라 일반적인 문서 생성의 일부로 만들어집니다. 심볼이 벡터 콘텐츠이므로 그것이 인코딩하는 데이터와 차지하는 기하 구조가 모두 그리는 시점에 알려져 있고, 덕분에 결정론적으로 배치할 수 있습니다

선형 계열의 레이아웃은 먼저 측정해 두면 도움이 됩니다. GetBarcodeWidth는 주어진 좁은 바 너비와 바코드 유형에 대해 바코드가 그려질 전체 너비를 반환하므로, 짐작으로 그린 뒤 페이지를 다 만들고 나서 겹침을 발견하는 대신 그리기 전에 정확한 가로 공간을 확보할 수 있습니다. 2D 심볼은 SymbolSizeModuleSize로 그려질 크기를 직접 지정하고 심볼이 그 공간을 채우므로 배치가 더 간단합니다. 어느 쪽이든 원칙은 같습니다. 스캔 환경에 따라 물리적 크기를 정하고, 심볼이 확보한 자리에 들어가는지 확인한 다음, 화면 미리보기부터 최종 인쇄까지 모든 경계를 벡터 기하가 선명하게 유지하도록 맡기는 것입니다

이 바코드들이 놓이는 더 넓은 페이지 구성 워크플로에 관해서는, 텍스트, 이미지, 폰트 추출을 다룬 글이 PDF에서 콘텐츠를 다시 읽어내는 기법을 설명하고, 직접 접근 방식의 대용량 PDF 병합과 분할 가이드가 대량 문서를 효율적으로 조립하는 방법을 보여줍니다. 둘 다 여기서 설명한 드로잉 API와 자연스럽게 짝을 이루며, 이 API는 이 블로그의 다른 글에서 다루는 텍스트, 그래픽, 폼, 서명 API와 함께 Delphi 및 C++Builder용 Delphi PDF Library의 일부로 제공됩니다