실물 상품을 배송한다면 언젠가는 실제 창고 조명, 실제 거리, 실제 핸드헬드 스캐너에서 읽혀야 하는 GS1-128 바코드를 인쇄하게 됩니다. 사소해 보이지만, PDF 생성에서는 가장 시끄럽게 실패하는 지점 중 하나입니다.
이 글은 GS1-128에서 “0.1 mm 정밀도“가 실제로 무엇을 뜻하는지, HTML/CSS 기반 PDF 생성 방식이 왜 그 정밀도를 안정적으로 내기 어려운지, DHL, FedEx, USPS, Amazon 입고 스캐너까지 첫 출력부터 제대로 읽히게 만드는 설계 규칙을 다룹니다.
바코드 정밀도의 실제 의미
GS1-128(이전 UCC/EAN-128)은 정교한 비율의 막대 폭과 공백 폭으로 데이터를 인코딩합니다. 가장 작은 단위는 X-dimension입니다. 가장 좁은 막대/공백의 폭이며, 나머지는 모두 X의 배수입니다(Code 128 내부 패턴에서는 1X, 2X, 3X, 4X).
스캐너는 상대 폭을 측정해 디코딩합니다. 인쇄된 폭이 흔들리면 디코딩이 실패합니다.
운영 환경에서 가장 흔한 실패 모드는 두 가지입니다.
- 심볼 내부의 X-dimension 불일치 — PDF 생성기가 인접한 막대를 서로 다른 방식으로 서브픽셀 반올림합니다. 처음 세 막대는 8 px인데 중간에 7 px 막대가 끼어듭니다. 스캐너는 일관되지 않은 패턴으로 봅니다.
- 잘못된 전체 길이 / 스케일링 — 심볼을 그린 뒤 다시 스케일해 X-dimension이 GS1 최소값 아래로 내려갑니다(1.0× 배율에서는 보통 0.495 mm).
둘 다 증상은 비슷합니다. 단일 샘플에서는 삑 하고 읽히지만, 생산 배치에서는 30장 중 1장꼴로 거부됩니다. 개발용 스캐너가 창고 스캐너보다 훨씬 관대하기 때문에 QA에서 놓치기 쉽습니다.
0.1 mm 규칙
여기서 중요한 정밀도는 바코드 전체 길이가 사양상 목표값에서 0.1 mm 이내여야 한다는 뜻입니다. “각 막대 폭이 0.1 mm“라는 뜻이 아닙니다. 막대는 보통 0.495 mm 이상입니다. 0.1 mm 한계는 전체 심볼의 누적 치수 정확도에 걸려 있습니다.
18자리 숫자를 담은 일반적인 GS1-128을 예로 들면:
- 심볼에는 약 120개의 막대와 공백이 들어갑니다
- 1.0× 배율에서 전체 길이는 약 58 mm입니다
- 전체 길이 0.1 mm 허용오차는 전체 기준 약 0.17% 정확도입니다
- 막대 하나당 일관성 예산으로 환산하면 약 0.001 mm입니다. 개별 막대 폭보다 훨씬 작습니다
그래서 “7.4 px이어야 할 막대를 PDF 생성기가 7 px로 찍었다“는 문제가 치명적입니다. 서브픽셀 반올림 오차는 120개 막대에 걸쳐 누적되고, 50번째와 80번째 막대 사이 어딘가에서 0.1 mm 허용오차를 넘게 됩니다.
HTML/CSS가 여기서 어려운 이유
많은 팀이 빠지는 경로는 이렇습니다. GS1-128 데이터를 문자열로 인코딩하고, SVG(더 나쁘게는 개별 <div> 막대)를 만들고, HTML에 넣은 뒤 Puppeteer나 Prince로 PDF를 출력합니다.
그 연결고리 하나하나가 치수 오차를 만듭니다.
1. 브라우저 래스터화는 반올림합니다
HTML 안의 SVG도 shape-rendering="crispEdges"가 설정되어 있고, 주변 컨테이너가 정수 픽셀 경계에 정확히 맞으며, PDF DPI가 막대 폭과 깔끔하게 배수 관계를 이룰 때가 아니라면 브라우저의 그리기 단계에서 서브픽셀 반올림을 거칩니다. 세 조건 모두 실수로 깨기 쉽습니다.
2. CSS 배치가 조용히 흔듭니다
6개월 전 다른 배치 문제를 고치려고 스타일시트에 넣은 transform: scale(0.95) 하나가 페이지의 모든 바코드를 조용히 왜곡할 수 있습니다. 경고는 없습니다. PDF는 괜찮아 보입니다. 스캐너만 다르게 반응합니다.
3. PDF 출력 단계 자체도 좌표를 양자화합니다
브라우저가 PDF로 직렬화할 때 그려진 결과를 PDF 그리기 연산자로 바꿉니다. 일부 출력기(특히 Chromium 계열)는 고정된 PDF 내부 격자에 좌표를 맞춥니다. 그 격자에 정확히 맞지 않는 좌표에 SVG 바코드가 놓이면, 결과는 거의 맞지만 오차가 누적되는 출력물이 됩니다.
4. 폰트 기반 인코딩은 더 위험합니다
일부 팀은 Code 128 폰트를 씁니다({ font: "Code128" }를 지정하고 데이터를 입력한 뒤 잘 되길 기대하는 방식). 폰트는 벡터이고 이론적으로는 스케일할 수 있지만, 작은 크기에서 폰트 힌팅은 사람 눈에 더 보기 좋게 폭을 조정하도록 설계되어 있습니다. 스캐너에는 정확히 반대 방향의 최적화입니다.
구조화된 생성 방식
gPdf는 데이터를 받아 GS1-128 사양에 따라 막대/공백 패턴을 계산하고, PDF 벡터 기본 도형을 직접 출력합니다. HTML도, SVG 변환도, 폰트 힌팅도 없습니다.
{
"pages": [{
"size": "label_100_150",
"elements": [
{
"type": "barcode",
"format": "gs1128",
"content": "(00)123456789012345678",
"x": 4,
"y": 8,
"width": 58.0,
"height": 18.0,
"barcode_text": { "enabled": true, "position": "bottom" }
}
]
}]
}
barcode 요소에서 width는 mm 단위의 심볼 전체 길이입니다. 인쇄된 라벨에서 캘리퍼스로 실제 측정하는 값입니다. 이것이 설정해야 할 올바른 제어값이고, width: 58.0이 보장하는 것도 바로 이것입니다.
- PDF 생성기는 목표 길이를 심볼의 막대 수로 나눠 X-dimension을 계산합니다. 막대 수는 데이터가 완전히 결정합니다.
- 모든 막대를 정확히 같은 X-dimension으로 그립니다.
- 그 폭을 부동소수점 좌표로 PDF에 씁니다(PDF 단위는 1/72 inch이고, 스캔 해상도에서 필요한 것보다 더 촘촘합니다).
- 폰트 힌팅도, CSS 픽셀 반올림도, 배치 단계의 왜곡도 없습니다.
결과적으로 프린터가 자체 스케일링을 추가하지 않는 한(대부분 기본값에서는 추가하지 않습니다), 전체 길이는 요청한 목표값에서 0.1 mm 이내로 유지됩니다.
실제로 무엇을 인쇄해야 하나
운영 중 스캐너 실패의 95%를 막는 규칙은 세 가지입니다.
규칙 1: X-dimension이 아니라 전체 길이를 지정합니다
width 필드는 측정 가능하기 때문에 올바른 제어값입니다. 출력된 라벨을 캘리퍼스로 재서 바로 검증할 수 있습니다. 반대로 X-dimension을 직접 지정하면 인코딩된 데이터 길이에 따라 심볼 전체 길이가 달라지고, SKU가 달라질 때마다 바코드 폭도 달라져 라벨 QA가 어려워집니다.
대부분의 라벨 크기에서는:
- 4×6 inch 배송 라벨: 폭 100 mm, GS1-128은 보통 약 58–72 mm
- 4×4 inch 컴플라이언스 라벨: 약 45–58 mm
- 2×1 inch 카톤 라벨(Amazon UPC): GS1-128 영역이 아니므로 UPC-A를 사용
규칙 2: quiet zone은 항상 필요합니다
GS1-128은 양쪽에 ≥ 10X quiet zone이 필요합니다. 스캐너가 심볼의 경계를 찾는 데 쓰는 빈 흰 여백입니다. 1.0× 배율(X = 0.495 mm)에서는 양쪽에 최소 4.95 mm의 깨끗한 공간이 필요합니다.
전형적인 실패는 개발자가 다른 라벨 요소 옆에 맞추려고 바코드를 x: 0에 붙이는 경우입니다. 그러면 스캐너가 시작점을 찾지 못합니다. PDF 생성기는 quiet zone을 자동으로 확보해야 합니다(gPdf는 그렇게 합니다). 사용하는 도구도 반드시 확인하세요.
규칙 3: 개발용 스캐너가 아니라 실제 대상 스캐너에서 테스트합니다
휴대폰 카메라 스캐너는 Honeywell이나 Zebra 산업용 스캐너보다 관대합니다. 표준 QA 경로는 다음과 같습니다.
- 실제 생산 프린터에서 생산 속도로 라벨 50장을 출력합니다.
- 실제 컨베이어 속도에서 실제 스캐너 모델로 통과시킵니다.
- 판독률이 99% 미만이면 무언가가 틀어진 것입니다. 보통 X-dimension 일관성이 원인입니다.
“MacBook 카메라에서는 잘 읽혔다“는 근거로 물류 파트너에게 출하하지 마세요.
다중 포맷이 현실입니다
라벨에는 GS1-128만 필요한 경우가 드뭅니다. 흔한 조합은 다음과 같습니다.
| Symbol | Use | Spec source |
|---|---|---|
| GS1-128 | 물류 단위, GTIN + serial + lot | GS1 General Specifications |
| QR with FNC1 | 모바일 스캔 가능한 전자상거래 | ISO/IEC 18004 |
| Data Matrix | 제약(DSCSA / EU FMD) | ISO/IEC 16022 |
| PDF417 | 운전면허증, 탑승권 | ISO/IEC 15438 |
| Aztec | 교통 티켓 | ISO/IEC 24778 |
| MaxiCode | UPS 전용 | ISO/IEC 16023 |
GS1-128만 처리하는 PDF 생성기는 결국 두 번째 도구를 붙이게 만듭니다. 배송과 물류 처리 흐름은 거의 항상 두 가지 이상의 포맷을 필요로 하기 때문에, gPdf는 여섯 가지 포맷을 하나의 생성 엔진에 함께 묶었습니다.
운영 중 스캐너 거부가 발생하면
이미 운영 환경에서 스캐너 거부 문제가 있다면 진단 순서는 다음과 같습니다.
- 실패한 라벨을 샘플링합니다 — 집계 지표만 믿지 마세요. 실제로 실패한 물리 라벨을 확보합니다.
- 캘리퍼스로 측정합니다 — 전체 길이와 X-dimension을 봅니다. 사양 허용오차 안에 있지 않다면 그게 버그입니다.
- 아래의 사람이 읽는 텍스트를 확인합니다 — 막대 판독에 실패한 스캐너는 OCR 대체 판독을 시도하는 경우가 있습니다. 둘 다 실패하면 심볼 자체가 잘못 만들어진 것입니다.
- quiet zone을 검증합니다 — 양쪽 흰 여백을 측정합니다. 로고, 구분선, 다른 바코드가 너무 가까이 인쇄됐다면 그게 문제입니다.
- 다른 스캐너 모델로 시도합니다 — 일부 스캐너에는 펌웨어 특성이 있습니다. 모델 A는 읽고 모델 B는 못 읽는다면 문제는 생성기 자체가 아니라 상호운용성일 수 있습니다.
- 검증된 기준 라벨과 비교합니다 — 벤더는 정확한 사양의 기준 이미지를 공개합니다. 육안으로도 다르면 그 차이에서 거꾸로 추적합니다.
TL;DR
GS1-128 정밀도는 얼마나 얇은 막대를 인쇄할 수 있느냐가 아니라, X-dimension이 전체 심볼에 걸쳐 밀리미터의 일부 오차 안에서 일관되게 유지되느냐의 문제입니다. HTML/CSS 기반 PDF 생성 방식은 여러 단계에서 서브픽셀 드리프트를 만들고, PDF 벡터 기본 도형을 직접 출력하는 구조화된 생성 방식은 그 드리프트 원인을 통째로 건너뜁니다.
현재 PDF 스택에서 1–5% 스캐너 거부율이 보인다면 그 지점이 유력한 단서입니다. Playground에서 GS1-128 샘플의 width 필드를 라벨 사양에 정확히 맞춰 생성할 수 있습니다. 출력물을 캘리퍼스로 측정해 비교해 보세요.