Spaces:
Running
Running
| """ | |
| Markdown raporlarını PDF formatına çevirir. | |
| Kullanım: | |
| python convert_to_pdf.py <md_dosyasi> | |
| python convert_to_pdf.py test_outputs/report.md | |
| python convert_to_pdf.py # test_outputs/ klasöründeki tüm .md dosyalarını çevirir | |
| """ | |
| import sys | |
| import os | |
| import io | |
| import re | |
| import base64 | |
| from pathlib import Path | |
| import markdown | |
| from xhtml2pdf import pisa | |
| IMAGE_PATTERN = re.compile( | |
| r'(\*\*Image\*\*:\s*)([^\n]+\.(jpg|jpeg|png|gif|bmp|webp))', | |
| re.IGNORECASE, | |
| ) | |
| IMG_CSS = """ | |
| .report-image { | |
| display: block; | |
| max-width: 100%; | |
| max-height: 300px; | |
| margin: 10px 0 16px 0; | |
| border: 1px solid #ccc; | |
| border-radius: 4px; | |
| } | |
| .image-label { | |
| font-weight: bold; | |
| color: #003366; | |
| margin-bottom: 4px; | |
| } | |
| """ | |
| def _image_to_base64(img_path: str) -> str | None: | |
| path = Path(img_path.strip()) | |
| if not path.exists(): | |
| return None | |
| mime = {".jpg": "image/jpeg", ".jpeg": "image/jpeg", | |
| ".png": "image/png", ".gif": "image/gif", | |
| ".bmp": "image/bmp", ".webp": "image/webp"}.get(path.suffix.lower(), "image/jpeg") | |
| data = base64.b64encode(path.read_bytes()).decode() | |
| return f"data:{mime};base64,{data}" | |
| def _embed_images(md_text: str) -> str: | |
| def replacer(m: re.Match) -> str: | |
| img_path = m.group(2).strip() | |
| data_uri = _image_to_base64(img_path) | |
| if data_uri: | |
| return ( | |
| f'<p class="image-label">Image</p>' | |
| f'<img class="report-image" src="{data_uri}" alt="Detection Image">' | |
| ) | |
| return m.group(0) | |
| return IMAGE_PATTERN.sub(replacer, md_text) | |
| CSS = """ | |
| @page { | |
| size: A4; | |
| margin: 2cm 2.5cm 2cm 2.5cm; | |
| } | |
| body { | |
| font-family: "Helvetica", "Arial", sans-serif; | |
| font-size: 10pt; | |
| line-height: 1.6; | |
| color: #1a1a1a; | |
| } | |
| h1 { | |
| font-size: 16pt; | |
| color: #003366; | |
| border-bottom: 2px solid #003366; | |
| padding-bottom: 6px; | |
| margin-bottom: 12px; | |
| } | |
| h2 { | |
| font-size: 13pt; | |
| color: #003366; | |
| margin-top: 18px; | |
| margin-bottom: 6px; | |
| } | |
| h3 { | |
| font-size: 11pt; | |
| color: #1a4d80; | |
| margin-top: 12px; | |
| margin-bottom: 4px; | |
| } | |
| p { | |
| margin: 6px 0; | |
| } | |
| strong { | |
| color: #111; | |
| } | |
| em { | |
| color: #333; | |
| } | |
| ul, ol { | |
| margin: 6px 0 6px 20px; | |
| padding: 0; | |
| } | |
| li { | |
| margin-bottom: 3px; | |
| } | |
| hr { | |
| border: none; | |
| border-top: 1px solid #cccccc; | |
| margin: 14px 0; | |
| } | |
| code { | |
| background: #f4f4f4; | |
| padding: 1px 4px; | |
| font-size: 9pt; | |
| border-radius: 3px; | |
| } | |
| blockquote { | |
| border-left: 3px solid #003366; | |
| margin: 8px 0 8px 16px; | |
| padding-left: 10px; | |
| color: #555; | |
| } | |
| .header-meta { | |
| font-size: 9pt; | |
| color: #666; | |
| margin-bottom: 16px; | |
| } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 10px 0; | |
| font-size: 9pt; | |
| } | |
| th { | |
| background: #003366; | |
| color: white; | |
| padding: 5px 8px; | |
| text-align: left; | |
| } | |
| td { | |
| border: 1px solid #ccc; | |
| padding: 4px 8px; | |
| } | |
| tr:nth-child(even) td { | |
| background: #f5f8ff; | |
| } | |
| """ | |
| def _resize_b64_image(b64: str, max_size: int = 800) -> str: | |
| from PIL import Image as PILImage | |
| data = base64.b64decode(b64) | |
| img = PILImage.open(io.BytesIO(data)) | |
| img.thumbnail((max_size, max_size), PILImage.LANCZOS) | |
| buf = io.BytesIO() | |
| img.save(buf, format="PNG") | |
| return base64.b64encode(buf.getvalue()).decode() | |
| def md_text_to_pdf_bytes(md_text: str, annotated_image_b64: str | None = None) -> bytes: | |
| """ | |
| Markdown metnini doğrudan PDF baytlarına çevirir (dosya I/O yok). | |
| annotated_image_b64: base64 kodlanmış PNG — rapor başına eklenir. | |
| """ | |
| if annotated_image_b64: | |
| annotated_image_b64 = _resize_b64_image(annotated_image_b64) | |
| image_block = ( | |
| '<p class="image-label">Detection Image</p>' | |
| f'<img class="report-image" src="data:image/png;base64,{annotated_image_b64}" alt="Detection Image">' | |
| ) | |
| else: | |
| image_block = "" | |
| html_body = markdown.markdown( | |
| md_text, | |
| extensions=["tables", "fenced_code", "nl2br", "sane_lists"], | |
| ) | |
| full_html = f"""<!DOCTYPE html> | |
| <html lang="tr"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <style>{CSS}{IMG_CSS}</style> | |
| </head> | |
| <body> | |
| {image_block} | |
| {html_body} | |
| </body> | |
| </html>""" | |
| buf = io.BytesIO() | |
| result = pisa.CreatePDF(full_html, dest=buf, encoding="utf-8") | |
| if result.err: | |
| raise RuntimeError(f"PDF oluşturulurken hata: {result.err}") | |
| return buf.getvalue() | |
| def md_to_pdf(md_path: str | Path, pdf_path: str | Path | None = None) -> Path: | |
| md_path = Path(md_path) | |
| if not md_path.exists(): | |
| raise FileNotFoundError(f"Dosya bulunamadı: {md_path}") | |
| if pdf_path is None: | |
| pdf_path = md_path.with_suffix(".pdf") | |
| pdf_path = Path(pdf_path) | |
| pdf_path.parent.mkdir(parents=True, exist_ok=True) | |
| md_text = md_path.read_text(encoding="utf-8") | |
| # Görsel yollarını base64 gömülü <img> etiketlerine çevir | |
| preprocessed = _embed_images(md_text) | |
| html_body = markdown.markdown( | |
| preprocessed, | |
| extensions=["tables", "fenced_code", "nl2br", "sane_lists"], | |
| ) | |
| full_html = f"""<!DOCTYPE html> | |
| <html lang="tr"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <style>{CSS}{IMG_CSS}</style> | |
| </head> | |
| <body> | |
| {html_body} | |
| </body> | |
| </html>""" | |
| with open(pdf_path, "wb") as f: | |
| result = pisa.CreatePDF(full_html, dest=f, encoding="utf-8") | |
| if result.err: | |
| raise RuntimeError(f"PDF oluşturulurken hata: {result.err}") | |
| print(f"PDF oluşturuldu: {pdf_path}") | |
| return pdf_path | |
| def convert_all(directory: str | Path = "test_outputs") -> list[Path]: | |
| directory = Path(directory) | |
| md_files = list(directory.glob("**/*.md")) | |
| if not md_files: | |
| print(f"'{directory}' klasöründe .md dosyası bulunamadı.") | |
| return [] | |
| results = [] | |
| for md_file in md_files: | |
| try: | |
| pdf = md_to_pdf(md_file) | |
| results.append(pdf) | |
| except Exception as e: | |
| print(f"Hata ({md_file.name}): {e}") | |
| return results | |
| if __name__ == "__main__": | |
| if len(sys.argv) == 1: | |
| convert_all("test_outputs") | |
| elif len(sys.argv) == 2: | |
| target = sys.argv[1] | |
| path = Path(target) | |
| if path.is_dir(): | |
| convert_all(path) | |
| elif path.suffix == ".md": | |
| md_to_pdf(path) | |
| else: | |
| print(f"Geçersiz dosya: {target} (.md dosyası veya klasör belirtin)") | |
| sys.exit(1) | |
| elif len(sys.argv) == 3: | |
| md_to_pdf(sys.argv[1], sys.argv[2]) | |
| else: | |
| print("Kullanım: python convert_to_pdf.py [md_dosyasi] [cikti.pdf]") | |
| sys.exit(1) | |