feat: 토정비결 및 PDF 저장 기능 추가

- 토정비결 페이지 구현 (연간/월별 운세)
- 분야별 운세 (재물, 건강, 관운, 애정)
- PDF 저장 기능 구현 (jsPDF + html2canvas)
- 모든 결과 페이지에 PDF 다운로드 기능 추가
- PDFButton 재사용 가능한 컴포넌트 생성
- 홈페이지에 토정비결 링크 추가
- 페이지 간 네비게이션 링크 업데이트

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 23:44:55 +09:00
parent 08d6f71fd1
commit f85e857bea
11 changed files with 950 additions and 17 deletions

72
lib/pdf-utils.ts Normal file
View File

@@ -0,0 +1,72 @@
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
/**
* HTML 요소를 PDF로 변환하여 다운로드
* @param elementId - PDF로 변환할 HTML 요소의 ID
* @param filename - 다운로드될 PDF 파일명
*/
export async function downloadPDF(elementId: string, filename: string) {
try {
const element = document.getElementById(elementId);
if (!element) {
throw new Error(`Element with id "${elementId}" not found`);
}
// 로딩 표시
const originalContent = element.innerHTML;
// HTML을 캔버스로 변환
const canvas = await html2canvas(element, {
scale: 2, // 해상도 향상
useCORS: true,
logging: false,
backgroundColor: '#ffffff'
});
// 캔버스를 이미지로 변환
const imgData = canvas.toDataURL('image/png');
// PDF 생성
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: 'a4'
});
const imgWidth = 210; // A4 width in mm
const pageHeight = 297; // A4 height in mm
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = 0;
// 첫 페이지 추가
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
// 여러 페이지가 필요한 경우
while (heightLeft > 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
// PDF 다운로드
pdf.save(filename);
return true;
} catch (error) {
console.error('PDF 생성 중 오류 발생:', error);
alert('PDF 생성에 실패했습니다. 다시 시도해주세요.');
return false;
}
}
/**
* 현재 페이지를 PDF로 다운로드
* @param filename - 다운로드될 PDF 파일명
*/
export async function downloadCurrentPageAsPDF(filename: string) {
return downloadPDF('pdf-content', filename);
}