import os import io import base64 import re import fitz # PyMuPDF from flask import Flask, render_template_string, request, send_file, jsonify app = Flask(__name__) # Font path - prefers Anek Gujarati if available, falls back to system font FONT_PATH = os.path.join(os.path.dirname(__file__), "GujaratiFont.ttf") UPLOAD_FOLDER = os.path.join(os.path.dirname(__file__), "uploads") os.makedirs(UPLOAD_FOLDER, exist_ok=True) HTML = """ PDF નામ ઉમેરો | Invitation Editor

✉️ ચાંદલા વિધી — આમંત્રણ સંપાદક

PDF invitation editor • Add recipient name to invitation

📄 Step 1: PDF પસંદ કરો
📋 PDF લોડ થઈ ગઈ — pages
PDF lock કરો — દર વખતે upload નહીં કરવું પડે
✍️ Step 2: નામ ટાઇપ કરો
📁 filename.pdf

18
1
NormalLightMediumBoldExtra Bold
ઇન્વિટેશન ટેક્સ્ટ માટે ઘાટો રંગ સૂચવવામાં આવે છે

📍 ટેક્સ્ટ સ્થિતિ (Position)

💡 પ્રિવ્યૂ જોઈ X/Y બદલો જ્યાં "સ્નેહીશ્રી," ની પછી ખાલી જગ્યા છે

🎯 Step 3: Preview & Download
🖼️ PDF Preview
📄 Page 1 (Cover)
📝 Page 2 (Details)
📋

PDF અપલોડ કરો અને
Preview બટન દબાવો

Page 2 પર "સ્નેહીશ્રી," ની
પછી નામ ઉમેરાશે

