PDF
-
아직 완전하게 구현하진 못했지만 Delta라는 데이터 구조를 일부 파싱하면서 PDF로 변환하는데 성공하였음.
-
간격, 폰트 크기, 이미지 크기 등 노트의 구조와 완전히 동일하게 구현하기는 힘들어 완성도 자체는 떨어지는 편이지만, 점점 보완해나갈 것.
1. 사용 패키지
1. pdf 패키지
-
패키지명: pdf
-
기본 설명:
- Dart(Flutter)에서 코드로 PDF 문서를 생성할 수 있는 라이브러리.
pw.Document, pw.Page, pw.MultiPage, pw.Text, pw.Image 등 다양한 PDF 구성 위젯 제공.
-
주요 기능/특징:
- 텍스트/이미지/테이블/리치텍스트 등 다양한 레이아웃 지원
- 커스텀 폰트 사용 가능 (한국어 포함)
- A4, 레터 등 용지 크기 지정, 마진, 정렬 등 세밀한 레이아웃 컨트롤
- 페이지 자동 분할 지원 (
MultiPage)
- Flutter 위젯과는 완전히 별개로, “코드로 PDF 문서 구조를 직접 만듦”
-
실제 사용:
-
Quill 에디터의 Delta(문서 데이터)를 PDF용 위젯(pw.Text, pw.Image 등)으로 변환해서
한 장 한 장 “코드로” 쌓아서 PDF 파일로 저장.
-
예:
final pdf = pw.Document();
pdf.addPage(pw.MultiPage(
build: (context) => [pw.Text('Hello PDF')],
));
await file.writeAsBytes(await pdf.save());
-
단점:
- Flutter의 실제 화면을 그대로 “캡처”하는 기능은 없음
- 레이아웃을 직접 다 짜야 하고, 스타일/폰트 매칭 등 신경 쓸 게 많음
2. printing 패키지
-
패키지명: printing
-
기본 설명:
- PDF 파일을 실제로 프린터로 인쇄하거나,
기기/OS에 따라 공유, 미리보기, PDF로 저장하는 기능 제공
pdf 패키지와 “연동”해서 많이 씀
-
주요 기능/특징:
- iOS/Android 모두에서 OS의 “공유” UI(파일 내보내기, 프린트 등) 호출
- PDF 미리보기 (
PdfPreview 위젯)
- 실제 프린터 연동(네트워크 프린터 등)
- PDF 파일을 이미지로 변환 등 기타 기능
-
실제 사용:
-
생성한 PDF 파일을 “내보내기(share)” 버튼으로 바로 공유/저장/프린트 할 때 활용
-
예:
await Printing.sharePdf(bytes: await pdf.save(), filename: 'nota_note.pdf');
-
단점:
- PDF 자체를 만드는 기능은 거의 없고,
pdf 패키지로 만든 PDF를 공유/저장/인쇄하는 역할만 함
3. 그 외 보조 패키지
-
path_provider
- 임시 폴더/앱 폴더 경로 구하기(파일 저장 경로 확보)
-
share_plus
- PDF 파일을 다른 앱(카카오톡/이메일 등)으로 공유할 때
-
http
- 온라인 이미지/파일을 PDF로 삽입할 때 다운로드
2. 왜 이 패키지들을 썼는가? (기술적 배경)
-
pdf
- Quill 에디터의 Delta 구조는 JSON-like 데이터
- 이걸 Flutter 위젯처럼 실제로 “그리는 것”이 아니라, PDF용 Dart 위젯으로 다시 일일이 매핑/파싱해서 진짜 PDF 파일로 만들어야 했음.
- (즉, pdf 패키지는 HTML → PDF, Quill Delta → PDF 등 “코드로 그리는 모든 PDF”의 기본)
-
printing
- 파일로 저장/공유/미리보기/프린트 등
PDF를 사용자의 기기 밖으로 전달하는 관문 역할
- 단독 PDF 생성은 불가, 항상 pdf 패키지와 같이 사용
-
share_plus
- 생성된 PDF를 이메일, 메신저, 파일앱 등으로 보내기 위한 “공유” 기능
3. 구현 방식
1. Quill → PDF 파싱
-
pdf 패키지로
Quill Document의 Delta를 파싱해서
pw.MultiPage 내부에
- 각 줄은
pw.Text/pw.RichText
- 이미지는
pw.Image
- 리스트/코드블럭/인용문 등은 컨테이너 조합
으로 “직접 구현”
-
헤더, 정렬, 글자크기, 굵기 등 스타일은
Quill Delta의 attribute를 분석해서,
pdf 패키지의 스타일 속성에 매핑
2. PDF 파일 생성 & 저장
- 완성된
pw.Document 객체를
await pdf.save()로 바이트로 만들고,
앱의 임시폴더(getTemporaryDirectory())에 저장
3. PDF 파일 공유/내보내기
printing 또는 **share_plus**를 통해
PDF 파일을 미리보기, 인쇄, 파일로 내보내기, 카카오톡 등 공유 기능에 연결
4. 방식별 장단점
(A) Delta 파싱(pdf 방식)
-
장점
- 텍스트 복사/검색 가능
- 진짜 PDF, 페이지 자동분할, 용량 작음
- 스타일/폰트/이미지 등 데이터 기반 정확한 변환 가능
-
단점
- 구현이 복잡함 (스타일, 줄간격, 정렬 다 직접 구현)
- Quill의 특이한(커스텀) 속성은 별도 파싱 필요
(B) 스크린샷(RepaintBoundary 캡처)
-
장점
- “화면에 보이는 그대로” PDF에 넣을 수 있음
- 구현이 쉽다 (이미지 변환만)
-
단점
- 텍스트 복사/검색 불가
- 화질, 해상도, 이미지 크기, 페이지 분할 등에 한계
- 화면 사이즈에 따라 비율 안 맞을 수 있음
이 다음은 실제로 구현했던 과정에 따라 자세히 설명하겠음.
5. 기본적인 시나리오: Flutter Quill → PDF로 내보내기
- 목표
Flutter 앱에서 사용자가 입력/편집한 노트(Quill 문서/리치 텍스트 등)를,
실제 노트 화면과 거의 똑같이 PDF 파일로 변환해서 저장하거나 공유.
1. 초기에 시도한 방법: RepaintBoundary(스크린샷 캡처) 방식
원리
- Flutter의 위젯을
RepaintBoundary로 감싼 뒤,
toImage로 현재 화면을 그대로 이미지로 캡처.
- 그 이미지를 PDF 페이지에 넣어서 저장/공유.
장점
- 아무 파싱 없이, 보이는 그대로 캡처하므로 Quill, 커스텀위젯 등 어떤 UI든 결과가 완벽히 똑같이 나옴.
- 표/이미지/커스텀 레이아웃/폰트 등 "진짜 보이는 대로" PDF에 박제됨.
단점
- 화면 전체를 캡처하는 구조라,
→ "한 화면에 안 보이는 내용"은 빠짐
→ 스크롤 내린 뒤만 따로 캡처됨(여러 페이지 분할 어려움).
- 기기 해상도에 따라 결과 해상도/비율이 달라질 수 있음.
- 진짜 PDF(텍스트로 복사/검색 가능한 문서)가 아니라
이미지로 박제된 PDF라서, 글자 복사/검색이 불가.
- 실제 노트 길이가 길거나, 여러 페이지 분할을 자동으로 하는 방식을 찾지못함.
2. 실제로 사용한 방식: Delta(Quill JSON) → PDF 파싱/생성
원리
- Quill 에디터가 내부적으로 갖고 있는 "Delta"라는 데이터 구조는
→ 사용자가 쓴 텍스트, 스타일, 이미지, 리스트, 헤더, 코드블럭 등
모두 json 구조로 저장되어 있음.
- 이 Delta 구조를 직접 해석(파싱)해서,
PDF 패키지(pw.Document)에 맞는 위젯으로 변환.
장점
- "실제 텍스트로 된 진짜 PDF"
→ 글자 복사, 검색, PDF로서의 호환성 뛰어남.
- 길이가 길어도 자동으로 페이지 분할(페이지 넘어가도 문제 없음).
- 해상도/비율 이슈 없음.
- 커스텀 폰트/스타일, 줄 간격, 헤더, 인용문, 코드블럭, 리스트 등
→ 거의 모두 실제 에디터와 맞게 커스터마이즈 가능.
- 이미지도 PDF에서 노트 너비에 맞게 자동 조절해서 출력 가능.
단점
- Delta를 직접 파싱해서, PDF로 변환해주는 함수를 일일이 커스텀해야 함.
- (Flutter에서) 표/수식 등 일부 복잡한 구조는 손수 처리해줘야 함.
- 완벽히 1:1로 맞추는 건 매우 힘들지만, 스타일만 잘 맞추면 비슷하게는 구현 가능.
3. 실제 적용/코드 구조
핵심 흐름
-
Quill 에디터에서 사용자가 작성한 문서(Document)를 읽어와서
-
document.toDelta()로 Delta 구조를 가져온다.
-
Delta의 각 요소(op)를 하나씩 읽으며
- 텍스트면: 텍스트 스타일, 색, 사이즈, bold/italic 등 분석해서 PDF 위젯 생성
- 헤더면: h1/h2/h3 등 크기 다르게 PDF에 적용
- 코드블럭/인용문/리스트 등은 PDF에 맞는 스타일로 변환
- 이미지면: url을 읽어와서 PDF 이미지 위젯으로, 노트 폭에 맞게 자동 사이즈 조절
- 줄간격/패딩 등도 실제 에디터와 맞게 맞춤
-
이 모든 PDF 위젯을 Column으로 묶어
-
PDF 페이지(A4 등) 가운데에 **실제 노트 폭(예시 350px)**만큼만 제한해서 넣음
-
페이지가 넘치면 자동으로 여러 페이지로 분할
-
최종적으로 PDF 파일로 저장/공유
4. 실제로 겪은 문제와 해결 방향
1. 이미지 크기
- 무작정 PDF에 이미지 넣으면, 화면보다 너무 크게/작게 나옴
→ 노트 실제 너비(350~360px)에 맞게 width를 제한해서 출력
2. 공백/줄간격 문제
- 줄마다 패딩/마진이 다르면 실제보다 더 많이 띄워짐
→ 한 줄 한 줄마다 padding, margin을 조절
- 리스트/헤더/코드블럭 등은 적당한 여백을 부여
3. 텍스트 크기
- Quill 기본 본문은 14, header1은 22, header2는 18, header3은 16, 코드블럭은 13 등
→ 실제 에디터 폰트사이즈와 동일하게 PDF 폰트사이즈를 맞춤
4. 이미지/텍스트 정렬
- 기본은 좌측, center/right일 경우 PDF도 똑같이 맞춰줌
5. Pretendard 등 커스텀 폰트 적용
- PDF에선
pw.Font로 Pretendard 불러와서 본문/헤더/코드블럭 모두 적용
6.정리
-
대부분의 실전 앱(노트, 문서, 리포트 등)에서는 pdf + printing + path_provider + share_plus 조합을 씀
-
화면 캡처 방식은 임시 프린트, 즉석 시각화 등에만 사용
-
Quill, Markdown, HTML 등 에디터에서 “진짜 PDF” 내보내기는
무조건 파싱 + pdf 패키지 기반의 위젯 직접 구현
-
폰트, 이미지, 용지 폭 등은 반드시 “기준값”을 직접 지정
-
공유/내보내기에서는 항상 임시폴더/앱폴더 파일로 저장한 후 share/printing 사용
- PDF에서 실제 에디터와 1:1이 안 맞을 때
→ pageContentWidth(노트 너비), fontSize, 각종 padding/margin, 이미지 높이 등
직접 여러 번 비교/수정하면서 맞춰야 실제 화면과 거의 일치합니다.
- Flutter Quill이 구조를 바꿀 경우 → Delta to PDF 파싱 코드도 같이 수정 필요