awacke1 commited on
Commit
f8790d9
Β·
verified Β·
1 Parent(s): 3cb3215

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -78
app.py CHANGED
@@ -2,7 +2,6 @@ import gradio as gr
2
  from pathlib import Path
3
  import datetime
4
  import re
5
- import requests
6
  import os
7
  import shutil
8
  import fitz # PyMuPDF
@@ -32,27 +31,29 @@ LAYOUTS = {
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
39
  OUTPUT_DIR.mkdir(exist_ok=True)
40
  PREVIEW_DIR.mkdir(exist_ok=True)
41
- FONT_DIR.mkdir(exist_ok=True)
42
 
43
 
44
  # --- Font & Emoji Handling ---
45
 
46
- def download_and_register_fonts():
47
- """Finds and registers all .ttf files from the local 'fonts' directory."""
48
  print("--- Font Registration Process Starting ---")
49
- font_names = []
 
 
50
  print(f"Scanning for fonts in: {FONT_DIR.absolute()}")
 
51
  font_files = list(FONT_DIR.glob("*.ttf"))
52
  print(f"Found {len(font_files)} .ttf files: {[f.name for f in font_files]}")
53
 
54
  if not font_files:
55
- print("WARNING: No .ttf files found in the 'fonts' directory. Please add font files to use them in the application.")
56
 
57
  for font_path in font_files:
58
  try:
@@ -60,51 +61,103 @@ def download_and_register_fonts():
60
  print(f"Registering font: '{font_name}' from {font_path.name}")
61
  pdfmetrics.registerFont(TTFont(font_name, str(font_path)))
62
 
63
- # CRITICAL FIX for bold/italic error:
64
  pdfmetrics.registerFont(TTFont(f"{font_name}-Bold", str(font_path)))
65
  pdfmetrics.registerFont(TTFont(f"{font_name}-Italic", str(font_path)))
66
  pdfmetrics.registerFont(TTFont(f"{font_name}-BoldItalic", str(font_path)))
67
  pdfmetrics.registerFontFamily(font_name, normal=font_name, bold=f"{font_name}-Bold", italic=f"{font_name}-Italic", boldItalic=f"{font_name}-BoldItalic")
68
 
69
- # Exclude specific emoji fonts from the main selection list as they are handled automatically.
70
- # We use a more general check to catch variants like NotoEmoji-Bold etc.
71
- if "notoemoji" not in font_name.lower():
72
- font_names.append(font_name)
73
  except Exception as e:
74
  print(f"Could not register font {font_path.name}: {e}")
75
- print(f"Successfully registered user-selectable fonts: {font_names}")
 
 
 
 
 
 
76
  print("--- Font Registration Process Finished ---")
77
- return sorted(font_names)
78
 
79
- def apply_emoji_font(text: str) -> str:
80
  """Wraps emoji characters in a <font> tag to use the dedicated emoji font."""
81
- # Assuming 'NotoColorEmoji-Regular' is the intended font for color emojis.
82
- # The font name here must match the one registered from the filename.
83
- emoji_font_to_use = "NotoColorEmoji-Regular"
84
  emoji_pattern = re.compile(f"([{re.escape(''.join(map(chr, range(0x1f600, 0x1f650))))}"
85
  f"{re.escape(''.join(map(chr, range(0x1f300, 0x1f5ff))))}"
86
  f"{re.escape(''.join(map(chr, range(0x1f900, 0x1f9ff))))}"
87
  f"{re.escape(''.join(map(chr, range(0x2600, 0x26ff))))}"
88
  f"{re.escape(''.join(map(chr, range(0x2700, 0x27bf))))}]+)")
89
- return emoji_pattern.sub(fr'<font name="{emoji_font_to_use}">\1</font>', text)
90
 
91
 
92
  # --- PDF Generation & Handling ---
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
  styles = getSampleStyleSheet()
97
  style_normal = ParagraphStyle('BodyText', fontName=font_name, spaceAfter=6, leading=14)
98
  style_h1 = ParagraphStyle('h1', parent=styles['h1'], fontName=font_name, spaceBefore=12, fontSize=20, leading=24)
99
- style_code = ParagraphStyle('Code', fontName='Courier', backColor=colors.whitesmoke)
100
-
 
 
 
101
  story = []
102
- for line in markdown_text.split('\n'):
103
- line_with_emoji = apply_emoji_font(line)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  formatted_line = re.sub(r'_(.*?)_', r'<i>\1</i>', re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line_with_emoji))
105
 
