
URL - https://image.polarisofficetools.com/font-finder/
이미지에 쓰인 폰트가 무엇인지 알아내는 일은 생각보다 어렵다. OCR은 글자가 무엇인지는 알려주지만, 그 글자가 어떤 서체로 그려졌는지는 알려주지 않는다. 서버 기반 이미지 검색이나 상용 API를 사용할 수도 있지만, 비용과 개인정보 보호라는 현실적인 제약이 남는다.
POLARIS OFFICE TOOLS+의 이미지 폰트 찾기는 이 문제를 브라우저 안에서 해결한다. PaddleOCR.js가 텍스트와 위치를 찾고, OpenCV.js가 비교 가능한 글립 마스크를 만든다. 이후 Canvas로 후보 폰트를 여러 조건에서 렌더링하고 Pixelmatch를 포함한 복합 점수로 상위 5개를 선정한다. 원본 이미지는 서버로 전송하지 않는다.
이 글의 점수는 특정 폰트임을 보증하는 식별 신뢰도가 아니다. 현재 브라우저에서 비교할 수 있었던 후보군 안의 상대적 픽셀 유사도다.

결과 카드에는 순위, 폰트 이름, 자체 호스팅 또는 시스템 폰트 여부, 추정 굵기, 상대 유사도와 세부 지표가 표시된다. 자체 호스팅 및 오픈소스 폰트는 다운로드 페이지로, 운영체제에 포함된 상용 폰트는 Microsoft 또는 Apple의 공식 정보 페이지로 연결한다.
폰트 비교 기능을 만들 때 단순히 이미지 전체와 Canvas 결과를 겹쳐 보면 다음 문제가 발생한다.
따라서 이 기능은 OCR, 이미지 정규화, 폰트 로딩, 탐색 최적화, 복합 점수와 실패 격리를 하나의 파이프라인으로 묶어야 했다.
UI 스레드는 파일 입력, 영역 선택, 폰트 로딩과 결과 표시를 담당한다. OCR과 OpenCV 전처리는 ocr.worker.ts, 반복적인 마스크 점수 계산은 fontMatch.worker.ts에서 실행한다. 무거운 작업을 두 Worker로 나눈 이유는 비교 중에도 취소 버튼, 진행 상태와 키보드 탐색이 멈추지 않게 하기 위해서다.
OCR 모델, ONNX Runtime Web과 OpenCV WASM, WOFF2 폰트 파일은 모두 서비스와 동일한 출처에서 제공한다. 이미지 데이터는 Worker 사이에서만 이동하며 외부 API로 전송되지 않는다.


