Spaces:
Paused
Paused
| from fastapi import FastAPI | |
| from fastapi.responses import HTMLResponse | |
| from fastapi.staticfiles import StaticFiles | |
| import pathlib, os, uvicorn, base64 | |
| BASE = pathlib.Path(__file__).parent | |
| app = FastAPI() | |
| app.mount("/static", StaticFiles(directory=BASE), name="static") | |
| # PDF ๋๋ ํ ๋ฆฌ ์ค์ | |
| PDF_DIR = BASE / "pdf" | |
| if not PDF_DIR.exists(): | |
| PDF_DIR.mkdir(parents=True) | |
| # PDF ํ์ผ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ | |
| def get_pdf_files(): | |
| pdf_files = [] | |
| if PDF_DIR.exists(): | |
| pdf_files = [f for f in PDF_DIR.glob("*.pdf")] | |
| return pdf_files | |
| # PDF ์ธ๋ค์ผ ์์ฑ ๋ฐ ํ๋ก์ ํธ ๋ฐ์ดํฐ ์ค๋น | |
| def generate_pdf_projects(): | |
| projects_data = [] | |
| pdf_files = get_pdf_files() | |
| for pdf_file in pdf_files: | |
| projects_data.append({ | |
| "path": str(pdf_file), | |
| "name": pdf_file.stem | |
| }) | |
| return projects_data | |
| HTML = """ | |
| <!doctype html><html lang="ko"><head> | |
| <meta charset="utf-8"><title>FlipBook Space</title> | |
| <link rel="stylesheet" href="/static/flipbook.css"> | |
| <script src="/static/three.js"></script> | |
| <script src="/static/iscroll.js"></script> | |
| <script src="/static/mark.js"></script> | |
| <script src="/static/mod3d.js"></script> | |
| <script src="/static/pdf.js"></script> | |
| <script src="/static/flipbook.js"></script> | |
| <script src="/static/flipbook.book3.js"></script> | |
| <script src="/static/flipbook.scroll.js"></script> | |
| <script src="/static/flipbook.swipe.js"></script> | |
| <script src="/static/flipbook.webgl.js"></script> | |
| <style> | |
| body{margin:0;background:#f0f0f0;font-family:sans-serif} | |
| header{max-width:960px;margin:0 auto;padding:18px 20px;display:flex;align-items:center} | |
| #homeBtn{display:none;width:38px;height:38px;border:none;border-radius:50%;cursor:pointer; | |
| background:#0077c2;color:#fff;font-size:20px;margin-right:12px} | |
| #homeBtn:hover{background:#005999} | |
| h2{margin:0;font-size:1.5rem;font-weight:600} | |
| #home,#viewerPage{max-width:960px;margin:0 auto;padding:0 20px 40px} | |
| .grid{display:grid;grid-template-columns:repeat(auto-fill,180px);gap:16px;margin-top:24px} | |
| .card{ | |
| background:#fff url('/static/book2.jpg') no-repeat center center; | |
| background-size: 169%; /* ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง๋ฅผ ํ์ฌ๋ณด๋ค 30% ๋ ํค์ (130% * 1.3 = 169%) */ | |
| border:1px solid #ccc; | |
| border-radius:6px; | |
| cursor:pointer; | |
| box-shadow:0 2px 4px rgba(0,0,0,.12); | |
| width: 180px; | |
| height: 240px; | |
| position: relative; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .card img{ | |
| width:55%; /* ์ธ๋ค์ผ ํฌ๊ธฐ ์ค์ (๋ฐฐ๊ฒฝ์ด ์ปค์ ธ์ ๋น์จ ์ ์ง) */ | |
| height:auto; | |
| object-fit:contain; | |
| position:absolute; /* ์ ๋ ์์น๋ก ๋ณ๊ฒฝ */ | |
| top:50%; /* ์๋จ์์ 50% */ | |
| left:50%; /* ์ข์ธก์์ 50% */ | |
| transform: translate(-50%, -70%); /* ์ ์ค์์์ 20% ์๋ก ์ด๋ */ | |
| border: 1px solid #ddd; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.2); | |
| } | |
| .card p{ | |
| text-align:center; | |
| margin:6px 0; | |
| position: absolute; | |
| bottom: 10px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: rgba(255, 255, 255, 0.7); | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| width: 85%; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| max-width: 150px; | |
| font-size: 11px; /* ๊ธฐ๋ณธ 16px์์ ์ฝ 30% ๊ฐ์ */ | |
| } | |
| button.upload{all:unset;cursor:pointer;border:1px solid #bbb;padding:8px 14px;border-radius:6px;background:#fff;margin:0 8px} | |
| #viewer{ | |
| width:90%; | |
| height:90vh; | |
| max-width:90%; | |
| margin:0; | |
| background:#fff; | |
| border:1px solid #ddd; | |
| border-radius:8px; | |
| position:fixed; | |
| top:50%; | |
| left:50%; | |
| transform:translate(-50%, -50%); | |
| z-index:1000; | |
| box-shadow:0 4px 20px rgba(0,0,0,0.15); | |
| /* ํ๋ฉด ๋น์จ์ ๋ง๊ฒ ์กฐ์ */ | |
| max-height: calc(90vh - 40px); /* ์ฌ๋ฐฑ ๊ณ ๋ ค */ | |
| aspect-ratio: auto; /* ๋น์จ ์๋ ์กฐ์ */ | |
| object-fit: contain; /* ๋ด์ฉ๋ฌผ ๋น์จ ์ ์ง */ | |
| overflow: hidden; /* ๋ด์ฉ ๋์นจ ๋ฐฉ์ง */ | |
| } | |
| /* FlipBook ์ปจํธ๋กค๋ฐ ๊ด๋ จ ์คํ์ผ ์ค๋ฒ๋ผ์ด๋ */ | |
| .flipbook-container .fb3d-menu-bar { | |
| z-index: 2000 !important; /* ์ปจํธ๋กค๋ฐ๊ฐ ๋ค๋ฅธ ์์๋ณด๋ค ์์ ์ค๋๋ก ํจ */ | |
| opacity: 1 !important; /* ํญ์ ํ์๋๋๋ก ํจ */ | |
| bottom: 0 !important; /* ํ๋จ์ ๊ณ ์ */ | |
| background-color: rgba(0,0,0,0.7) !important; /* ๋ฐฐ๊ฒฝ์ ์ค์ */ | |
| border-radius: 0 0 8px 8px !important; /* ํ๋จ ๋ชจ์๋ฆฌ๋ง ๋ฅ๊ธ๊ฒ */ | |
| padding: 8px 0 !important; /* ์ํ ํจ๋ฉ ์ถ๊ฐ */ | |
| } | |
| .flipbook-container .fb3d-menu-bar > ul > li > img, | |
| .flipbook-container .fb3d-menu-bar > ul > li > div { | |
| opacity: 1 !important; /* ๋ฉ๋ด ์์ด์ฝ ํญ์ ํ์ */ | |
| transform: scale(1.2) !important; /* ์์ด์ฝ ํฌ๊ธฐ ์ฝ๊ฐ ํค์ */ | |
| } | |
| .flipbook-container .fb3d-menu-bar > ul > li { | |
| margin: 0 8px !important; /* ๋ฉ๋ด ์์ดํ ๊ฐ๊ฒฉ ์กฐ์ */ | |
| } | |
| /* ๋ฉ๋ด ํดํ ์คํ์ผ ๊ฐ์ */ | |
| .flipbook-container .fb3d-menu-bar > ul > li > span { | |
| background-color: rgba(0,0,0,0.8) !important; | |
| color: white !important; | |
| border-radius: 4px !important; | |
| padding: 4px 8px !important; | |
| font-size: 12px !important; | |
| bottom: 45px !important; /* ํดํ ์์น ์กฐ์ */ | |
| } | |
| </style></head><body> | |
| <header> | |
| <button id="homeBtn" title="ํ์ผ๋ก">๐ </button> | |
| <h2>My FlipBook Projects</h2> | |
| </header> | |
| <section id="home"> | |
| <div> | |
| <label class="upload">๐ท ์ด๋ฏธ์ง <input id="imgInput" type="file" accept="image/*" multiple hidden></label> | |
| <label class="upload">๐ PDF <input id="pdfInput" type="file" accept="application/pdf" hidden></label> | |
| </div> | |
| <div class="grid" id="grid"></div> | |
| </section> | |
| <section id="viewerPage" style="display:none"> | |
| <div id="viewer"></div> | |
| </section> | |
| <script> | |
| let projects=[], fb=null; | |
| const grid=$id('grid'), viewer=$id('viewer'); | |
| pdfjsLib.GlobalWorkerOptions.workerSrc='/static/pdf.worker.js'; | |
| // ์๋ฒ์์ ๋ฏธ๋ฆฌ ๋ก๋๋ PDF ํ๋ก์ ํธ | |
| let serverProjects = []; | |
| /* ๐ ์ค๋์ค unlock โ ๋ด์ฅ Audio ์ ๊ฐ์ MP3 ๊ฒฝ๋ก ์ฌ์ฉ */ | |
| ['click','touchstart'].forEach(evt=>{ | |
| document.addEventListener(evt,function u(){new Audio('static/turnPage2.mp3') | |
| .play().then(a=>a.pause()).catch(()=>{});document.removeEventListener(evt,u,{capture:true});}, | |
| {once:true,capture:true}); | |
| }); | |
| /* โโ ์ ํธ โโ */ | |
| function $id(id){return document.getElementById(id)} | |
| function addCard(i,thumb,title){ | |
| const d=document.createElement('div'); | |
| d.className='card'; | |
| d.onclick=()=>open(i); | |
| // ์ ๋ชฉ 10๊ธ์ ์ ํ ๋ฐ ๋ง์ค์ํ ์ฒ๋ฆฌ | |
| const displayTitle = title ? | |
| (title.length > 10 ? title.substring(0, 10) + '...' : title) : | |
| 'ํ๋ก์ ํธ ' + (i+1); | |
| d.innerHTML=`<img src="${thumb}"><p title="${title || 'ํ๋ก์ ํธ ' + (i+1)}">${displayTitle}</p>`; | |
| grid.appendChild(d); | |
| } | |
| /* โโ ์ด๋ฏธ์ง ์ ๋ก๋ โโ */ | |
| $id('imgInput').onchange=e=>{ | |
| const files=[...e.target.files]; if(!files.length) return; | |
| const pages=[],tot=files.length;let done=0; | |
| files.forEach((f,i)=>{const r=new FileReader();r.onload=x=>{pages[i]={src:x.target.result,thumb:x.target.result}; | |
| if(++done===tot) save(pages);};r.readAsDataURL(f);}); | |
| }; | |
| /* โโ PDF ์ ๋ก๋ โโ */ | |
| $id('pdfInput').onchange=e=>{ | |
| const file=e.target.files[0]; if(!file) return; | |
| const fr=new FileReader(); | |
| fr.onload=v=>{ | |
| pdfjsLib.getDocument({data:v.target.result}).promise.then(async pdf=>{ | |
| const pages=[]; | |
| for(let p=1;p<=pdf.numPages;p++){ | |
| const pg=await pdf.getPage(p), vp=pg.getViewport({scale:1}); | |
| const c=document.createElement('canvas');c.width=vp.width;c.height=vp.height; | |
| await pg.render({canvasContext:c.getContext('2d'),viewport:vp}).promise; | |
| pages.push({src:c.toDataURL(),thumb:c.toDataURL()}); | |
| } | |
| save(pages, file.name.replace('.pdf', '')); | |
| }); | |
| };fr.readAsArrayBuffer(file); | |
| }; | |
| /* โโ ํ๋ก์ ํธ ์ ์ฅ โโ */ | |
| function save(pages, title){ | |
| const id=projects.push(pages)-1; | |
| addCard(id,pages[0].thumb, title); | |
| } | |
| /* โโ ์๋ฒ PDF ๋ก๋ โโ */ | |
| async function loadServerPDFs() { | |
| try { | |
| const response = await fetch('/api/pdf-projects'); | |
| serverProjects = await response.json(); | |
| // ์๋ฒ PDF ๋ก๋ ๋ฐ ์ธ๋ค์ผ ์์ฑ | |
| for(let i = 0; i < serverProjects.length; i++) { | |
| const project = serverProjects[i]; | |
| const response = await fetch(`/api/pdf-thumbnail?path=${encodeURIComponent(project.path)}`); | |
| const data = await response.json(); | |
| if(data.thumbnail) { | |
| const pages = [{ | |
| src: data.thumbnail, | |
| thumb: data.thumbnail, | |
| path: project.path | |
| }]; | |
| save(pages, project.name); | |
| } | |
| } | |
| } catch(error) { | |
| console.error('์๋ฒ PDF ๋ก๋ ์คํจ:', error); | |
| } | |
| } | |
| /* โโ ์นด๋ โ FlipBook โโ */ | |
| function open(i){ | |
| toggle(false); | |
| const pages = projects[i]; | |
| // ๋ก์ปฌ ํ๋ก์ ํธ ๋๋ ์๋ฒ PDF ๋ก๋ | |
| if(fb){fb.destroy();viewer.innerHTML='';} | |
| if(pages[0].path) { | |
| // ๋ก๋ฉ ํ์ | |
| viewer.innerHTML = '<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;"><div style="border:4px solid #f3f3f3;border-top:4px solid #3498db;border-radius:50%;width:50px;height:50px;margin:0 auto;animation:spin 2s linear infinite;"></div><p style="margin-top:20px;font-size:16px;">PDF ๋ก๋ฉ ์ค...</p></div>'; | |
| // ์คํ์ผ ์ถ๊ฐ | |
| if (!document.getElementById('loadingStyle')) { | |
| const style = document.createElement('style'); | |
| style.id = 'loadingStyle'; | |
| style.textContent = '@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}'; | |
| document.head.appendChild(style); | |
| } | |
| // ์๋ฒ PDF ํ์ผ ๋ก๋ | |
| fetch(`/api/pdf-content?path=${encodeURIComponent(pages[0].path)}`) | |
| .then(response => { | |
| if (!response.ok) { | |
| throw new Error('PDF ๋ก๋ ์คํจ: ' + response.statusText); | |
| } | |
| return response.arrayBuffer(); | |
| }) | |
| .then(pdfData => { | |
| // PDF ๋ฐ์ดํฐ ๋ก๋ ํ์ธ ๋ก๊น | |
| console.log('PDF ๋ฐ์ดํฐ ๋ก๋ ์๋ฃ:', pdfData.byteLength + ' ๋ฐ์ดํธ'); | |
| return pdfjsLib.getDocument({data: pdfData}).promise; | |
| }) | |
| .then(async pdf => { | |
| console.log('PDF ๋ฌธ์ ๋ก๋ ์๋ฃ. ํ์ด์ง ์:', pdf.numPages); | |
| const pdfPages = []; | |
| const progressElement = viewer.querySelector('p'); | |
| for(let p = 1; p <= pdf.numPages; p++) { | |
| if (progressElement) { | |
| progressElement.textContent = `PDF ํ์ด์ง ๋ก๋ฉ ์ค... (${p}/${pdf.numPages})`; | |
| } | |
| try { | |
| const pg = await pdf.getPage(p); | |
| const vp = pg.getViewport({scale: 1}); | |
| const c = document.createElement('canvas'); | |
| c.width = vp.width; | |
| c.height = vp.height; | |
| await pg.render({canvasContext: c.getContext('2d'), viewport: vp}).promise; | |
| pdfPages.push({src: c.toDataURL(), thumb: c.toDataURL()}); | |
| } catch (pageError) { | |
| console.error(`ํ์ด์ง ${p} ๋ ๋๋ง ์ค๋ฅ:`, pageError); | |
| } | |
| } | |
| console.log('๋ชจ๋ ํ์ด์ง ๋ ๋๋ง ์๋ฃ:', pdfPages.length); | |
| if (pdfPages.length > 0) { | |
| createFlipBook(pdfPages); | |
| } else { | |
| throw new Error('PDF์์ ํ์ด์ง๋ฅผ ์ถ์ถํ ์ ์์ต๋๋ค.'); | |
| } | |
| }) | |
| .catch(error => { | |
| console.error('PDF ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์:', error); | |
| viewer.innerHTML = `<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;"><p style="color:red;font-size:16px;">PDF๋ฅผ ๋ก๋ํ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค:<br>${error.message}</p><button id="backBtn" style="margin-top:20px;padding:10px 20px;background:#0077c2;color:white;border:none;border-radius:4px;cursor:pointer;">ํ์ผ๋ก ๋์๊ฐ๊ธฐ</button></div>`; | |
| document.getElementById('backBtn').addEventListener('click', function() { | |
| toggle(true); | |
| }); | |
| }); | |
| } else { | |
| // ์ ๋ก๋๋ ํ๋ก์ ํธ ๋ณด๊ธฐ | |
| console.log('๋ก์ปฌ ์ ๋ก๋๋ ํ๋ก์ ํธ ๋ ๋๋ง:', pages.length + 'ํ์ด์ง'); | |
| createFlipBook(pages); | |
| } | |
| } | |
| function createFlipBook(pages) { | |
| console.log('FlipBook ์์ฑ ์์. ํ์ด์ง ์:', pages.length); | |
| try { | |
| // ํ๋ฉด ๋น์จ ๊ณ์ฐ | |
| const calculateAspectRatio = () => { | |
| const windowWidth = window.innerWidth; | |
| const windowHeight = window.innerHeight; | |
| const aspectRatio = windowWidth / windowHeight; | |
| // ๋๋น ๋๋ ๋์ด ๊ธฐ์ค์ผ๋ก ์ต๋ 90% ์ ํ | |
| let width, height; | |
| if (aspectRatio > 1) { // ๊ฐ๋ก ํ๋ฉด | |
| height = Math.min(windowHeight * 0.9, windowHeight - 40); | |
| width = height * aspectRatio * 0.8; // ๊ฐ๋ก ํ๋ฉด์์๋ ์ฝ๊ฐ ์ค์ | |
| if (width > windowWidth * 0.9) { | |
| width = windowWidth * 0.9; | |
| height = width / (aspectRatio * 0.8); | |
| } | |
| } else { // ์ธ๋ก ํ๋ฉด | |
| width = Math.min(windowWidth * 0.9, windowWidth - 40); | |
| height = width / aspectRatio * 0.9; // ์ธ๋ก ํ๋ฉด์์๋ ์ฝ๊ฐ ๋๋ฆผ | |
| if (height > windowHeight * 0.9) { | |
| height = windowHeight * 0.9; | |
| width = height * aspectRatio * 0.9; | |
| } | |
| } | |
| // ์ต์ ์ฌ์ด์ฆ ๋ฐํ | |
| return { | |
| width: Math.round(width), | |
| height: Math.round(height) | |
| }; | |
| }; | |
| // ์ด๊ธฐ ํ๋ฉด ๋น์จ ๊ณ์ฐ | |
| const size = calculateAspectRatio(); | |
| viewer.style.width = size.width + 'px'; | |
| viewer.style.height = size.height + 'px'; | |
| fb = new FlipBook(viewer, { | |
| pages: pages, | |
| viewMode: 'webgl', | |
| autoSize: true, | |
| flipDuration: 800, | |
| backgroundColor: '#fff', | |
| /* ๐ ๋ด์ฅ ์ฌ์ด๋ */ | |
| sound: true, | |
| assets: {flipMp3: 'static/turnPage2.mp3', hardFlipMp3: 'static/turnPage2.mp3'}, | |
| controlsProps: { | |
| enableFullscreen: true, | |
| enableToc: true, | |
| enableDownload: false, | |
| enablePrint: false, | |
| enableZoom: true, | |
| enableShare: false, | |
| enableSearch: true, | |
| enableAutoPlay: true, | |
| enableAnnotation: false, | |
| enableSound: true, | |
| enableLightbox: false, | |
| layout: 10, // ๋ ์ด์์ ์ต์ | |
| skin: 'light', // ์คํจ ์คํ์ผ | |
| autoNavigationTime: 3600, // ์๋ ๋๊น ์๊ฐ(์ด) | |
| hideControls: false, // ์ปจํธ๋กค ์จ๊น ๋นํ์ฑํ | |
| paddingTop: 10, // ์๋จ ํจ๋ฉ | |
| paddingLeft: 10, // ์ข์ธก ํจ๋ฉ | |
| paddingRight: 10, // ์ฐ์ธก ํจ๋ฉ | |
| paddingBottom: 10, // ํ๋จ ํจ๋ฉ | |
| pageTextureSize: 1024, // ํ์ด์ง ํ ์ค์ฒ ํฌ๊ธฐ | |
| thumbnails: true, // ์ฌ๋ค์ผ ํ์ฑํ | |
| autoHideControls: false, // ์๋ ์จ๊น ๋นํ์ฑํ | |
| controlsTimeout: 8000 // ์ปจํธ๋กค ํ์ ์๊ฐ ์ฐ์ฅ | |
| } | |
| }); | |
| // ํ๋ฉด ํฌ๊ธฐ ๋ณ๊ฒฝ ์ FlipBook ํฌ๊ธฐ ์กฐ์ | |
| window.addEventListener('resize', () => { | |
| if (fb) { | |
| const newSize = calculateAspectRatio(); | |
| viewer.style.width = newSize.width + 'px'; | |
| viewer.style.height = newSize.height + 'px'; | |
| fb.resize(); | |
| } | |
| }); | |
| // FlipBook ์์ฑ ํ ์ปจํธ๋กค๋ฐ ๊ฐ์ ํ์ | |
| setTimeout(() => { | |
| try { | |
| // ์ปจํธ๋กค๋ฐ ๊ด๋ จ ์์ ์ฐพ๊ธฐ ๋ฐ ์คํ์ผ ์ ์ฉ | |
| const menuBars = document.querySelectorAll('.flipbook-container .fb3d-menu-bar'); | |
| if (menuBars && menuBars.length > 0) { | |
| menuBars.forEach(menuBar => { | |
| menuBar.style.display = 'block'; | |
| menuBar.style.opacity = '1'; | |
| menuBar.style.visibility = 'visible'; | |
| menuBar.style.zIndex = '9999'; | |
| }); | |
| } | |
| } catch (e) { | |
| console.warn('์ปจํธ๋กค๋ฐ ์คํ์ผ ์ ์ฉ ์ค ์ค๋ฅ:', e); | |
| } | |
| }, 1000); | |
| console.log('FlipBook ์์ฑ ์๋ฃ'); | |
| } catch (error) { | |
| console.error('FlipBook ์์ฑ ์ค ์ค๋ฅ ๋ฐ์:', error); | |
| alert('FlipBook์ ์์ฑํ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: ' + error.message); | |
| } | |
| } | |
| /* โโ ๋ค๋น๊ฒ์ด์ โโ */ | |
| $id('homeBtn').onclick=()=>{ | |
| if(fb) { | |
| fb.destroy(); | |
| viewer.innerHTML = ''; | |
| fb = null; | |
| } | |
| toggle(true); | |
| }; | |
| function toggle(showHome){ | |
| $id('home').style.display=showHome?'block':'none'; | |
| $id('viewerPage').style.display=showHome?'none':'block'; | |
| $id('homeBtn').style.display=showHome?'none':'inline-block'; | |
| // ์ถ๊ฐ: ์ ์ฒด ํ๋ฉด ๋ชจ๋์์ homeBtn ์์น ์กฐ์ | |
| if(!showHome) { | |
| $id('homeBtn').style.position = 'fixed'; | |
| $id('homeBtn').style.top = '20px'; | |
| $id('homeBtn').style.left = '20px'; | |
| $id('homeBtn').style.zIndex = '9999'; // ์ต์์ z-index๋ก ๋ณ๊ฒฝ | |
| $id('homeBtn').style.fontSize = '24px'; // ํฌ๊ธฐ ์ฆ๊ฐ | |
| $id('homeBtn').style.width = '48px'; | |
| $id('homeBtn').style.height = '48px'; | |
| $id('homeBtn').style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)'; // ๊ทธ๋ฆผ์ ์ถ๊ฐ | |
| // ๋ฐฐ๊ฒฝ ์ค๋ฒ๋ ์ด ์ถ๊ฐ | |
| document.body.style.backgroundColor = '#3a3a3a'; | |
| } else { | |
| $id('homeBtn').style.position = ''; | |
| $id('homeBtn').style.top = ''; | |
| $id('homeBtn').style.left = ''; | |
| $id('homeBtn').style.zIndex = ''; | |
| $id('homeBtn').style.fontSize = ''; | |
| $id('homeBtn').style.width = ''; | |
| $id('homeBtn').style.height = ''; | |
| $id('homeBtn').style.boxShadow = ''; | |
| // ๋ฐฐ๊ฒฝ ์๋๋๋ก | |
| document.body.style.backgroundColor = '#f0f0f0'; | |
| } | |
| } | |
| // ํ์ด์ง ๋ก๋ ์ ์๋ฒ PDF ๋ก๋ | |
| window.addEventListener('DOMContentLoaded', loadServerPDFs); | |
| </script> | |
| </body></html> | |
| """ | |
| # API ์๋ํฌ์ธํธ: PDF ํ๋ก์ ํธ ๋ชฉ๋ก | |
| async def get_pdf_projects(): | |
| return generate_pdf_projects() | |
| # API ์๋ํฌ์ธํธ: PDF ์ธ๋ค์ผ ์์ฑ | |
| async def get_pdf_thumbnail(path: str): | |
| try: | |
| import fitz # PyMuPDF | |
| # PDF ํ์ผ ์ด๊ธฐ | |
| doc = fitz.open(path) | |
| # ์ฒซ ํ์ด์ง ๊ฐ์ ธ์ค๊ธฐ | |
| if doc.page_count > 0: | |
| page = doc[0] | |
| # ์ธ๋ค์ผ์ฉ ์ด๋ฏธ์ง ๋ ๋๋ง (ํด์๋ ์กฐ์ ) | |
| pix = page.get_pixmap(matrix=fitz.Matrix(0.5, 0.5)) | |
| img_data = pix.tobytes("png") | |
| # Base64 ์ธ์ฝ๋ฉ | |
| b64_img = base64.b64encode(img_data).decode('utf-8') | |
| return {"thumbnail": f"data:image/png;base64,{b64_img}"} | |
| return {"thumbnail": None} | |
| except Exception as e: | |
| return {"error": str(e), "thumbnail": None} | |
| async def get_pdf_content(path: str): | |
| try: | |
| # ํ์ผ ์กด์ฌ ์ฌ๋ถ ํ์ธ | |
| pdf_path = pathlib.Path(path) | |
| if not pdf_path.exists(): | |
| return {"error": f"ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค: {path}"}, 404 | |
| # ํ์ผ ์ฝ๊ธฐ | |
| with open(path, "rb") as pdf_file: | |
| content = pdf_file.read() | |
| # ํ์ผ๋ช ์ฒ๋ฆฌ - URL ์ธ์ฝ๋ฉ์ผ๋ก ํ๊ธ ๋ฑ ํน์ ๋ฌธ์ ์ฒ๋ฆฌ | |
| import urllib.parse | |
| filename = pdf_path.name | |
| encoded_filename = urllib.parse.quote(filename) | |
| # ์๋ต ํค๋ ์ค์ - RFC 6266 ํ์ค ์ฌ์ฉ | |
| headers = { | |
| "Content-Type": "application/pdf", | |
| "Content-Disposition": f"inline; filename=\"{encoded_filename}\"; filename*=UTF-8''{encoded_filename}" | |
| } | |
| # ํ์ผ ์ฝํ ์ธ ์ง์ ๋ฐํ (dict๊ฐ ์๋ Response ๊ฐ์ฒด) | |
| from fastapi.responses import Response | |
| return Response(content=content, media_type="application/pdf") | |
| except Exception as e: | |
| import traceback | |
| error_details = traceback.format_exc() | |
| print(f"PDF ์ฝํ ์ธ ๋ก๋ ์ค๋ฅ: {str(e)}\n{error_details}") | |
| # ์ค๋ฅ ์๋ต ๋ฐํ (JSON ํ์) | |
| from fastapi.responses import JSONResponse | |
| return JSONResponse(content={"error": str(e)}, status_code=500) | |
| async def root(): | |
| return HTML | |
| if __name__ == "__main__": | |
| uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860))) |