106
  if line.startswith("# "): story.append(Paragraph(formatted_line[2:], style_h1))
 
 
 
 
 
107
  else: story.append(Paragraph(formatted_line, style_normal))
 
108
  return story
109
 
110
  def create_pdf_preview(pdf_path: Path):
@@ -112,39 +165,29 @@ def create_pdf_preview(pdf_path: Path):
112
  preview_path = PREVIEW_DIR / f"{pdf_path.stem}.png"
113
  try:
114
  doc = fitz.open(pdf_path)
115
- page = doc.load_page(0)
116
- pix = page.get_pixmap()
117
- pix.save(str(preview_path))
118
- doc.close()
119
  return str(preview_path)
120
  except Exception as e:
121
- print(f"Could not create preview for {pdf_path.name}: {e}")
122
- return None
123
 
124
  # --- Main API Function ---
125
-
126
  def generate_pdfs_api(files, layouts, fonts, num_columns, progress=gr.Progress(track_tqdm=True)):
127
- """Main function to drive PDF generation from the Gradio UI."""
128
  if not files: raise gr.Error("Please upload at least one Markdown or Image file.")
129
  if not layouts: raise gr.Error("Please select at least one page layout.")
130
  if not fonts: raise gr.Error("Please select at least one font.")
131
 
132
- shutil.rmtree(OUTPUT_DIR, ignore_errors=True)
133
- shutil.rmtree(PREVIEW_DIR, ignore_errors=True)
134
- OUTPUT_DIR.mkdir()
135
- PREVIEW_DIR.mkdir()
136
 
137
  grouped_files = defaultdict(lambda: {'md': [], 'img': []})
138
  for f in files:
139
  file_path = Path(f.name)
140
  stem = file_path.stem.split('_')[0] if '_' in file_path.stem else file_path.stem
141
- if file_path.suffix.lower() == '.md':
142
- grouped_files[stem]['md'].append(file_path)
143
- elif file_path.suffix.lower() in ['.png', '.jpg', '.jpeg']:
144
- grouped_files[stem]['img'].append(file_path)
145
 
146
- log_updates = ""
147
- generated_pdf_paths = []
148
 
149
  for stem, assets in progress.tqdm(grouped_files.items(), desc="Processing File Groups"):
150
  for layout_name in layouts:
@@ -154,7 +197,7 @@ def generate_pdfs_api(files, layouts, fonts, num_columns, progress=gr.Progress(t
154
  if assets['md']:
155
  md_content = "\n\n---PAGE_BREAK---\n\n".join([p.read_text(encoding='utf-8') for p in assets['md']])
156
  md_buffer = io.BytesIO()
157
- story = markdown_to_story(md_content, font_name)
158
  pagesize = LAYOUTS[layout_name]["size"]
159
 
160
  if num_columns > 1:
@@ -164,13 +207,11 @@ def generate_pdfs_api(files, layouts, fonts, num_columns, progress=gr.Progress(t
164
  doc.addPageTemplates([PageTemplate(id='MultiCol', frames=frames)])
165
  else:
166
  doc = SimpleDocTemplate(md_buffer, pagesize=pagesize)
167
-
168
  doc.build(story)
169
  merger.append(fileobj=md_buffer)
170
 
171
  for img_path in assets['img']:
172
- with Image.open(img_path) as img:
173
- img_width, img_height = img.size
174
  img_buffer = io.BytesIO()
175
  doc = SimpleDocTemplate(img_buffer, pagesize=(img_width, img_height), leftMargin=0, rightMargin=0, topMargin=0, bottomMargin=0)
176
  doc.build([ReportLabImage(img_path, width=img_width, height=img_height)])
@@ -180,8 +221,7 @@ def generate_pdfs_api(files, layouts, fonts, num_columns, progress=gr.Progress(t
180
  time_str = datetime.datetime.now().strftime('%m-%d-%a_%I%M%p').upper()
181
  filename = f"{stem}_{time_str}_{layout_name.replace(' ','-')}_{font_name}_Cols{num_columns}.pdf"
182
  output_path = OUTPUT_DIR / filename
183
- with open(output_path, "wb") as f:
184
- merger.write(f)
185
  generated_pdf_paths.append(output_path)
186
  log_updates += f"Generated: {filename}\n"
187
 
@@ -191,13 +231,13 @@ def generate_pdfs_api(files, layouts, fonts, num_columns, progress=gr.Progress(t
191
  return final_gallery, log_updates, [str(p) for p in generated_pdf_paths]
192
 
193
  # --- Gradio UI Definition ---
194
- AVAILABLE_FONTS = download_and_register_fonts()
195
  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```"
196
  with open(CWD / "sample.md", "w") as f: f.write(SAMPLE_MARKDOWN)
197
 
198
  with gr.Blocks(theme=gr.themes.Soft(), title="Advanced PDF Generator") as demo:
199
  gr.Markdown("# πŸ“„ Advanced PDF Generator")
200
- gr.Markdown("Upload Markdown and Image files. Group assets with a common name (e.g., `Doc_part1.md`, `Doc_img1.png`). The app will generate PDFs for all selected variations and display them below.")
201
 
202
  with gr.Row():
203
  with gr.Column(scale=1):
@@ -210,35 +250,10 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Advanced PDF Generator") as demo:
210
 
211
  with gr.Column(scale=2):
212
  gr.Markdown("### πŸ–ΌοΈ PDF Preview Gallery")
213
- gallery_output = gr.Gallery(label="Generated PDF Previews", show_label=False, elem_id="gallery", columns=3, height="auto")
214
  log_output = gr.Markdown(label="Generation Log", value="Logs will appear here...")
215
  downloadable_files_output = gr.Files(label="Download Generated PDFs")
216
 
217
- def auto_run_demo(request: gr.Request):
218
- """Function to run on app load to generate a sample PDF."""
219
- print("Running initial demo generation...")
220
- # Create a dummy file object for Gradio
221
- sample_md_path = CWD / "sample.md"
222
- if not sample_md_path.exists():
223
- return [], "Sample.md not found.", []
224
-
225
- with open(sample_md_path, "rb") as f:
226
- sample_bytes = f.read()
227
-
228
- # This part is tricky with Gradio's file handling on load.
229
- # For simplicity, we'll pass the path and have the API function handle it.
230
- # A more robust solution might involve writing to a temp file.
231
- # This is a conceptual fix for the demo.
232
- class TempFile:
233
- def __init__(self, name):
234
- self.name = name
235
-
236
- demo_files = [TempFile(name=str(sample_md_path))]
237
-
238
- previews, logs, files = generate_pdfs_api(demo_files, ["A4 Portrait"], [AVAILABLE_FONTS[0]] if AVAILABLE_FONTS else [], 1)
239
- return previews, logs, files
240
-
241
- demo.load(auto_run_demo, inputs=None, outputs=[gallery_output, log_output, downloadable_files_output])
242
  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])
243
 
244
  if __name__ == "__main__":
 
2
  from pathlib import Path
3
  import datetime
4
  import re
 
5
  import os
6
  import shutil
7
  import fitz # PyMuPDF
 
31
  }
32
  OUTPUT_DIR = CWD / "generated_pdfs"
33
  PREVIEW_DIR = CWD / "previews"
34
+ # FONT_DIR is now the main directory of the app.
35
+ FONT_DIR = CWD
36
 
37
  # Create necessary directories
38
  OUTPUT_DIR.mkdir(exist_ok=True)
39
  PREVIEW_DIR.mkdir(exist_ok=True)
 
40
 
41
 
42
  # --- Font & Emoji Handling ---
43
 
44
+ def register_local_fonts():
45
+ """Finds and registers all .ttf files from the application's base directory."""
46
  print("--- Font Registration Process Starting ---")
47
+ text_font_names = []
48
+ emoji_font_name = None
49
+
50
  print(f"Scanning for fonts in: {FONT_DIR.absolute()}")
51
+ # Use glob to find all .ttf files in the base directory
52
  font_files = list(FONT_DIR.glob("*.ttf"))
53
  print(f"Found {len(font_files)} .ttf files: {[f.name for f in font_files]}")
54
 
55
  if not font_files:
56
+ print("WARNING: No .ttf files found. Please add font files to the application directory.")
57
 
58
  for font_path in font_files:
59
  try:
 
61
  print(f"Registering font: '{font_name}' from {font_path.name}")
62
  pdfmetrics.registerFont(TTFont(font_name, str(font_path)))
63
 
64
+ # CRITICAL FIX for bold/italic error: Register dummy variants for all fonts.
65
  pdfmetrics.registerFont(TTFont(f"{font_name}-Bold", str(font_path)))
66
  pdfmetrics.registerFont(TTFont(f"{font_name}-Italic", str(font_path)))
67
  pdfmetrics.registerFont(TTFont(f"{font_name}-BoldItalic", str(font_path)))
68
  pdfmetrics.registerFontFamily(font_name, normal=font_name, bold=f"{font_name}-Bold", italic=f"{font_name}-Italic", boldItalic=f"{font_name}-BoldItalic")
69
 
70
+ if "notocoloremoji-regular" in font_name.lower():
71
+ emoji_font_name = font_name
72
+ elif "notoemoji" not in font_name.lower(): # Exclude other non-color emoji fonts from selection
73
+ text_font_names.append(font_name)
74
  except Exception as e:
75
  print(f"Could not register font {font_path.name}: {e}")
76
+
77
+ if not text_font_names:
78
+ print("WARNING: No text fonts found. Adding 'Helvetica' as a default.")
79
+ text_font_names.append('Helvetica')
80
+
81
+ print(f"Successfully registered user-selectable fonts: {text_font_names}")
82
+ print(f"Emoji font set to: {emoji_font_name}")
83
  print("--- Font Registration Process Finished ---")
84
+ return sorted(text_font_names), emoji_font_name
85
 
86
+ def apply_emoji_font(text: str, emoji_font_name: str) -> str:
87
  """Wraps emoji characters in a <font> tag to use the dedicated emoji font."""
88
+ if not emoji_font_name:
89
+ return text # Return original text if no emoji font is available
90
+
91
  emoji_pattern = re.compile(f"([{re.escape(''.join(map(chr, range(0x1f600, 0x1f650))))}"
92
  f"{re.escape(''.join(map(chr, range(0x1f300, 0x1f5ff))))}"
93
  f"{re.escape(''.join(map(chr, range(0x1f900, 0x1f9ff))))}"
94
  f"{re.escape(''.join(map(chr, range(0x2600, 0x26ff))))}"
95
  f"{re.escape(''.join(map(chr, range(0x2700, 0x27bf))))}]+)")
96
+ return emoji_pattern.sub(fr'<font name="{emoji_font_name}">\1</font>', text)
97
 
98
 
99
  # --- PDF Generation & Handling ---
100
 
101
+ def markdown_to_story(markdown_text: str, font_name: str, emoji_font: str):
102
+ """Converts markdown to a ReportLab story, with improved handling of elements."""
103
  styles = getSampleStyleSheet()
104
  style_normal = ParagraphStyle('BodyText', fontName=font_name, spaceAfter=6, leading=14)
105
  style_h1 = ParagraphStyle('h1', parent=styles['h1'], fontName=font_name, spaceBefore=12, fontSize=20, leading=24)
106
+ style_h2 = ParagraphStyle('h2', parent=styles['h2'], fontName=font_name, fontSize=16, leading=20)
107
+ style_h3 = ParagraphStyle('h3', parent=styles['h3'], fontName=font_name, fontSize=14, leading=18)
108
+ style_code = ParagraphStyle('Code', fontName='Courier', backColor=colors.whitesmoke, textColor=colors.darkblue, borderWidth=1, borderColor=colors.lightgrey, padding=5)
109
+ style_table_header = ParagraphStyle('TableHeader', parent=style_normal, fontName=font_name + "-Bold" if font_name != 'Helvetica' else 'Helvetica-Bold')
110
+
111
  story = []
112
+ lines = markdown_text.split('\n')
113
+
114
+ in_code_block, in_table = False, False
115
+ code_block_text, table_data = "", []
116
+
117
+ for line in lines:
118
+ if line.strip().startswith("```"):
119
+ if in_code_block:
120
+ story.append(Paragraph(code_block_text.replace('\n', '<br/>'), style_code))
121
+ in_code_block = False; code_block_text = ""
122
+ else:
123
+ in_code_block = True
124
+ continue
125
+ if in_code_block:
126
+ code_block_text += line.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;') + '\n'
127
+ continue
128
+
129
+ if line.strip().startswith('|'):
130
+ if not in_table: in_table = True
131
+ if all(c in '-|: ' for c in line.strip()): continue
132
+ cells = [apply_emoji_font(cell.strip(), emoji_font) for cell in line.strip().strip('|').split('|')]
133
+ table_data.append(cells)
134
+ continue
135
+
136
+ if in_table:
137
+ in_table = False
138
+ if table_data:
139
+ header = [Paragraph(cell, style_table_header) for cell in table_data[0]]
140
+ formatted_rows = [[Paragraph(cell, style_normal) for cell in row] for row in table_data[1:]]
141
+ table = Table([header] + formatted_rows, hAlign='LEFT', repeatRows=1)
142
+ table.setStyle(TableStyle([
143
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
144
+ ('GRID', (0, 0), (-1, -1), 1, colors.darkgrey),
145
+ ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
146
+ ]))
147
+ story.append(table); story.append(Spacer(1, 0.2 * inch))
148
+ table_data = []
149
+
150
+ line_with_emoji = apply_emoji_font(line, emoji_font)
151
  formatted_line = re.sub(r'_(.*?)_', r'<i>\1</i>', re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line_with_emoji))
152
 
153
  if line.startswith("# "): story.append(Paragraph(formatted_line[2:], style_h1))
154
+ elif line.startswith("## "): story.append(Paragraph(formatted_line[3:], style_h2))
155
+ elif line.startswith("### "): story.append(Paragraph(formatted_line[4:], style_h3))
156
+ elif line.strip().startswith(("* ", "- ")): story.append(Paragraph(formatted_line.strip()[2:], style_normal, bulletText='β€’'))
157
+ elif re.match(r'^\d+\.\s', line.strip()): story.append(Paragraph(formatted_line.strip(), style_normal))
158
+ elif line.strip() == "": story.append(Spacer(1, 0.1 * inch))
159
  else: story.append(Paragraph(formatted_line, style_normal))
160
+
161
  return story
162
 
163
  def create_pdf_preview(pdf_path: Path):
 
165
  preview_path = PREVIEW_DIR / f"{pdf_path.stem}.png"
166
  try:
167
  doc = fitz.open(pdf_path)
168
+ page = doc.load_page(0); pix = page.get_pixmap()
169
+ pix.save(str(preview_path)); doc.close()
 
 
170
  return str(preview_path)
171
  except Exception as e:
172
+ print(f"Could not create preview for {pdf_path.name}: {e}"); return None
 
173
 
174
  # --- Main API Function ---
 
175
  def generate_pdfs_api(files, layouts, fonts, num_columns, progress=gr.Progress(track_tqdm=True)):
 
176
  if not files: raise gr.Error("Please upload at least one Markdown or Image file.")
177
  if not layouts: raise gr.Error("Please select at least one page layout.")
178
  if not fonts: raise gr.Error("Please select at least one font.")
179
 
180
+ shutil.rmtree(OUTPUT_DIR, ignore_errors=True); shutil.rmtree(PREVIEW_DIR, ignore_errors=True)
181
+ OUTPUT_DIR.mkdir(); PREVIEW_DIR.mkdir()
 
 
182
 
183
  grouped_files = defaultdict(lambda: {'md': [], 'img': []})
184
  for f in files:
185
  file_path = Path(f.name)
186
  stem = file_path.stem.split('_')[0] if '_' in file_path.stem else file_path.stem
187
+ if file_path.suffix.lower() == '.md': grouped_files[stem]['md'].append(file_path)
188
+ elif file_path.suffix.lower() in ['.png', '.jpg', '.jpeg']: grouped_files[stem]['img'].append(file_path)
 
 
189
 
190
+ log_updates, generated_pdf_paths = "", []
 
191
 
192
  for stem, assets in progress.tqdm(grouped_files.items(), desc="Processing File Groups"):
193
  for layout_name in layouts:
 
197
  if assets['md']:
198
  md_content = "\n\n---PAGE_BREAK---\n\n".join([p.read_text(encoding='utf-8') for p in assets['md']])
199
  md_buffer = io.BytesIO()
200
+ story = markdown_to_story(md_content, font_name, EMOJI_FONT_NAME)
201
  pagesize = LAYOUTS[layout_name]["size"]
202
 
203
  if num_columns > 1:
 
207
  doc.addPageTemplates([PageTemplate(id='MultiCol', frames=frames)])
208
  else:
209
  doc = SimpleDocTemplate(md_buffer, pagesize=pagesize)
 
210
  doc.build(story)
211
  merger.append(fileobj=md_buffer)
212
 
213
  for img_path in assets['img']:
214
+ with Image.open(img_path) as img: img_width, img_height = img.size
 
215
  img_buffer = io.BytesIO()
216
  doc = SimpleDocTemplate(img_buffer, pagesize=(img_width, img_height), leftMargin=0, rightMargin=0, topMargin=0, bottomMargin=0)
217
  doc.build([ReportLabImage(img_path, width=img_width, height=img_height)])
 
221
  time_str = datetime.datetime.now().strftime('%m-%d-%a_%I%M%p').upper()
222
  filename = f"{stem}_{time_str}_{layout_name.replace(' ','-')}_{font_name}_Cols{num_columns}.pdf"
223
  output_path = OUTPUT_DIR / filename
224
+ with open(output_path, "wb") as f: merger.write(f)
 
225
  generated_pdf_paths.append(output_path)
226
  log_updates += f"Generated: {filename}\n"
227
 
 
231
  return final_gallery, log_updates, [str(p) for p in generated_pdf_paths]
232
 
233
  # --- Gradio UI Definition ---
234
+ AVAILABLE_FONTS, EMOJI_FONT_NAME = register_local_fonts()
235
  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```"
236
  with open(CWD / "sample.md", "w") as f: f.write(SAMPLE_MARKDOWN)
237
 
238
  with gr.Blocks(theme=gr.themes.Soft(), title="Advanced PDF Generator") as demo:
239
  gr.Markdown("# πŸ“„ Advanced PDF Generator")
240
+ gr.Markdown("Upload Markdown and Image files. The app will find any `.ttf` fonts in its directory. Group assets with a common name (e.g., `Doc_part1.md`, `Doc_img1.png`) to combine them.")
241
 
242
  with gr.Row():
243
  with gr.Column(scale=1):
 
250
 
251
  with gr.Column(scale=2):
252
  gr.Markdown("### πŸ–ΌοΈ PDF Preview Gallery")
253
+ gallery_output = gr.Gallery(label="Generated PDF Previews", show_label=False, elem_id="gallery", columns=3, height="auto", object_fit="contain")
254
  log_output = gr.Markdown(label="Generation Log", value="Logs will appear here...")
255
  downloadable_files_output = gr.Files(label="Download Generated PDFs")
256
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  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])
258
 
259
  if __name__ == "__main__":