지원 형식은 PNG, JPEG, WebP, BMP이며 최대 파일 크기는 25MB다. 디코딩한 이미지의 긴 변이 2,400px를 넘으면 Canvas에서 비율을 유지한 채 축소한다. OCR에 불필요하게 큰 픽셀을 넘기지 않아 메모리와 처리 시간을 제한한다.
PaddleOCR.js의 한국어 PP-OCRv5 모바일 모델을 ONNX Runtime Web의 WASM 백엔드에서 실행한다. 결과는 문자열만이 아니라 네 꼭짓점으로 구성된 폴리곤과 인식 점수를 포함한다.
사용자는 이미지 위의 폴리곤 또는 키보드로 탐색 가능한 영역 목록에서 하나를 고른다. OCR 문자열은 비교 전에 수정할 수 있다. 공백을 제외해 2글자 미만이면 비교를 막고, 4글자 미만이면 후보 간 차이가 작을 수 있음을 알린다.
선택한 네 점에서 원근 변환 행렬을 계산하고 사각 이미지로 보정한다. 이후 다음 순서로 비교 샘플을 만든다.
원근 보정
→ 회색조 변환
→ 적응형 Gaussian 이진화 또는 작은 영역의 Otsu 이진화
→ 어두운 픽셀 비율을 이용한 전경 극성 판정
→ 전경의 tight crop
→ 글립 높이 80px + 여백을 포함한 96px 캔버스 정규화
출력 폭은 문자열 길이에 따라 96~640px 범위로 제한된다. 이 단계가 끝나면 배경 색과 촬영 각도의 영향을 크게 줄인 0과 1의 마스크를 얻는다.
후보는 한글 팩 18종과 라틴 팩 12종, 총 30종의 자체 호스팅 Google Fonts 계열로 구성된다. 텍스트에 한글이 있는지 먼저 판단해 필요한 스크립트 팩만 동적 import한다.
각 폰트는 WOFF2와 unicode-range 조각으로 제공된다. 브라우저는 실제 문자열에 필요한 조각만 가져오고, 코드는 document.fonts.load()와 document.fonts.check()가 완료된 뒤 렌더링을 시작한다. 빌드 검사에서는 라이선스, lockfile 무결성, 굵기별 CSS와 대표 문자열 기준 30MB 예산을 확인한다.
시스템 폰트는 임의로 열거하지 않는다. Windows, Apple, Android/Linux의 대표 폰트 allowlist만 검사한다. 후보를 monospace와 serif 두 fallback으로 각각 렌더링했을 때 미설치 기준 글꼴과 픽셀이 같으면 해당 후보를 제외한다. 권한과 브라우저 지원 범위가 제한적인 queryLocalFonts()는 사용하지 않았다.
원본 글자가 64px로 보인다고 해서 Canvas의 64px이 같은 실제 글립 높이를 만드는 것은 아니다. 글꼴마다 ascent, descent와 내부 여백이 다르기 때문이다. 그래서 이 구현은 이진 탐색으로 실제 잉크 높이를 맞춘 다음 아래 조합을 만든다.
크기와 자간 조합은 굵기당 9개다. 모든 조합에 비싼 점수를 계산하지 않고, 먼저 빠른 Dice overlap으로 상위 4개를 고른다. 이 네 조합에만 3×3 위치 탐색과 전체 점수를 적용하므로 후보 하나당 최대 36회의 정밀 점수 계산으로 제한된다.
픽셀 일치율 하나만 사용하면 1~2px 이동에도 점수가 크게 떨어진다. 반대로 겹치는 면적만 보면 획 끝과 곡선의 차이를 놓치기 쉽다. 최종 점수는 세 지표의 역할을 나눴다.
두 전경 마스크가 차지하는 면적의 교집합을 본다.
Dice = 2 × |원본 ∩ 후보| / (|원본| + |후보|)
원본과 후보 각각에 Manhattan distance transform을 적용하고 양방향 평균 거리를 구한다. 평균 거리는 exp(-distance / 3)으로 0~1 유사도로 변환한다. 작은 위치 오차를 완충하면서 획의 윤곽이 가까운지를 본다.
두 마스크를 흑백 RGBA로 바꾸고 Pixelmatch의 threshold 0.15, 안티앨리어싱 포함 조건으로 불일치 픽셀을 계산한다. 이는 최종적인 픽셀 수준 검증 역할을 한다.
raw = 0.55 × Dice
+ 0.30 × EdgeSimilarity
+ 0.15 × PixelAgreement
score = clamp(raw × 100 - AspectRatioPenalty, 0, 100)
글립 bounding box의 종횡비가 원본과 다르면 로그 비율 기반 패널티를 최대 10점까지 차감한다. 최종 표시값은 소수점 첫째 자리까지 반올림한다.
브라우저, 운영체제와 네트워크 캐시는 사용자마다 다르다. 특정 폰트 조각이 손상되거나 시스템 후보가 실제로 렌더링되지 않을 수 있다. 이런 실패 하나 때문에 전체 비교가 중단되면 도구로서 쓸 수 없다.
각 후보의 로딩과 점수 계산은 독립적으로 실패할 수 있으며, 실패한 후보만 skippedCount에 포함한다. 비교 가능한 후보가 하나라도 있으면 나머지 결과로 Top 5를 만든다. 모든 후보가 실패했을 때만 재시도 가능한 전체 오류를 표시한다.
취소 또는 새 이미지 선택 시 요청 세대 번호를 증가시켜 이전 응답을 무시하고, Worker와 타이머를 정리한다. 생성한 객체 URL도 React effect cleanup에서 해제한다.
정렬은 점수 내림차순이며, 같은 점수에서는 폰트명으로 정렬해 결과를 결정적으로 유지한다. 상위 5개 카드는 같은 크기의 캔버스에서 원본과 후보를 비교할 수 있게 한다.
다운로드 링크도 폰트 성격에 따라 구분했다.
모든 외부 링크는 새 탭에서 열며 noopener noreferrer를 적용한다.
집중 Playwright 테스트와 개발 빌드를 통과했으며, 폰트 자산 검사도 빌드 전에 자동 실행된다.
v1은 수평에 가까운 한글·라틴 단색 인쇄 글자를 대상으로 한다. 다음 조건은 결과 품질을 보장하지 않는다.
이 한계 때문에 결과를 “정답 확률”이 아니라 “현재 후보군 안의 상대 유사도”로 표현한다.
정확도를 더 높이려면 문자 단위 분할과 특징 벡터 검색을 결합할 수 있다. 픽셀 비교로 전체 후보를 순회하기 전에 가벼운 임베딩으로 후보를 줄이고, 상위 후보에만 현재의 정밀 점수를 적용하는 방식이다. 여러 배경과 촬영 조건을 포함한 회귀 데이터셋, variable font 축 탐색과 이탤릭·세로쓰기 전용 정규화도 후속 과제다.
다만 v1의 목표는 거대한 모델이 아니라 비용 없이, 이미지를 외부로 보내지 않고, 결과 근거를 눈으로 비교할 수 있는 폰트 탐색기다. 브라우저의 OCR, Canvas와 표준 Font Loading API만으로도 이 목표에 충분히 가까이 갈 수 있었다.
| [Prompt] Find critical bugs by 커서 (0) | 2026.05.02 |
|---|---|
| [Apps In Toss] 네뷸라 자동 검수 실패 (0) | 2026.04.17 |
| [Apps In Toss] 💡 심리테스트 & 퀴즈 매니아들 주목! 나를 알아가는 꿀잼 플랫폼 'AI 자아탐구소' 소개 (0) | 2026.02.21 |
| [Apps in Toss] Phase 2. TDS (0) | 2026.02.05 |
| [Apps in Toss] Phase 1. 아이데이션 (0) | 2026.02.04 |
댓글 영역