Zunayedthebot commited on
Commit
3e6b063
·
verified ·
1 Parent(s): d2b1838

Upload 132 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +27 -0
  2. controller/__init__.py +0 -0
  3. controller/__pycache__/__init__.cpython-311.pyc +0 -0
  4. controller/__pycache__/__init__.cpython-313.pyc +0 -0
  5. controller/__pycache__/auth_controller.cpython-311.pyc +0 -0
  6. controller/__pycache__/auth_controller.cpython-313.pyc +0 -0
  7. controller/__pycache__/bbbb.cpython-311.pyc +0 -0
  8. controller/__pycache__/chat_controller.cpython-313.pyc +0 -0
  9. controller/__pycache__/graph_controller.cpython-311.pyc +0 -0
  10. controller/__pycache__/graph_controller.cpython-313.pyc +0 -0
  11. controller/__pycache__/latex_to_text_controller.cpython-313.pyc +0 -0
  12. controller/__pycache__/mathcamera_controller.cpython-311.pyc +0 -0
  13. controller/__pycache__/pdf_controller.cpython-311.pyc +0 -0
  14. controller/__pycache__/pdf_controller.cpython-313.pyc +0 -0
  15. controller/__pycache__/pdffly_controller.cpython-311.pyc +0 -0
  16. controller/__pycache__/pdffly_controller.cpython-313.pyc +0 -0
  17. controller/__pycache__/pdflly_controller.cpython-311.pyc +0 -0
  18. controller/__pycache__/pdflly_controller.cpython-313.pyc +0 -0
  19. controller/__pycache__/pix2text_controller.cpython-311.pyc +0 -0
  20. controller/__pycache__/pix2text_controller.cpython-313.pyc +0 -0
  21. controller/__pycache__/scribble_controller.cpython-311.pyc +0 -0
  22. controller/__pycache__/scribble_controller.cpython-313.pyc +0 -0
  23. controller/__pycache__/table_controller.cpython-311.pyc +0 -0
  24. controller/__pycache__/table_controller.cpython-313.pyc +0 -0
  25. controller/auth_controller.py +97 -0
  26. controller/graph_controller.py +189 -0
  27. controller/models/__init__.py +0 -0
  28. controller/models/__pycache__/__init__.cpython-311.pyc +0 -0
  29. controller/models/__pycache__/__init__.cpython-313.pyc +0 -0
  30. controller/models/__pycache__/camera_to_latex.cpython-311.pyc +0 -0
  31. controller/models/__pycache__/camera_to_latex.cpython-313.pyc +0 -0
  32. controller/models/__pycache__/math_equation.cpython-311.pyc +0 -0
  33. controller/models/__pycache__/math_equation.cpython-313.pyc +0 -0
  34. controller/models/__pycache__/scribble_to_latex.cpython-311.pyc +0 -0
  35. controller/models/__pycache__/scribble_to_latex.cpython-313.pyc +0 -0
  36. controller/models/camera_to_latex.py +29 -0
  37. controller/models/math_equation.py +37 -0
  38. controller/models/scribble_to_latex.py +22 -0
  39. controller/pdf_controller.py +166 -0
  40. controller/pdffly_controller.py +274 -0
  41. controller/pdflly_controller.py +166 -0
  42. controller/pix2text_controller.py +223 -0
  43. controller/scribble_controller.py +159 -0
  44. controller/table_controller.py +192 -0
  45. data/users.json +74 -0
  46. models/__init__.py +1 -0
  47. models/__pycache__/__init__.cpython-311.pyc +0 -0
  48. models/__pycache__/__init__.cpython-313.pyc +0 -0
  49. models/__pycache__/user.cpython-311.pyc +0 -0
  50. models/__pycache__/user.cpython-313.pyc +0 -0
.gitattributes CHANGED
@@ -33,3 +33,30 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/uploads/Assignment_1_Solution.pdf filter=lfs diff=lfs merge=lfs -text
37
+ static/uploads/b0d6064f-3245-40bc-87b5-bfbb459979fd.jpeg filter=lfs diff=lfs merge=lfs -text
38
+ static/uploads/capture.jpg filter=lfs diff=lfs merge=lfs -text
39
+ static/uploads/CSE331.pdf filter=lfs diff=lfs merge=lfs -text
40
+ static/uploads/input2.png filter=lfs diff=lfs merge=lfs -text
41
+ static/uploads/math_formula_sheet.pdf filter=lfs diff=lfs merge=lfs -text
42
+ static/uploads/page_10.png filter=lfs diff=lfs merge=lfs -text
43
+ static/uploads/page_11.png filter=lfs diff=lfs merge=lfs -text
44
+ static/uploads/page_2.png filter=lfs diff=lfs merge=lfs -text
45
+ static/uploads/page_3.png filter=lfs diff=lfs merge=lfs -text
46
+ static/uploads/page_4.png filter=lfs diff=lfs merge=lfs -text
47
+ static/uploads/page_5.png filter=lfs diff=lfs merge=lfs -text
48
+ static/uploads/page_6.png filter=lfs diff=lfs merge=lfs -text
49
+ static/uploads/page_7.png filter=lfs diff=lfs merge=lfs -text
50
+ static/uploads/page_8.png filter=lfs diff=lfs merge=lfs -text
51
+ static/uploads/page_9.png filter=lfs diff=lfs merge=lfs -text
52
+ static/uploads/table_26.png filter=lfs diff=lfs merge=lfs -text
53
+ static/uploads/table_27.png filter=lfs diff=lfs merge=lfs -text
54
+ static/uploads/table_28.png filter=lfs diff=lfs merge=lfs -text
55
+ static/uploads/table_29.png filter=lfs diff=lfs merge=lfs -text
56
+ static/uploads/table_30.png filter=lfs diff=lfs merge=lfs -text
57
+ static/uploads/table_31.png filter=lfs diff=lfs merge=lfs -text
58
+ static/uploads/table_32.png filter=lfs diff=lfs merge=lfs -text
59
+ static/uploads/table_33.png filter=lfs diff=lfs merge=lfs -text
60
+ static/uploads/table_34.png filter=lfs diff=lfs merge=lfs -text
61
+ static/uploads/table_35.png filter=lfs diff=lfs merge=lfs -text
62
+ static/uploads/WhatsApp[[:space:]]Image[[:space:]]2025-10-26[[:space:]]at[[:space:]]01.38.22_3d87e144.jpg filter=lfs diff=lfs merge=lfs -text
controller/__init__.py ADDED
File without changes
controller/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (165 Bytes). View file
 
controller/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (136 Bytes). View file
 
controller/__pycache__/auth_controller.cpython-311.pyc ADDED
Binary file (4.73 kB). View file
 
controller/__pycache__/auth_controller.cpython-313.pyc ADDED
Binary file (4.2 kB). View file
 
controller/__pycache__/bbbb.cpython-311.pyc ADDED
Binary file (5.48 kB). View file
 
controller/__pycache__/chat_controller.cpython-313.pyc ADDED
Binary file (4.22 kB). View file
 
controller/__pycache__/graph_controller.cpython-311.pyc ADDED
Binary file (7.84 kB). View file
 
controller/__pycache__/graph_controller.cpython-313.pyc ADDED
Binary file (7.05 kB). View file
 
controller/__pycache__/latex_to_text_controller.cpython-313.pyc ADDED
Binary file (4.65 kB). View file
 
controller/__pycache__/mathcamera_controller.cpython-311.pyc ADDED
Binary file (5.49 kB). View file
 
controller/__pycache__/pdf_controller.cpython-311.pyc ADDED
Binary file (8.25 kB). View file
 
controller/__pycache__/pdf_controller.cpython-313.pyc ADDED
Binary file (7.09 kB). View file
 
controller/__pycache__/pdffly_controller.cpython-311.pyc ADDED
Binary file (12.8 kB). View file
 
controller/__pycache__/pdffly_controller.cpython-313.pyc ADDED
Binary file (11.3 kB). View file
 
controller/__pycache__/pdflly_controller.cpython-311.pyc ADDED
Binary file (3.19 kB). View file
 
controller/__pycache__/pdflly_controller.cpython-313.pyc ADDED
Binary file (7.1 kB). View file
 
controller/__pycache__/pix2text_controller.cpython-311.pyc ADDED
Binary file (10.2 kB). View file
 
controller/__pycache__/pix2text_controller.cpython-313.pyc ADDED
Binary file (9.03 kB). View file
 
controller/__pycache__/scribble_controller.cpython-311.pyc ADDED
Binary file (7.97 kB). View file
 
controller/__pycache__/scribble_controller.cpython-313.pyc ADDED
Binary file (7.01 kB). View file
 
controller/__pycache__/table_controller.cpython-311.pyc ADDED
Binary file (9.45 kB). View file
 
controller/__pycache__/table_controller.cpython-313.pyc ADDED
Binary file (8.3 kB). View file
 
