From d71c191d583331f6bbadabd5677bf57e6cefbaff Mon Sep 17 00:00:00 2001 From: Priyanshu Verma Date: Wed, 6 Nov 2024 13:15:08 +0000 Subject: [PATCH] feat: Text-to-handwriting Magic Tool added --- .gitignore | 3 +- app/api_routes.py | 74 +++++++++- app/fonts/handwriting.pkl | Bin 0 -> 131771 bytes app/fonts/handwriting.ttf | Bin 0 -> 34744 bytes client/bun.lockb | Bin 330562 -> 330562 bytes .../src/components/modals/command-modal.tsx | 7 + .../src/components/modals/ttH-magic-modal.tsx | 136 ++++++++++++++++++ client/src/contexts/modals.tsx | 2 + client/src/index.css | 2 +- client/src/pages/Chatbot.tsx | 11 ++ client/src/stores/modal-store.ts | 1 + 11 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 app/fonts/handwriting.pkl create mode 100644 app/fonts/handwriting.ttf create mode 100644 client/src/components/modals/ttH-magic-modal.tsx diff --git a/.gitignore b/.gitignore index 50da8f0..601b34f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ /tailwind/* *.log /temp_audio/* -app/temp_audio/* \ No newline at end of file +app/temp_audio/* +app/temp_pdfs/* \ No newline at end of file diff --git a/app/api_routes.py b/app/api_routes.py index d7aba91..e41a074 100644 --- a/app/api_routes.py +++ b/app/api_routes.py @@ -1,6 +1,8 @@ from flask import Flask, Blueprint, request, jsonify, session, Response, send_file +from fpdf import FPDF import re import os +import uuid from sqlalchemy import func from .models import User, Chatbot, Chat, Image, Comment, ChatbotVersion from sqlalchemy.exc import IntegrityError @@ -792,7 +794,6 @@ def api_tts(): return jsonify({"success": False, "message": "Text not found"}), 400 filepath = text_to_mp3(text) - print(filepath) response = send_file(filepath, as_attachment=True) @@ -850,3 +851,74 @@ def api_ocr(): except Exception as e: return jsonify({"success": False, "message": str(e)}), 500 + + +class HandwrittenPDF(FPDF): + def header(self): + pass + + def footer(self): + pass + + def add_custom_font(self, font_name, font_path): + self.add_font(font_name, "", font_path, uni=True) + + +@api_bp.route("/api/tth", methods=["POST"]) +@jwt_required() +def api_tth(): + try: + data = request.get_json() + text = data.get("text", "") + font_size = data.get("font_size", 12) + pdf = HandwrittenPDF() + + custom_font_name = "Handwritten" # Font identifier + font_path = os.path.join(os.path.dirname(__file__), "fonts", "handwriting.ttf") + pdf.add_custom_font(custom_font_name, font_path) + + pdf.add_page() + pdf.set_font(custom_font_name, size=font_size) # Use the custom font + pdf.set_text_color(0, 0, 255) # Blue ink color + + # Text formatting + line_height = font_size * 0.9 + margin = 10 + page_width = pdf.w - 2 * margin + pdf.set_left_margin(margin) + pdf.set_right_margin(margin) + + # Wrap text to fit within the page width + lines = text.split("\n") + for line in lines: + words = line.split() + current_line = "" + for word in words: + test_line = f"{current_line} {word}".strip() + if pdf.get_string_width(test_line) <= page_width: + current_line = test_line + else: + pdf.cell(0, line_height, current_line, ln=True) + current_line = word + if current_line: + pdf.cell(0, line_height, current_line, ln=True) + pdf.ln(line_height * 0.2) # Add extra line space after each paragraph + + base_path = os.path.dirname( + os.path.abspath(__file__) + ) # Get the absolute path of the script + temp_dir = os.path.join(base_path, "temp_pdfs") + os.makedirs(temp_dir, exist_ok=True) + # Save the PDF + output_path = f"{temp_dir}/{uuid.uuid4()}.pdf" + pdf.output(output_path) + + response = send_file(output_path, as_attachment=True) + + @response.call_on_close + def remove_file(): + os.remove(output_path) + + return response + except Exception as e: + return jsonify({"success": False, "message": str(e)}), 500 diff --git a/app/fonts/handwriting.pkl b/app/fonts/handwriting.pkl new file mode 100644 index 0000000000000000000000000000000000000000..4d8a19bc3994d7c7fabea9aa9f9d0287690f82f0 GIT binary patch literal 131771 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 literal 0 HcmV?d00001 diff --git a/client/bun.lockb b/client/bun.lockb index 2bd537632c812d36ac2d083c49611a013caf6782..f0da48e41af637a08ff52594c2bf5c8c16ce4c23 100755 GIT binary patch delta 49 zcmX>!P2|utk%kt=7N#xC!mgZ*aYn{^hGu#u(`8(lk8?1_8Jg-D>KU}Zc4glF+La~z FC;)h44@3X} delta 49 tcmX>!P2|utk%kt=7N#xC!mgZ53{Ws##+CUv2aMbP+Ld|xYgd-=qX01|3=04N diff --git a/client/src/components/modals/command-modal.tsx b/client/src/components/modals/command-modal.tsx index 3793722..a9c0bc6 100644 --- a/client/src/components/modals/command-modal.tsx +++ b/client/src/components/modals/command-modal.tsx @@ -19,6 +19,7 @@ import { Image, Languages, PanelTopInactive, + PenLineIcon, Plus, TextCursorInput, } from "lucide-react"; @@ -28,6 +29,7 @@ import { useOcrMagic, useSettingsModal, useTranslateMagicModal, + usettHMagic, useTtsMagicModal, } from "@/stores/modal-store"; import { useNavigate } from "react-router-dom"; @@ -41,6 +43,7 @@ export function CommandModal() { const imagineModal = useImagineModal(); const ttsModal = useTtsMagicModal(); const ocrModal = useOcrMagic(); + const ttHModal = usettHMagic(); const translateModal = useTranslateMagicModal(); const navigate = useNavigate(); @@ -79,6 +82,10 @@ export function CommandModal() { Text Extractor (OCR) + ttHModal.onOpen({ text: "" })}> + + Text To Handwriting + imagineModal.onOpen()}> {t("commandbox.image_generation")} diff --git a/client/src/components/modals/ttH-magic-modal.tsx b/client/src/components/modals/ttH-magic-modal.tsx new file mode 100644 index 0000000..3358836 --- /dev/null +++ b/client/src/components/modals/ttH-magic-modal.tsx @@ -0,0 +1,136 @@ +import { + AlertDialog, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { usettHMagic } from "@/stores/modal-store"; +import { useEffect, useState } from "react"; +import { Button } from "../ui/button"; +import toast from "react-hot-toast"; +import { Textarea } from "../ui/textarea"; +import { Download, X } from "lucide-react"; +import { Label } from "../ui/label"; +import { Input } from "../ui/input"; +import axios from "axios"; +import { SERVER_URL } from "@/lib/utils"; + +const markdownToPlainText = (markdown: string) => { + return markdown + .replace(/(\*\*|__)(.*?)\1/g, "$2") // Bold + .replace(/(\*|_)(.*?)\1/g, "$2") // Italics + .replace(/~~(.*?)~~/g, "$1") // Strikethrough + .replace(/`{1,2}(.*?)`{1,2}/g, "$1") // Inline code + .replace(/### (.*?)\n/g, "$1\n") // H3 + .replace(/## (.*?)\n/g, "$1\n") // H2 + .replace(/# (.*?)\n/g, "$1\n") // H1 + .replace(/>\s?(.*?)(?=\n|$)/g, "$1") // Blockquote + .replace(/^\s*\n/g, "") // Remove empty lines + .replace(/\n+/g, "\n") // Consolidate newlines + .trim(); // Trim whitespace +}; + +export default function TtHMagicModal() { + const modal = usettHMagic(); + const initialText = markdownToPlainText(modal.extras.text || ""); + const [text, setText] = useState(""); + const [loading, setLoading] = useState(false); + const [fontSize, setFontSize] = useState(12); + + // Set initial text when the modal opens + useEffect(() => { + if (modal.isOpen) { + setText(initialText); // Set the initial text from modal extras + } + }, [modal.isOpen, initialText]); // Depend on modal open state and initial text + + // Function to download as PDF + const downloadAsPDF = async () => { + setLoading(true); + try { + const token = localStorage.getItem("token"); + + const authHeaders = { + Authorization: `Bearer ${token || ""}`, + }; + + const response = await axios.post( + `${SERVER_URL}/api/tth`, + { text, font_size: 12 }, + { responseType: "blob", headers: authHeaders } // Important to handle binary data + ); + + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", "handwritten_notes.pdf"); + document.body.appendChild(link); + link.click(); + link.remove(); + toast.success("PDF created successfully!"); + } catch (error) { + console.log("Error creating PDF:", error); + toast.error("Failed to create PDF."); + } finally { + setLoading(false); + } + }; + + return ( + modal.onClose()}> + + + +
+

Text-to-Handwriting Magic

+ +
+
+ + Convert Text to Hand written Notes + +
+