PDF 변환 트러블 슈팅

김동연·2025년 6월 25일

개발기록일지(Flutter)

목록 보기
24/32

1. PDF에 “아무 내용도 안 나옴” 혹은 “빈 페이지만 생성됨”

증상

  • PDF가 정상적으로 생성되지만 완전히 빈 페이지만 나옴.
  • 텍스트, 이미지, 아무것도 없음.

주요 원인 & 점검

  • Delta 파싱 코드에서, Quill의 Document를 제대로 전달하지 않음.
  • .toDelta()에 값이 없거나, 빈 Document를 넘김.
  • PDF 생성 코드에서 위젯을 만들지 않거나, 리스트가 비어 있음.
  • pw.MultiPagebuild 콜백에서 빈 리스트 반환.

해결법

  • PDF 함수에 넘기는 Document에 실제 데이터가 있는지 디버깅 로그로 확인.
  • document.toDelta().toList()에서 실제로 op들이 나오는지 프린트.
  • PDF 위젯 만들 때, 각 파트(텍스트, 이미지 등)마다 예외가 나면 catch에서 print/log로 디버깅.

2. 이미지가 너무 크거나, 너무 작거나, 한 페이지에 꽉 차서 짤림

증상

  • 이미지가 PDF 한 페이지에 넘치게 출력, 혹은 아주 작게 보임.
  • 여러 이미지는 겹쳐서 보임/공백이 너무 많음.

주요 원인 & 점검

  • 이미지 위젯에 width/height 파라미터가 없거나 고정값(너무 큰 값).
  • 실제 노트 위젯 크기(예: 350px)와 PDF 너비(A4, 595pt)가 다름.
  • 이미지 비율 유지 옵션 미설정 (fit: pw.BoxFit.contain 등).

해결법

  • PDF에서 사용할 **“노트 본문 폭”**을 고정(예: 350~400pt)으로 맞추고,
    이미지는 해당 폭에 맞게만 출력하도록
    pw.Image(image, width: contentWidth, fit: pw.BoxFit.contain) 적용.
  • 이미지 aspect ratio(가로/세로 비율) 유지하도록 fit 파라미터 체크.
  • 이미지를 포함하는 Container의 padding/margin으로 적절한 여백 조절.

3. 글자 크기/폰트/스타일이 실제 노트와 완전히 다름

증상

  • PDF의 텍스트가 너무 크거나/작거나, bold/italic/색상 등이 실제와 다름.
  • 코드블럭, 헤더 등의 폰트 크기도 달라서 어색함.

주요 원인 & 점검

  • PDF 위젯에서 fontSize, fontWeight, color 등 스타일을 일관되게 적용하지 않음.
  • Quill Delta의 attribute 구조(size, bold, color, header 등) 파싱 누락.

해결법

  • 실제 노트 앱에서 쓰는 폰트 크기, 스타일을 파싱 코드에 정확히 맵핑.

  • 예시)

    • 본문: 14pt, header1: 22pt, header2: 18pt, 코드블럭: 13pt 등
  • bold/italic/underline 등은 Delta attribute 체크 후,
    pw.TextStyle(fontWeight: ..., fontStyle: ..., decoration: ...)로 모두 반영.

  • Pretendard 등 커스텀 폰트도 반드시 PDF용으로 로딩해서 사용.


4. 페이지가 분할이 안 되거나, 내용이 중간에 잘림/겹침

증상

  • 긴 노트인데 PDF 한 장에 다 나오거나, 내용이 짤려서 한 쪽만 출력됨.
  • 또는 여러 장으로 분할되지만, 줄 사이/이미지 등에서 어색하게 분리.

주요 원인 & 점검

  • pw.MultiPage 대신 pw.Page만 써서 수동으로 분할 안 됨.
  • 각 줄/이미지별로 너무 큰 height/padding을 넣어서 강제로 페이지가 넘어가기도 함.
  • 너무 복잡한 위젯(특히 Table/Wrap 등)을 한 페이지에 억지로 넣으려다 overflow.

해결법

  • PDF 생성시 반드시 pw.MultiPage 사용.
  • 한 줄 단위, 한 이미지 단위로 pw.Widget을 생성해서 List에 쌓고,
    이 리스트를 MultiPage의 build에 바로 넘기면 자동 분할됨.
  • 페이지 폭(contentWidth)만 제한, 세로는 자동으로 넘어가게.
  • (표/긴 이미지 등) 분할이 안 되면, 적절히 줄이거나 별도의 페이지로 빼기.

5. Quill 에디터와 “정렬/여백/공백”이 안 맞음

증상

  • 실제 에디터에서는 예쁘게 정렬되지만,
    PDF에선 좌우가 달라지고 줄 간격이 맞지 않음.
  • 리스트/헤더/코드블럭의 간격이 너무 넓거나 좁음.

주요 원인 & 점검

  • 각 요소별로 여백(padding, margin)을 적절히 주지 않음.
  • align, list, header 등 Delta 속성을 PDF에 제대로 적용 안 함.