controller/auth_controller.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ from flask import Blueprint, render_template, request, jsonify, redirect, url_for, session, current_app
4
+ from flask_login import LoginManager, login_user, logout_user, login_required, current_user
5
+ import requests
6
+ from models.user import User
7
+
8
+ auth_bp = Blueprint('auth', __name__)
9
+
10
+ # Google OAuth configuration (these would normally come from environment variables)
11
+ GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID', 'your-google-client-id')
12
+ GOOGLE_CLIENT_SECRET = os.environ.get('GOOGLE_CLIENT_SECRET', 'your-google-client-secret')
13
+ GOOGLE_REDIRECT_URI = os.environ.get('GOOGLE_REDIRECT_URI', 'http://localhost:5000/auth/google/callback')
14
+
15
+ # Initialize login manager
16
+ login_manager = LoginManager()
17
+
18
+ def init_app(app):
19
+ """Initialize the login manager with the app"""
20
+ login_manager.init_app(app)
21
+ # Set login view - using setattr to avoid type checking issues
22
+ setattr(login_manager, 'login_view', 'auth.login')
23
+ return login_manager
24
+
25
+ @login_manager.user_loader
26
+ def load_user(user_id):
27
+ return User.get(user_id)
28
+
29
+
30
+ @auth_bp.route("/login")
31
+ def login():
32
+ """Display the login page"""
33
+ return render_template('login.html')
34
+
35
+
36
+ @auth_bp.route("/google/login")
37
+ def google_login():
38
+ """Redirect to Google OAuth login"""
39
+ # In a real implementation, you would redirect to Google's OAuth endpoint
40
+ # For now, we'll create a dummy user for testing
41
+ user = User.create_or_update(
42
+ id="google_user_123",
43
+ email="user@gmail.com",
44
+ name="Google User",
45
+ picture=None
46
+ )
47
+
48
+ if user:
49
+ login_user(user)
50
+ return redirect(url_for('index'))
51
+ else:
52
+ return "Failed to create user", 500
53
+
54
+
55
+ @auth_bp.route("/guest/login", methods=['POST'])
56
+ def guest_login():
57
+ """Login as a guest user"""
58
+ name = request.form.get('name')
59
+ if not name:
60
+ return "Name is required", 400
61
+
62
+ # Create a guest user
63
+ user = User.create_or_update(
64
+ id=f"guest_{name.lower().replace(' ', '_')}_{int(time.time())}",
65
+ email=f"{name.lower().replace(' ', '.')}@guest.texlab.com",
66
+ name=name,
67
+ picture=None
68
+ )
69
+
70
+ if user:
71
+ login_user(user)
72
+ return redirect(url_for('index'))
73
+ else:
74
+ return "Failed to create user", 500
75
+
76
+
77
+ @auth_bp.route("/logout")
78
+ @login_required
79
+ def logout():
80
+ logout_user()
81
+ return redirect(url_for('auth.login'))
82
+
83
+
84
+ @auth_bp.route("/user")
85
+ def get_current_user():
86
+ if current_user.is_authenticated:
87
+ return jsonify({
88
+ 'authenticated': True,
89
+ 'user': {
90
+ 'id': current_user.id,
91
+ 'email': current_user.email,
92
+ 'name': current_user.name,
93
+ 'picture': current_user.picture
94
+ }
95
+ })
96
+ else:
97
+ return jsonify({'authenticated': False})
controller/graph_controller.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from flask import Blueprint, render_template, request, jsonify
3
+ import numpy as np
4
+ import matplotlib
5
+ matplotlib.use('Agg') # Use non-interactive backend
6
+ import matplotlib.pyplot as plt
7
+ import base64
8
+ from io import BytesIO
9
+ import re
10
+
11
+ graph_bp = Blueprint('graph_bp', __name__)
12
+
13
+ UPLOAD_FOLDER = 'static/uploads'
14
+ GRAPHS_FOLDER = 'static/graphs'
15
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
16
+ os.makedirs(GRAPHS_FOLDER, exist_ok=True)
17
+
18
+
19
+ def parse_plot_command(command):
20
+ """Parse plot command and extract function and range"""
21
+ # Remove extra spaces
22
+ command = command.strip()
23
+
24
+ # Match patterns like "plot x^2 from -5 to 5" or "plot sin(x) from 0 to 2*pi"
25
+ pattern = r"plot\s+(.+?)\s+from\s+(-?[\d\*\.pi]+)\s+to\s+(-?[\d\*\.pi]+)"
26
+ match = re.match(pattern, command, re.IGNORECASE)
27
+
28
+ if match:
29
+ function = match.group(1)
30
+ x_min = match.group(2)
31
+ x_max = match.group(3)
32
+
33
+ # Convert pi expressions
34
+ x_min = x_min.replace('pi', 'np.pi')
35
+ x_max = x_max.replace('pi', 'np.pi')
36
+
37
+ try:
38
+ x_min_val = eval(x_min)
39
+ x_max_val = eval(x_max)
40
+ return function, x_min_val, x_max_val
41
+ except:
42
+ raise ValueError("Invalid range values")
43
+
44
+ # Match simpler patterns like "plot x^2"
45
+ simple_pattern = r"plot\s+(.+)"
46
+ simple_match = re.match(simple_pattern, command, re.IGNORECASE)
47
+
48
+ if simple_match:
49
+ function = simple_match.group(1)
50
+ return function, -10, 10 # Default range
51
+
52
+ raise ValueError("Invalid plot command format")
53
+
54
+
55
+ def evaluate_function(func_str, x):
56
+ """Safely evaluate mathematical function"""
57
+ # Replace common math functions
58
+ func_str = func_str.replace('^', '**')
59
+ func_str = func_str.replace('sin', 'np.sin')
60
+ func_str = func_str.replace('cos', 'np.cos')
61
+ func_str = func_str.replace('tan', 'np.tan')
62
+ func_str = func_str.replace('log', 'np.log')
63
+ func_str = func_str.replace('exp', 'np.exp')
64
+ func_str = func_str.replace('sqrt', 'np.sqrt')
65
+ func_str = func_str.replace('abs', 'np.abs')
66
+
67
+ # Replace x with the actual value
68
+ func_str = func_str.replace('x', f'({x})')
69
+
70
+ try:
71
+ return eval(func_str)
72
+ except:
73
+ raise ValueError(f"Error evaluating function: {func_str}")
74
+
75
+
76
+ def generate_tikz_latex(function, x_min, x_max):
77
+ """Generate TikZ/pgfplots LaTeX code for the function"""
78
+ # Convert function to pgfplots format
79
+ pgf_function = function.replace('^', '^')
80
+ pgf_function = pgf_function.replace('sqrt', 'sqrt')
81
+
82
+ # Create complete LaTeX document with TikZ/pgfplots
83
+ latex_code = r'''\documentclass{article}
84
+ \usepackage{pgfplots}
85
+ \usepackage{amsmath}
86
+ \pgfplotsset{compat=1.18}
87
+
88
+ \begin{document}
89
+
90
+ \begin{figure}[h]
91
+ \centering
92
+ \begin{tikzpicture}
93
+ \begin{axis}[
94
+ xlabel={$x$},
95
+ ylabel={$y$},
96
+ grid=major,
97
+ width=12cm,
98
+ height=8cm,
99
+ samples=200,
100
+ domain=''' + f"{x_min}:{x_max}" + r''',
101
+ legend pos=north west,
102
+ axis lines=middle,
103
+ ]
104
+ \addplot[blue, thick] {''' + pgf_function + r'''};
105
+ \legend{$f(x)=''' + function + r'''$}
106
+ \end{axis}
107
+ \end{tikzpicture}
108
+ \caption{Graph of $f(x) = ''' + function + r'''$}
109
+ \label{fig:graph}
110
+ \end{figure}
111
+
112
+ \end{document}'''
113
+
114
+ return latex_code
115
+
116
+
117
+ def generate_plot(function, x_min, x_max):
118
+ """Generate plot and return base64 image"""
119
+ try:
120
+ # Create x values
121
+ x = np.linspace(x_min, x_max, 1000)
122
+
123
+ # Evaluate function for all x values
124
+ y = []
125
+ for xi in x:
126
+ try:
127
+ yi = evaluate_function(function, xi)
128
+ y.append(yi)
129
+ except:
130
+ y.append(np.nan) # Handle undefined points
131
+
132
+ y = np.array(y)
133
+
134
+ # Create plot
135
+ plt.figure(figsize=(10, 6))
136
+ plt.plot(x, y, linewidth=2, color='#667eea')
137
+ plt.grid(True, alpha=0.3)
138
+ plt.xlabel('x')
139
+ plt.ylabel('y')
140
+ plt.title(f'Graph of f(x) = {function}')
141
+
142
+ # Save to base64 string
143
+ buffer = BytesIO()
144
+ plt.savefig(buffer, format='png', dpi=150, bbox_inches='tight')
145
+ buffer.seek(0)
146
+ image_base64 = base64.b64encode(buffer.getvalue()).decode()
147
+ plt.close()
148
+
149
+ return image_base64
150
+ except Exception as e:
151
+ raise ValueError(f"Error generating plot: {str(e)}")
152
+
153
+
154
+ @graph_bp.route("/graph")
155
+ def graph_page():
156
+ return render_template("graph.html")
157
+
158
+
159
+ @graph_bp.route("/graph/generate", methods=["POST"])
160
+ def generate_graph():
161
+ """Generate graph from user input"""
162
+ try:
163
+ data = request.get_json()
164
+ if not data:
165
+ return jsonify({'error': 'No data provided'}), 400
166
+
167
+ command = data.get('command', '')
168
+ if not command:
169
+ return jsonify({'error': 'No command provided'}), 400
170
+
171
+ # Parse the command
172
+ function, x_min, x_max = parse_plot_command(command)
173
+
174
+ # Generate the plot image
175
+ image_base64 = generate_plot(function, x_min, x_max)
176
+
177
+ # Generate TikZ/pgfplots LaTeX code
178
+ latex_code = generate_tikz_latex(function, x_min, x_max)
179
+
180
+ return jsonify({
181
+ 'success': True,
182
+ 'image': image_base64,
183
+ 'function': function,
184
+ 'range': f'[{x_min}, {x_max}]',
185
+ 'latex': latex_code
186
+ })
187
+
188
+ except Exception as e:
189
+ return jsonify({'error': str(e)}), 500
controller/models/__init__.py ADDED
File without changes
controller/models/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (172 Bytes). View file
 
