@@ -41,6 +41,7 @@ def render_mermaid_to_png(mermaid_code, output_path, width=1400, height=1000, sc
4141 return False
4242
4343 # HTML template with Mermaid.js from CDN
44+ # Using Mermaid v11 (latest stable version)
4445 html_template = f"""
4546 <!DOCTYPE html>
4647 <html>
@@ -53,7 +54,8 @@ def render_mermaid_to_png(mermaid_code, output_path, width=1400, height=1000, sc
5354 flowchart: {{
5455 useMaxWidth: false,
5556 htmlLabels: true
56- }}
57+ }},
58+ securityLevel: 'loose'
5759 }});
5860 </script>
5961 <style>
@@ -93,18 +95,121 @@ def render_mermaid_to_png(mermaid_code, output_path, width=1400, height=1000, sc
9395 # Wait for Mermaid to render
9496 page .wait_for_selector ('#diagram svg' , timeout = 15000 )
9597
96- # Wait a bit more for stabilization
97- page .wait_for_timeout (1000 )
98+ # Smart polling: Wait for valid dimensions (up to 5 seconds)
99+ # This allows simple diagrams to render quickly while giving complex ones time
100+ page .evaluate ('''() => {
101+ return new Promise((resolve) => {
102+ const svg = document.querySelector('#diagram svg');
103+ const maxAttempts = 50; // 50 attempts * 100ms = 5 seconds max
104+ let attempts = 0;
105+
106+ const checkDimensions = () => {
107+ attempts++;
108+
109+ // Try to get valid dimensions
110+ let hasValidDimensions = false;
111+ try {
112+ const bbox = svg.getBBox();
113+ if (bbox && bbox.width > 0 && bbox.height > 0 &&
114+ !isNaN(bbox.width) && !isNaN(bbox.height)) {
115+ hasValidDimensions = true;
116+ }
117+ } catch (e) {
118+ // getBBox failed, try other methods
119+ }
120+
121+ // Check viewBox as fallback
122+ if (!hasValidDimensions) {
123+ const viewBox = svg.getAttribute('viewBox');
124+ if (viewBox) {
125+ const parts = viewBox.split(/\\ s+/);
126+ if (parts.length >= 4) {
127+ const w = parseFloat(parts[2]);
128+ const h = parseFloat(parts[3]);
129+ if (w > 0 && h > 0 && !isNaN(w) && !isNaN(h)) {
130+ hasValidDimensions = true;
131+ }
132+ }
133+ }
134+ }
135+
136+ // If we have valid dimensions or reached max attempts, resolve
137+ if (hasValidDimensions || attempts >= maxAttempts) {
138+ resolve();
139+ } else {
140+ // Check again in 100ms
141+ setTimeout(checkDimensions, 100);
142+ }
143+ };
144+
145+ // Start checking after initial 500ms delay
146+ setTimeout(checkDimensions, 500);
147+ });
148+ }''' )
98149
99150 # CRITICAL: Prepare SVG with proper viewBox (removes whitespace)
100151 # Then render to canvas at exact target dimensions
101152 svg_data = page .evaluate (f'''() => {{
102153 const svg = document.querySelector('#diagram svg');
103154
104- // Get actual content bounding box
105- const bbox = svg.getBBox();
106- const naturalWidth = bbox.width;
107- const naturalHeight = bbox.height;
155+ // Try multiple methods to get valid dimensions
156+ let naturalWidth, naturalHeight, bbox;
157+
158+ // Method 1: Try getBBox() first
159+ try {{
160+ bbox = svg.getBBox();
161+ if (bbox && bbox.width > 0 && bbox.height > 0 &&
162+ !isNaN(bbox.width) && !isNaN(bbox.height)) {{
163+ naturalWidth = bbox.width;
164+ naturalHeight = bbox.height;
165+ }}
166+ }} catch (e) {{
167+ console.log('getBBox failed:', e);
168+ }}
169+
170+ // Method 2: Try SVG viewBox attribute
171+ if (!naturalWidth || !naturalHeight) {{
172+ const viewBox = svg.getAttribute('viewBox');
173+ if (viewBox) {{
174+ const parts = viewBox.split(/\\ s+/);
175+ if (parts.length >= 4) {{
176+ const w = parseFloat(parts[2]);
177+ const h = parseFloat(parts[3]);
178+ if (w > 0 && h > 0 && !isNaN(w) && !isNaN(h)) {{
179+ naturalWidth = w;
180+ naturalHeight = h;
181+ }}
182+ }}
183+ }}
184+ }}
185+
186+ // Method 3: Try SVG width/height attributes
187+ if (!naturalWidth || !naturalHeight) {{
188+ const w = parseFloat(svg.getAttribute('width'));
189+ const h = parseFloat(svg.getAttribute('height'));
190+ if (w > 0 && h > 0 && !isNaN(w) && !isNaN(h)) {{
191+ naturalWidth = w;
192+ naturalHeight = h;
193+ }}
194+ }}
195+
196+ // Method 4: Try getBoundingClientRect()
197+ if (!naturalWidth || !naturalHeight) {{
198+ const rect = svg.getBoundingClientRect();
199+ if (rect && rect.width > 0 && rect.height > 0) {{
200+ naturalWidth = rect.width;
201+ naturalHeight = rect.height;
202+ }}
203+ }}
204+
205+ // Method 5: Use defaults as last resort
206+ if (!naturalWidth || naturalWidth <= 0 || isNaN(naturalWidth)) {{
207+ naturalWidth = { width } ;
208+ }}
209+ if (!naturalHeight || naturalHeight <= 0 || isNaN(naturalHeight)) {{
210+ naturalHeight = { height } ;
211+ }}
212+
108213 const aspectRatio = naturalHeight / naturalWidth;
109214
110215 // Calculate target dimensions (width * scale for quality)
@@ -119,7 +224,11 @@ def render_mermaid_to_png(mermaid_code, output_path, width=1400, height=1000, sc
119224 }}
120225
121226 // Set viewBox to content bounds (removes whitespace)
122- svg.setAttribute('viewBox', `${{bbox.x}} ${{bbox.y}} ${{bbox.width}} ${{bbox.height}}`);
227+ // Only set if bbox has valid values
228+ if (bbox && bbox.width > 0 && bbox.height > 0 &&
229+ !isNaN(bbox.width) && !isNaN(bbox.height)) {{
230+ svg.setAttribute('viewBox', `${{bbox.x}} ${{bbox.y}} ${{bbox.width}} ${{bbox.height}}`);
231+ }}
123232
124233 // Return dimensions for canvas rendering
125234 return {{
0 commit comments