CursiveCurse/Markup-To-Handwritten-PDF
1
1# app.py (for Gradio)2import gradio as gr3import note_generator # Your refactored script4import io5import os6import time # To create unique filenames if needed7import traceback # For detailed error logging8 9OUTPUT_DIR = "outputs" # Optional: Directory to save generated PDFs temporarily10os.makedirs(OUTPUT_DIR, exist_ok=True)11 12# --- Load Markup Guide ---13try:14 with open("MARKUP_GUIDE.md", "r", encoding="utf-8") as f:15 markup_guide_content = f.read()16except FileNotFoundError:17 markup_guide_content = "Error: MARKUP_GUIDE.md not found."18except Exception as e:19 markup_guide_content = f"Error reading markup guide: {e}"20 21# --- Core PDF Generation Function for Gradio ---22def generate_notes_pdf(markup_text_paste, markup_file_upload_path): # Renamed input var for clarity23 """24 Takes markup text (either pasted or from file path), generates PDF,25 and returns the path to the generated PDF file.26 """27 markup_text = ""28 input_source_name = ""29 30 # markup_file_upload_path will be the temporary file path provided by Gradio31 if markup_file_upload_path is not None:32 try:33 # Read directly from the provided path34 with open(markup_file_upload_path, "r", encoding="utf-8") as f:35 markup_text = f.read()36 input_source_name = os.path.basename(markup_file_upload_path) # Get filename part37 print(f"Using uploaded file: {input_source_name} (path: {markup_file_upload_path})")38 except Exception as e:39 gr.Warning(f"Error reading uploaded file: {e}")40 return None, f"Error reading file: {e}" # Return None for file, and error message41 elif markup_text_paste and markup_text_paste.strip():42 markup_text = markup_text_paste43 input_source_name = "Pasted Text"44 print("Using pasted text.")45 else:46 gr.Warning("Please provide markup text via paste or upload a file.")47 return None, "No input provided." # Return None for file, and status message48 49 if not markup_text:50 return None, "Markup text is empty."51 52 print(f"Generating PDF from '{input_source_name}'...")53 try:54 # 1. Parse the markup55 parsed_elements = note_generator.parse_markup(markup_text)56 57 if not parsed_elements:58 gr.Warning("Parsing resulted in no content elements. Cannot generate PDF.")59 return None, "Parsing resulted in empty content."60 else:61 # 2. Generate PDF bytes IN MEMORY62 pdf_bytes = note_generator.generate_pdf_bytes(parsed_elements)63 64 if not pdf_bytes:65 gr.Error("PDF generation failed or produced empty output.")66 return None, "PDF generation failed."67 else:68 # 3. Save bytes to a temporary file for Gradio output69 output_filename = os.path.join(OUTPUT_DIR, f"generated_notes_{int(time.time())}.pdf")70 # Or just use a fixed name:71 # output_filename = os.path.join(OUTPUT_DIR, "generated_notes.pdf")72 73 with open(output_filename, "wb") as f_out:74 f_out.write(pdf_bytes)75 76 print(f"Successfully generated PDF: {output_filename}")77 # Return the path to the file for Gradio's File output component78 # and a success message for the status Textbox79 return output_filename, f"๐ PDF generated successfully from '{input_source_name}'!"80 81 except Exception as e:82 gr.Error(f"An error occurred during PDF generation: {e}")83 print(f"Error details: {e}") # Log detailed error84 traceback.print_exc() # Print traceback to console/logs85 # Return None for file, and a user-friendly error message86 return None, f"An error occurred: {str(e)[:100]}..." # Truncate long errors for UI87 88# --- Gradio Interface Definition ---89with gr.Blocks(theme=gr.themes.Soft(), title="Handwritten Notes PDF Generator") as demo:90 gr.Markdown("# Handwritten Notes PDF Generator")91 gr.Markdown("Paste your formatted text or upload a `.txt` file to generate notes based on the markup guide below.")92 93 with gr.Row():94 with gr.Column(scale=2):95 with gr.Tabs():96 with gr.TabItem("Paste Text"):97 input_paste = gr.Textbox(lines=15, label="Paste Formatted Markup Here", placeholder="[MAIN_TITLE]My Notes[/MAIN_TITLE]\n[SUB_TITLE]Topic 1[/SUB_TITLE]\n[POINT:->]First point...\n[BOX]\nContent inside a box.\n[/BOX]")98 with gr.TabItem("Upload File"):99 # *** THE FIX IS HERE ***100 input_file = gr.File(label="Upload .txt Markup File", type="filepath", file_types=[".txt"])101 102 generate_button = gr.Button("Generate Notes PDF", variant="primary")103 status_message = gr.Textbox(label="Status", interactive=False) # To show success/error messages104 105 with gr.Column(scale=1):106 output_pdf = gr.File(label="Download Generated PDF", interactive=False)107 108 with gr.Accordion("Markup Guide", open=False):109 gr.Markdown(markup_guide_content)110 111 # --- Event Handling ---112 # Define what happens when the button is clicked113 generate_button.click(114 fn=generate_notes_pdf,115 inputs=[input_paste, input_file], # input_file now provides a path string116 outputs=[output_pdf, status_message] # Map outputs to components117 )118 119 # Clear other input when one is used (optional, but good UX)120 # Minor adjustments needed for updates in older Gradio versions if gr.update is needed121 # But simple clearing might work without explicit gr.update122 def clear_paste_if_file(file_path):123 if file_path is not None:124 # For older Gradio, returning the component type with a new value works125 return gr.Textbox(value="")126 return gr.Textbox() # No change127 128 def clear_file_if_paste(text_content):129 if text_content and text_content.strip():130 # Return the component type with value=None to clear131 return gr.File(value=None)132 return gr.File() # No change133 134 # Connect the change events135 input_file.change(fn=clear_paste_if_file, inputs=input_file, outputs=input_paste)136 input_paste.change(fn=clear_file_if_paste, inputs=input_paste, outputs=input_file)137 138 139# --- Launch the Gradio App ---140if __name__ == "__main__":141 # Ensure fonts are available before launching142 if not os.path.exists(note_generator.FONT_MAIN_PATH) or not os.path.exists(note_generator.FONT_SUB_PATH):143 print("\n" + "="*30)144 print("ERROR: Font files not found!")145 print(f"Make sure '{note_generator.FONT_MAIN_PATH}' and '{note_generator.FONT_SUB_PATH}' are in the same directory as app.py.")146 print("="*30 + "\n")147 else:148 print("Font files found. Ready to launch.")149 150 # Share=True creates a public link151 # Set debug=True for more detailed logs152 demo.launch(debug=True) # Share=True removed for safety unless needed