controller/models/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (143 Bytes). View file
 
controller/models/__pycache__/camera_to_latex.cpython-311.pyc ADDED
Binary file (2 kB). View file
 
controller/models/__pycache__/camera_to_latex.cpython-313.pyc ADDED
Binary file (1.76 kB). View file
 
controller/models/__pycache__/math_equation.cpython-311.pyc ADDED
Binary file (1.92 kB). View file
 
controller/models/__pycache__/math_equation.cpython-313.pyc ADDED
Binary file (1.69 kB). View file
 
controller/models/__pycache__/scribble_to_latex.cpython-311.pyc ADDED
Binary file (1.37 kB). View file
 
controller/models/__pycache__/scribble_to_latex.cpython-313.pyc ADDED
Binary file (1.16 kB). View file
 
controller/models/camera_to_latex.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # controller/models/camera_to_latex.py
2
+
3
+ import base64
4
+ import io
5
+ from PIL import Image
6
+ from transformers import TrOCRProcessor
7
+ from optimum.onnxruntime import ORTModelForVision2Seq
8
+
9
+ print("🔹 Loading Pix2Text model for Camera → LaTeX...")
10
+
11
+ processor = TrOCRProcessor.from_pretrained("breezedeus/pix2text-mfr")
12
+ model = ORTModelForVision2Seq.from_pretrained("breezedeus/pix2text-mfr", use_cache=False)
13
+
14
+
15
+ def camera_to_latex(image_base64: str) -> str:
16
+ try:
17
+ # Remove header (e.g., "data:image/png;base64,")
18
+ image_data = image_base64.split(",")[1]
19
+ image_bytes = base64.b64decode(image_data)
20
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
21
+
22
+ pixel_values = processor(images=image, return_tensors="pt").pixel_values
23
+ generated_ids = model.generate(pixel_values)
24
+ text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
25
+
26
+ return text.strip()
27
+ except Exception as e:
28
+ print(f"❌ Error in camera_to_latex: {e}")
29
+ return "⚠️ Error generating LaTeX"
controller/models/math_equation.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template
2
+ import os
3
+ from werkzeug.utils import secure_filename
4
+ from pix2text import Pix2Text # Make sure you installed it
5
+ from PIL import Image
6
+
7
+ app = Flask(__name__)
8
+ app.config['UPLOAD_FOLDER'] = 'static/uploads'
9
+ app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'}
10
+
11
+ p2t = Pix2Text() # Load Pix2Text model
12
+
13
+ def allowed_file(filename):
14
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
15
+
16
+ @app.route('/math/process', methods=['POST'])
17
+ def process_math():
18
+ if 'image' not in request.files:
19
+ return jsonify({'success': False, 'error': 'No file part'})
20
+
21
+ file = request.files['image']
22
+ if file.filename == '':
23
+ return jsonify({'success': False, 'error': 'No selected file'})
24
+
25
+ if file and allowed_file(file.filename):
26
+ filename = secure_filename(file.filename)
27
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
28
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
29
+ file.save(filepath)
30
+
31
+ try:
32
+ result = p2t(Image.open(filepath)) # Convert image → LaTeX
33
+ return jsonify({'success': True, 'latex': result, 'image_path': filepath})
34
+ except Exception as e:
35
+ return jsonify({'success': False, 'error': str(e)})
36
+ else:
37
+ return jsonify({'success': False, 'error': 'Invalid file type'})
controller/models/scribble_to_latex.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # controller/models/scribble_to_latex.py
2
+ import base64
3
+ from io import BytesIO
4
+ from PIL import Image
5
+ from .math_equation import image_to_latex # reuse your existing pix2tex model
6
+
7
+ def scribble_to_latex(image_data: str):
8
+ """
9
+ Convert scribble (base64 PNG) to LaTeX using Pix2Text model.
10
+ """
11
+ try:
12
+ # Decode base64 image
13
+ image_bytes = base64.b64decode(image_data.split(',')[1])
14
+ image = Image.open(BytesIO(image_bytes)).convert("RGB")
15
+
16
+ # Call your existing model
17
+ latex_code = image_to_latex(image)
18
+
19
+ return latex_code.strip()
20
+ except Exception as e:
21
+ print(f"❌ Error in scribble_to_latex: {e}")
22
+ return "⚠️ Failed to process scribble"
controller/pdf_controller.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import base64
4
+ import io
5
+ from flask import Blueprint, request, render_template, jsonify, current_app
6
+ import fitz # PyMuPDF
7
+ import PyPDF2
8
+ from PIL import Image
9
+ import tempfile
10
+
11
+ pdf_bp = Blueprint('pdf', __name__, url_prefix='/pdf')
12
+
13
+ def extract_text_from_pdf(filepath, page_number=None, coordinates=None):
14
+ """Extract text from PDF using PyMuPDF for better accuracy"""
15
+ try:
16
+ doc = fitz.open(filepath)
17
+
18
+ if page_number is not None:
19
+ # Extract from specific page
20
+ page = doc[page_number]
21
+
22
+ if coordinates:
23
+ # Extract text from specific area
24
+ x, y, width, height = coordinates
25
+ rect = fitz.Rect(x, y, x + width, y + height)
26
+ text = page.get_text("text", clip=rect)
27
+ else:
28
+ # Extract text from entire page
29
+ text = page.get_text("text")
30
+ else:
31
+ # Extract from entire document
32
+ text = ""
33
+ for page in doc:
34
+ text += page.get_text("text")
35
+
36
+ doc.close()
37
+ return text.strip()
38
+ except Exception as e:
39
+ print(f"Error extracting text with PyMuPDF: {e}")
40
+ # Fallback to PyPDF2
41
+ return extract_text_with_pypdf2(filepath, page_number, coordinates)
42
+
43
+ def extract_text_with_pypdf2(filepath, page_number=None, coordinates=None):
44
+ """Fallback text extraction using PyPDF2"""
45
+ try:
46
+ with open(filepath, 'rb') as file:
47
+ reader = PyPDF2.PdfReader(file)
48
+
49
+ if page_number is not None and page_number < len(reader.pages):
50
+ page = reader.pages[page_number]
51
+ return page.extract_text()
52
+ else:
53
+ text = ""
54
+ for page in reader.pages:
55
+ text += page.extract_text()
56
+ return text
57
+ except Exception as e:
58
+ return f"Error extracting text: {str(e)}"
59
+
60
+ def convert_text_to_latex(text):
61
+ """Simple conversion of text to LaTeX format"""
62
+ # This is a placeholder implementation
63
+ # In a real application, you would use an AI model to convert text to LaTeX
64
+ return text.replace('\\', '\\\\').replace('_', '\\_').replace('^', '\\^').replace('&', '\\&')
65
+
66
+ @pdf_bp.route('/')
67
+ def pdf_converter():
68
+ """Render the PDF converter page"""
69
+ return render_template('pdf.html')
70
+
71
+ @pdf_bp.route('/upload', methods=['POST'])
72
+ def upload_pdf():
73
+ """Handle PDF file upload"""
74
+ if 'pdf_file' not in request.files:
75
+ return jsonify({'success': False, 'error': 'No file provided'})
76
+
77
+ file = request.files['pdf_file']
78
+
79
+ if file.filename == '':
80
+ return jsonify({'success': False, 'error': 'No file selected'})
81
+
82
+ if file and file.filename.lower().endswith('.pdf'):
83
+ try:
84
+ # Save file temporarily
85
+ temp_dir = tempfile.gettempdir()
86
+ filename = file.filename
87
+ filepath = os.path.join(temp_dir, filename)
88
+ file.save(filepath)
89
+
90
+ # Get page count
91
+ with open(filepath, 'rb') as f:
92
+ reader = PyPDF2.PdfReader(f)
93
+ page_count = len(reader.pages)
94
+
95
+ return jsonify({
96
+ 'success': True,
97
+ 'filename': filename,
98
+ 'filepath': filepath,
99
+ 'pages': page_count
100
+ })
101
+ except Exception as e:
102
+ return jsonify({'success': False, 'error': f'Upload failed: {str(e)}'})
103
+
104
+ return jsonify({'success': False, 'error': 'Invalid file type. Please upload a PDF file.'})
105
+
106
+ @pdf_bp.route('/process', methods=['POST'])
107
+ def process_pdf():
108
+ """Process PDF and convert to LaTeX"""
109
+ try:
110
+ data = request.get_json()
111
+ filename = data.get('filename')
112
+ coordinates = data.get('coordinates')
113
+ page = data.get('page', 0)
114
+ convert_all = data.get('convert_all', False)
115
+
116
+ if not filename:
117
+ return jsonify({'success': False, 'error': 'No filename provided'})
118
+
119
+ # Get file path
120
+ temp_dir = tempfile.gettempdir()
121
+ filepath = os.path.join(temp_dir, filename)
122
+
123
+ if not os.path.exists(filepath):
124
+ return jsonify({'success': False, 'error': 'File not found'})
125
+
126
+ if convert_all:
127
+ # Convert entire PDF
128
+ text = extract_text_from_pdf(filepath)
129
+ elif coordinates:
130
+ # Convert selected area
131
+ text = extract_text_from_pdf(filepath, page, coordinates)
132
+ else:
133
+ # Convert specific page
134
+ text = extract_text_from_pdf(filepath, page)
135
+
136
+ # Convert to LaTeX (placeholder)
137
+ latex = convert_text_to_latex(text)
138
+
139
+ return jsonify({
140
+ 'success': True,
141
+ 'latex': latex,
142
+ 'text': text
143
+ })
144
+ except Exception as e:
145
+ return jsonify({'success': False, 'error': f'Processing failed: {str(e)}'})
146
+
147
+ @pdf_bp.route('/solve', methods=['POST'])
148
+ def solve_equation():
149
+ """Solve mathematical equations in LaTeX"""
150
+ try:
151
+ data = request.get_json()
152
+ latex = data.get('latex', '')
153
+
154
+ # This is a placeholder implementation
155
+ # In a real application, you would use SymPy or similar library
156
+ solution = {
157
+ 'type': 'expression',
158
+ 'result': f"Simplified: {latex}"
159
+ }
160
+
161
+ return jsonify({
162
+ 'success': True,
163
+ 'solution': solution
164
+ })
165
+ except Exception as e:
166
+ return jsonify({'success': False, 'error': f'Solving failed: {str(e)}'})
controller/pdffly_controller.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, request, jsonify, render_template
2
+ import os
3
+ import re
4
+ import fitz # PyMuPDF
5
+ from PIL import Image
6
+ from werkzeug.utils import secure_filename
7
+ try:
8
+ from pix2text import Pix2Text
9
+ PIX2TEXT_AVAILABLE = True
10
+ except ImportError:
11
+ PIX2TEXT_AVAILABLE = False
12
+ print("⚠️ Pix2Text not available. Install with: pip install pix2text")
13
+
14
+ pdffly_bp = Blueprint('pdffly', __name__)
15
+ UPLOAD_FOLDER = 'static/uploads'
16
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
17
+
18
+ # Load Pix2Text model once (for efficiency)
19
+ if PIX2TEXT_AVAILABLE:
20
+ print("🔹 Loading Pix2Text model for PDF → LaTeX...")
21
+ try:
22
+ p2t = Pix2Text()
23
+ print("✅ Pix2Text model loaded successfully")
24
+ except Exception as e:
25
+ print(f"⚠️ Error loading Pix2Text: {e}")
26
+ p2t = None
27
+ else:
28
+ p2t = None
29
+
30
+ def pdf_to_images(pdf_path):
31
+ """Convert PDF pages to images"""
32
+ doc = fitz.open(pdf_path)
33
+ image_paths = []
34
+ for i, page in enumerate(doc):
35
+ pix = page.get_pixmap(dpi=200)
36
+ img_path = os.path.join(UPLOAD_FOLDER, f"page_{i+1}.png")
37
+ pix.save(img_path)
38
+ image_paths.append(img_path)
39
+ doc.close()
40
+ return image_paths
41
+
42
+ def extract_text_from_pdf(pdf_path):
43
+ """Extract raw text from PDF (fallback method)"""
44
+ doc = fitz.open(pdf_path)
45
+ all_text = []
46
+ for page_num, page in enumerate(doc):
47
+ text = page.get_text()
48
+ all_text.append(f"Page {page_num + 1}:\n{text}\n")
49
+ doc.close()
50
+ return "\n".join(all_text)
51
+
52
+ def clean_latex_code(latex_str):
53
+ """Clean and format LaTeX code for Overleaf compilation"""
54
+ if not latex_str or not isinstance(latex_str, str):
55
+ return ""
56
+
57
+ # Remove common OCR artifacts and spaces in commands
58
+ latex_str = re.sub(r'\\operatorname\*?\s*\{\s*([a-z])\s+([a-z])\s+([a-z])\s*\}',
59
+ lambda m: f'\\{m.group(1)}{m.group(2)}{m.group(3)}', latex_str)
60
+
61
+ # Fix common math operators with spaces
62
+ replacements = {
63
+ r'\\operatorname\s*\{\s*l\s+i\s+m\s*\}': r'\\lim',
64
+ r'\\operatorname\s*\{\s*s\s+i\s+n\s*\}': r'\\sin',
65
+ r'\\operatorname\s*\{\s*c\s+o\s+s\s*\}': r'\\cos',
66
+ r'\\operatorname\s*\{\s*t\s+a\s+n\s*\}': r'\\tan',
67
+ r'\\operatorname\s*\{\s*l\s+o\s+g\s*\}': r'\\log',
68
+ r'\\operatorname\s*\{\s*l\s+n\s*\}': r'\\ln',
69
+ r'\\operatorname\s*\{\s*e\s+x\s+p\s*\}': r'\\exp',
70
+ r'\\operatorname\s*\{\s*m\s+a\s+x\s*\}': r'\\max',
71
+ r'\\operatorname\s*\{\s*m\s+i\s+n\s*\}': r'\\min',
72
+ }
73
+
74
+ for pattern, replacement in replacements.items():
75
+ latex_str = re.sub(pattern, replacement, latex_str, flags=re.IGNORECASE)
76
+
77
+ # Remove spaces inside any remaining \operatorname commands
78
+ latex_str = re.sub(r'\\operatorname\*?\s*\{([^}]+)\}',
79
+ lambda m: f'\\operatorname{{{m.group(1).replace(" ", "")}}}', latex_str)
80
+
81
+ # Replace $$ with \[ \]
82
+ latex_str = re.sub(r'\$\$([^$]+)\$\$', r'\\[\1\\]', latex_str)
83
+
84
+ # Remove obvious OCR gibberish (sequences of random chars/symbols)
85
+ latex_str = re.sub(r'[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f-\xff]+', '', latex_str)
86
+
87
+ # Balance braces and brackets
88
+ open_braces = latex_str.count('{')
89
+ close_braces = latex_str.count('}')
90
+ if open_braces > close_braces:
91
+ latex_str += '}' * (open_braces - close_braces)
92
+ elif close_braces > open_braces:
93
+ latex_str = '{' * (close_braces - open_braces) + latex_str
94
+
95
+ # Balance brackets
96
+ open_brackets = latex_str.count('[')
97
+ close_brackets = latex_str.count(']')
98
+ if open_brackets > close_brackets:
99
+ latex_str += ']' * (open_brackets - close_brackets)
100
+ elif close_brackets > open_brackets:
101
+ latex_str = '[' * (close_brackets - open_brackets) + latex_str
102
+
103
+ # Remove invalid math syntax patterns
104
+ latex_str = re.sub(r'\\\\+', r'\\\\', latex_str) # Multiple backslashes
105
+ latex_str = re.sub(r'\s+', ' ', latex_str) # Multiple spaces
106
+
107
+ return latex_str.strip()
108
+
109
+ def create_complete_latex_document(latex_content, title="PDF to LaTeX Conversion"):
110
+ """Wrap LaTeX content in a complete compilable document"""
111
+ document = r'''\documentclass{article}
112
+ \usepackage{amsmath}
113
+ \usepackage{amssymb}
114
+ \usepackage{amsfonts}
115
+ \usepackage{graphicx}
116
+
117
+ \title{''' + title + r'''}
118
+ \author{PDFly}
119
+ \date{\today}
120
+
121
+ \begin{document}
122
+
123
+ \maketitle
124
+
125
+ \begin{center}
126
+ \textit{This document was automatically generated from a PDF file using OCR and LaTeX conversion.}
127
+ \end{center}
128
+
129
+ \section*{Content}
130
+
131
+ ''' + latex_content + r'''
132
+
133
+ \end{document}'''
134
+
135
+ return document
136
+
137
+ @pdffly_bp.route("/", methods=["GET"])
138
+ def pdffly_page():
139
+ """Render the main PDFfly page."""
140
+ return render_template("pdffly.html")
141
+
142
+ @pdffly_bp.route('/upload', methods=['POST'])
143
+ def upload_and_convert_pdf():
144
+ """Upload PDF and convert to LaTeX"""
145
+ if 'file' not in request.files:
146
+ return jsonify({'error': 'No file found'}), 400
147
+
148
+ file = request.files['file']
149
+ if not file or file.filename == '':
150
+ return jsonify({'error': 'No file selected'}), 400
151
+
152
+ if not file.filename.lower().endswith('.pdf'):
153
+ return jsonify({'error': 'Only PDF files are allowed'}), 400
154
+
155
+ filename = secure_filename(file.filename)
156
+ pdf_path = os.path.join(UPLOAD_FOLDER, filename)
157
+ file.save(pdf_path)
158
+
159
+ try:
160
+ # Get page count
161
+ doc = fitz.open(pdf_path)
162
+ page_count = len(doc)
163
+ doc.close()
164
+
165
+ # Convert PDF → images
166
+ images = pdf_to_images(pdf_path)
167
+
168
+ # Run LaTeX recognition for each image
169
+ latex_results = []
170
+ all_latex_pages = []
171
+ for i, img_path in enumerate(images):
172
+ try:
173
+ if p2t:
174
+ result = p2t.recognize(img_path, resized_shape=768)
175
+ latex_code = result if isinstance(result, str) else str(result)
176
+ # Clean the LaTeX code
177
+ latex_code = clean_latex_code(latex_code)
178
+ all_latex_pages.append(f"% Page {i + 1}\n{latex_code}")
179
+ else:
180
+ # Fallback: extract text
181
+ latex_code = f"Text extraction (Pix2Text not available)"
182
+ all_latex_pages.append(latex_code)
183
+
184
+ latex_results.append({
185
+ 'page': i + 1,
186
+ 'image': img_path.replace('static/', '/static/'),
187
+ 'latex': latex_code
188
+ })
189
+ except Exception as e:
190
+ latex_results.append({
191
+ 'page': i + 1,
192
+ 'image': img_path.replace('static/', '/static/'),
193
+ 'error': str(e)
194
+ })
195
+ all_latex_pages.append(f"% Page {i + 1}: Error - {str(e)}")
196
+
197
+ # Create complete document
198
+ combined_latex = "\n\n".join(all_latex_pages)
199
+ complete_document = create_complete_latex_document(combined_latex, filename)
200
+
201
+ return jsonify({
202
+ 'success': True,
203
+ 'message': 'PDF converted successfully!',
204
+ 'pdf_path': pdf_path.replace('static/', '/static/'),
205
+ 'filename': filename,
206
+ 'pages': page_count,
207
+ 'results': latex_results,
208
+ 'complete_document': complete_document
209
+ })
210
+
211
+ except Exception as e:
212
+ return jsonify({
213
+ 'success': False,
214
+ 'error': f'Error processing PDF: {str(e)}'
215
+ }), 500
216
+
217
+ @pdffly_bp.route('/process', methods=['POST'])
218
+ def process_pdf():
219
+ """Process specific area or entire PDF"""
220
+ data = request.get_json()
221
+ filename = data.get('filename')
222
+ convert_all = data.get('convert_all', False)
223
+ page_num = data.get('page', 0)
224
+ coordinates = data.get('coordinates')
225
+
226
+ if not filename:
227
+ return jsonify({'success': False, 'error': 'No filename provided'}), 400
228
+
229
+ pdf_path = os.path.join(UPLOAD_FOLDER, filename)
230
+
231
+ if not os.path.exists(pdf_path):
232
+ return jsonify({'success': False, 'error': 'PDF file not found'}), 404
233
+
234
+ try:
235
+ if convert_all:
236
+ # Extract text from entire PDF
237
+ text = extract_text_from_pdf(pdf_path)
238
+ latex = f"\\text{{{text}}}"
239
+ else:
240
+ # Extract from specific page
241
+ doc = fitz.open(pdf_path)
242
+ if page_num < len(doc):
243
+ page = doc[page_num]
244
+ text = page.get_text()
245
+ latex = f"\\text{{{text}}}"
246
+ else:
247
+ latex = "Page not found"
248
+ doc.close()
249
+
250
+ return jsonify({
251
+ 'success': True,
252
+ 'latex': latex
253
+ })
254
+
255
+ except Exception as e:
256
+ return jsonify({
257
+ 'success': False,
258
+ 'error': str(e)
259
+ }), 500
260
+
261
+ @pdffly_bp.route('/solve', methods=['POST'])
262
+ def solve_latex():
263
+ """Solve mathematical content"""
264
+ data = request.get_json()
265
+ latex = data.get('latex', '')
266
+
267
+ # Simple solver response
268
+ return jsonify({
269
+ 'success': True,
270
+ 'solution': {
271
+ 'type': 'info',
272
+ 'message': 'Math solver integration pending'
273
+ }
274
+ })
controller/pdflly_controller.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import base64
4
+ import io
5
+ from flask import Blueprint, request, render_template, jsonify, current_app
6
+ import fitz # PyMuPDF
7
+ import PyPDF2
8
+ from PIL import Image
9
+ import tempfile
10
+
11
+ pdflly_bp = Blueprint('pdflly', __name__, url_prefix='/pdflly')
12
+
13
+ def extract_text_from_pdf(filepath, page_number=None, coordinates=None):
14
+ """Extract text from PDF using PyMuPDF for better accuracy"""
15
+ try:
16
+ doc = fitz.open(filepath)
17
+
18
+ if page_number is not None:
19
+ # Extract from specific page
20
+ page = doc[page_number]
21
+
22
+ if coordinates:
23
+ # Extract text from specific area
24
+ x, y, width, height = coordinates
25
+ rect = fitz.Rect(x, y, x + width, y + height)
26
+ text = page.get_text("text", clip=rect)
27
+ else:
28
+ # Extract text from entire page
29
+ text = page.get_text("text")
30
+ else:
31
+ # Extract from entire document
32
+ text = ""
33
+ for page in doc:
34
+ text += page.get_text("text")
35
+
36
+ doc.close()
37
+ return text.strip()
38
+ except Exception as e:
39
+ print(f"Error extracting text with PyMuPDF: {e}")
40
+ # Fallback to PyPDF2
41
+ return extract_text_with_pypdf2(filepath, page_number, coordinates)
42
+
43
+ def extract_text_with_pypdf2(filepath, page_number=None, coordinates=None):
44
+ """Fallback text extraction using PyPDF2"""
45
+ try:
46
+ with open(filepath, 'rb') as file:
47
+ reader = PyPDF2.PdfReader(file)
48
+
49
+ if page_number is not None and page_number < len(reader.pages):
50
+ page = reader.pages[page_number]
51
+ return page.extract_text()
52
+ else:
53
+ text = ""
54
+ for page in reader.pages:
55
+ text += page.extract_text()
56
+ return text
57
+ except Exception as e:
58
+ return f"Error extracting text: {str(e)}"
59
+
60
+ def convert_text_to_latex(text):
61
+ """Simple conversion of text to LaTeX format"""
62
+ # This is a placeholder implementation
63
+ # In a real application, you would use an AI model to convert text to LaTeX
64
+ return text.replace('\\', '\\\\').replace('_', '\\_').replace('^', '\\^').replace('&', '\\&')
65
+
66
+ @pdflly_bp.route('/')
67
+ def pdflly_converter():
68
+ """Render the PDFly converter page"""
69
+ return render_template('pdflly.html')
70
+
71
+ @pdflly_bp.route('/upload', methods=['POST'])
72
+ def upload_pdf():
73
+ """Handle PDF file upload"""
74
+ if 'pdf_file' not in request.files:
75
+ return jsonify({'success': False, 'error': 'No file provided'})
76
+
77
+ file = request.files['pdf_file']
78
+
79
+ if file.filename == '':
80
+ return jsonify({'success': False, 'error': 'No file selected'})
81
+
82
+ if file and file.filename.lower().endswith('.pdf'):
83
+ try:
84
+ # Save file temporarily
85
+ temp_dir = tempfile.gettempdir()
86
+ filename = file.filename
87
+ filepath = os.path.join(temp_dir, filename)
88
+ file.save(filepath)
89
+
90
+ # Get page count
91
+ with open(filepath, 'rb') as f:
92
+ reader = PyPDF2.PdfReader(f)
93
+ page_count = len(reader.pages)
94
+
95
+ return jsonify({
96
+ 'success': True,
97
+ 'filename': filename,
98
+ 'filepath': filepath,
99
+ 'pages': page_count
100
+ })
101
+ except Exception as e:
102
+ return jsonify({'success': False, 'error': f'Upload failed: {str(e)}'})
103
+
104
+ return jsonify({'success': False, 'error': 'Invalid file type. Please upload a PDF file.'})
105
+
106
+ @pdflly_bp.route('/process', methods=['POST'])
107
+ def process_pdf():
108
+ """Process PDF and convert to LaTeX"""
109
+ try:
110
+ data = request.get_json()
111
+ filename = data.get('filename')
112
+ coordinates = data.get('coordinates')
113
+ page = data.get('page', 0)
114
+ convert_all = data.get('convert_all', False)
115
+
116
+ if not filename:
117
+ return jsonify({'success': False, 'error': 'No filename provided'})
118
+
119
+ # Get file path
120
+ temp_dir = tempfile.gettempdir()
121
+ filepath = os.path.join(temp_dir, filename)
122
+
123
+ if not os.path.exists(filepath):
124
+ return jsonify({'success': False, 'error': 'File not found'})
125
+
126
+ if convert_all:
127
+ # Convert entire PDF
128
+ text = extract_text_from_pdf(filepath)
129
+ elif coordinates:
130
+ # Convert selected area
131
+ text = extract_text_from_pdf(filepath, page, coordinates)
132
+ else:
133
+ # Convert specific page
134
+ text = extract_text_from_pdf(filepath, page)
135
+
136
+ # Convert to LaTeX (placeholder)
137
+ latex = convert_text_to_latex(text)
138
+
139
+ return jsonify({
140
+ 'success': True,
141
+ 'latex': latex,
142
+ 'text': text
143
+ })
144
+ except Exception as e:
145
+ return jsonify({'success': False, 'error': f'Processing failed: {str(e)}'})
146
+
147
+ @pdflly_bp.route('/solve', methods=['POST'])
148
+ def solve_equation():
149
+ """Solve mathematical equations in LaTeX"""
150
+ try:
151
+ data = request.get_json()
152
+ latex = data.get('latex', '')
153
+
154
+ # This is a placeholder implementation
155
+ # In a real application, you would use SymPy or similar library
156
+ solution = {
157
+ 'type': 'expression',
158
+ 'result': f"Simplified: {latex}"
159
+ }
160
+
161
+ return jsonify({
162
+ 'success': True,
163
+ 'solution': solution
164
+ })
165
+ except Exception as e:
166
+ return jsonify({'success': False, 'error': f'Solving failed: {str(e)}'})
controller/pix2text_controller.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # controller/pix2text_bp.py
2
+ import os
3
+ import cv2
4
+ from flask import Blueprint, render_template, request, jsonify
5
+ from pix2text import Pix2Text
6
+ from utils.math_solver import solve_equation
7
+ from controller.models.camera_to_latex import camera_to_latex
8
+
9
+ # Initialize Pix2Text globally once
10
+ print("🔹 Loading Pix2Text model (mfd)...")
11
+ try:
12
+ p2t = Pix2Text(analyzer_config=dict(model_name='mfd'))
13
+ print("✅ Pix2Text model loaded successfully.")
14
+ except Exception as e:
15
+ print(f"❌ Pix2Text failed to initialize: {e}")
16
+ p2t = None
17
+
18
+ # Flask blueprint
19
+ pix2text_bp = Blueprint('pix2text_bp', __name__)
20
+
21
+ UPLOAD_FOLDER = 'static/uploads'
22
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
23
+
24
+ # Optional preprocessing
25
+ def preprocess_image(image_path):
26
+ """Preprocess image for better OCR results"""
27
+ try:
28
+ # Read image
29
+ img = cv2.imread(image_path)
30
+ if img is None:
31
+ raise ValueError("Could not read image")
32
+
33
+ # Convert to grayscale
34
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
35
+
36
+ # Apply mild Gaussian blur to reduce noise while preserving edges
37
+ blurred = cv2.GaussianBlur(gray, (3, 3), 0)
38
+
39
+ # Apply adaptive thresholding with parameters better suited for text
40
+ thresh = cv2.adaptiveThreshold(
41
+ blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, 2
42
+ )
43
+
44
+ # Save processed image
45
+ processed_path = os.path.join(
46
+ PROCESSED_FOLDER,
47
+ os.path.basename(image_path).replace('.', '_processed.')
48
+ )
49
+ cv2.imwrite(processed_path, thresh)
50
+
51
+ return processed_path
52
+ except Exception as e:
53
+ print(f"Preprocessing error: {e}")
54
+ return image_path # Return original if preprocessing fails
55
+
56
+ # -----------------------------
57
+ # Math Routes
58
+ # -----------------------------
59
+ @pix2text_bp.route("/math")
60
+ def math_page():
61
+ return render_template("math.html")
62
+
63
+ @pix2text_bp.route("/math/process", methods=["POST"])
64
+ def process_math_image():
65
+ try:
66
+ if 'image' not in request.files:
67
+ return jsonify({'error': 'No image file provided'}), 400
68
+
69
+ file = request.files['image']
70
+ if not file.filename:
71
+ return jsonify({'error': 'No file selected'}), 400
72
+
73
+ filename = file.filename
74
+ filepath = os.path.join(UPLOAD_FOLDER, filename)
75
+ file.save(filepath)
76
+
77
+ # Preprocess (optional)
78
+ processed_path = preprocess_image(filepath)
79
+
80
+ # Run Pix2Text
81
+ if p2t:
82
+ result = p2t.recognize(processed_path)
83
+ if isinstance(result, dict):
84
+ latex = result.get('text', '')
85
+ elif isinstance(result, list) and result and isinstance(result[0], dict):
86
+ latex = result[0].get('text', '')
87
+ else:
88
+ latex = str(result)
89
+ else:
90
+ latex = "\\text{Pix2Text not initialized}"
91
+
92
+ return jsonify({
93
+ 'success': True,
94
+ 'latex': latex,
95
+ 'image_path': filepath
96
+ })
97
+
98
+ except Exception as e:
99
+ print(f"❌ Error in /math/process: {e}")
100
+ return jsonify({'error': str(e)}), 500
101
+
102
+ @pix2text_bp.route("/math/solve", methods=["POST"])
103
+ def solve_math_equation():
104
+ try:
105
+ data = request.get_json()
106
+ if not data or 'latex' not in data:
107
+ return jsonify({'error': 'No equation provided'}), 400
108
+
109
+ solution = solve_equation(data['latex'])
110
+ return jsonify({'success': True, 'solution': solution})
111
+
112
+ except Exception as e:
113
+ print(f"❌ Error in /math/solve: {e}")
114
+ return jsonify({'error': str(e)}), 500
115
+
116
+ # -----------------------------
117
+ # Camera Routes
118
+ # -----------------------------
119
+ # @pix2text_bp.route("/camera")
120
+ # def camera_page():
121
+ # return render_template("camera.html")
122
+
123
+ @pix2text_bp.route("/camera")
124
+ def camera_page():
125
+ """Render the camera capture page"""
126
+ return render_template("camera.html")
127
+
128
+
129
+ @pix2text_bp.route("/camera/solve", methods=["POST"])
130
+ def solve_camera_equation():
131
+ """Solve a LaTeX equation from camera input"""
132
+ try:
133
+ data = request.get_json()
134
+ if not data:
135
+ return jsonify({'error': 'No data provided'}), 400
136
+
137
+ latex_equation = data.get('latex', '')
138
+ if not latex_equation:
139
+ return jsonify({'error': 'No equation provided'}), 400
140
+
141
+ # Solve the equation
142
+ solution = solve_equation(latex_equation)
143
+
144
+ return jsonify({
145
+ 'success': True,
146
+ 'solution': solution
147
+ })
148
+
149
+ except Exception as e:
150
+ return jsonify({'error': str(e)}), 500
151
+ return jsonify({'error': 'Unknown error'}), 500
152
+
153
+
154
+ @pix2text_bp.route("/camera/process", methods=["POST"])
155
+ def process_camera_image():
156
+ """Process camera captured image using Pix2Text"""
157
+ try:
158
+ if 'image' not in request.files:
159
+ return jsonify({'error': 'No image file provided'}), 400
160
+
161
+ file = request.files['image']
162
+ if file.filename == '':
163
+ return jsonify({'error': 'No image file selected'}), 400
164
+
165
+ if file and file.filename:
166
+ # Save original image
167
+ filename = file.filename
168
+ filepath = os.path.join(UPLOAD_FOLDER, filename)
169
+ file.save(filepath)
170
+
171
+ # For camera captures, try processing the original image first
172
+ # as preprocessing might distort mathematical symbols
173
+ processed_path = filepath
174
+
175
+ # Process with Pix2Text if available
176
+ if p2t:
177
+ print(f"Processing image: {processed_path}")
178
+ result = p2t.recognize(processed_path)
179
+ print(f"Raw result: {result}")
180
+
181
+ # Handle different result types
182
+ if isinstance(result, dict):
183
+ latex_code = result.get('text', '')
184
+ elif isinstance(result, list):
185
+ # If result is a list, extract text from first item
186
+ if result and isinstance(result[0], dict):
187
+ latex_code = result[0].get('text', '')
188
+ else:
189
+ latex_code = str(result)
190
+ else:
191
+ latex_code = str(result)
192
+
193
+ # If we get no result or very short result, try with preprocessing
194
+ if len(latex_code.strip()) < 2:
195
+ print("Result too short, trying with preprocessing...")
196
+ processed_path = preprocess_image(filepath)
197
+ result = p2t.recognize(processed_path)
198
+ print(f"Preprocessed result: {result}")
199
+
200
+ if isinstance(result, dict):
201
+ latex_code = result.get('text', '')
202
+ elif isinstance(result, list):
203
+ if result and isinstance(result[0], dict):
204
+ latex_code = result[0].get('text', '')
205
+ else:
206
+ latex_code = str(result)
207
+ else:
208
+ latex_code = str(result)
209
+
210
+ print(f"Final extracted LaTeX: {latex_code}")
211
+ else:
212
+ latex_code = "\\text{Pix2Text not available}"
213
+
214
+ return jsonify({
215
+ 'success': True,
216
+ 'latex': latex_code,
217
+ 'image_path': filepath
218
+ })
219
+
220
+ except Exception as e:
221
+ print(f"Error processing camera image: {e}")
222
+ return jsonify({'error': str(e)}), 500
223
+ return jsonify({'error': 'Unknown error'}), 500
controller/scribble_controller.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from flask import Blueprint, render_template, request, jsonify
3
+ import base64
4
+ from io import BytesIO
5
+ from PIL import Image
6
+ import numpy as np
7
+ import cv2
8
+ from utils.math_solver import solve_equation
9
+ from typing import Union, Tuple, Any
10
+
11
+ # Initialize Pix2Text with MFD (Mathematical Formula Detection) model for better accuracy
12
+ try:
13
+ from pix2text import Pix2Text
14
+ p2t = Pix2Text(analyzer_config=dict(model_name='mfd'))
15
+ except Exception as e:
16
+ print(f"Warning: Could not initialize Pix2Text: {e}")
17
+ p2t = None
18
+
19
+ scribble_bp = Blueprint('scribble_bp', __name__)
20
+
21
+ UPLOAD_FOLDER = 'static/uploads'
22
+ PROCESSED_FOLDER = 'static/processed'
23
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
24
+ os.makedirs(PROCESSED_FOLDER, exist_ok=True)
25
+
26
+
27
+ def preprocess_image(image_data):
28
+ """Preprocess image for better OCR results"""
29
+ try:
30
+ # Convert base64 to image
31
+ image_data = image_data.split(',')[1] # Remove data URL prefix
32
+ image = Image.open(BytesIO(base64.b64decode(image_data)))
33
+
34
+ # Convert to OpenCV format
35
+ opencv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
36
+
37
+ # Convert to grayscale
38
+ gray = cv2.cvtColor(opencv_image, cv2.COLOR_BGR2GRAY)
39
+
40
+ # Apply mild Gaussian blur to reduce noise while preserving edges
41
+ blurred = cv2.GaussianBlur(gray, (3, 3), 0)
42
+
43
+ # Apply adaptive thresholding with parameters better suited for handwritten text
44
+ thresh = cv2.adaptiveThreshold(
45
+ blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, 2
46
+ )
47
+
48
+ # Save processed image
49
+ processed_path = os.path.join(PROCESSED_FOLDER, 'scribble_processed.png')
50
+ cv2.imwrite(processed_path, thresh)
51
+
52
+ return processed_path
53
+ except Exception as e:
54
+ print(f"Preprocessing error: {e}")
55
+ # Save original if preprocessing fails
56
+ image_path = os.path.join(UPLOAD_FOLDER, 'scribble.png')
57
+ # Reopen image to save it
58
+ image = Image.open(BytesIO(base64.b64decode(image_data.split(',')[1])))
59
+ image.save(image_path)
60
+ return image_path
61
+
62
+
63
+ @scribble_bp.route("/scribble")
64
+ def scribble_page():
65
+ return render_template("scribble.html")
66
+
67
+
68
+ @scribble_bp.route("/scribble/process", methods=["POST"])
69
+ def process_scribble():
70
+ try:
71
+ data = request.get_json()
72
+ if not data:
73
+ return jsonify({'error': 'No data provided'}), 400
74
+
75
+ image_data = data.get('image', '')
76
+ if not image_data:
77
+ return jsonify({'error': 'No image data provided'}), 400
78
+
79
+ # Save original image first
80
+ image_path = os.path.join(UPLOAD_FOLDER, 'scribble_original.png')
81
+ image_data_content = image_data.split(',')[1]
82
+ image = Image.open(BytesIO(base64.b64decode(image_data_content)))
83
+ image.save(image_path)
84
+
85
+ # Process with Pix2Text if available
86
+ if p2t:
87
+ print(f"Processing scribble with MFD model: {image_path}")
88
+
89
+ # Try with original image first (works better for handwritten math)
90
+ result = p2t.recognize(image_path)
91
+ print(f"Original image result: {result}")
92
+
93
+ # Handle different result types
94
+ if isinstance(result, dict):
95
+ latex_code = result.get('text', '')
96
+ elif isinstance(result, list):
97
+ # If result is a list, extract text from first item
98
+ if result and isinstance(result[0], dict):
99
+ latex_code = result[0].get('text', '')
100
+ else:
101
+ latex_code = str(result)
102
+ else:
103
+ latex_code = str(result)
104
+
105
+ # If we get no result or very short result, try with preprocessing
106
+ if len(latex_code.strip()) < 2:
107
+ print("Result too short, trying with preprocessing...")
108
+ processed_path = preprocess_image(image_data)
109
+ result = p2t.recognize(processed_path)
110
+ print(f"Preprocessed image result: {result}")
111
+
112
+ if isinstance(result, dict):
113
+ latex_code = result.get('text', '')
114
+ elif isinstance(result, list):
115
+ if result and isinstance(result[0], dict):
116
+ latex_code = result[0].get('text', '')
117
+ else:
118
+ latex_code = str(result)
119
+ else:
120
+ latex_code = str(result)
121
+
122
+ print(f"Final extracted LaTeX: {latex_code}")
123
+ else:
124
+ latex_code = "\\text{Pix2Text not available}"
125
+
126
+ return jsonify({
127
+ 'success': True,
128
+ 'latex': latex_code
129
+ })
130
+
131
+ except Exception as e:
132
+ print(f"Error processing scribble: {e}")
133
+ return jsonify({'error': str(e)}), 500
134
+ return jsonify({'error': 'Unknown error'}), 500
135
+
136
+
137
+ @scribble_bp.route("/scribble/solve", methods=["POST"])
138
+ def solve_scribble_equation():
139
+ """Solve a LaTeX equation from scribble"""
140
+ try:
141
+ data = request.get_json()
142
+ if not data:
143
+ return jsonify({'error': 'No data provided'}), 400
144
+
145
+ latex_equation = data.get('latex', '')
146
+ if not latex_equation:
147
+ return jsonify({'error': 'No equation provided'}), 400
148
+
149
+ # Solve the equation
150
+ solution = solve_equation(latex_equation)
151
+
152
+ return jsonify({
153
+ 'success': True,
154
+ 'solution': solution
155
+ })
156
+
157
+ except Exception as e:
158
+ return jsonify({'error': str(e)}), 500
159
+ return jsonify({'error': 'Unknown error'}), 500
controller/table_controller.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import numpy as np
4
+ import base64
5
+ import io
6
+ from flask import Blueprint, render_template, request, jsonify
7
+ from PIL import Image
8
+ from utils.math_solver import solve_equation # optional, if you use math solving
9
+
10
+ # -------------------------------
11
+ # Blueprint setup
12
+ # -------------------------------
13
+ table_bp = Blueprint('table_bp', __name__)
14
+
15
+ UPLOAD_FOLDER = 'static/uploads'
16
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
17
+
18
+
19
+ # -------------------------------
20
+ # Core Table Detection Logic
21
+ # -------------------------------
22
+ def detect_table(image_path):
23
+ """Detect rows and columns from table image using Hough line detection."""
24
+ img = cv2.imread(image_path)
25
+ if img is None:
26
+ raise ValueError(f"Cannot read image at {image_path}")
27
+
28
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
29
+ gray = cv2.GaussianBlur(gray, (5, 5), 0)
30
+ edges = cv2.Canny(gray, 50, 150, apertureSize=3)
31
+
32
+ lines = cv2.HoughLinesP(
33
+ edges,
34
+ rho=1,
35
+ theta=np.pi / 180,
36
+ threshold=80,
37
+ minLineLength=60,
38
+ maxLineGap=15
39
+ )
40
+
41
+ if lines is None:
42
+ return 0, 0
43
+
44
+ horizontal, vertical = [], []
45
+
46
+ for x1, y1, x2, y2 in lines[:, 0]:
47
+ if abs(y2 - y1) < abs(x2 - x1): # horizontal line
48
+ horizontal.append((y1 + y2) / 2)
49
+ elif abs(x2 - x1) < abs(y2 - y1): # vertical line
50
+ vertical.append((x1 + x2) / 2)
51
+
52
+ def merge_lines(coords, gap=25):
53
+ coords = sorted(coords)
54
+ merged = []
55
+ if not coords:
56
+ return merged
57
+ group = [coords[0]]
58
+ for c in coords[1:]:
59
+ if abs(c - group[-1]) < gap:
60
+ group.append(c)
61
+ else:
62
+ merged.append(np.mean(group))
63
+ group = [c]
64
+ merged.append(np.mean(group))
65
+ return merged
66
+
67
+ horizontal = merge_lines(horizontal)
68
+ vertical = merge_lines(vertical)
69
+
70
+ num_rows = max(len(horizontal) - 1, 1)
71
+ num_cols = max(len(vertical) - 1, 1)
72
+ return num_rows, num_cols
73
+
74
+
75
+ # -------------------------------
76
+ # Generate LaTeX Table Code
77
+ # -------------------------------
78
+ def generate_latex_table(rows, cols):
79
+ col_format = '|' + '|'.join(['c'] * cols) + '|'
80
+ latex = f"\\begin{{tabular}}{{{col_format}}}\n\\hline\n"
81
+ for _ in range(rows):
82
+ latex += ' & '.join([''] * cols) + " \\\\ \\hline\n"
83
+ latex += "\\end{tabular}"
84
+ return latex
85
+
86
+
87
+ # -------------------------------
88
+ # Helpers
89
+ # -------------------------------
90
+ def save_base64_image(image_base64):
91
+ """Convert base64 image string to file and return the saved path."""
92
+ try:
93
+ if ',' in image_base64:
94
+ image_base64 = image_base64.split(',')[1]
95
+ image_bytes = base64.b64decode(image_base64)
96
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
97
+ filename = f"table_{len(os.listdir(UPLOAD_FOLDER)) + 1}.png"
98
+ filepath = os.path.join(UPLOAD_FOLDER, filename)
99
+ image.save(filepath)
100
+ return filepath
101
+ except Exception as e:
102
+ raise ValueError(f"Failed to decode base64 image: {e}")
103
+
104
+
105
+ # -------------------------------
106
+ # Routes
107
+ # -------------------------------
108
+
109
+ @table_bp.route("/table", methods=["GET"])
110
+ def table_page():
111
+ """Render the main table-to-LaTeX page."""
112
+ return render_template("table.html")
113
+
114
+
115
+ @table_bp.route("/table/process", methods=["POST"])
116
+ def process_table_image():
117
+ """
118
+ Handles both:
119
+ - Uploaded image file (FormData)
120
+ - Base64 image (drawn or camera)
121
+ """
122
+ try:
123
+ # 🟢 Case 1: File upload (from drag & drop or browse)
124
+ if 'image' in request.files:
125
+ file = request.files['image']
126
+ if not file.filename:
127
+ return jsonify({'success': False, 'error': 'No file selected.'}), 400
128
+
129
+ filename = file.filename
130
+ filepath = os.path.join(UPLOAD_FOLDER, filename)
131
+ file.save(filepath)
132
+
133
+ # 🟢 Case 2: Base64 image (from canvas draw)
134
+ elif request.is_json:
135
+ data = request.get_json()
136
+ if 'image' not in data:
137
+ return jsonify({'success': False, 'error': 'No image data provided'}), 400
138
+ filepath = save_base64_image(data['image'])
139
+
140
+ else:
141
+ return jsonify({'success': False, 'error': 'Invalid image input.'}), 400
142
+
143
+ # Detect table structure
144
+ rows, cols = detect_table(filepath)
145
+ latex_code = generate_latex_table(rows, cols)
146
+
147
+ return jsonify({
148
+ 'success': True,
149
+ 'latex': latex_code,
150
+ 'rows': rows,
151
+ 'cols': cols,
152
+ 'image_path': filepath
153
+ })
154
+
155
+ except Exception as e:
156
+ print(f"❌ Error in /table/process: {e}")
157
+ return jsonify({'success': False, 'error': str(e)}), 500
158
+
159
+
160
+ @table_bp.route("/table/generate", methods=["POST"])
161
+ def generate_table_from_input():
162
+ """Generate LaTeX manually from user-input rows/cols."""
163
+ try:
164
+ data = request.get_json()
165
+ rows = int(data.get('rows', 0))
166
+ cols = int(data.get('cols', 0))
167
+
168
+ if rows <= 0 or cols <= 0:
169
+ return jsonify({'success': False, 'error': 'Invalid rows or columns'}), 400
170
+
171
+ latex_code = generate_latex_table(rows, cols)
172
+
173
+ return jsonify({'success': True, 'latex': latex_code})
174
+ except Exception as e:
175
+ print(f"❌ Error in /table/generate: {e}")
176
+ return jsonify({'success': False, 'error': str(e)}), 500
177
+
178
+
179
+ @table_bp.route("/table/solve", methods=["POST"])
180
+ def solve_table_content():
181
+ """Optional: solve math expressions inside the LaTeX table (if supported)."""
182
+ try:
183
+ data = request.get_json()
184
+ if not data or 'latex' not in data:
185
+ return jsonify({'success': False, 'error': 'No LaTeX data provided'}), 400
186
+
187
+ solution = solve_equation(data['latex'])
188
+ return jsonify({'success': True, 'solution': solution})
189
+
190
+ except Exception as e:
191
+ print(f"❌ Error in /table/solve: {e}")
192
+ return jsonify({'success': False, 'error': str(e)}), 500
data/users.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "test_user_123": {
3
+ "id": "test_user_123",
4
+ "email": "test@example.com",
5
+ "name": "Test User",
6
+ "picture": null
7
+ },
8
+ "google_user_123": {
9
+ "id": "google_user_123",
10
+ "email": "user@gmail.com",
11
+ "name": "Google User",
12
+ "picture": null
13
+ },
14
+ "guest_gg_1762329533": {
15
+ "id": "guest_gg_1762329533",
16
+ "email": "gg@guest.texlab.com",
17
+ "name": "gg",
18
+ "picture": null
19
+ },
20
+ "guest_gg_1762330083": {
21
+ "id": "guest_gg_1762330083",
22
+ "email": "gg@guest.texlab.com",
23
+ "name": "gg",
24
+ "picture": null
25
+ },
26
+ "guest_syk_1762332308": {
27
+ "id": "guest_syk_1762332308",
28
+ "email": "syk@guest.texlab.com",
29
+ "name": "syk",
30
+ "picture": null
31
+ },
32
+ "guest_gg_1762333085": {
33
+ "id": "guest_gg_1762333085",
34
+ "email": "gg@guest.texlab.com",
35
+ "name": "gg",
36
+ "picture": null
37
+ },
38
+ "guest_syk_1762333094": {
39
+ "id": "guest_syk_1762333094",
40
+ "email": "syk@guest.texlab.com",
41
+ "name": "syk",
42
+ "picture": null
43
+ },
44
+ "guest_syk_1762333160": {
45
+ "id": "guest_syk_1762333160",
46
+ "email": "syk@guest.texlab.com",
47
+ "name": "syk",
48
+ "picture": null
49
+ },
50
+ "guest_syk_1762333181": {
51
+ "id": "guest_syk_1762333181",
52
+ "email": "syk@guest.texlab.com",
53
+ "name": "syk",
54
+ "picture": null
55
+ },
56
+ "guest_ali_1762333646": {
57
+ "id": "guest_ali_1762333646",
58
+ "email": "ali@guest.texlab.com",
59
+ "name": "ali",
60
+ "picture": null
61
+ },
62
+ "guest_ali_ashraf_1762334613": {
63
+ "id": "guest_ali_ashraf_1762334613",
64
+ "email": "ali.ashraf@guest.texlab.com",
65
+ "name": "ALI ASHRAF",
66
+ "picture": null
67
+ },
68
+ "guest_ashraf_1762608609": {
69
+ "id": "guest_ashraf_1762608609",
70
+ "email": "ashraf@guest.texlab.com",
71
+ "name": "Ashraf",
72
+ "picture": null
73
+ }
74
+ }
models/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Models package
models/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (176 Bytes). View file
 
models/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (147 Bytes). View file
 
models/__pycache__/user.cpython-311.pyc ADDED
Binary file (3.64 kB). View file
 
models/__pycache__/user.cpython-313.pyc ADDED
Binary file (3.01 kB). View file