ℹ️ Page 2 પર "સ્નેહીશ્રી,........" ની ખાલી જગ્યામાં નામ ઉમેરવામાં આવશે.
X=165, Y=340 ડિફૉલ્ટ position છે — preview જોઈ adjust કરો.
""" def hex_to_rgb_float(hex_color): hex_color = hex_color.lstrip('#') return tuple(int(hex_color[i:i+2], 16) / 255.0 for i in (0, 2, 4)) def is_gujarati(text): """Return True if text contains any Gujarati Unicode characters (U+0A80–U+0AFF).""" return any('\u0A80' <= ch <= '\u0AFF' for ch in text) def render_text_as_image(text, font_path, font_size_pt, color_float, boldness=1): """ Render text to a transparent PNG using Pillow+FreeType (handles Gujarati shaping). Returns (png_bytes, img_w_px, img_h_px). """ from PIL import Image, ImageDraw, ImageFont # Convert PDF points → screen pixels (96 DPI) font_size_px = max(8, int(font_size_pt * 96 / 72)) color_rgba = tuple(int(c * 255) for c in color_float) + (255,) try: font = ImageFont.truetype(font_path, font_size_px) except Exception: font = ImageFont.load_default() # Measure exact text bounding box probe = Image.new('RGBA', (4000, 400), (0, 0, 0, 0)) draw = ImageDraw.Draw(probe) bbox = draw.textbbox((0, 0), text, font=font) # (left, top, right, bottom) text_w = max(bbox[2] - bbox[0], 1) text_h = max(bbox[3] - bbox[1], 1) pad = 6 extra = boldness - 1 # extra horizontal pixels for simulated bold img_w = text_w + pad * 2 + extra + 4 img_h = text_h + pad * 2 img = Image.new('RGBA', (img_w, img_h), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Draw text `boldness` times with tiny horizontal offsets to simulate weight step = 0.5 for i in range(boldness): draw.text( (pad - bbox[0] + i * step, pad - bbox[1]), text, font=font, fill=color_rgba ) buf = io.BytesIO() img.save(buf, format='PNG') buf.seek(0) return buf.getvalue(), img_w, img_h def process_pdf(pdf_bytes, name_text, font_size=18, pos_x=165, pos_y=340, color=(0.48, 0.10, 0.23), page_num=1, boldness=1): """Overlay name text on page 2 of the PDF using image-based rendering.""" doc = fitz.open(stream=pdf_bytes, filetype="pdf") if page_num < 1 or page_num > len(doc): page_num = min(page_num, len(doc)) page = doc[page_num - 1] # ── Choose font file ──────────────────────────────────────────────────── # Use Gujarati font for Gujarati text; for English try system fonts. use_gujarati = is_gujarati(name_text) and os.path.exists(FONT_PATH) if use_gujarati: font_path = FONT_PATH else: # Common system font locations (Windows / Linux / macOS) candidates = [ os.path.join(os.path.dirname(__file__), "GujaratiFont.ttf"), "C:/Windows/Fonts/arial.ttf", "C:/Windows/Fonts/calibri.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", "/System/Library/Fonts/Supplemental/Arial.ttf", ] font_path = next((p for p in candidates if os.path.exists(p)), None) # ── Render text → PNG → embed in PDF ─────────────────────────────────── if font_path: try: img_bytes, img_w_px, img_h_px = render_text_as_image( name_text, font_path, font_size, color, boldness ) # Convert pixel size → PDF points (96 DPI screen → 72 pt) pdf_w = img_w_px * 72 / 96 pdf_h = img_h_px * 72 / 96 # pos_y is the text baseline; place image just above it rect = fitz.Rect(pos_x, pos_y - pdf_h, pos_x + pdf_w, pos_y) page.insert_image(rect, stream=img_bytes, keep_proportion=False) except Exception as img_err: # Last-resort fallback: plain insert_text (may not render Gujarati) page.insert_text( fitz.Point(pos_x, pos_y), name_text, fontname="helv", fontsize=font_size, color=color ) else: # No font found at all — use built-in helv page.insert_text( fitz.Point(pos_x, pos_y), name_text, fontname="helv", fontsize=font_size, color=color ) output = io.BytesIO() doc.save(output) doc.close() output.seek(0) return output.read() def render_page_as_image(pdf_bytes, page_num=2, zoom=2.0): """Render a PDF page as PNG image (base64).""" doc = fitz.open(stream=pdf_bytes, filetype="pdf") if page_num < 1 or page_num > len(doc): page_num = min(page_num, len(doc)) page = doc[page_num - 1] mat = fitz.Matrix(zoom, zoom) pix = page.get_pixmap(matrix=mat) img_bytes = pix.tobytes("png") doc.close() return base64.b64encode(img_bytes).decode() @app.route('/') def index(): return render_template_string(HTML) @app.route('/transliterate', methods=['POST']) def transliterate(): """Convert English phonetic text to Gujarati using Google Input Tools API.""" try: import urllib.request import urllib.parse import json as _json data = request.get_json() text = data.get('text', '').strip() if not text: return jsonify({'result': ''}) # Transliterate each word separately for better accuracy words = text.split() gujarati_words = [] for word in words: encoded = urllib.parse.quote(word) url = ( f"https://inputtools.google.com/request" f"?text={encoded}&itc=gu-t-i0-und&num=1&cp=0&cs=1&ie=utf-8&oe=utf-8" ) req = urllib.request.Request( url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'} ) with urllib.request.urlopen(req, timeout=6) as resp: result = _json.loads(resp.read().decode('utf-8')) # Response format: ["SUCCESS", [["word", ["gu_word", ...], ...]], ...] if result and result[0] == 'SUCCESS' and result[1]: gu_word = result[1][0][1][0] gujarati_words.append(gu_word) else: gujarati_words.append(word) # keep original if conversion fails return jsonify({'result': ' '.join(gujarati_words)}) except Exception as e: return jsonify({'error': f'Transliteration failed: {str(e)}'}), 500 @app.route('/preview', methods=['POST']) def preview(): try: data = request.get_json() pdf_b64 = data.get('pdf', '') name = data.get('name', '') font_size = int(data.get('fontSize', 18)) pos_x = float(data.get('posX', 165)) pos_y = float(data.get('posY', 340)) color = (data.get('colorR', 0.48), data.get('colorG', 0.10), data.get('colorB', 0.23)) page_num = int(data.get('page', 2)) boldness = int(data.get('boldness', 1)) pdf_bytes = base64.b64decode(pdf_b64) # Only overlay text on page 2 if viewing page 2 and name is provided if page_num == 2 and name.strip(): modified = process_pdf(pdf_bytes, name, font_size, pos_x, pos_y, color, page_num=2, boldness=boldness) else: modified = pdf_bytes img_b64 = render_page_as_image(modified, page_num=page_num, zoom=1.8) return jsonify({'image': img_b64}) except Exception as e: return jsonify({'error': str(e)}), 400 @app.route('/download', methods=['POST']) def download(): try: data = request.get_json() pdf_b64 = data.get('pdf', '') name = data.get('name', '').strip() font_size = int(data.get('fontSize', 18)) pos_x = float(data.get('posX', 165)) pos_y = float(data.get('posY', 340)) color = (data.get('colorR', 0.48), data.get('colorG', 0.10), data.get('colorB', 0.23)) boldness = int(data.get('boldness', 1)) if not name: return jsonify({'error': 'Name is required'}), 400 pdf_bytes = base64.b64decode(pdf_b64) modified = process_pdf(pdf_bytes, name, font_size, pos_x, pos_y, color, page_num=2, boldness=boldness) # Filename from name safe_name = re.sub(r'[^\w\s\u0A80-\u0AFF]', '', name).strip().replace(' ', '_') or 'invitation' filename = safe_name + '.pdf' return send_file( io.BytesIO(modified), mimetype='application/pdf', as_attachment=True, download_name=filename ) except Exception as e: return jsonify({'error': str(e)}), 500 # ── Locked PDF path ──────────────────────────────────────────────────────────── LOCKED_PDF_PATH = os.path.join(os.path.dirname(__file__), "locked_invitation.pdf") LOCKED_NAME_PATH = os.path.join(os.path.dirname(__file__), "locked_name.txt") @app.route('/lock_pdf', methods=['POST']) def lock_pdf(): """Save the uploaded PDF permanently on the server.""" try: data = request.get_json() pdf_b64 = data.get('pdf', '') filename = data.get('filename', 'invitation.pdf') if not pdf_b64: return jsonify({'error': 'No PDF data'}), 400 pdf_bytes = base64.b64decode(pdf_b64) with open(LOCKED_PDF_PATH, 'wb') as f: f.write(pdf_bytes) with open(LOCKED_NAME_PATH, 'w', encoding='utf-8') as f: f.write(filename) return jsonify({'ok': True}) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/get_locked_pdf', methods=['GET']) def get_locked_pdf(): """Return the locked PDF as base64 if it exists.""" if not os.path.exists(LOCKED_PDF_PATH): return jsonify({}), 404 with open(LOCKED_PDF_PATH, 'rb') as f: pdf_b64 = base64.b64encode(f.read()).decode() filename = 'invitation.pdf' if os.path.exists(LOCKED_NAME_PATH): with open(LOCKED_NAME_PATH, 'r', encoding='utf-8') as f: filename = f.read().strip() return jsonify({'pdf': pdf_b64, 'filename': filename}) @app.route('/unlock_pdf', methods=['POST']) def unlock_pdf(): """Delete the locked PDF from the server.""" try: if os.path.exists(LOCKED_PDF_PATH): os.remove(LOCKED_PDF_PATH) if os.path.exists(LOCKED_NAME_PATH): os.remove(LOCKED_NAME_PATH) return jsonify({'ok': True}) except Exception as e: return jsonify({'error': str(e)}), 500 if __name__ == '__main__': print("\n" + "="*55) print(" ✉️ ચાંદલા વિધી PDF Editor") print("="*55) print(" 🌐 Open in browser:") print(" Local: http://localhost:5050") print(" Network: http://:5050") print("="*55) print(" 📱 Mobile: same URL on your phone (same WiFi)") print(" 📄 Gujarati font:", "✅ Lohit-Gujarati" if os.path.exists(FONT_PATH) else "⚠️ Not found") print("="*55 + "\n") app.run(host='0.0.0.0', port=5050, debug=False)