Spaces:
Sleeping
Sleeping
firepenguindisopanda commited on
Commit ·
7108b9e
1
Parent(s): f8aef8d
Fixed: pdf creation issue
Browse files- app/routers/web.py +23 -29
- scripts/debug_pdf_uv.py +24 -0
app/routers/web.py
CHANGED
|
@@ -74,6 +74,18 @@ def generate_pdf_document(project_description: str, markdown_outputs: dict[str,
|
|
| 74 |
pdf = FPDF()
|
| 75 |
pdf.set_auto_page_break(auto=True, margin=15)
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
# Add title page
|
| 78 |
pdf.add_page()
|
| 79 |
pdf.set_font("Arial", "B", 24)
|
|
@@ -94,12 +106,7 @@ def generate_pdf_document(project_description: str, markdown_outputs: dict[str,
|
|
| 94 |
for line in description_lines:
|
| 95 |
cleaned_line = clean_markdown(line.strip())
|
| 96 |
if cleaned_line:
|
| 97 |
-
|
| 98 |
-
pdf.multi_cell(0, 6, cleaned_line)
|
| 99 |
-
except Exception:
|
| 100 |
-
# Handle encoding issues
|
| 101 |
-
safe_line = cleaned_line.encode('latin-1', 'replace').decode('latin-1')
|
| 102 |
-
pdf.multi_cell(0, 6, safe_line)
|
| 103 |
|
| 104 |
pdf.ln(10)
|
| 105 |
|
|
@@ -159,11 +166,7 @@ def generate_pdf_document(project_description: str, markdown_outputs: dict[str,
|
|
| 159 |
else:
|
| 160 |
pdf.set_font("Arial", "B", 10)
|
| 161 |
|
| 162 |
-
|
| 163 |
-
pdf.multi_cell(0, 6, heading_text)
|
| 164 |
-
except Exception:
|
| 165 |
-
safe_text = heading_text.encode('latin-1', 'replace').decode('latin-1')
|
| 166 |
-
pdf.multi_cell(0, 6, safe_text)
|
| 167 |
|
| 168 |
pdf.set_font("Arial", "", 10)
|
| 169 |
pdf.ln(2)
|
|
@@ -177,40 +180,32 @@ def generate_pdf_document(project_description: str, markdown_outputs: dict[str,
|
|
| 177 |
# Handle bullet points
|
| 178 |
if line.strip().startswith(('- ', '* ', '+ ')):
|
| 179 |
cleaned = clean_markdown(line.strip()[2:])
|
| 180 |
-
|
| 181 |
-
pdf.multi_cell(0, 5, f" • {cleaned}")
|
| 182 |
-
except Exception:
|
| 183 |
-
safe_text = cleaned.encode('latin-1', 'replace').decode('latin-1')
|
| 184 |
-
pdf.multi_cell(0, 5, f" • {safe_text}")
|
| 185 |
continue
|
| 186 |
|
| 187 |
# Handle numbered lists
|
| 188 |
if re.match(r'^\d+\.\s', line.strip()):
|
| 189 |
cleaned = clean_markdown(line.strip())
|
| 190 |
-
|
| 191 |
-
pdf.multi_cell(0, 5, f" {cleaned}")
|
| 192 |
-
except Exception:
|
| 193 |
-
safe_text = cleaned.encode('latin-1', 'replace').decode('latin-1')
|
| 194 |
-
pdf.multi_cell(0, 5, f" {safe_text}")
|
| 195 |
continue
|
| 196 |
|
| 197 |
# Regular text
|
| 198 |
if line.strip():
|
| 199 |
cleaned = clean_markdown(line)
|
| 200 |
-
|
| 201 |
-
pdf.multi_cell(0, 5, cleaned)
|
| 202 |
-
except Exception:
|
| 203 |
-
safe_text = cleaned.encode('latin-1', 'replace').decode('latin-1')
|
| 204 |
-
pdf.multi_cell(0, 5, safe_text)
|
| 205 |
else:
|
| 206 |
pdf.ln(3)
|
| 207 |
|
| 208 |
# Generate PDF in memory
|
| 209 |
pdf_output = BytesIO()
|
| 210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
pdf_output.write(pdf_bytes)
|
| 212 |
pdf_output.seek(0)
|
| 213 |
-
|
| 214 |
return pdf_output
|
| 215 |
|
| 216 |
|
|
@@ -249,4 +244,3 @@ async def download_pdf(request: PDFRequest):
|
|
| 249 |
)
|
| 250 |
|
| 251 |
|
| 252 |
-
# NOTE: HTML UI routes removed in favor of API-only implementation.
|
|
|
|
| 74 |
pdf = FPDF()
|
| 75 |
pdf.set_auto_page_break(auto=True, margin=15)
|
| 76 |
|
| 77 |
+
def safe_multi_cell(height: int, text: str):
|
| 78 |
+
"""Safely write text to PDF, handling encoding and layout errors."""
|
| 79 |
+
if not text or not text.strip():
|
| 80 |
+
return
|
| 81 |
+
# Sanitize text for latin-1 encoding used by core fonts
|
| 82 |
+
safe_text = text.encode('latin-1', 'replace').decode('latin-1')
|
| 83 |
+
try:
|
| 84 |
+
pdf.multi_cell(0, height, safe_text)
|
| 85 |
+
except Exception:
|
| 86 |
+
# If multi_cell still fails (e.g., layout issue), skip this line
|
| 87 |
+
pass
|
| 88 |
+
|
| 89 |
# Add title page
|
| 90 |
pdf.add_page()
|
| 91 |
pdf.set_font("Arial", "B", 24)
|
|
|
|
| 106 |
for line in description_lines:
|
| 107 |
cleaned_line = clean_markdown(line.strip())
|
| 108 |
if cleaned_line:
|
| 109 |
+
safe_multi_cell(6, cleaned_line)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
pdf.ln(10)
|
| 112 |
|
|
|
|
| 166 |
else:
|
| 167 |
pdf.set_font("Arial", "B", 10)
|
| 168 |
|
| 169 |
+
safe_multi_cell(6, heading_text)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
pdf.set_font("Arial", "", 10)
|
| 172 |
pdf.ln(2)
|
|
|
|
| 180 |
# Handle bullet points
|
| 181 |
if line.strip().startswith(('- ', '* ', '+ ')):
|
| 182 |
cleaned = clean_markdown(line.strip()[2:])
|
| 183 |
+
safe_multi_cell(5, f" - {cleaned}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
continue
|
| 185 |
|
| 186 |
# Handle numbered lists
|
| 187 |
if re.match(r'^\d+\.\s', line.strip()):
|
| 188 |
cleaned = clean_markdown(line.strip())
|
| 189 |
+
safe_multi_cell(5, f" {cleaned}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
continue
|
| 191 |
|
| 192 |
# Regular text
|
| 193 |
if line.strip():
|
| 194 |
cleaned = clean_markdown(line)
|
| 195 |
+
safe_multi_cell(5, cleaned)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
else:
|
| 197 |
pdf.ln(3)
|
| 198 |
|
| 199 |
# Generate PDF in memory
|
| 200 |
pdf_output = BytesIO()
|
| 201 |
+
pdf_str = pdf.output(dest='S')
|
| 202 |
+
if isinstance(pdf_str, str):
|
| 203 |
+
pdf_bytes = pdf_str.encode('latin-1')
|
| 204 |
+
else:
|
| 205 |
+
pdf_bytes = pdf_str
|
| 206 |
pdf_output.write(pdf_bytes)
|
| 207 |
pdf_output.seek(0)
|
| 208 |
+
|
| 209 |
return pdf_output
|
| 210 |
|
| 211 |
|
|
|
|
| 244 |
)
|
| 245 |
|
| 246 |
|
|
|
scripts/debug_pdf_uv.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import sys
|
| 3 |
+
import tempfile
|
| 4 |
+
|
| 5 |
+
# Ensure project package imports work when run from workspace root
|
| 6 |
+
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
| 7 |
+
|
| 8 |
+
from app.routers.web import generate_pdf_document
|
| 9 |
+
|
| 10 |
+
if __name__ == '__main__':
|
| 11 |
+
pdf = generate_pdf_document(
|
| 12 |
+
"Test project description for UV run",
|
| 13 |
+
{
|
| 14 |
+
"product_owner": "# Product Vision\nThis is a test product.\n- Item A\n- Item B",
|
| 15 |
+
"developer": "## Implementation\n- Use FastAPI\n- Use React"
|
| 16 |
+
}
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
out_path = Path(tempfile.gettempdir()) / "srs_test_uv.pdf"
|
| 20 |
+
with open(out_path, "wb") as f:
|
| 21 |
+
f.write(pdf.getvalue())
|
| 22 |
+
|
| 23 |
+
print(f"Wrote PDF to: {out_path}")
|
| 24 |
+
print(f"Size: {out_path.stat().st_size} bytes")
|