diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 2325631..d3a95ae 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -65,6 +65,7 @@ def _health(): from blueprints.compress_pdf import compress_pdf_bp from blueprints.protect_pdf import protect_pdf_bp from blueprints.unlock_pdf import unlock_pdf_bp + from blueprints.pdf_to_pptx import pdf_pptx_bp app.register_blueprint(pdf_bp) app.register_blueprint(pdf_docx_bp) @@ -82,5 +83,6 @@ def _health(): app.register_blueprint(compress_pdf_bp) app.register_blueprint(protect_pdf_bp) app.register_blueprint(unlock_pdf_bp) + app.register_blueprint(pdf_pptx_bp) return app diff --git a/backend/blueprints/pdf_to_pptx.py b/backend/blueprints/pdf_to_pptx.py new file mode 100644 index 0000000..b5be183 --- /dev/null +++ b/backend/blueprints/pdf_to_pptx.py @@ -0,0 +1,82 @@ +import traceback +from io import BytesIO + +import fitz +from pptx import Presentation +from pptx.util import Emu + +from flask import Blueprint, request, send_file + +from utils.helpers import error, safe_gc_collect +from utils.validators import validate_uploaded_file, validate_pdf_file + +pdf_pptx_bp = Blueprint("pdf_pptx", __name__) + + +@pdf_pptx_bp.route("/convertPptx", methods=["POST"]) +def convert_pdf_to_pptx(): + doc = None + output = None + try: + pdf_file, filename, upload_error = validate_uploaded_file(request, "file") + if upload_error: + return upload_error + + pdf_error = validate_pdf_file(pdf_file, filename) + if pdf_error: + return pdf_error + + pdf_bytes = pdf_file.read() + doc = fitz.open(stream=pdf_bytes, filetype="pdf") + + if len(doc) == 0: + return error("PDF file has no pages", 400) + + prs = Presentation() + prs.slide_width = Emu(int(doc[0].rect.width * 12700)) + prs.slide_height = Emu(int(doc[0].rect.height * 12700)) + + layout_index = min(6, len(prs.slide_layouts) - 1) + blank_layout = prs.slide_layouts[layout_index] + + for page_num in range(len(doc)): + page = doc.load_page(page_num) + mat = fitz.Matrix(200 / 72, 200 / 72) + pix = page.get_pixmap(matrix=mat, alpha=False) + + img_bytes = BytesIO(pix.tobytes(output="png")) + slide = prs.slides.add_slide(blank_layout) + slide.shapes.add_picture( + img_bytes, + 0, 0, + prs.slide_width, + prs.slide_height, + ) + img_bytes.close() + pix = None + + output = BytesIO() + prs.save(output) + output.seek(0) + + base = filename.rsplit(".", 1)[0] + download_name = f"{base}.pptx" + + return send_file( + output, + mimetype="application/vnd.openxmlformats-officedocument.presentationml.presentation", + as_attachment=True, + download_name=download_name, + ) + + except Exception as e: + traceback.print_exc() + return error(str(e), 500) + + finally: + if doc: + try: + doc.close() + except Exception: + pass + safe_gc_collect() diff --git a/backend/requirements.txt b/backend/requirements.txt index 2a3546e..1562743 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -67,3 +67,5 @@ tzdata==2025.2 urllib3==2.5.0 Werkzeug==3.1.3 markdown2>=1.0.0 +pdf2docx>=0.5.8 +python-pptx>=1.0.2 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index aa3c3ab..94d527d 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -41,6 +41,7 @@ const PdfCompress = lazy(() => import("./pages/PdfCompress")); const PdfUnlock = lazy(() => import("./pages/PdfUnlock")); const PdfMetadata = lazy(() => import("./pages/PdfMetadata")); const PdfToText = lazy(() => import("./pages/PdfToText")); +const PdfPptx = lazy(() => import("./pages/PdfPptx")); const PdfInfo = lazy(() => import("./pages/PdfInfo")); const PdfPageNumber = lazy(() => import("./pages/PdfPageNumber")); const CsvToJson = lazy(() => import("./pages/CsvtoJson")); @@ -87,6 +88,7 @@ function App() { } /> } /> } /> + } /> } /> } /> diff --git a/frontend/src/data/toolsData.jsx b/frontend/src/data/toolsData.jsx index d3e8166..6aead9a 100644 --- a/frontend/src/data/toolsData.jsx +++ b/frontend/src/data/toolsData.jsx @@ -23,6 +23,7 @@ import { Type, BookOpen, Hash, + Presentation, } from "lucide-react"; const tools = [ @@ -381,7 +382,18 @@ const tools = [ path: "/csv-to-json", gradient: "from-emerald-500/10 to-blue-500/10", iconGradient: "from-emerald-500 to-blue-500", - } + }, + { + id: "pdf-to-powerpoint", + name: "PDF to PowerPoint", + category: "Conversion Tools", + icon: , + description: + "Convert PDF documents into editable PowerPoint (.pptx) presentations.", + path: "/pdf-to-powerpoint", + gradient: "from-red-500/10 to-orange-500/10", + iconGradient: "from-red-500 to-orange-500", + }, ]; diff --git a/frontend/src/pages/PdfPptx.jsx b/frontend/src/pages/PdfPptx.jsx new file mode 100644 index 0000000..4ce1fba --- /dev/null +++ b/frontend/src/pages/PdfPptx.jsx @@ -0,0 +1,44 @@ +import { useCallback } from "react"; +import ToolPageTemplate from "../components/ToolPageTemplate"; +import { FileText } from "lucide-react"; + +function PdfPptx() { + const validateFile = useCallback((selectedFile) => { + if (selectedFile && selectedFile.type === "application/pdf") { + return { + isValid: true, + message: `File "${selectedFile.name}" selected (${( + selectedFile.size / 1024 + ).toFixed(1)} KB)`, + }; + } + return { + isValid: false, + message: "Please select a valid PDF file.", + }; + }, []); + + const getDownloadFilename = (fileName) => + fileName.replace(/\.pdf$/i, ".pptx"); + + return ( + } + defaultText="Choose PDF file or drag & drop here" + supportText="Each PDF page becomes a separate slide." + /> + ); +} + +export default PdfPptx;