Spaces:
				
			
			
	
			
			
		Sleeping
		
	
	
	
			
			
	
	
	
	
		
		
		Sleeping
		
	Commit 
							
							·
						
						9e2aa25
	
1
								Parent(s):
							
							371facd
								
Refactor code structure for improved readability and maintainability
Browse files- .gitignore +140 -0
- .python-version +1 -0
- README.md +145 -0
- dummy.txt +24 -0
- fonts/QEJulianDean.ttf +0 -0
- fonts/QEKevinKnowles.ttf +0 -0
- main.py +245 -0
- pyproject.toml +9 -0
- uv.lock +0 -0
    	
        .gitignore
    ADDED
    
    | @@ -0,0 +1,140 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # Byte-compiled / optimized / DLL files
         | 
| 2 | 
            +
            __pycache__/
         | 
| 3 | 
            +
            *.py[cod]
         | 
| 4 | 
            +
            *$py.class
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # C extensions
         | 
| 7 | 
            +
            *.so
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # Distribution / packaging
         | 
| 10 | 
            +
            .Python
         | 
| 11 | 
            +
            build/
         | 
| 12 | 
            +
            develop-eggs/
         | 
| 13 | 
            +
            dist/
         | 
| 14 | 
            +
            downloads/
         | 
| 15 | 
            +
            eggs/
         | 
| 16 | 
            +
            .eggs/
         | 
| 17 | 
            +
            lib/
         | 
| 18 | 
            +
            lib64/
         | 
| 19 | 
            +
            parts/
         | 
| 20 | 
            +
            sdist/
         | 
| 21 | 
            +
            var/
         | 
| 22 | 
            +
            wheels/
         | 
| 23 | 
            +
            share/python-wheels/
         | 
| 24 | 
            +
            *.egg-info/
         | 
| 25 | 
            +
            .installed.cfg
         | 
| 26 | 
            +
            *.egg
         | 
| 27 | 
            +
            MANIFEST
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            # PyInstaller
         | 
| 30 | 
            +
            #  Usually these files are written by a python script from a template
         | 
| 31 | 
            +
            #  before PyInstaller builds the exe, so as to inject date/other infos into it.
         | 
| 32 | 
            +
            *.manifest
         | 
| 33 | 
            +
            *.spec
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            # Installer logs
         | 
| 36 | 
            +
            pip-log.txt
         | 
| 37 | 
            +
            pip-delete-this-directory.txt
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            # Unit test / coverage reports
         | 
| 40 | 
            +
            htmlcov/
         | 
| 41 | 
            +
            .tox/
         | 
| 42 | 
            +
            .nox/
         | 
| 43 | 
            +
            .coverage
         | 
| 44 | 
            +
            .coverage.*
         | 
| 45 | 
            +
            .cache
         | 
| 46 | 
            +
            nosetests.xml
         | 
| 47 | 
            +
            coverage.xml
         | 
| 48 | 
            +
            *.cover
         | 
| 49 | 
            +
            *.py,cover
         | 
| 50 | 
            +
            .hypothesis/
         | 
| 51 | 
            +
            .pytest_cache/
         | 
| 52 | 
            +
            cover/
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            # Translations
         | 
| 55 | 
            +
            *.mo
         | 
| 56 | 
            +
            *.pot
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            # Django stuff:
         | 
| 59 | 
            +
            *.log
         | 
| 60 | 
            +
            local_settings.py
         | 
| 61 | 
            +
            db.sqlite3
         | 
| 62 | 
            +
            db.sqlite3-journal
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            # Flask stuff:
         | 
| 65 | 
            +
            instance/
         | 
| 66 | 
            +
            .webassets-cache
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            # Scrapy stuff:
         | 
| 69 | 
            +
            .scrapy
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            # Sphinx documentation
         | 
| 72 | 
            +
            docs/_build/
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            # PyBuilder
         | 
| 75 | 
            +
            .pybuilder/
         | 
| 76 | 
            +
            target/
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            # Jupyter Notebook
         | 
| 79 | 
            +
            .ipynb_checkpoints
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            # IPython
         | 
| 82 | 
            +
            profile_default/
         | 
