블로그 목록
apps

expo-print로 HTML을 PDF로 만들기 — 앱에서 영수증·문서 바로 뽑기

앱에서 주문 영수증을 PDF로 내보내야 할 일이 생겼다. 처음엔 서버에서 PDF를 만들어 내려받게 할까 했는데, 데이터가 다 클라이언트에 있는 상황이라 서버 왕복이 아까웠다. 결국 기기에서 바로 PDF를 만드는 쪽으로 갔고, expo-print가 딱 그 일을 한다.

핵심은 하나다. HTML 문자열을 넘기면 PDF 파일로 렌더링해준다. 레이아웃을 CSS로 짜면 되니까 별도의 PDF DSL을 배울 필요가 없다.

설치

npx expo install expo-print expo-sharing

공유까지 하려면 expo-sharing도 같이 넣는다.

HTML을 PDF로

printToFileAsync()에 HTML을 넘기면 임시 파일 경로(uri)를 돌려준다.

import * as Print from 'expo-print';
import * as Sharing from 'expo-sharing';

async function makeReceipt(order) {
  const html = `
    <html>
      <body style="font-family: -apple-system, sans-serif; padding: 32px;">
        <h1 style="font-size: 20px;">주문 영수증</h1>
        <p>주문번호: ${order.id}</p>
        <hr />
        ${order.items
          .map(
            (it) => `
          <div style="display:flex; justify-content:space-between;">
            <span>${it.name} x ${it.qty}</span>
            <span>${(it.price * it.qty).toLocaleString()}원</span>
          </div>`
          )
          .join('')}
        <hr />
        <h3 style="text-align:right;">합계 ${order.total.toLocaleString()}원</h3>
      </body>
    </html>
  `;

  const { uri } = await Print.printToFileAsync({ html });
  await Sharing.shareAsync(uri, { UTI: '.pdf', mimeType: 'application/pdf' });
}

shareAsync까지 붙이면 카카오톡·메일·파일 앱으로 그대로 내보낼 수 있다. 사용자가 "PDF로 저장"을 고르면 기기에 저장된다.

프린터로 바로 보내기

파일을 만들지 않고 시스템 인쇄 다이얼로그를 띄우고 싶으면 printAsync()를 쓴다.

await Print.printAsync({ html });

iOS는 AirPrint, Android는 연결된 프린터 목록이 뜬다. 실제 종이 출력이 목적이면 이쪽이 맞다.

한글이 깨지거나 잘릴 때

두 가지를 자주 밟았다.

  • 웹폰트를 @font-face로 넣지 말자. 렌더링 시점에 폰트를 못 받아 네모(□)로 나온다. 시스템 폰트(-apple-system, sans-serif)를 쓰면 한글이 안전하게 나온다.
  • 이미지는 base64로. 로고 같은 걸 http URL로 넣으면 로딩 전에 렌더링돼서 빈칸이 된다. data:image/png;base64,...로 인라인 하면 확실하다.

정리

서버 없이 클라이언트에서 PDF를 뽑아야 한다면 expo-print가 가장 빠른 길이다. HTML/CSS를 그대로 쓰니 레이아웃 조정이 쉽고, printToFileAsync + shareAsync 조합이면 저장·공유가 한 번에 끝난다. 폰트는 시스템 폰트, 이미지는 base64 — 이 두 개만 지키면 깨질 일이 거의 없다.