22import logging
33from docutils import nodes
44from docutils .parsers .rst import directives , Directive
5+ from typing import Any
56import sphinx
6- from sphinx .ext .graphviz import latex_visit_graphviz , text_visit_graphviz , graphviz
7+ from sphinx .ext .graphviz import (
8+ latex_visit_graphviz ,
9+ text_visit_graphviz ,
10+ render_dot ,
11+ GraphvizError ,
12+ ClickableMapDefinition ,
13+ __ ,
14+ )
715from ..ext_helper import get_env_state_info
816from ..ext_io_helper import download_requirejs , get_url_content_timeout
917from ..runpython .sphinx_runpython_extension import run_python_script
@@ -179,14 +187,13 @@ def run(self):
179187 logger .warning ("[gdot] too many output lines %s" , content )
180188 content = spl [- 1 ]
181189
182- if format == "svg" :
183- node = graphviz ()
184- node ["code" ] = content
185- node ["options" ] = {"docname" : docname }
186- else :
187- node = gdot_node (
188- format = format , code = content , url = url , options = {"docname" : docname }
189- )
190+ node = gdot_node (
191+ format = format ,
192+ code = content ,
193+ url = url ,
194+ options = {"docname" : docname },
195+ use_sphinx_graphviz = True ,
196+ )
190197 return [node ]
191198
192199
@@ -209,8 +216,76 @@ def depart_gdot_node_rst(self, node):
209216 self .end_state (wrap = False )
210217
211218
219+ def render_dot_html (
220+ self ,
221+ node : gdot_node ,
222+ code : str ,
223+ options : dict [str , Any ],
224+ prefix : str = "gdot" ,
225+ imgcls : str | None = None ,
226+ alt : str | None = None ,
227+ filename : str | None = None ,
228+ format : str = "svg" ,
229+ ) -> tuple [str , str ]:
230+ if format not in {"png" , "svg" }:
231+ logger = logging .getLogger (__name__ )
232+ logger .warning (__ ("format must be either 'png' or 'svg', but is %r" ), format )
233+ try :
234+ fname , outfn = render_dot (self , code , options , format , prefix , filename )
235+ except GraphvizError as exc :
236+ logger .warning (__ ("dot code %r: %s" ), code , exc )
237+ raise nodes .SkipNode from exc
238+
239+ classes = [imgcls , "graphviz" , * node .get ("classes" , [])]
240+ imgcls = " " .join (filter (None , classes ))
241+
242+ if fname is None :
243+ self .body .append (self .encode (code ))
244+ else :
245+ src = fname .as_posix ()
246+ if alt is None :
247+ alt = node .get ("alt" , self .encode (code ).strip ())
248+ if "align" in node :
249+ align = node ["align" ]
250+ self .body .append (f'<div align="{ align } " class="align-{ align } ">' )
251+ if format == "svg" :
252+ self .body .append ('<div class="graphviz">' )
253+ self .body .append (
254+ f'<object data="{ src } " type="image/svg+xml" class="{ imgcls } ">\n '
255+ )
256+ self .body .append (f'<p class="warning">{ alt } </p>' )
257+ self .body .append ("</object></div>\n " )
258+ else :
259+ assert outfn is not None
260+ with open (f"{ outfn } .map" , encoding = "utf-8" ) as mapfile :
261+ map_content = mapfile .read ()
262+ imgmap = ClickableMapDefinition (f"{ outfn } .map" , map_content , dot = code )
263+ if imgmap .clickable :
264+ # has a map
265+ self .body .append ('<div class="graphviz">' )
266+ self .body .append (
267+ f'<img src="{ src } " alt="{ alt } " usemap="#{ imgmap .id } " class="{ imgcls } " />'
268+ )
269+ self .body .append ("</div>\n " )
270+ self .body .append (imgmap .generate_clickable_map ())
271+ else :
272+ # nothing in image map
273+ self .body .append ('<div class="graphviz">' )
274+ self .body .append (f'<img src="{ src } " alt="{ alt } " class="{ imgcls } " />' )
275+ self .body .append ("</div>\n " )
276+ if "align" in node :
277+ self .body .append ("</div>\n " )
278+
279+ raise nodes .SkipNode
280+
281+
212282def visit_gdot_node_html_svg (self , node ):
213283 """visit collapse_node"""
284+ if node ["use_sphinx_graphviz" ]:
285+ render_dot_html (
286+ self , node , node ["code" ], node ["options" ], filename = node .get ("filename" )
287+ )
288+ return
214289
215290 def process (text ):
216291 text = text .replace ("\\ " , "\\ \\ " )
0 commit comments