import os import re import subprocess from reportlab.lib.pagesizes import A4 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import cm from reportlab.lib import colors from reportlab.platypus import ( SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable, PageBreak, Image, KeepTogether ) from reportlab.lib.enums import TA_CENTER, TA_LEFT # Paths DOCS_DIR = r"C:\Users\office\Documents\Final_year_project\fakeshield\docs" MD_FILE = os.path.join(DOCS_DIR, "video_lab_documentation.md") PDF_FILE = os.path.join(DOCS_DIR, "AI_Video_Lab_Technical_Specs.pdf") MMDC_PATH = r"C:\Users\office\AppData\Roaming\npm\mmdc.ps1" # Extract and compile Mermaid diagrams print("Extracting Mermaid blocks from markdown...") with open(MD_FILE, "r", encoding="utf-8") as f: md_content = f.read() mermaid_pattern = r"```mermaid\n(.*?)\n```" mermaid_blocks = re.findall(mermaid_pattern, md_content, re.DOTALL) png_diagrams = [] for idx, mmd_code in enumerate(mermaid_blocks): temp_mmd = os.path.join(DOCS_DIR, f"temp_diag_vid_{idx}.mmd") out_png = os.path.join(DOCS_DIR, f"video_lab_diagram_{idx}.png") with open(temp_mmd, "w", encoding="utf-8") as f: f.write(mmd_code) print(f"Compiling Mermaid diagram {idx} to PNG...") try: # Run mmdc using powershell cmd = ["powershell", "-File", MMDC_PATH, "-i", temp_mmd, "-o", out_png, "-t", "dark", "-b", "#0f172a"] subprocess.run(cmd, check=True) png_diagrams.append(out_png) except Exception as e: print(f"Error compiling diagram {idx}: {e}") png_diagrams.append(None) finally: if os.path.exists(temp_mmd): os.remove(temp_mmd) # Set up ReportLab Document doc = SimpleDocTemplate( PDF_FILE, pagesize=A4, rightMargin=1.5*cm, leftMargin=1.5*cm, topMargin=2.0*cm, bottomMargin=2.0*cm ) # Color Scheme DARK = colors.HexColor("#0F172A") # Primary slate CYAN = colors.HexColor("#00E5CC") # Accent cyan GRAY = colors.HexColor("#64748B") # Muted text LIGHT_BG = colors.HexColor("#F8FAFC") # Page backgrounds BORDER = colors.HexColor("#E2E8F0") # Light border ALERT_BG = colors.HexColor("#EFF6FF") # Alert callout bg ALERT_BORDER = colors.HexColor("#3B82F6") # Alert callout border styles = getSampleStyleSheet() # Custom Paragraph Styles styles.add(ParagraphStyle("DocTitle", parent=styles["Normal"], fontName="Helvetica-Bold", fontSize=22, leading=26, textColor=DARK, spaceAfter=15, alignment=TA_LEFT)) styles.add(ParagraphStyle("DocSubtitle", parent=styles["Normal"], fontName="Helvetica", fontSize=10, leading=14, textColor=GRAY, spaceAfter=30, alignment=TA_LEFT)) styles.add(ParagraphStyle("CustomH1", parent=styles["Heading1"], fontName="Helvetica-Bold", fontSize=13, leading=17, textColor=DARK, spaceBefore=22, spaceAfter=10, keepWithNext=True)) styles.add(ParagraphStyle("CustomH2", parent=styles["Heading2"], fontName="Helvetica-Bold", fontSize=11, leading=15, textColor=DARK, spaceBefore=16, spaceAfter=8, keepWithNext=True)) styles.add(ParagraphStyle("CustomH3", parent=styles["Heading3"], fontName="Helvetica-Bold", fontSize=9, leading=13, textColor=DARK, spaceBefore=12, spaceAfter=6, keepWithNext=True)) styles.add(ParagraphStyle("Body", parent=styles["Normal"], fontName="Helvetica", fontSize=8.5, leading=12.5, textColor=colors.HexColor("#334155"), spaceAfter=8)) styles.add(ParagraphStyle("ListItem", parent=styles["Normal"], fontName="Helvetica", fontSize=8.5, leading=12.5, textColor=colors.HexColor("#334155"), leftIndent=15, firstLineIndent=-10, spaceAfter=4)) styles.add(ParagraphStyle("CodeBlock", parent=styles["Normal"], fontName="Courier", fontSize=7.5, leading=10.5, textColor=colors.HexColor("#0F172A"), backColor=colors.HexColor("#F1F5F9"), borderColor=BORDER, borderWidth=0.5, borderPadding=8, spaceAfter=8, borderRadius=4)) styles.add(ParagraphStyle("TableText", parent=styles["Normal"], fontName="Helvetica", fontSize=8, leading=11, textColor=colors.HexColor("#334155"))) styles.add(ParagraphStyle("TableHeaderText", parent=styles["Normal"], fontName="Helvetica-Bold", fontSize=8, leading=11, textColor=colors.white)) def clean_markdown_formatting(text): text = re.sub(r"\*\*(.*?)\*\*", r"\1", text) text = re.sub(r"\*(.*?)\*", r"\1", text) text = re.sub(r"`(.*?)`", r'\1', text) # Basic math clean up for ReportLab text = text.replace("$$\\text{Noise} = \\text{Frame}_{\\text{gray}} - \\text{GaussianFilter}(\\text{Frame}_{\\text{gray}}, \\sigma=2.0)$$".replace("\\", "\\"), "Noise = Frame_gray - GaussianFilter(Frame_gray, sigma=2.0)") text = text.replace("$", "") return text # Build Story Flow story = [] # Header header_table = Table([[Paragraph("FAKESHIELD FORENSIC LABORATORIES", ParagraphStyle("HeaderL", parent=styles["Normal"], fontSize=9, textColor=GRAY, fontName="Helvetica-Bold")), Paragraph("VIDEO LAB TECHNICAL DOCUMENTATION", ParagraphStyle("HeaderR", parent=styles["Normal"], fontSize=9, textColor=GRAY, fontName="Helvetica-Bold", alignment=2))]], colWidths=[9*cm, 9*cm]) header_table.setStyle(TableStyle([("VALIGN", (0,0), (-1,-1), "MIDDLE"), ("BOTTOMPADDING", (0,0), (-1,-1), 4), ("TOPPADDING", (0,0), (-1,-1), 4)])) story.append(header_table) story.append(HRFlowable(width="100%", color=DARK, thickness=1.5, spaceAfter=15)) # Document Title story.append(Paragraph("📹 AI Video Forensic Lab (Process 5.0)", styles["DocTitle"])) story.append(Paragraph("System Architecture, Phased Pipeline, Forensic Model Ensemble, and Multi-modal Fusion", styles["DocSubtitle"])) # Parse markdown sections lines = md_content.split("\n") i = 0 diag_idx = 0 in_code = False code_text = [] while i < len(lines): line = lines[i].strip() if line.startswith("```"): if line.startswith("```mermaid"): i += 1 while i < len(lines) and not lines[i].strip().startswith("```"): i += 1 if diag_idx < len(png_diagrams) and png_diagrams[diag_idx]: story.append(Spacer(1, 0.4*cm)) img_path = png_diagrams[diag_idx] story.append(KeepTogether([Paragraph(f"Figure {diag_idx + 1}: Video Lab Forensic Processing Workflow (v11.0)", ParagraphStyle("FigCap", parent=styles["Normal"], fontName="Helvetica-Bold", fontSize=8, textColor=DARK, spaceAfter=8, alignment=TA_CENTER)), Image(img_path, width=17*cm, height=9.5*cm), Spacer(1, 0.5*cm)])) diag_idx += 1 i += 1 continue elif in_code: in_code = False code_content = "\n".join(code_text) story.append(Paragraph(code_content.replace("\n", "
").replace(" ", " "), styles["CodeBlock"])) code_text = [] else: in_code = True i += 1 continue if in_code: code_text.append(line) i += 1 continue if not line: i += 1; continue if line.startswith("# "): i += 1; continue elif line.startswith("## "): story.append(Spacer(1, 0.3*cm)) header_text = clean_markdown_formatting(line[3:]) story.append(Paragraph(header_text, styles["CustomH1"])) story.append(HRFlowable(width="100%", color=BORDER, thickness=0.8, spaceAfter=8)) i += 1; continue elif line.startswith("### "): header_text = clean_markdown_formatting(line[4:]) story.append(Paragraph(header_text, styles["CustomH2"])) i += 1; continue elif line.startswith("#### "): header_text = clean_markdown_formatting(line[5:]) story.append(Paragraph(header_text, styles["CustomH3"])) i += 1; continue if line.startswith("* ") or line.startswith("- "): list_text = clean_markdown_formatting(line[2:]) story.append(Paragraph(f"• {list_text}", styles["ListItem"])) i += 1; continue if re.match(r"^\d+\.\s+", line): num, text = line.split(".", 1) story.append(Paragraph(f"{num.strip()}. {clean_markdown_formatting(text.strip())}", styles["ListItem"])) i += 1; continue if line.startswith("|"): table_lines = [] while i < len(lines) and lines[i].strip().startswith("|"): table_lines.append(lines[i].strip()); i += 1 headers_raw = [h.strip() for h in table_lines[0].split("|")[1:-1]] headers_p = [Paragraph(f"{clean_markdown_formatting(h)}", styles["TableHeaderText"]) for h in headers_raw] rows_p = [] for row_line in table_lines[2:]: cells_raw = [c.strip() for c in row_line.split("|")[1:-1]] cells_p = [Paragraph(clean_markdown_formatting(c), styles["TableText"]) for c in cells_raw] rows_p.append(cells_p) col_count = len(headers_raw) col_widths = [18.0 / col_count * cm] * col_count p_table = Table([headers_p] + rows_p, colWidths=col_widths) p_table.setStyle(TableStyle([("BACKGROUND", (0,0), (-1,0), DARK), ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, LIGHT_BG]), ("GRID", (0,0), (-1,-1), 0.5, BORDER), ("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5)])) story.append(Spacer(1, 0.2*cm)); story.append(p_table); story.append(Spacer(1, 0.3*cm)); continue story.append(Paragraph(clean_markdown_formatting(line), styles["Body"])); i += 1 def add_footer(canvas, doc): canvas.saveState() canvas.setStrokeColor(BORDER); canvas.setLineWidth(0.5); canvas.line(1.5*cm, 1.5*cm, 19.5*cm, 1.5*cm) canvas.setFont("Helvetica", 7.5); canvas.setFillColor(GRAY) canvas.drawString(1.5*cm, 1.1*cm, "FakeShield Automated Intelligence Systems — AI Video Lab v5.0.0-PRO") canvas.drawRightString(19.5*cm, 1.1*cm, f"Page {doc.page}"); canvas.restoreState() print(f"Building document to {PDF_FILE}...") doc.build(story, onFirstPage=add_footer, onLaterPages=add_footer) print("PDF compilation complete!")