#!/usr/bin/env python3 """Extract Mermaid diagrams from markdown files and convert to PNG images.""" import os import re import subprocess import json import cairosvg BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DOCS_DIR = os.path.join(BASE_DIR, 'docs') OUTPUT_DIR = os.path.join(BASE_DIR, 'thesis_output/figures') MMDC = os.path.join(BASE_DIR, 'node_modules/.bin/mmdc') CONFIG_FILE = os.path.join(BASE_DIR, 'mermaid.config.json') # Light blue color for bar charts BAR_COLOR = '#0098CD' # Default bar colors that need to be replaced (varies by theme) DEFAULT_BAR_COLORS = ['#ECECFF', '#FFF4DD', '#ececff', '#fff4dd'] def extract_mermaid_diagrams(): """Extract all mermaid diagrams from markdown files.""" diagrams = [] md_files = [ '02_contexto_estado_arte.md', '03_objetivos_metodologia.md', '04_desarrollo_especifico.md', '07_anexo_a.md', ] for md_file in md_files: filepath = os.path.join(DOCS_DIR, md_file) if not os.path.exists(filepath): continue with open(filepath, 'r', encoding='utf-8') as f: content = f.read() # Find all mermaid blocks pattern = r'```mermaid\n(.*?)```' matches = re.findall(pattern, content, re.DOTALL) for i, mermaid_code in enumerate(matches): # Try to extract title from YAML front matter # Match title with quotes: title: "Something" or title: 'Something' title_match = re.search(r'title:\s*["\']([^"\']+)["\']', mermaid_code) if not title_match: # Match title without quotes: title: Something title_match = re.search(r'title:\s*([^"\'\n]+)', mermaid_code) title = title_match.group(1).strip() if title_match else f"Diagrama {len(diagrams) + 1}" diagrams.append({ 'source': md_file, 'code': mermaid_code.strip(), 'title': title, 'index': len(diagrams) + 1 }) return diagrams def convert_to_png(diagrams): """Convert mermaid diagrams to PNG using mmdc.""" os.makedirs(OUTPUT_DIR, exist_ok=True) generated = [] for diagram in diagrams: # Write mermaid code to temp file temp_file = os.path.join(OUTPUT_DIR, f'temp_{diagram["index"]}.mmd') output_file = os.path.join(OUTPUT_DIR, f'figura_{diagram["index"]}.png') # Check if this is a bar chart (xychart-beta) is_bar_chart = 'xychart-beta' in diagram['code'] with open(temp_file, 'w', encoding='utf-8') as f: f.write(diagram['code']) try: if is_bar_chart: # For bar charts: generate SVG, fix colors, convert to PNG svg_file = os.path.join(OUTPUT_DIR, f'temp_{diagram["index"]}.svg') result = subprocess.run( [MMDC, '-i', temp_file, '-o', svg_file, '-b', 'white', '-w', '1600', '-c', CONFIG_FILE], capture_output=True, text=True, timeout=60 ) if os.path.exists(svg_file): # Read SVG and replace bar color with open(svg_file, 'r', encoding='utf-8') as f: svg_content = f.read() # Replace default bar colors with light blue (both fill and stroke) for default_color in DEFAULT_BAR_COLORS: svg_content = svg_content.replace(f'fill="{default_color}"', f'fill="{BAR_COLOR}"') svg_content = svg_content.replace(f"fill='{default_color}'", f"fill='{BAR_COLOR}'") svg_content = svg_content.replace(f'stroke="{default_color}"', f'stroke="{BAR_COLOR}"') svg_content = svg_content.replace(f"stroke='{default_color}'", f"stroke='{BAR_COLOR}'") # Convert SVG to PNG using cairosvg (with scale for high resolution) cairosvg.svg2png(bytestring=svg_content.encode('utf-8'), write_to=output_file, scale=3) # Clean up SVG os.remove(svg_file) else: # For other diagrams: direct PNG generation result = subprocess.run( [MMDC, '-i', temp_file, '-o', output_file, '-b', 'white', '-w', '1600', '-s', '3', '-c', CONFIG_FILE], capture_output=True, text=True, timeout=60 ) if os.path.exists(output_file): print(f"✓ Generated: figura_{diagram['index']}.png - {diagram['title']}") generated.append({ 'file': f'figura_{diagram["index"]}.png', 'title': diagram['title'], 'index': diagram['index'] }) else: print(f"✗ Failed: figura_{diagram['index']}.png - {result.stderr}") except subprocess.TimeoutExpired: print(f"✗ Timeout: figura_{diagram['index']}.png") except Exception as e: print(f"✗ Error: figura_{diagram['index']}.png - {e}") # Clean up temp file if os.path.exists(temp_file): os.remove(temp_file) return generated def main(): print("Extracting Mermaid diagrams from markdown files...") diagrams = extract_mermaid_diagrams() print(f"Found {len(diagrams)} diagrams\n") print("Converting to PNG images...") generated = convert_to_png(diagrams) print(f"\n✓ Generated {len(generated)} figures in {OUTPUT_DIR}") # Save manifest for apply_content.py to use manifest_file = os.path.join(OUTPUT_DIR, 'figures_manifest.json') with open(manifest_file, 'w', encoding='utf-8') as f: json.dump(generated, f, indent=2, ensure_ascii=False) print(f"✓ Saved manifest to {manifest_file}") if __name__ == '__main__': main()