Skip to content

Commit 1f4ba1f

Browse files
committed
Add PNG support
1 parent 9b4ca40 commit 1f4ba1f

File tree

3 files changed

+74
-31
lines changed

3 files changed

+74
-31
lines changed

README.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[Mermaid](https://mermaid.js.org/) extension for [Python-Markdown](https://python-markdown.github.io/) using [mermaid-cli](https://github.com/mermaid-js/mermaid-cli).
44

5-
Mermaid code blocks are converted to SVG and treated as [data: URI](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data). This allows for PDF generation with tools like [WeasyPrint](https://weasyprint.org/) without the need for JavaScript, even during web browsing.
5+
Mermaid code blocks are converted to SVG/PNG and treated as [data: URI](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data). This allows for PDF generation with tools like [WeasyPrint](https://weasyprint.org/) without the need for JavaScript, even during web browsing.
66

77
## Install
88

@@ -24,14 +24,8 @@ markdown_text = """```mermaid
2424
sequenceDiagram
2525
participant Alice
2626
participant Bob
27-
Alice->>John: Hello John, how are you?
28-
loop HealthCheck
29-
John->>John: Fight against hypochondria
30-
end
31-
Note right of John: Rational thoughts <br/>prevail!
32-
John-->>Alice: Great!
33-
John->>Bob: How about you?
34-
Bob-->>John: Jolly good!
27+
Bob->>Alice: Hi Alice
28+
Alice->>Bob: Hi Bob
3529
```"""
3630

3731
html_output = markdown.markdown(markdown_text, extensions=[MermaidDataURIExtension()])
@@ -45,7 +39,7 @@ Gg6IDc1MHB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB3aGl0ZTsiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3L
4539
...
4640
...
4741
...
48-
IHgxPSIyNzYiLz48L3N2Zz4=" alt="Mermaid diagram"></p>
42+
IHgxPSIyNzYiLz48L3N2Zz4=" ></p>
4943
```
5044

5145
## MkDocs Integration
@@ -55,3 +49,22 @@ IHgxPSIyNzYiLz48L3N2Zz4=" alt="Mermaid diagram"></p>
5549
markdown_extensions:
5650
- mermaid_data_uri
5751
```
52+
53+
## Diagram
54+
55+
```mermaid
56+
sequenceDiagram
57+
participant application as Application<br/>(eg MkDocs)
58+
participant markdown as Python Markdown
59+
participant extension as MermaidDataURIExtension
60+
participant mmdc as Mermaid CLI
61+
62+
application->>markdown: Markdown + Mermaid
63+
markdown->>extension: Preprocessor
64+
extension->>mmdc: Mermaid
65+
mmdc-->>mmdc: Convert
66+
mmdc-->>extension: Image Data
67+
extension-->>extension: Base64 encode
68+
extension-->>markdown: Markdown + data URI image
69+
markdown-->>application: HTML + data URI image
70+
```

docs/mermaid.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ sequenceDiagram
3434
!!! warning
3535
When generating PDFs, text within the graph syntax doesn't get output in WeasyPrint. However, it does appear when using Playwright.
3636

37-
```mermaid
37+
```mermaid image=png
3838
graph LR
3939
A[Square Rect] -- Link text --> B((Circle))
4040
A --> C(Round Rect)
@@ -47,7 +47,7 @@ graph LR
4747
!!! warning
4848
When generating PDFs, text within the graph syntax doesn't get output in WeasyPrint. However, it does appear when using Playwright.
4949

50-
```mermaid
50+
```mermaid image=png
5151
graph TB
5252
sq[Square shape] --> ci((Circle shape))
5353

src/mermaid_data_uri/extension.py

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,42 @@ def run(self, lines: List[str]) -> List[str]:
2222
is_in_mermaid = False
2323

2424
for line in lines:
25-
if line.strip() == '```mermaid':
25+
if line.strip().startswith('```mermaid'):
2626
is_in_mermaid = True
2727
mermaid_block: List[str] = []
28+
# Extract options after '```mermaid'
29+
options = line.strip()[10:].strip()
30+
option_dict = {}
31+
if options:
32+
for option in options.split():
33+
key, _, value = option.partition('=')
34+
option_dict[key] = value
2835
continue
2936
elif line.strip() == '```' and is_in_mermaid:
3037
is_in_mermaid = False
3138
if mermaid_block:
3239
mermaid_code = '\n'.join(mermaid_block)
33-
svg_content = self._mermaid2svg(mermaid_code)
34-
if svg_content:
35-
data_uri = self._svg2data_uri(svg_content)
36-
new_lines.append(f'<img src="{data_uri}" alt="Mermaid diagram">')
40+
41+
# Image type handling
42+
if 'image' in option_dict:
43+
image_type = option_dict['image']
44+
del option_dict['image']
45+
if image_type not in ['svg', 'png']:
46+
image_type = 'svg'
47+
else:
48+
image_type = 'svg'
49+
50+
base64image = self._mermaid2base64image(mermaid_code, image_type)
51+
if base64image:
52+
# Build the <img> tag with extracted options
53+
if image_type == 'svg':
54+
img_tag = f'<img src="data:image/svg+xml;base64,{base64image}"'
55+
else:
56+
img_tag = f'<img src="data:image/png;base64,{base64image}"'
57+
for key, value in option_dict.items():
58+
img_tag += f' {key}={value}'
59+
img_tag += ' />'
60+
new_lines.append(img_tag)
3761
else:
3862
new_lines.append('```mermaid')
3963
new_lines.extend(mermaid_block)
@@ -47,41 +71,47 @@ def run(self, lines: List[str]) -> List[str]:
4771

4872
return new_lines
4973

50-
def _svg2data_uri(self, svg_content: str) -> str:
51-
"""Convert SVG content to Data URI."""
52-
base64_svg = base64.b64encode(svg_content.encode('utf-8')).decode('utf-8')
53-
data_uri = f'data:image/svg+xml;base64,{base64_svg}'
54-
return data_uri
55-
56-
def _mermaid2svg(self, mermaid_code: str) -> str:
74+
def _mermaid2base64image(self, mermaid_code: str, image_type: str) -> str:
5775
"""Convert mermaid code to SVG using mmdc (Mermaid CLI)."""
5876
with tempfile.NamedTemporaryFile(mode='w', suffix='.mmd', delete=False) as tmp_mmd:
5977
tmp_mmd.write(mermaid_code)
6078
mmd_filepath = tmp_mmd.name
6179

62-
with tempfile.NamedTemporaryFile(mode='w', suffix='.svg', delete=False) as tmp_svg:
63-
svg_filepath = tmp_svg.name
80+
with tempfile.NamedTemporaryFile(mode='w', suffix=f'.{image_type}', delete=False) as tmp_img:
81+
img_filepath = tmp_img.name
6482

6583
try:
6684
command = [
6785
'mmdc',
6886
'--input',
6987
mmd_filepath,
7088
'--output',
71-
svg_filepath,
89+
img_filepath,
90+
'--outputFormat',
91+
image_type,
7292
'--puppeteerConfigFile',
7393
os.path.join(os.path.dirname(__file__), 'puppeteer-config.json'),
7494
]
7595
subprocess.run(command, check=True, capture_output=True)
76-
with open(svg_filepath, 'r', encoding='utf-8') as f:
77-
svg_content = f.read()
78-
return svg_content
96+
if image_type == 'svg':
97+
with open(img_filepath, 'r', encoding='utf-8') as f:
98+
svg_content: str = f.read()
99+
elif image_type == 'png':
100+
with open(img_filepath, 'rb') as f:
101+
png_content: bytes = f.read()
79102
except subprocess.CalledProcessError as e:
80103
print(f'Error generating SVG: {e.stderr.decode()}')
81104
return ''
82105
finally:
83106
os.remove(mmd_filepath)
84-
os.remove(svg_filepath)
107+
os.remove(img_filepath)
108+
109+
if image_type == 'svg':
110+
base64image = base64.b64encode(svg_content.encode('utf-8')).decode('utf-8')
111+
else:
112+
base64image = base64.b64encode(png_content).decode('utf-8')
113+
114+
return base64image
85115

86116

87117
class MermaidDataURIExtension(Extension):

0 commit comments

Comments
 (0)