| 83 | 
            +
            ipython_config.py
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            # pyenv
         | 
| 86 | 
            +
            #   For a library or package, you might want to ignore these files since the code is
         | 
| 87 | 
            +
            #   intended to run in multiple environments; otherwise, check them in:
         | 
| 88 | 
            +
            # .python-version
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            # pipenv
         | 
| 91 | 
            +
            #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
         | 
| 92 | 
            +
            #   However, in case of collaboration, if having platform-specific dependencies or dependencies
         | 
| 93 | 
            +
            #   having no cross-platform support, pipenv may install dependencies that don't work, or not
         | 
| 94 | 
            +
            #   install all needed dependencies.
         | 
| 95 | 
            +
            #Pipfile.lock
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            # PEP 582; used by e.g. github.com/David-OConnor/pyflow
         | 
| 98 | 
            +
            __pypackages__/
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            # Celery stuff
         | 
| 101 | 
            +
            celerybeat-schedule
         | 
| 102 | 
            +
            celerybeat.pid
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            # SageMath parsed files
         | 
| 105 | 
            +
            *.sage.py
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            # Environments
         | 
| 108 | 
            +
            .env
         | 
| 109 | 
            +
            .venv
         | 
| 110 | 
            +
            env/
         | 
| 111 | 
            +
            venv/
         | 
| 112 | 
            +
            ENV/
         | 
| 113 | 
            +
            env.bak/
         | 
| 114 | 
            +
            venv.bak/
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            # Spyder project settings
         | 
| 117 | 
            +
            .spyderproject
         | 
| 118 | 
            +
            .spyproject
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            # Rope project settings
         | 
| 121 | 
            +
            .ropeproject
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            # mkdocs documentation
         | 
| 124 | 
            +
            /site
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            # mypy
         | 
| 127 | 
            +
            .mypy_cache/
         | 
| 128 | 
            +
            .dmypy.json
         | 
| 129 | 
            +
            dmypy.json
         | 
| 130 | 
            +
             | 
| 131 | 
            +
            # Pyre type checker
         | 
| 132 | 
            +
            .pyre/
         | 
| 133 | 
            +
             | 
| 134 | 
            +
            # pytype static type analyzer
         | 
| 135 | 
            +
            .pytype/
         | 
| 136 | 
            +
             | 
| 137 | 
            +
            # Cython debug symbols
         | 
| 138 | 
            +
            cython_debug/
         | 
| 139 | 
            +
             | 
| 140 | 
            +
            # Ignore changes to dummy.txt when committing
         | 
    	
        .python-version
    ADDED
    
    | @@ -0,0 +1 @@ | |
|  | 
|  | |
| 1 | 
            +
            3.11
         | 
    	
        README.md
    ADDED
    
    | @@ -0,0 +1,145 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # ✍️ Text2Pen
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            A modern, customizable tool to convert your typed text into realistic handwriting on A4 paper.  
         | 
| 4 | 
            +
            Supports multi-column layouts, handwriting font selection, background customization, color, jitter, and more—all with a clean Gradio web UI.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            ---
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            ## 🚀 Features
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            - **Modern Gradio Web UI**: Easy-to-use, clean, and responsive.
         | 
| 11 | 
            +
            - **Direct Text Input or File Upload**: Paste text or upload `.txt` files.
         | 
| 12 | 
            +
            - **Handwriting Fonts**: Choose from built-in fonts or upload your own `.ttf`.
         | 
| 13 | 
            +
            - **Backgrounds**: Use blank, ruled, or custom backgrounds.
         | 
| 14 | 
            +
            - **Multi-Column Layout**: Split your page into 1, 2, or 3 columns.
         | 
| 15 | 
            +
            - **Customizable**: Font size, line spacing, margin, color, jitter, and more.
         | 
| 16 | 
            +
            - **Preview & Download**: Preview pages as images, download as PDF (generated on demand).
         | 
| 17 | 
            +
            - **A4 Output**: Perfect for cheat sheets, study notes, or printing.
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            ---
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ## 📸 Screenshots
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ### Main UI
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ### Customization Tab
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            ### Example Output
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            ---
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            ## 🛠️ Installation
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            1. **Clone the repository:**
         | 
