전체 글
약 2분

PDF 글자층에서 한글을 공백으로 바꾸고 있었습니다

OCR 좌표 계산을 공통 코드로 옮기다가 한글을 지우는 sanitize 조건을 찾았습니다. 한글 폰트를 넣고 JVM에서 생성한 PDF의 ToUnicode를 확인했습니다.

#OCR #ML Kit #PDF #폰트 #Android

들어가며

Pocket PDF의 OCR 글자층 코드를 옮기다가 sanitize 함수에서 다음 조건을 발견했습니다.

ch.isLetterOrDigit() || ch.isWhitespace() ->
    sb.append(if (ch.code < 256) ch else ' ')

문자나 숫자를 처리하는 분기에서 코드값이 256 이상이면 공백을 넣고 있었습니다. 한글 음절은 U+AC00(44032)부터 시작합니다. OCR이 한글을 읽어도, PDF에 넣기 전에 모두 공백이 되는 코드였습니다.

홈 화면에서는 ‘찍은 문서가 검색되는 깨끗한 새 문서로’, 빈 화면에서는 ‘자동 보정 후 검색 가능한 PDF로’라고 안내하고 있었습니다. 하지만 이 글자층으로는 안내한 한국어 검색을 제공할 수 없었습니다.

스캔 이미지는 남고 검색할 텍스트만 사라졌습니다

스캔한 PDF는 문서의 이미지를 보여줍니다. 검색과 복사를 지원하려면 이미지 속 글자를 OCR로 읽고, 그 결과를 PDF에 텍스트로 넣어야 합니다.

Pocket PDF는 이미지와 같은 위치에 보이지 않는 텍스트를 배치했습니다. 원본 이미지가 그대로 보이므로 텍스트층에서 한글이 빠져도 PDF를 눈으로 읽는 데에는 문제가 없었습니다.

OCR에는 Google ML Kit의 한국어 인식기를 사용했습니다. 인식한 텍스트와 위치를 받아 RenderingMode.NEITHER로 PDF에 적었습니다. 글자는 배치하지만 화면에는 그리지 않는 모드입니다.

그런데 PDF에 쓸 폰트는 PDType1Font.HELVETICA였습니다. 별도 파일 없이 쓸 수 있는 표준 폰트였지만 한글을 담을 수 없었습니다. 한국어 인식기를 붙여 놓고도, 그 결과를 적는 단계에서는 처리하지 못하는 문자를 sanitize로 지우고 있었습니다.

공통 코드로 옮기면서 조건을 읽었습니다

당시 작업은 이미지 → PDF에도 OCR 글자층을 넣는 것이었습니다. 스캔 → PDF에 있던 좌표 계산을 복사하는 대신 InvisibleTextLayer로 옮겼습니다. 그 과정에서 sanitize를 다시 읽게 됐습니다.

한글을 남기도록 조건만 바꾸면 끝나는 문제는 아니었습니다. 남긴 문자를 PDF에 적을 수 있도록 한글 글리프를 가진 폰트도 필요했습니다.

앱에서 쓰던 Pretendard를 PDFBox가 읽을 수 있는 형식으로 바꿔 넣었습니다. 새 폰트를 쓰는 경로에서는 한글을 지우는 필터를 거치지 않도록 했습니다. 폰트를 변환하고 PDF에서 검증한 과정은 구현 코드와 함께 따로 적었습니다.

마치며

pdfbox-android를 JVM에서 실행해 PDF를 만들고, ToUnicode 매핑에서 입력한 문자열을 되읽었습니다. 한글과 통화 기호, 악센트가 있는 라틴 문자까지 원문대로 나왔습니다. 이번에는 화면에 문서가 보이는지만 확인하고 끝내지 않았습니다.

이 확인은 실제 카메라로 문서를 찍고 PDF 뷰어에서 검색하는 시험까지 포함하지는 않았습니다. 한국어 영수증으로 실기기 검색을 확인하는 일과 출시 빌드 크기를 재는 일은 당시 후속 작업으로 남겼습니다.