awacke1 commited on
Commit
a6e4388
Β·
verified Β·
1 Parent(s): 461cdbc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +29 -26
app.py CHANGED
@@ -20,6 +20,8 @@ from reportlab.pdfbase import pdfmetrics
20
  from reportlab.pdfbase.ttfonts import TTFont
21
 
22
  # --- Configuration & Setup ---
 
 
23
  LAYOUTS = {
24
  "A4 Portrait": {"size": A4},
25
  "A4 Landscape": {"size": landscape(A4)},
@@ -28,9 +30,9 @@ LAYOUTS = {
28
  "Legal Portrait": {"size": legal},
29
  "Legal Landscape": {"size": landscape(legal)},
30
  }
31
- OUTPUT_DIR = Path("generated_pdfs")
32
- PREVIEW_DIR = Path("previews")
33
- FONT_DIR = Path("fonts")
34
  EMOJI_FONT_NAME = "NotoColorEmoji"
35
 
36
  # Create necessary directories
@@ -43,6 +45,7 @@ FONT_DIR.mkdir(exist_ok=True)
43
 
44
  def download_and_register_fonts():
45
  """Downloads default fonts if needed, then finds and registers all .ttf files."""
 
46
  fonts_to_download = {
47
  "DejaVuSans.ttf": "https://github.com/dejavu-fonts/dejavu-fonts/blob/main/ttf/DejaVuSans.ttf?raw=true",
48
  "NotoColorEmoji.ttf": "https://github.com/googlefonts/noto-emoji/blob/main/fonts/NotoColorEmoji.ttf?raw=true"
@@ -60,14 +63,17 @@ def download_and_register_fonts():
60
  print(f"Failed to download {filename}: {e}")
61
 
62
  font_names = []
63
- for font_path in FONT_DIR.glob("*.ttf"):
 
 
 
 
64
  try:
65
  font_name = font_path.stem
 
66
  pdfmetrics.registerFont(TTFont(font_name, str(font_path)))
67
 
68
  # CRITICAL FIX for bold/italic error:
69
- # Register dummy bold/italic variants for all fonts, including emoji fonts.
70
- # ReportLab will use these fallbacks instead of crashing if a true variant is not found.
71
  pdfmetrics.registerFont(TTFont(f"{font_name}-Bold", str(font_path)))
72
  pdfmetrics.registerFont(TTFont(f"{font_name}-Italic", str(font_path)))
73
  pdfmetrics.registerFont(TTFont(f"{font_name}-BoldItalic", str(font_path)))
@@ -77,6 +83,8 @@ def download_and_register_fonts():
77
  font_names.append(font_name)
78
  except Exception as e:
79
  print(f"Could not register font {font_path.name}: {e}")
 
 
80
  return sorted(font_names)
81
 
82
  def apply_emoji_font(text: str) -> str:
@@ -93,20 +101,17 @@ def apply_emoji_font(text: str) -> str:
93
 
94
  def markdown_to_story(markdown_text: str, font_name: str):
95
  """Converts markdown to a ReportLab story, handling emojis and page breaks."""
96
- # This function remains largely the same but relies on the global font registration fix.
97
  styles = getSampleStyleSheet()
98
  style_normal = ParagraphStyle('BodyText', fontName=font_name, spaceAfter=6, leading=14)
99
  style_h1 = ParagraphStyle('h1', parent=styles['h1'], fontName=font_name, spaceBefore=12, fontSize=20, leading=24)
100
  style_code = ParagraphStyle('Code', fontName='Courier', backColor=colors.whitesmoke)
101
 
102
  story = []
103
- # Process content line-by-line
104
  for line in markdown_text.split('\n'):
105
  line_with_emoji = apply_emoji_font(line)
106
  formatted_line = re.sub(r'_(.*?)_', r'<i>\1</i>', re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line_with_emoji))
107
 
108
  if line.startswith("# "): story.append(Paragraph(formatted_line[2:], style_h1))
109
- # ... other markdown elements would be handled similarly
110
  else: story.append(Paragraph(formatted_line, style_normal))
111
  return story
112
 
@@ -119,7 +124,7 @@ def create_pdf_preview(pdf_path: Path):
119
  pix = page.get_pixmap()
120
  pix.save(str(preview_path))
121
  doc.close()
122
- return preview_path
123
  except Exception as e:
124
  print(f"Could not create preview for {pdf_path.name}: {e}")
125
  return None
@@ -137,7 +142,6 @@ def generate_pdfs_api(files, layouts, fonts, num_columns, progress=gr.Progress(t
137
  OUTPUT_DIR.mkdir()
138
  PREVIEW_DIR.mkdir()
139
 
140
- # Group files by a common stem (e.g., AARON_1.md and AARON_2.png are grouped under "AARON")
141
  grouped_files = defaultdict(lambda: {'md': [], 'img': []})
142
  for f in files:
143
  file_path = Path(f.name)
@@ -155,7 +159,6 @@ def generate_pdfs_api(files, layouts, fonts, num_columns, progress=gr.Progress(t
155
  for font_name in fonts:
156
  merger = PdfWriter()
157
 
158
- # --- Process Markdown ---
159
  if assets['md']:
160
  md_content = "\n\n---PAGE_BREAK---\n\n".join([p.read_text(encoding='utf-8') for p in assets['md']])
161
  md_buffer = io.BytesIO()
@@ -173,18 +176,14 @@ def generate_pdfs_api(files, layouts, fonts, num_columns, progress=gr.Progress(t
173
  doc.build(story)
174
  merger.append(fileobj=md_buffer)
175
 
176
- # --- Process Images ---
177
  for img_path in assets['img']:
178
  with Image.open(img_path) as img:
179
  img_width, img_height = img.size
180
-
181
  img_buffer = io.BytesIO()
182
- # Page size is determined by the image itself
183
  doc = SimpleDocTemplate(img_buffer, pagesize=(img_width, img_height), leftMargin=0, rightMargin=0, topMargin=0, bottomMargin=0)
184
  doc.build([ReportLabImage(img_path, width=img_width, height=img_height)])
185
  merger.append(fileobj=img_buffer)
186
 
187
- # --- Save Merged PDF ---
188
  if len(merger.pages) > 0:
189
  time_str = datetime.datetime.now().strftime('%m-%d-%a_%I%M%p').upper()
190
  filename = f"{stem}_{time_str}_{layout_name.replace(' ','-')}_{font_name}_Cols{num_columns}.pdf"
@@ -194,16 +193,15 @@ def generate_pdfs_api(files, layouts, fonts, num_columns, progress=gr.Progress(t
194
  generated_pdf_paths.append(output_path)
195
  log_updates += f"Generated: {filename}\n"
196
 
197
- # --- Create Previews for Gallery ---
198
  gallery_previews = [create_pdf_preview(p) for p in generated_pdf_paths]
199
  final_gallery = [g for g in gallery_previews if g is not None]
200
 
201
- return final_gallery, log_updates
202
 
203
  # --- Gradio UI Definition ---
204
  AVAILABLE_FONTS = download_and_register_fonts()
205
  SAMPLE_MARKDOWN = "# Sample Document πŸš€\n\nThis shows **bold**, _italic_, and emojis like 😊 and πŸ’».\n\n### A Table\n| Flavor | Rating |\n|---|---|\n| Chocolate 🍫| 10/10 |\n| Vanilla 🍦| 9/10 |\n\n```python\n# Code blocks too!\ndef hello():\n return 'Hello from PDF!'\n```"
206
- with open("sample.md", "w") as f: f.write(SAMPLE_MARKDOWN)
207
 
208
  with gr.Blocks(theme=gr.themes.Soft(), title="Advanced PDF Generator") as demo:
209
  gr.Markdown("# πŸ“„ Advanced PDF Generator")
@@ -212,7 +210,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Advanced PDF Generator") as demo:
212
  with gr.Row():
213
  with gr.Column(scale=1):
214
  gr.Markdown("### βš™οΈ Generation Settings")
215
- uploaded_files = gr.File(label="Upload Markdown & Image Files", file_count="multiple", file_types=[".md", ".png", ".jpg"])
216
  num_columns_slider = gr.Slider(label="Number of Columns for Text", minimum=1, maximum=4, step=1, value=1)
217
  selected_layouts = gr.CheckboxGroup(choices=list(LAYOUTS.keys()), label="Select Page Layouts", value=["A4 Portrait"])
218
  selected_fonts = gr.CheckboxGroup(choices=AVAILABLE_FONTS, label="Select Text Fonts", value=[AVAILABLE_FONTS[0]] if AVAILABLE_FONTS else [])
@@ -222,16 +220,21 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Advanced PDF Generator") as demo:
222
  gr.Markdown("### πŸ–ΌοΈ PDF Preview Gallery")
223
  gallery_output = gr.Gallery(label="Generated PDF Previews", show_label=False, elem_id="gallery", columns=3, height="auto")
224
  log_output = gr.Markdown(label="Generation Log", value="Logs will appear here...")
 
225
 
226
- def auto_run_demo():
227
  """Function to run on app load to generate a sample PDF."""
228
  print("Running initial demo generation...")
229
- demo_files = [gr.File(name="sample.md", data=b'')] # Gradio needs a File object
230
- previews, logs = generate_pdfs_api(demo_files, ["A4 Portrait"], [AVAILABLE_FONTS[0]] if AVAILABLE_FONTS else [], 1)
231
- return previews, logs
 
 
 
 
232
 
233
- demo.load(auto_run_demo, inputs=None, outputs=[gallery_output, log_output])
234
- generate_btn.click(fn=generate_pdfs_api, inputs=[uploaded_files, selected_layouts, selected_fonts, num_columns_slider], outputs=[gallery_output, log_output])
235
 
236
  if __name__ == "__main__":
237
  demo.launch()
 
20
  from reportlab.pdfbase.ttfonts import TTFont
21
 
22
  # --- Configuration & Setup ---
23
+ # Use the current working directory as the base for all paths for robustness.
24
+ CWD = Path.cwd()
25
  LAYOUTS = {
26
  "A4 Portrait": {"size": A4},
27
  "A4 Landscape": {"size": landscape(A4)},
 
30
  "Legal Portrait": {"size": legal},
31
  "Legal Landscape": {"size": landscape(legal)},
32
  }
33
+ OUTPUT_DIR = CWD / "generated_pdfs"
34
+ PREVIEW_DIR = CWD / "previews"
35
+ FONT_DIR = CWD / "fonts"
36
  EMOJI_FONT_NAME = "NotoColorEmoji"
37
 
38
  # Create necessary directories
 
45
 
46
  def download_and_register_fonts():
47
  """Downloads default fonts if needed, then finds and registers all .ttf files."""
48
+ print("--- Font Registration Process Starting ---")
49
  fonts_to_download = {
50
  "DejaVuSans.ttf": "https://github.com/dejavu-fonts/dejavu-fonts/blob/main/ttf/DejaVuSans.ttf?raw=true",
51
  "NotoColorEmoji.ttf": "https://github.com/googlefonts/noto-emoji/blob/main/fonts/NotoColorEmoji.ttf?raw=true"
 
63
  print(f"Failed to download {filename}: {e}")
64
 
65
  font_names = []
66
+ print(f"Scanning for fonts in: {FONT_DIR.absolute()}")
67
+ font_files = list(FONT_DIR.glob("*.ttf"))
68
+ print(f"Found {len(font_files)} .ttf files: {[f.name for f in font_files]}")
69
+
70
+ for font_path in font_files:
71
  try:
72
  font_name = font_path.stem
73
+ print(f"Registering font: '{font_name}' from {font_path.name}")
74
  pdfmetrics.registerFont(TTFont(font_name, str(font_path)))
75
 
76
  # CRITICAL FIX for bold/italic error:
 
 
77
  pdfmetrics.registerFont(TTFont(f"{font_name}-Bold", str(font_path)))
78
  pdfmetrics.registerFont(TTFont(f"{font_name}-Italic", str(font_path)))
79
  pdfmetrics.registerFont(TTFont(f"{font_name}-BoldItalic", str(font_path)))
 
83
  font_names.append(font_name)
84
  except Exception as e:
85
  print(f"Could not register font {font_path.name}: {e}")
86
+ print(f"Successfully registered user-selectable fonts: {font_names}")
87
+ print("--- Font Registration Process Finished ---")
88
  return sorted(font_names)
89
 
90
  def apply_emoji_font(text: str) -> str:
 
101
 
102
  def markdown_to_story(markdown_text: str, font_name: str):
103
  """Converts markdown to a ReportLab story, handling emojis and page breaks."""
 
104
  styles = getSampleStyleSheet()
105
  style_normal = ParagraphStyle('BodyText', fontName=font_name, spaceAfter=6, leading=14)
106
  style_h1 = ParagraphStyle('h1', parent=styles['h1'], fontName=font_name, spaceBefore=12, fontSize=20, leading=24)
107
  style_code = ParagraphStyle('Code', fontName='Courier', backColor=colors.whitesmoke)
108
 
109
  story = []
 
110
  for line in markdown_text.split('\n'):
111
  line_with_emoji = apply_emoji_font(line)
112
  formatted_line = re.sub(r'_(.*?)_', r'<i>\1</i>', re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line_with_emoji))
113
 
114
  if line.startswith("# "): story.append(Paragraph(formatted_line[2:], style_h1))
 
115
  else: story.append(Paragraph(formatted_line, style_normal))
116
  return story
117
 
 
124
  pix = page.get_pixmap()
125
  pix.save(str(preview_path))
126
  doc.close()
127
+ return str(preview_path)
128
  except Exception as e:
129
  print(f"Could not create preview for {pdf_path.name}: {e}")
130
  return None
 
142
  OUTPUT_DIR.mkdir()
143
  PREVIEW_DIR.mkdir()
144
 
 
145
  grouped_files = defaultdict(lambda: {'md': [], 'img': []})
146
  for f in files:
147
  file_path = Path(f.name)
 
159
  for font_name in fonts:
160
  merger = PdfWriter()
161
 
 
162
  if assets['md']:
163
  md_content = "\n\n---PAGE_BREAK---\n\n".join([p.read_text(encoding='utf-8') for p in assets['md']])
164
  md_buffer = io.BytesIO()
 
176
  doc.build(story)
177
  merger.append(fileobj=md_buffer)
178
 
 
179
  for img_path in assets['img']:
180
  with Image.open(img_path) as img:
181
  img_width, img_height = img.size
 
182
  img_buffer = io.BytesIO()
 
183
  doc = SimpleDocTemplate(img_buffer, pagesize=(img_width, img_height), leftMargin=0, rightMargin=0, topMargin=0, bottomMargin=0)
184
  doc.build([ReportLabImage(img_path, width=img_width, height=img_height)])
185
  merger.append(fileobj=img_buffer)
186
 
 
187
  if len(merger.pages) > 0:
188
  time_str = datetime.datetime.now().strftime('%m-%d-%a_%I%M%p').upper()
189
  filename = f"{stem}_{time_str}_{layout_name.replace(' ','-')}_{font_name}_Cols{num_columns}.pdf"
 
193
  generated_pdf_paths.append(output_path)
194
  log_updates += f"Generated: {filename}\n"
195
 
 
196
  gallery_previews = [create_pdf_preview(p) for p in generated_pdf_paths]
197
  final_gallery = [g for g in gallery_previews if g is not None]
198
 
199
+ return final_gallery, log_updates, [str(p) for p in generated_pdf_paths]
200
 
201
  # --- Gradio UI Definition ---
202
  AVAILABLE_FONTS = download_and_register_fonts()
203
  SAMPLE_MARKDOWN = "# Sample Document πŸš€\n\nThis shows **bold**, _italic_, and emojis like 😊 and πŸ’».\n\n### A Table\n| Flavor | Rating |\n|---|---|\n| Chocolate 🍫| 10/10 |\n| Vanilla 🍦| 9/10 |\n\n```python\n# Code blocks too!\ndef hello():\n return 'Hello from PDF!'\n```"
204
+ with open(CWD / "sample.md", "w") as f: f.write(SAMPLE_MARKDOWN)
205
 
206
  with gr.Blocks(theme=gr.themes.Soft(), title="Advanced PDF Generator") as demo:
207
  gr.Markdown("# πŸ“„ Advanced PDF Generator")
 
210
  with gr.Row():
211
  with gr.Column(scale=1):
212
  gr.Markdown("### βš™οΈ Generation Settings")
213
+ uploaded_files = gr.File(label="Upload Markdown & Image Files", file_count="multiple", file_types=[".md", ".png", ".jpg", ".jpeg"])
214
  num_columns_slider = gr.Slider(label="Number of Columns for Text", minimum=1, maximum=4, step=1, value=1)
215
  selected_layouts = gr.CheckboxGroup(choices=list(LAYOUTS.keys()), label="Select Page Layouts", value=["A4 Portrait"])
216
  selected_fonts = gr.CheckboxGroup(choices=AVAILABLE_FONTS, label="Select Text Fonts", value=[AVAILABLE_FONTS[0]] if AVAILABLE_FONTS else [])
 
220
  gr.Markdown("### πŸ–ΌοΈ PDF Preview Gallery")
221
  gallery_output = gr.Gallery(label="Generated PDF Previews", show_label=False, elem_id="gallery", columns=3, height="auto")
222
  log_output = gr.Markdown(label="Generation Log", value="Logs will appear here...")
223
+ downloadable_files_output = gr.Files(label="Download Generated PDFs")
224
 
225
+ def auto_run_demo(request: gr.Request):
226
  """Function to run on app load to generate a sample PDF."""
227
  print("Running initial demo generation...")
228
+ # Create a dummy file object for Gradio
229
+ with open(CWD / "sample.md", "rb") as f:
230
+ sample_bytes = f.read()
231
+
232
+ demo_files = [gr.processing_utils.SavedFile(name=str(CWD / "sample.md"), data=sample_bytes, is_file=True)]
233
+ previews, logs, files = generate_pdfs_api(demo_files, ["A4 Portrait"], [AVAILABLE_FONTS[0]] if AVAILABLE_FONTS else [], 1)
234
+ return previews, logs, files
235
 
236
+ demo.load(auto_run_demo, inputs=None, outputs=[gallery_output, log_output, downloadable_files_output])
237
+ generate_btn.click(fn=generate_pdfs_api, inputs=[uploaded_files, selected_layouts, selected_fonts, num_columns_slider], outputs=[gallery_output, log_output, downloadable_files_output])
238
 
239
  if __name__ == "__main__":
240
  demo.launch()