| 40 | 
            +
               ```sh
         | 
| 41 | 
            +
               git clone https://github.com/yourusername/Text2Pen.git
         | 
| 42 | 
            +
               cd Text2Pen
         | 
| 43 | 
            +
               ```
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            2. **Install dependencies:**
         | 
| 46 | 
            +
               ```sh
         | 
| 47 | 
            +
               pip install -r requirements.txt
         | 
| 48 | 
            +
               ```
         | 
| 49 | 
            +
               *Requirements: `gradio`, `pillow`*
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            3. **(Optional) Add Fonts/Backgrounds:**
         | 
| 52 | 
            +
               - Place `.ttf` fonts in the `fonts/` folder.
         | 
| 53 | 
            +
               - Place background images in the `backgrounds/` folder.
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            ---
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            ## 🏃 Usage
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            ### Start the App
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            ```sh
         | 
| 62 | 
            +
            python main.py
         | 
| 63 | 
            +
            ```
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            - The Gradio UI will open in your browser.
         | 
| 66 | 
            +
            - Enter or upload your text, customize options, and generate handwriting.
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            ### Output
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            - **Preview**: See generated pages as images.
         | 
| 71 | 
            +
            - **Download PDF**: Click "Generate & Download PDF" to get a PDF (temporary file, not saved).
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            ---
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            ## 📂 Project Structure
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            ```
         | 
| 78 | 
            +
            Text2Pen/
         | 
| 79 | 
            +
            │
         | 
| 80 | 
            +
            ├── main.py
         | 
| 81 | 
            +
            ├── README.md
         | 
| 82 | 
            +
            ├── requirements.txt
         | 
| 83 | 
            +
            ├── fonts/
         | 
| 84 | 
            +
            │   ├── QEJulianDean.ttf
         | 
| 85 | 
            +
            │   └── QEKevinKnowles.ttf
         | 
| 86 | 
            +
            ├── backgrounds/
         | 
| 87 | 
            +
            │   └── ruled_a4.png
         | 
| 88 | 
            +
            ├── generated/
         | 
| 89 | 
            +
            │   ├── images/
         | 
| 90 | 
            +
            │   └── pdfs/
         | 
| 91 | 
            +
            ├── sample_ui_1.png
         | 
| 92 | 
            +
            ├── sample_ui_2.png
         | 
| 93 | 
            +
            └── sample_output_page.png
         | 
| 94 | 
            +
            ```
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            ---
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            ## ✏️ Customization
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            - **Fonts**: Add your own `.ttf` files to the `fonts/` folder or upload via the UI.
         | 
| 101 | 
            +
            - **Backgrounds**: Add PNG/JPG backgrounds to the `backgrounds/` folder or upload via the UI.
         | 
| 102 | 
            +
            - **Advanced Options**: Adjust font size, line spacing, margin, jitter, columns, and color in the Customization tab.
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            ---
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            ## 📝 Notes
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            - PDF files are generated as temporary files and are not stored permanently.
         | 
| 109 | 
            +
            - Images are saved in `generated/images/` for preview purposes.
         | 
| 110 | 
            +
            - For best results, use high-quality handwriting fonts and A4-sized backgrounds.
         | 
| 111 | 
            +
             | 
| 112 | 
            +
            ---
         | 
| 113 | 
            +
             | 
| 114 | 
            +
            ## 📄 License
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            MIT License
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            ---
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            ## 🙏 Credits
         | 
| 121 | 
            +
             | 