해결법

  • Delta의 각 속성을 일일이 파싱해서 PDF 위젯의 정렬/여백에 정확히 반영.

  • 예시)

    • attributes['align'] == 'center'pw.TextAlign.center
    • 리스트 항목엔 왼쪽 여백(padding), 헤더/본문에는 위아래 마진.
    • 코드블럭/인용문은 배경/테두리 등 추가 스타일 부여.

6. PDF 내 텍스트 복사/검색이 불가(이미지 PDF 문제)

증상

  • PDF가 보이긴 하는데, 복사/검색이 안 됨.
    (PDF 뷰어에서 드래그해도 텍스트 선택 불가)

주요 원인 & 점검

  • PDF를 스크린샷(이미지) 기반으로 생성했기 때문.
  • 즉, Delta 파싱이 아닌 RepaintBoundary 방식.

해결법

  • 반드시 Delta를 직접 파싱해서,
    PDF의 pw.Text, pw.RichText로 텍스트를 추가해야
    PDF 뷰어에서 복사/검색 가능.
  • 이미지로 “보이는 대로”를 원하는 게 아니라면, 파싱 방식을 권장.

7. PDF 공유 시, “2개의 문서가 공유됨” (텍스트/이미지 중복)

증상

  • PDF로 내보내면 공유 옵션에서 “두 파일이 공유됨”
    (예: PDF와 다른 일반파일 등)

주요 원인 & 점검

  • PDF 저장 함수/공유 함수에서 다른 포맷(텍스트 등)이 같이 넘어감.
  • Share 패키지 사용 시, 전달 인자에 PDF 파일만 넣었는지 확인 필요.

해결법

  • Share.shareXFiles([XFile(file.path)], subject: ...)
    단일 PDF 파일만 공유되게 파라미터 확인.
  • 이전 임시파일/텍스트 파일/중간파일 등은 share 함수 인자에 포함되지 않도록 정리.

8. Duplicate GlobalKey 오류, 위젯 겹침

증상

  • “Duplicate GlobalKey detected in widget tree”
  • 위젯 트리가 꼬이거나, 화면이 깨짐

주요 원인 & 점검

  • 동일한 GlobalKey를 여러 위젯/위치에서 동시에 사용.

해결법

  • PDF 생성용 RepaintBoundary(스크린샷 방식)에서
    반드시 전용 GlobalKey를 Offstage용과 실제 에디터에 동시에 쓰지 않기.
  • Delta 파싱 방식에서는 이 문제가 없음.

정리

  • PDF에서 "진짜 노트처럼, 길이 제한 없이, 텍스트/이미지/리스트/코드블럭/헤더/폰트/정렬"을 제대로 뽑으려면
    Delta(Quill JSON)를 직접 파싱해서 PDF로 변환하는 것이 가장 안정적임.

  • 실제로 앱 서비스에선 파싱 방식만 씀(스크린샷 방식은 특수 목적).

  • 각종 이슈는

    1. Delta 데이터 구조와 PDF 위젯 파싱 일치,
    2. 스타일/여백/이미지 크기 직접 매칭,
    3. 한 줄씩 디버깅,
      으로 대부분 해결 가능.

혹시 실제로 겪은 특정 증상/에러 로그 있으면, 바로 말씀해주시면
해당 케이스 맞춤 트러블슈팅도 드릴 수 있습니다!
추가로, "노트 사이즈/공백/폰트/이미지 자동조절"의 튜닝이 더 필요하면 알려주세요.

PDF 변환 관련 트러블슈팅 정리


1. 한글이 PDF에 표시되지 않는 오류

오류 메시지

flutter: Unable to find a font to draw "니" (U+b2c8) try to provide a TextStyle.fontFallback

원인

  • pdf 패키지는 기본적으로 한글 폰트를 내장하고 있지 않음
  • 커스텀 한글 폰트를 명시적으로 불러와야 함

해결 방법

  • assets/fonts/Pretendard-Regular.ttf (또는 NotoSansKR 등) 폰트를
    Flutter assets에 추가

  • 아래와 같이 pdf에 폰트 적용하는 코드로 수정

    Future<pw.Font> loadKoreanFont() async {
      final fontData = await rootBundle.load('assets/fonts/Pretendard-Regular.ttf');
      return pw.Font.ttf(fontData);
    }
    
    // PDF 생성 시
    final ttf = await loadKoreanFont();
    pw.Text(text, style: pw.TextStyle(font: ttf, fontSize: 18))
  • pubspec.yaml에 해당 폰트 파일 assets 등록 필수


2. iOS에서 PDF 저장 경로가 Finder(파일앱)에서 안 보임

원인

  • iOS 시뮬레이터(또는 실제 디바이스)는
    getApplicationDocumentsDirectory()에 저장
  • 이 경로는 일반적으로 iOS 파일 앱에서 직접 접근 불가
  • Android는 외부저장소로 바로 접근 가능하지만, iOS는 SandBox 구조 때문

해결 방법

  • PDF로 저장한 후,

    • iOS에서 공유(share) 기능 추가 (예: 메일, 카톡, 파일앱 등으로 내보내기)
    • share_plus 등 공유 패키지 사용
  • 임시 파일 생성 → 바로 “공유 다이얼로그” 띄워서 내보내기 제공


