
Unicode TR39에서 누락된 793개의 혼동 쌍, 세계 최초의 교차 스크립트 데이터셋, 230개 폰트와 22,000개 이상의 문자에 걸친 폰트 인식 SSIM 점수
벡터 아웃라인 레이캐스팅을 사용한 경험적 글리프 유사도 점수 산정. 245개 시스템 폰트에서 유니코드 혼동 가능 문자 쌍을 렌더링하고, 폰트 아웃라인에서 직접 구조적 유사성을 측정(래스터화 없음)하며, 폰트별 연속 거리 점수가 포함된 점수 JSON 아티팩트를 생성합니다.
22,581개 문자와 12개 문자 체계에 걸쳐 5,260만 개의 단일 문자 비교 및 1억 9,000만 개의 다중 문자 비교에서 얻은 주요 결과:
출력은 namespace-guard에 직접 입력되어 패키지 이름, 도메인 이름 및 식별자에서 런타임 혼동 가능 탐지를 수행합니다.
RaySpace는 36개 각도에서 폰트 아웃라인을 통해 평행 레이를 투사하고, 글리프당 5개 계층의 정보(교차 횟수, 교차 위치, 교차 각도, 핑 거리(각 교차점에서 획 너비), 핑 최대(교차 사이 카운터 너비))를 캡처합니다. 이는 문자당, 폰트당 컴팩트 시그니처를 생성합니다. 두 시그니처는 5개 계층 모두에 걸쳐 가중 L1 거리로 비교됩니다.
3단계 필터 캐스케이드로 완전 탐색을 실현 가능하게 만듦:
시그니처 뱅크(245개 폰트에서 294,646개 이상 항목; --include-uppercase 사용 시 더 많음)는 한 번 사전 계산됩니다. 그런 다음 발견은 뱅크에 대한 단일 스레드 산술로 실행되어, 작업자 스레드나 GPU 없이 31분 만에 52.6백만 쌍 비교를 완료합니다.
npm install
# 1. 레이 시그니처 뱅크 구축 (사전 조건, ~24분)
npx tsx scripts/build-signature-bank.ts
# 1b. 대문자 라틴 A-Z 포함 (선택 사항)
# 기본적으로 뱅크는 IDNA PVALID 코드포인트(소문자, 숫자, 기호)만 포함합니다.
# --include-uppercase를 사용하여 대문자 A-Z를 추가하십시오. 이는 폰트 식별 및
# 대문자 글리프 모양이 중요한 상표 시각 비교에 유용합니다. 빌더는 재개 가능하므로
# 추가 코드포인트만 계산합니다.
npx tsx scripts/build-signature-bank.ts --include-uppercase
# 1c. 임의 코드포인트 범위 포함 (선택 사항)
npx tsx scripts/build-signature-bank.ts --extra-range=0041-005A
# 2. 단일 문자 발견 (22,581 문자, 12개 스크립트, ~36분)
npx tsx scripts/discover-singlechar-sdf.ts --scorer=ray
# 3. 다중 문자 (바이그램) 발견 (676 바이그램, ~63분)
npx tsx scripts/discover-multichar-sdf.ts --scorer=ray
# 4. 알려진 TR39 다중 문자 혼동 가능 점수 산정 (~5분)
npx tsx scripts/score-multichar-sdf.ts --scorer=ray
원래 SSIM 기반 파이프라인은 230개 폰트에 걸쳐 2,650만 비교를 수행했습니다. 여전히 작동하지만, 모든 발견 및 점수 산정 작업에서 RaySpace로 대체되었습니다.
# TR39 혼동 가능 쌍 점수 산정
npx tsx scripts/build-index.ts # 렌더 인덱스 (~160초, 11,370 PNG)
npx tsx scripts/score-all-pairs.ts # 모든 쌍 점수 산정 (~65초, 235K 비교)
# 새로운 혼동 가능 발견
npx tsx scripts/build-candidates.ts # 후보 세트 (~23K 문자)
npx tsx scripts/build-index.ts --candidates # 후보 렌더링 (~40분, 89K PNG)
npx tsx scripts/score-candidates.ts # 라틴 대상에 대한 점수 산정 (~15분, 2.9M 비교)
# 두 파이프라인에서 높은 점수 발견 추출
npx tsx scripts/extract-discoveries.ts
"제로" = 아웃라인이 비트 단위로 동일한 레이 시그니처(거리 0.000)를 생성하는 폰트. 라틴 w와 키릴 ԝ는 두 글리프를 모두 포함하는 19개 폰트 모두에서 동일합니다.
조지아 문자 Coda(U+10FF)는 라틴 o, 키릴 o, 그리스 오미크론과 함께 모두 거리 0.08 미만의 4방향 혼동 가능 고리를 형성합니다.
oy/키릴 uk 발견은 뛰어난 새로운 발견입니다. 라틴 바이그램 "oy"는 Helvetica에서 거리 0.000, Arial Unicode MS에서 0.0005로 키릴 이중자 문자 uk(ѹ)와 시각적으로 동일합니다.
| 평균 임계값 | 단일 문자 고유 쌍 |
|---|
세 가지 권장 운영 계층:
특정 폰트에 대해 존재하는 혼동 가능 쌍을 쿼리합니다. 새로운 서체를 출시하는 폰트 디자이너, 시스템 폰트 변경을 평가하는 브라우저 공급업체, 또는 IDN 도메인과 같은 보안에 민감한 컨텍스트에서 디스플레이 폰트를 선택하는 사람에게 유용합니다.
npx tsx scripts/query-font.ts --list-fonts # 발견 데이터의 218개 폰트
npx tsx scripts/query-font.ts "Arial" # Arial의 모든 쌍 (SSIM >= 0.7)
npx tsx scripts/query-font.ts "Arial" --threshold 0.8 # 높은 신뢰도만
npx tsx scripts/query-font.ts "Arial" --compare "Georgia" # SSIM 델타로 두 폰트 비교
npx tsx scripts/query-font.ts "Arial" --json # 다운스트림 처리를 위한 JSON
폰트 이름 매칭은 대소문자를 구분하지 않는 부분 문자열이므로 "arial"은 Arial, Arial Black 및 Arial Unicode MS와 일치합니다. 비교 모드는 가장 큰 SSIM 차이순으로 정렬하여, 폰트를 전환할 때 어떤 쌍이 더 좋아지거나 나빠지는지 정확히 표시합니다.
점수 산정 파이프라인의 발견 파일 필요(gitignore 처리됨, 로컬에서 재생성).
| 파일 | 설명 |
|---|---|
data/output/render-index/ | 렌더 PNG + 인덱스 (SSIM 파이프라인) |
data/output/singlechar-sdf-scores.jsonl |
confusable-weights-v2.json 생성. RaySpace 거리가 SSIM을 대체. 표준 임계값에서 4,174쌍.confusable-weights.json을 소비하여 confusableDistance({ weights })를 통해 측정된 시각적 위험 점수를 제공합니다.paultendo.github.io에서 이 프로젝트의 발견 사항과 방법론을 다루는 글:
RaySpace 방법론 및 발견:
SSIM 파이프라인 발견:
이 프로젝트의 동기가 된 더 넓은 문제 공간을 다루는 글:
| 소스 | 대상 | 스크립트 | 평균 | 폰트 | 제로 |
|---|
| w U+0077 | ԝ U+051D | 라틴-키릴 | 0.000 | 19 | 19 |
| j U+006A | ϳ U+03F3 | 라틴-그리스 | 0.013 | 21 | 18 |
| i U+0069 | і U+0456 | 라틴-키릴 | 0.018 | 62 | 50 |
| s U+0073 | ѕ U+0455 | 라틴-키릴 | 0.018 | 62 | 46 |
| c U+0063 | с U+0441 | 라틴-키릴 | 0.019 | 61 | 45 |
| o U+006F | о U+043E | 라틴-키릴 | 0.020 | 61 | 44 |
| j U+006A | ј U+0458 | 라틴-키릴 | 0.021 | 60 | 48 |
| x U+0078 | х U+0445 | 라틴-키릴 | 0.023 | 59 | 50 |
| p U+0070 | р U+0440 | 라틴-키릴 | 0.024 | 61 | 46 |
| e U+0065 | е U+0435 | 라틴-키릴 | 0.032 | 61 | 44 |
| a U+0061 | а U+0430 | 라틴-키릴 | 0.042 | 61 | 45 |
| 소스 | 대상 | 스크립트 | 평균 | 폰트 |
|---|
| ο U+03BF | ჿ U+10FF | 그리스-조지아 | 0.057 | 2 |
| ヘ U+30D8 | へ U+3078 | 가타카나-히라가나 | 0.122 | 11 |
| 丶 U+4E36 | ヽ U+30FD | 한자-가타카나 | 0.125 | 9 |
| 二 U+4E8C | ニ U+30CB | 한자-가타카나 | 0.249 | 11 |
| 口 U+53E3 | ロ U+30ED | 한자-가타카나 | 0.268 | 11 |
| 바이그램 | 대상 | 평균 | 폰트 | 비고 |
|---|
| ll | ॥ U+0965 (데바나가리 이중 단다) | 0.176 | 8 | 교차 스크립트 |
| oy | ѹ U+0479 (키릴 uk) | 0.322 | 16 | 새로운 교차 스크립트 바이그램 혼동 가능 |
| rn | m U+006D | 0.531 | 95 | 33개 폰트가 0.40 미만 |
| bl | ы U+044B (키릴 yeru) | 0.797 | 49 | 교차 스크립트 |
| 다중 문자 고유 쌍 |
|---|
| < 0.50 | 138 | 13 |
| < 1.00 | 4,174 | 1,631 |
| < 1.50 | 59,700 | (잡음) |
| < 2.00 | 249,976 | 2,524,275 |
| 파일 | 설명 |
|---|
data/output/confusable-discoveries.json | 높은 SSIM (>= 0.7) 또는 픽셀 동일한 110개 TR39 쌍 |
data/output/candidate-discoveries.json | TR39에 없는 793개의 새로운 쌍, 평균 SSIM >= 0.7 |
data/output/confusable-weights.json | namespace-guard 통합을 위한 1,397개의 가중치 간선 |
data/output/cross-script-discoveries.json | 563개의 교차 스크립트 혼동 가능 쌍 |
data/output/cross-script-summary.json | 스크립트 쌍별 교차 스크립트 요약 |
data/output/multichar-discoveries.json | 다중 문자 혼동 가능 발견 |
| 단일 문자 RaySpace 점수 |
data/output/multichar-rayspace-scores.jsonl | 다중 문자 RaySpace 점수 |
data/output/signature-bank/ | 레이 시그니처 뱅크 (294,646개 항목, 7.9GB 압축) |