| 122 | 
            +
            - [Gradio](https://gradio.app/)
         | 
| 123 | 
            +
            - [Pillow](https://python-pillow.org/)
         | 
| 124 | 
            +
            - Handwriting fonts from [Google Fonts](https://fonts.google.com/) and [DaFont](https://www.dafont.com/)
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            ---
         | 
| 127 | 
            +
             | 
| 128 | 
            +
            ## 💡 Example Use Cases
         | 
| 129 | 
            +
             | 
| 130 | 
            +
            - Micro cheat sheets for exams
         | 
| 131 | 
            +
            - Study notes that look handwritten
         | 
| 132 | 
            +
            - Generating realistic handwritten letters
         | 
| 133 | 
            +
            - Printing custom notes for planners or journals
         | 
| 134 | 
            +
             | 
| 135 | 
            +
            ---
         | 
| 136 | 
            +
             | 
| 137 | 
            +
            ## 🤝 Contributing
         | 
| 138 | 
            +
             | 
| 139 | 
            +
            Pull requests and suggestions are welcome!
         | 
| 140 | 
            +
             | 
| 141 | 
            +
            ---
         | 
| 142 | 
            +
             | 
| 143 | 
            +
            ## 📧 Contact
         | 
| 144 | 
            +
             | 
| 145 | 
            +
            For questions or feedback, open an issue or contact [your-email@example.com](mailto:your-email@example.com).
         | 
    	
        dummy.txt
    ADDED
    
    | @@ -0,0 +1,24 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            Domain0 in Xen runs all the device drivers for the host machine hardware
         | 
| 2 | 
            +
            Hardware virtualization extensions do NOT offer binary translation acceleration
         | 
| 3 | 
            +
            TinyOS does NOT support updating part of the code at runtime
         | 
| 4 | 
            +
            Grant regions in Tock OS do NOT store kernel states
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Short QnA--
         | 
| 7 | 
            +
            Here is pseudocode of a simple spinlock implementation using test-and-set:
         | 
| 8 | 
            +
            This code achieves mutual exclusion, but what are the two potential problems of this lock implementation?
         | 
| 9 | 
            +
            Answer:
         | 
| 10 | 
            +
            No bounded waiting → starvation
         | 
| 11 | 
            +
            High cache coherence traffic due to frequent test-and-set on the same memory location
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            Consider a hypothetical hard disk drive with 3000 RPM, 100MB/s of data transfer rate, and 10 ms of seek time. If the file system reads data from the disk in a block size of 2MB per access, the latency is [ ] ms and the throughput is [ ] MB/s.
         | 
| 14 | 
            +
            Latency: 0.04 seconds (or 40 ms)
         | 
| 15 | 
            +
            Throughput: 50 MB/s
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Consider a virtualization scenario where the hypervisor and the guest OS use 4-level paging. If nested/extended paging is used, what is the number of memory accesses required to translate a guest virtual address to a host physical address? Also, what is the number for shadow paging?
         | 
| 18 | 
            +
            Nested/Extended Paging: 21 memory accesses
         | 
| 19 | 
            +
            Shadow Paging: 5 memory accesses
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            Why event-driven systems can be more memory efficient than multi-threaded systems?
         | 
| 22 | 
            +
            Event-driven systems use a single stack for scheduling tasks, as opposed to multi-threaded systems which require a separate stack per thread, thus using more memory.
         | 
| 23 | 
            +
             | 
| 24 | 
            +
             | 
    	
        fonts/QEJulianDean.ttf
    ADDED
    
    | Binary file (13.8 kB). View file | 
|  | 
    	
        fonts/QEKevinKnowles.ttf
    ADDED
    
    | Binary file (36.8 kB). View file | 
|  | 
    	
        main.py
    ADDED
    
    | @@ -0,0 +1,245 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            #Importing Library
         | 
| 2 | 
            +
            # import argparse
         | 
| 3 | 
            +
            import random
         | 
| 4 | 
            +
            from PIL import Image, ImageDraw, ImageFont
         | 
| 5 | 
            +
            import gradio as gr
         | 
| 6 | 
            +
            import io
         | 
| 7 | 
            +
            import os
         | 
| 8 | 
            +
            import tempfile
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            A4_WIDTH, A4_HEIGHT = 2480, 3508  # A4 at 300dpi in pixels
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            DEMO_TEXT = """This is a demo of the Text2Pen!
         | 
| 13 | 
            +
            - You can type or paste your own text here.
         | 
| 14 | 
            +
            - Or upload a .txt file.
         | 
| 15 | 
            +
            - Choose a handwriting font, font size, and more.
         | 
| 16 | 
            +
            - Try different columns and jitters for a natural look.
         | 
| 17 | 
            +
            Enjoy!"""
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            # Optionally, provide some bundled fonts/backgrounds
         | 
| 20 | 
            +
            BUILTIN_FONTS = {
         | 
| 21 | 
            +
                "Julian Hand": "fonts/QEJulianDean.ttf",
         | 
| 22 | 
            +
                "Kevin Flower": "fonts/QEKevinKnowles.ttf",
         | 
| 23 | 
            +
                "Upload your own": None
         | 
| 24 | 
            +
            }
         | 
| 25 | 
            +
            BUILTIN_BGS = {
         | 
| 26 | 
            +
                "Blank (White)": None,
         | 
| 27 | 
            +
                "Ruled Paper": "backgrounds/ruled_a4.png",
         | 
| 28 | 
            +
                "Upload your own": None
         | 
| 29 | 
            +
            }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            def create_new_page(bg_path=None, bg_img=None):
         | 
| 32 | 
            +
                if bg_img is not None:
         | 
| 33 | 
            +
                    bg = bg_img.convert("RGBA")
         | 
| 34 | 
            +
                    if bg.size != (A4_WIDTH, A4_HEIGHT):
         | 
| 35 | 
            +
                        bg = bg.resize((A4_WIDTH, A4_HEIGHT), Image.LANCZOS)
         | 
| 36 | 
            +
                    return bg
         | 
| 37 | 
            +
                if bg_path:
         | 
| 38 | 
            +
                    try:
         | 
| 39 | 
            +
                        bg = Image.open(bg_path).convert("RGBA")
         | 
| 40 | 
            +
                        if bg.size != (A4_WIDTH, A4_HEIGHT):
         | 
| 41 | 
            +
                            bg = bg.resize((A4_WIDTH, A4_HEIGHT), Image.LANCZOS)
         | 
| 42 | 
            +
                        return bg
         | 
| 43 | 
            +
                    except Exception:
         | 
| 44 | 
            +
                        pass
         | 
| 45 | 
            +
                # fallback: blank white A4
         | 
| 46 | 
            +
                return Image.new("RGBA", (A4_WIDTH, A4_HEIGHT), (255,255,255,255))
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            def get_text_size(text, font):
         | 
| 49 | 
            +
                if hasattr(font, "getbbox"):
         | 
| 50 | 
            +
                    bbox = font.getbbox(text)
         | 
| 51 | 
            +
                    width = bbox[2] - bbox[0]
         | 
| 52 | 
            +
                    height = bbox[3] - bbox[1]
         | 
| 53 | 
            +
                    return width, height
         | 
| 54 | 
            +
                else:
         | 
| 55 | 
            +
                    return font.getsize(text)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            # Ensure output folders exist
         | 
| 58 | 
            +
            GENERATED_IMG_DIR = "generated/images"
         | 
| 59 | 
            +
            GENERATED_PDF_DIR = "generated/pdfs"
         | 
| 60 | 
            +
            os.makedirs(GENERATED_IMG_DIR, exist_ok=True)
         | 
| 61 | 
            +
            os.makedirs(GENERATED_PDF_DIR, exist_ok=True)
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            def handwriting_render(
         | 
| 64 | 
            +
                textfile,
         | 
| 65 | 
            +
                textinput,
         | 
| 66 | 
            +
                font_choice,
         | 
| 67 | 
            +
                fontfile,
         | 
| 68 | 
            +
                fontsize,
         | 
| 69 | 
            +
                linespacing,
         | 
| 70 | 
            +
                margin,
         | 
| 71 | 
            +
                jitter,
         | 
| 72 | 
            +
                columns,
         | 
| 73 | 
            +
                bg_choice,
         | 
| 74 | 
            +
                bgfile,
         | 
| 75 | 
            +
                color,
         | 
| 76 | 
            +
                preview_first,
         | 
| 77 | 
            +
                download_pdf=False  # new argument to control PDF generation
         | 
| 78 | 
            +
            ):
         | 
| 79 | 
            +
                # Text: file takes priority, else textarea
         | 
| 80 | 
            +
                if textfile is not None:
         | 
| 81 | 
            +
                    text = textfile.read().decode("utf-8")
         | 
| 82 | 
            +
                elif textinput and textinput.strip():
         | 
| 83 | 
            +
                    text = textinput
         | 
| 84 | 
            +
                else:
         | 
| 85 | 
            +
                    return None, None
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                # Font: built-in or uploaded
         | 
| 88 | 
            +
                font_path = BUILTIN_FONTS.get(font_choice)
         | 
| 89 | 
            +
                if font_path is None and fontfile is not None:
         | 
| 90 | 
            +
                    font_path = fontfile.name
         | 
| 91 | 
            +
                try:
         | 
| 92 | 
            +
                    font = ImageFont.truetype(font_path, fontsize)
         | 
| 93 | 
            +
                except Exception:
         | 
| 94 | 
            +
                    font = ImageFont.load_default()
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                # BG: built-in or uploaded
         | 
| 97 | 
            +
                bg_path = BUILTIN_BGS.get(bg_choice)
         | 
| 98 | 
            +
                bg_img = None
         | 
| 99 | 
            +
                if bg_path is None and bgfile is not None:
         | 
| 100 | 
            +
                    bg_img = Image.open(bgfile).convert("RGBA")
         | 
| 101 | 
            +
                elif bg_path is not None:
         | 
| 102 | 
            +
                    try:
         | 
| 103 | 
            +
                        bg_img = Image.open(bg_path).convert("RGBA")
         | 
| 104 | 
            +
                    except Exception:
         | 
| 105 | 
            +
                        bg_img = None
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                col_count = int(columns)
         | 
| 108 | 
            +
                col_width = (A4_WIDTH - (col_count + 1) * margin) // col_count
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                pages = []
         | 
| 111 | 
            +
                BG = create_new_page(bg_img=bg_img)
         | 
| 112 | 
            +
                draw = ImageDraw.Draw(BG)
         | 
| 113 | 
            +
                sheet_width, sheet_height = BG.size
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                col = 0
         | 
| 116 | 
            +
                gap = margin
         | 
| 117 | 
            +
                ht = margin
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                words = text.replace("\n", " \n ").split(" ")
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                for word in words:
         | 
| 122 | 
            +
                    if word == "\n":
         | 
| 123 | 
            +
                        gap = margin + col * (col_width + margin)
         | 
| 124 | 
            +
                        ht += fontsize + linespacing
         | 
| 125 | 
            +
                        continue
         | 
| 126 | 
            +
                    word_width, word_height = get_text_size(word, font)
         | 
| 127 | 
            +
                    # Word wrapping in column
         | 
| 128 | 
            +
                    if gap + word_width > margin + (col + 1) * col_width + col * margin:
         | 
| 129 | 
            +
                        gap = margin + col * (col_width + margin)
         | 
| 130 | 
            +
                        ht += fontsize + linespacing
         | 
| 131 | 
            +
                    # If column overflows vertically, move to next column or page
         | 
| 132 | 
            +
                    if ht + word_height > sheet_height - margin:
         | 
| 133 | 
            +
                        if col < col_count - 1:
         | 
| 134 | 
            +
                            col += 1
         | 
| 135 | 
            +
                            gap = margin + col * (col_width + margin)
         | 
| 136 | 
            +
                            ht = margin
         | 
| 137 | 
            +
                        else:
         | 
| 138 | 
            +
                            pages.append(BG)
         | 
| 139 | 
            +
                            BG = create_new_page(bg_img=bg_img)
         | 
| 140 | 
            +
                            draw = ImageDraw.Draw(BG)
         | 
| 141 | 
            +
                            col = 0
         | 
| 142 | 
            +
                            gap = margin
         | 
| 143 | 
            +
                            ht = margin
         | 
| 144 | 
            +
                    # Draw each character with jitter
         | 
| 145 | 
            +
                    for ch in word:
         | 
| 146 | 
            +
                        ch_width, ch_height = get_text_size(ch, font)
         | 
| 147 | 
            +
                        jitter_x = random.randint(-jitter, jitter) if jitter > 0 else 0
         | 
| 148 | 
            +
                        jitter_y = random.randint(-jitter, jitter) if jitter > 0 else 0
         | 
| 149 | 
            +
                        draw.text((gap + jitter_x, ht + jitter_y), ch, font=font, fill=color)
         | 
| 150 | 
            +
                        gap += ch_width
         | 
| 151 | 
            +
                    gap += get_text_size(" ", font)[0]
         | 
| 152 | 
            +
                pages.append(BG)
         | 
| 153 | 
            +
                # Prepare outputs
         | 
| 154 | 
            +
                img_list = []
         | 
| 155 | 
            +
                if preview_first:
         | 
| 156 | 
            +
                    pages_to_show = [pages[0]]
         | 
| 157 | 
            +
                else:
         | 
| 158 | 
            +
                    pages_to_show = pages
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                # Save images to disk and collect their filepaths for Gradio Gallery
         | 
| 161 | 
            +
                img_paths = []
         | 
| 162 | 
            +
                for idx, page in enumerate(pages_to_show):
         | 
| 163 | 
            +
                    img_filename = os.path.join(GENERATED_IMG_DIR, f"handwriting_page_{idx+1}.png")
         | 
| 164 | 
            +
                    page.save(img_filename)
         | 
| 165 | 
            +
                    img_paths.append(img_filename)
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                # Only generate PDF if requested
         | 
| 168 | 
            +
                pdf_filename = None
         | 
| 169 | 
            +
                if download_pdf:
         | 
| 170 | 
            +
                    with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_pdf:
         | 
| 171 | 
            +
                        if len(pages) == 1:
         | 
| 172 | 
            +
                            pages[0].save(tmp_pdf.name, format="PDF", resolution=300)
         | 
| 173 | 
            +
                        else:
         | 
| 174 | 
            +
                            pages[0].save(tmp_pdf.name, format="PDF", save_all=True, append_images=pages[1:], resolution=300)
         | 
| 175 | 
            +
                        pdf_filename = tmp_pdf.name
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                return img_paths, pdf_filename
         | 
| 178 | 
            +
             | 
| 179 | 
            +
            def gradio_ui():
         | 
| 180 | 
            +
                with gr.Blocks(theme=gr.themes.Soft()) as demo:
         | 
| 181 | 
            +
                    gr.Markdown(
         | 
| 182 | 
            +
                        """
         | 
| 183 | 
            +
                        # ✍️ Text2Pen
         | 
| 184 | 
            +
                        Easily convert your text into realistic handwriting on A4 paper.
         | 
| 185 | 
            +
                        """
         | 
| 186 | 
            +
                    )
         | 
| 187 | 
            +
                    with gr.Tab("Text & Output"):
         | 
| 188 | 
            +
                        with gr.Row(equal_height=True):
         | 
| 189 | 
            +
                            with gr.Column(scale=2):
         | 
| 190 | 
            +
                                textinput = gr.Textbox(
         | 
| 191 | 
            +
                                    label="Enter Text",
         | 
| 192 | 
            +
                                    value=DEMO_TEXT,
         | 
| 193 | 
            +
                                    lines=12,
         | 
| 194 | 
            +
                                    max_lines=30,
         | 
| 195 | 
            +
                                    placeholder="Type or paste your text here..."
         | 
| 196 | 
            +
                                )
         | 
| 197 | 
            +
                                textfile = gr.File(label="Or Upload Text File (.txt)", file_types=[".txt"])
         | 
| 198 | 
            +
                                run_btn = gr.Button("Generate Handwriting", elem_id="generate-btn")
         | 
| 199 | 
            +
                            with gr.Column(scale=1, min_width=180):
         | 
| 200 | 
            +
                                gallery = gr.Gallery(label="Preview Pages", columns=1, height=500)
         | 
| 201 | 
            +
                                pdfout = gr.File(label="Download PDF")
         | 
| 202 | 
            +
                                pdf_btn = gr.Button("Generate & Download PDF", elem_id="pdf-btn")
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                    with gr.Tab("Customization"):
         | 
| 205 | 
            +
                        with gr.Row():
         | 
| 206 | 
            +
                            with gr.Column(scale=1):
         | 
| 207 | 
            +
                                gr.Markdown("### Advanced Options")
         | 
| 208 | 
            +
                                fontsize = gr.Slider(24, 120, value=48, step=1, label="Font Size (pt)")
         | 
| 209 | 
            +
                                linespacing = gr.Slider(0, 50, value=10, step=1, label="Line Spacing (px)")
         | 
| 210 | 
            +
                                margin = gr.Slider(20, 200, value=70, step=1, label="Margin (px)")
         | 
| 211 | 
            +
                                jitter = gr.Slider(0, 10, value=0, step=1, label="Jitter (px, for natural look)")
         | 
| 212 | 
            +
                                columns = gr.Radio(choices=["1", "2", "3"], value="1", label="Columns per Page")
         | 
| 213 | 
            +
                                preview_first = gr.Checkbox(label="Preview Only First Page", value=True)
         | 
| 214 | 
            +
                            with gr.Column(scale=1):
         | 
| 215 | 
            +
                                gr.Markdown("### Font Options")
         | 
| 216 | 
            +
                                font_choice = gr.Dropdown(list(BUILTIN_FONTS.keys()), value="Julian Hand", label="Handwriting Font")
         | 
| 217 | 
            +
                                fontfile = gr.File(label="Or Upload TTF Font", file_types=[".ttf"])
         | 
| 218 | 
            +
                                gr.Markdown("### Background Options")
         | 
| 219 | 
            +
                                color = gr.ColorPicker(label="Handwriting Color", value="#000000")
         | 
| 220 | 
            +
                                bg_choice = gr.Dropdown(list(BUILTIN_BGS.keys()), value="Blank (White)", label="Background")
         | 
| 221 | 
            +
                                bgfile = gr.File(label="Or Upload Background Image (A4 PNG/JPG)", file_types=[".png", ".jpg", ".jpeg"])
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                    # Link the customization tab values to the main tab
         | 
| 224 | 
            +
                    run_btn.click(
         | 
| 225 | 
            +
                        lambda *args: handwriting_render(*args, download_pdf=False),
         | 
| 226 | 
            +
                        inputs=[
         | 
| 227 | 
            +
                            textfile, textinput, font_choice, fontfile, fontsize, linespacing, margin,
         | 
| 228 | 
            +
                            jitter, columns, bg_choice, bgfile, color, preview_first
         | 
| 229 | 
            +
                        ],
         | 
| 230 | 
            +
                        outputs=[gallery, pdfout]  # Always output both, but pdfout will be None unless requested
         | 
| 231 | 
            +
                    )
         | 
| 232 | 
            +
                    # Add a separate button for PDF download
         | 
| 233 | 
            +
                    pdf_btn.click(
         | 
| 234 | 
            +
                        lambda *args: handwriting_render(*args, download_pdf=True),
         | 
| 235 | 
            +
                        inputs=[
         | 
| 236 | 
            +
                            textfile, textinput, font_choice, fontfile, fontsize, linespacing, margin,
         | 
| 237 | 
            +
                            jitter, columns, bg_choice, bgfile, color, preview_first
         | 
| 238 | 
            +
                        ],
         | 
| 239 | 
            +
                        outputs=[gallery, pdfout]
         | 
| 240 | 
            +
                    )
         | 
| 241 | 
            +
                demo.launch()
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            if __name__ == "__main__":
         | 
| 244 | 
            +
                gradio_ui()
         | 
| 245 | 
            +
             | 
    	
        pyproject.toml
    ADDED
    
    | @@ -0,0 +1,9 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            [project]
         | 
| 2 | 
            +
            name = "Text2Pen"
         | 
| 3 | 
            +
            version = "0.1.0"
         | 
| 4 | 
            +
            description = "Add your description here"
         | 
| 5 | 
            +
            readme = "README.md"
         | 
| 6 | 
            +
            requires-python = ">=3.11"
         | 
| 7 | 
            +
            dependencies = [
         | 
| 8 | 
            +
                "gradio>=5.33.0",
         | 
| 9 | 
            +
            ]
         | 
    	
        uv.lock
    ADDED
    
    | The diff for this file is too large to render. 
		See raw diff | 
|  | 