3. PDF로 변환 시 화면의 일부(스크롤 전체가 아님)만 PDF에 출력되는 현상

원인

  • RepaintBoundary로 캡처할 경우,
    “화면에 보이는 부분”만 이미지로 캡처됨
  • 긴 문서(스크롤 전체)는 자동으로 캡처되지 않음

해결 방법

  • 스크롤 전체를 렌더링/캡처하려면

    • CustomPainter 등으로 “수동으로 전체 높이만큼 Widget 렌더링”
    • 또는, 내용을 여러 장으로 나눠 반복 캡처 후 PDF 페이지마다 넣기
    • 또는, Delta 파싱하여 직접 PDF 위젯화 (정석)
  • 현실적 대안:

    • 우선 MVP는 “보이는 화면” 캡처로,
    • 추후 전체 페이지 분할 캡처/변환 방식 적용

4. Quill/텍스트 PDF 방식에서 이미지·표 등 부가 콘텐츠가 PDF에 반영되지 않음

원인

  • quillController.document.toPlainText()를 사용하면
    텍스트만 추출되고, 이미지/표 등은 빠짐
  • “구조(Delta) → PDF” 매핑이 없으면 텍스트만 PDF에 들어감

해결 방법

  • Delta의 각 Operation을 분석해서 이미지/리스트/표/색상도
    직접 PDF 위젯으로 변환해야 함(난이도 ↑)
  • MVP에서는 텍스트 기반 PDF와 이미지 PDF 둘 다 제공(유저 선택)

5. pdf 패키지의 한글 폰트 적용 코드가 동작하지 않거나, 경로 오류 발생

원인

  • 메모장 내의 폰트가 한글을 지원하지 않음.
  • 폰트 파일 경로가 실제로 프로젝트 내 assets에 존재하지 않거나 pubspec.yaml에 누락

해결 방법

  • 메모장 내의 폰트를 fonts/assets에 .ttf 형식으로 저장하거나 이미 사용하고 있던 pretendard font를 활용하여 대체함.
  • pubspec.yaml 내 fonts/assets 항목을 확인 후 flutter pub get
  • 실제 경로와 코드의 경로 일치 여부 재확인

7. iOS에서 안드로이드 전용 API 호출 오류

오류 상황

  • iOS에서 아래 코드처럼 Android API인 getExternalStorageDirectory()를 호출하려고 시도함:

    final dir = await getExternalStorageDirectory();
  • iOS에서는 이 API가 없으므로, 런타임에서 'MissingPluginException' 혹은 'Method not implemented' 등의 에러가 발생

실제 에러 예시

[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: MissingPluginException(No implementation found for method getExternalStorageDirectory on channel plugins.flutter.io/path_provider)

또는

Exception: 지원하지 않는 플랫폼입니다.

원인

  • path_provider 패키지의 getExternalStorageDirectory() 함수는 Android 전용
  • iOS에서는 샌드박스 구조 때문에 외부저장소 개념이 없고, getApplicationDocumentsDirectory()만 제공

해결 방법

1. 플랫폼 분기 처리

아래처럼 플랫폼별로 분기해서 API 호출해야 함

import 'dart:io';
import 'package:path_provider/path_provider.dart';

Future<Directory> _getSaveDirectory() async {
  if (Platform.isAndroid) {
    return await getExternalStorageDirectory() ??
        await getApplicationDocumentsDirectory();
  } else if (Platform.isIOS) {
    return await getApplicationDocumentsDirectory();
  } else {
    throw Exception('지원하지 않는 플랫폼입니다.');
  }
}

2. 실제 코드 사용 예

final dir = await _getSaveDirectory();
final file = File('${dir.path}/$fileName.pdf');
await file.writeAsBytes(await pdf.save());

3. 주의사항

  • iOS는 Documents 폴더만 사용 가능
    (사용자 접근: 앱 자체 파일 앱/백업 또는 공유 기능 제공 필요)
  • Android 11+는 권한 강화 (runtime permission 필요, MANAGE_EXTERNAL_STORAGE 등)

트러블슈팅 요약

증상/오류원인해결 방법
iOS에서 Android API 호출플랫폼 분기 미구현, Android API 호출플랫폼별 분기, iOS는 Documents 사용

요약 표

증상/오류원인해결 방법
한글 PDF 오류pdf 폰트 미지정커스텀 한글폰트 로딩/적용
iOS 저장경로 접근 불가샌드박스 구조, Finder서 직접 못 봄파일 공유(share) 기능 추가
화면 일부만 PDF 변환됨RepaintBoundary로 보이는 부분만 캡처전체 스크롤 분할 캡처 or Delta→PDF
이미지가 PDF에 없음텍스트만 추출Delta→PDF 파싱로직 추가 or 캡처 방식
폰트 적용 안됨asset 누락/경로 불일치assets 폴더/경로/pubspec 확인
안드로이드 권한 오류퍼미션 미설정AndroidManifest.xml에 퍼미션 추가
iOS에서 Android API 호출플랫폼 분기 미구현, Android API 호출플랫폼별 분기, iOS는 Documents 사용

0개의 댓글