chiark / gitweb /
doxygen: implement support for \dot and \dotfile.
authorVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Oct 2018 16:26:25 +0000 (18:26 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Oct 2018 18:17:49 +0000 (20:17 +0200)
Ugh, this blew up again. Three days to get here.

19 files changed:
doc/doxygen.rst
doxygen/dox2html5.py
doxygen/test/contents_custom/Doxyfile
doxygen/test/contents_custom/ab.dot [new file with mode: 0644]
doxygen/test/contents_custom/dot-236.html [new file with mode: 0644]
doxygen/test/contents_custom/dot-238.html [new file with mode: 0644]
doxygen/test/contents_custom/dot.html [new file with mode: 0644]
doxygen/test/contents_custom/input.dox
doxygen/test/contents_dot/Doxyfile [new file with mode: 0644]
doxygen/test/contents_dot/ab.dot [new file with mode: 0644]
doxygen/test/contents_dot/colors.dot [new file with mode: 0644]
doxygen/test/contents_dot/index-236.html [new file with mode: 0644]
doxygen/test/contents_dot/index-238.html [new file with mode: 0644]
doxygen/test/contents_dot/index.html [new file with mode: 0644]
doxygen/test/contents_dot/input.dox [new file with mode: 0644]
doxygen/test/contents_dot/warnings.html [new file with mode: 0644]
doxygen/test/test_contents.py
doxygen/test/test_doxyfile.py
pelican-plugins/dot2svg.py

index da3efc4f60160d1191fcc9c2702cae47f18ec9f4..121e894d22ed3fc826f03eafd891b27cd7aea2d3 100644 (file)
@@ -147,6 +147,9 @@ If you see something unexpected or not see something expected, check the
 -   Math rendered as embedded SVG instead of raster images / MathJax. The
     supported feature set is equivalent to the `m.math Pelican plugin <{filename}/plugins/math-and-code.rst#math>`_,
     see its documentation for more information.
+-   Graphviz / Dot diagrams rendered as embedded SVG. The supported feature set
+    is equivalent to the `m.dot Pelican plugin <{filename}/plugins/plots-and-graphs.rst#graphs>`_,
+    see its documentation for more information.
 -   Uses Pygments for better code highlighting. The supported feature set is
     equivalent to the `m.code Pelican plugin <{filename}/plugins/math-and-code.rst#code>`_,
     see its documentation for more information.
@@ -280,6 +283,15 @@ Variable                        Description
                                 searched relative to the Doxyfile base dir and
                                 to the ``dox2html5.py`` script dir as a
                                 fallback.
+:ini:`DOT_FONTNAME`             Font name to use for ``@dot`` and ``@dotfile``
+                                commands. To ensure consistent look with the
+                                default m.css themes, set it to
+                                ``Source Sans Pro``. Doxygen default is
+                                ``Helvetica``.
+:ini:`DOT_FONTSIZE`             Font size to use for ``@dot`` and ``@dotfile``
+                                commands. To ensure consistent look with the
+                                default m.css themes, set it to ``16``.
+                                Doxygen default is ``10``.
 =============================== ===============================================
 
 In addition, the m.css Doxygen theme recognizes the following extra options:
@@ -592,6 +604,13 @@ as well:
     @image image.png width=250px
     */
 
+`Dot graphs`_
+-------------
+
+Grapviz ``dot`` graphs from the ``@dot`` and ``@dotfile`` commands are rendered
+as an inline SVG. Graph name and the ``sizespec`` works equivalently to the
+`Images and figures`_.
+
 `Pages, sections and table of contents`_
 ----------------------------------------
 
index 14933cd43ad41a5ba121cc33444b215c80785cf3..d4987befb199692f7d32796c18369a2093617118 100755 (executable)
@@ -50,6 +50,7 @@ from pygments.formatters import HtmlFormatter
 from pygments.lexers import TextLexer, BashSessionLexer, get_lexer_by_name, find_lexer_class_for_filename
 
 sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../pelican-plugins'))
+import dot2svg
 import latex2svg
 import latex2svgextra
 import ansilexer
@@ -560,7 +561,7 @@ def parse_desc_internal(state: State, element: ET.Element, immediate_parent: ET.
         # - <verbatim>, <preformatted> (those are the same thing!)
         # - <parblock> (a weird grouping thing that we abuse for <div>s)
         # - <variablelist>, <itemizedlist>, <orderedlist>
-        # - <image>, <table>
+        # - <image>, <dot>, <dotfile>, <table>
         # - <mcss:div>
         # - <formula> (if block)
         # - <programlisting> (if block)
@@ -582,7 +583,7 @@ def parse_desc_internal(state: State, element: ET.Element, immediate_parent: ET.
             end_previous_paragraph = False
 
             # Straightforward elements
-            if i.tag in ['heading', 'blockquote', 'hruler', 'xrefsect', 'variablelist', 'verbatim', 'parblock', 'preformatted', 'itemizedlist', 'orderedlist', 'image', 'table', '{http://mcss.mosra.cz/doxygen/}div']:
+            if i.tag in ['heading', 'blockquote', 'hruler', 'xrefsect', 'variablelist', 'verbatim', 'parblock', 'preformatted', 'itemizedlist', 'orderedlist', 'image', 'dot', 'dotfile', 'table', '{http://mcss.mosra.cz/doxygen/}div']:
                 end_previous_paragraph = True
 
             # <simplesect> describing return type is cut out of text flow, so
@@ -1065,6 +1066,41 @@ def parse_desc_internal(state: State, element: ET.Element, immediate_parent: ET.
                     out.parsed += '<img class="m-image{}" src="{}" alt="Image"{} />'.format(
                         ' ' + add_css_class if add_css_class else '', name, sizespec)
 
+        elif i.tag in ['dot', 'dotfile']:
+            # can be in <para> but often also in <div> and other m.css-specific
+            # elements
+            has_block_elements = True
+
+            # Why the heck can't it just read the file and paste it into the
+            # XML?!
+            caption = None
+            if i.tag == 'dotfile':
+                if 'name' in i.attrib:
+                    with open(i.attrib['name'], 'r') as f:
+                        source = f.read()
+                else:
+                    logging.warning("{}: file passed to @dotfile was not found, rendering an empty graph")
+                    source = 'digraph "" {}'
+                caption = i.text
+            else:
+                source = i.text
+                if 'caption' in i.attrib: caption = i.attrib['caption']
+
+            size = None
+            if 'width' in i.attrib:
+                size = 'width: {};'.format(i.attrib['width'])
+            elif 'height' in i.attrib:
+                size = 'height: {};'.format(i.attrib['height'])
+
+            if caption:
+                out.parsed += '<figure class="m-figure">{}<figcaption>{}</figcaption></figure>'.format(dot2svg.dot2svg(
+                    source, size=size,
+                    attribs=' class="m-graph{}"'.format(' ' + add_css_class if add_css_class else '')),
+                    caption)
+            else:
+                out.parsed += '<div class="m-graph{}">{}</div>'.format(
+                    ' ' + add_css_class if add_css_class else '', dot2svg.dot2svg(source, size))
+
         elif i.tag == 'hruler':
             assert element.tag == 'para' # is inside a paragraph :/
             out.parsed += '<hr/>'
@@ -2847,6 +2883,8 @@ def parse_doxyfile(state: State, doxyfile, config = None):
             'https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600',
             '../css/m-dark+doxygen.compiled.css'],
         'HTML_EXTRA_FILES': [],
+        'DOT_FONTNAME': ['Helvetica'],
+        'DOT_FONTSIZE': ['10'],
 
         'M_CLASS_TREE_EXPAND_LEVELS': ['1'],
         'M_FILE_TREE_EXPAND_LEVELS': ['1'],
@@ -2957,6 +2995,7 @@ list using <span class="m-label m-dim">&darr;</span> and
               'OUTPUT_DIRECTORY',
               'HTML_OUTPUT',
               'XML_OUTPUT',
+              'DOT_FONTNAME',
               'M_MAIN_PROJECT_URL',
               'M_HTML_HEADER',
               'M_PAGE_HEADER',
@@ -2969,7 +3008,8 @@ list using <span class="m-label m-dim">&darr;</span> and
         if i in config: state.doxyfile[i] = '\n'.join(config[i])
 
     # Int values that we want
-    for i in ['M_CLASS_TREE_EXPAND_LEVELS',
+    for i in ['DOT_FONTSIZE',
+              'M_CLASS_TREE_EXPAND_LEVELS',
               'M_FILE_TREE_EXPAND_LEVELS']:
         if i in config: state.doxyfile[i] = int(' '.join(config[i]))
 
@@ -3017,6 +3057,9 @@ def run(doxyfile, templates=default_templates, wildcard=default_wildcard, index_
     else:
         latex2svgextra.unpickle_cache(None)
 
+    # Configure graphviz/dot
+    dot2svg.configure(state.doxyfile['DOT_FONTNAME'], state.doxyfile['DOT_FONTSIZE'])
+
     if sort_globbed_files:
         xml_files_metadata.sort()
         xml_files.sort()
index a2cb09dc16148bf318cf2a15f21ffa248f5622cd..32e461f3d9d7bb16bf9ff5c0a0592fe4a9cc48aa 100644 (file)
@@ -6,6 +6,10 @@ GENERATE_LATEX          = NO
 GENERATE_XML            = YES
 XML_PROGRAMLISTING      = NO
 
+DOTFILE_DIRS            = .
+DOT_FONTNAME            = DejaVu Sans
+DOT_FONTSIZE            = 16
+
 ##! M_PAGE_FINE_PRINT   =
 ##! M_THEME_COLOR       =
 ##! M_FAVICON           =
diff --git a/doxygen/test/contents_custom/ab.dot b/doxygen/test/contents_custom/ab.dot
new file mode 100644 (file)
index 0000000..69d0ddf
--- /dev/null
@@ -0,0 +1,4 @@
+strict graph "" {
+    a -- b
+    a -- b
+}
diff --git a/doxygen/test/contents_custom/dot-236.html b/doxygen/test/contents_custom/dot-236.html
new file mode 100644 (file)
index 0000000..b7b246d
--- /dev/null
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Dot | My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+doxygen.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          Dot
+        </h1>
+<p>A red graph:</p><div class="m-graph m-danger"><svg style="width: 3.875rem; height: 7.500rem;" viewBox="0.00 0.00 62.00 120.00">
+<g transform="scale(1 1) rotate(0) translate(4 116)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-93" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-89.2">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-19" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-15.2">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-74.5708C27,-63.3339 27,-48.7727 27,-37.5182"/>
+</g>
+</g>
+</svg>
+</div><p>A blue graph, from a file:</p><div class="m-graph m-info"><svg style="width: 3.875rem; height: 7.500rem;" viewBox="0.00 0.00 62.00 120.00">
+<g transform="scale(1 1) rotate(0) translate(4 116)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-93" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-89.2">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-19" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-15.2">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-74.5708C27,-63.3339 27,-48.7727 27,-37.5182"/>
+</g>
+</g>
+</svg>
+</div>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/doxygen/test/contents_custom/dot-238.html b/doxygen/test/contents_custom/dot-238.html
new file mode 100644 (file)
index 0000000..295af83
--- /dev/null
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Dot | My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+doxygen.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          Dot
+        </h1>
+<p>A red graph:</p><div class="m-graph m-danger"><svg style="width: 3.875rem; height: 7.375rem;" viewBox="0.00 0.00 62.00 117.54">
+<g transform="scale(1 1) rotate(0) translate(4 113.539)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-91.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-87.3543">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-18.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-72.6607C27,-61.8548 27,-48.0281 27,-37.1687"/>
+</g>
+</g>
+</svg>
+</div><p>A blue graph, from a file:</p><div class="m-graph m-info"><svg style="width: 3.875rem; height: 7.375rem;" viewBox="0.00 0.00 62.00 117.54">
+<g transform="scale(1 1) rotate(0) translate(4 113.539)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-91.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-87.3543">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-18.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-72.6607C27,-61.8548 27,-48.0281 27,-37.1687"/>
+</g>
+</g>
+</svg>
+</div>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/doxygen/test/contents_custom/dot.html b/doxygen/test/contents_custom/dot.html
new file mode 100644 (file)
index 0000000..da5879c
--- /dev/null
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Dot | My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+doxygen.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          Dot
+        </h1>
+<p>A red graph:</p><div class="m-graph m-danger"><svg style="width: 3.875rem; height: 7.375rem;" viewBox="0.00 0.00 62.00 117.54">
+<g transform="scale(1 1) rotate(0) translate(4 113.5391)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-91.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-87.3543">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-18.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-72.4144C27,-61.4654 27,-47.7036 27,-36.8093"/>
+</g>
+</g>
+</svg>
+</div><p>A blue graph, from a file:</p><div class="m-graph m-info"><svg style="width: 3.875rem; height: 7.375rem;" viewBox="0.00 0.00 62.00 117.54">
+<g transform="scale(1 1) rotate(0) translate(4 113.5391)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-91.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-87.3543">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-18.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-72.4144C27,-61.4654 27,-47.7036 27,-36.8093"/>
+</g>
+</g>
+</svg>
+</div>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index a1f542ce158a97e8ce0f361ec72bcfb2a04a7331..e52258c44695b065526642b94ffeea181c10f55b 100644 (file)
@@ -80,3 +80,23 @@ A green formula:
 
 A yellow @m_class{m-warning} @f$ \Sigma @f$ inline formula.
 */
+
+/** @page dot Dot
+
+A red graph:
+
+@m_class{m-danger}
+
+@dot
+strict graph "" {
+    a -- b
+    a -- b
+}
+@enddot
+
+A blue graph, from a file:
+
+@m_class{m-info}
+
+@dotfile ab.dot
+*/
diff --git a/doxygen/test/contents_dot/Doxyfile b/doxygen/test/contents_dot/Doxyfile
new file mode 100644 (file)
index 0000000..9cd0d80
--- /dev/null
@@ -0,0 +1,17 @@
+INPUT                   = input.dox
+QUIET                   = YES
+GENERATE_HTML           = NO
+GENERATE_LATEX          = NO
+GENERATE_XML            = YES
+XML_PROGRAMLISTING      = NO
+
+DOTFILE_DIRS            = .
+DOT_FONTNAME            = DejaVu Sans
+DOT_FONTSIZE            = 16
+
+##! M_PAGE_FINE_PRINT   =
+##! M_THEME_COLOR       =
+##! M_FAVICON           =
+##! M_LINKS_NAVBAR1     =
+##! M_LINKS_NAVBAR2     =
+##! M_SEARCH_DISABLED   = YES
diff --git a/doxygen/test/contents_dot/ab.dot b/doxygen/test/contents_dot/ab.dot
new file mode 100644 (file)
index 0000000..69d0ddf
--- /dev/null
@@ -0,0 +1,4 @@
+strict graph "" {
+    a -- b
+    a -- b
+}
diff --git a/doxygen/test/contents_dot/colors.dot b/doxygen/test/contents_dot/colors.dot
new file mode 100644 (file)
index 0000000..699de0f
--- /dev/null
@@ -0,0 +1,6 @@
+digraph Colors {
+    a [class="m-success"]
+    b [style=filled shape=circle class="m-dim"]
+    a -> b [class="m-warning" label="yes"]
+    b -> b [class="m-primary" label="no"]
+}
diff --git a/doxygen/test/contents_dot/index-236.html b/doxygen/test/contents_dot/index-236.html
new file mode 100644 (file)
index 0000000..7af6722
--- /dev/null
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+doxygen.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          My Project
+        </h1>
+<p>Note: the test uses DejaVu Sans instead of Source Sans Pro in order to have predictable rendering on the CIs.</p><div class="m-graph"><svg style="width: 16.375rem; height: 5.250rem;" viewBox="0.00 0.00 262.00 84.00">
+<g transform="scale(1 1) rotate(0) translate(4 80)">
+<title>Basics</title>
+<g class="m-node">
+<title>a</title>
+<polygon points="54,-40 0,-40 0,-4 54,-4 54,-40"/>
+<text text-anchor="middle" x="27" y="-18.2">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="115" cy="-22" rx="18.2906" ry="18.2906"/>
+<ellipse cx="115" cy="-22" rx="22.2701" ry="22.2701"/>
+<text text-anchor="middle" x="115" y="-18.2">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&gt;b</title>
+<path d="M54.2701,-22C63.1708,-22 73.2044,-22 82.4712,-22"/>
+<polygon points="82.5066,-25.5001 92.5066,-22 82.5065,-18.5001 82.5066,-25.5001"/>
+</g>
+<g class="m-node m-flat">
+<title>c</title>
+<ellipse cx="227" cy="-22" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="227" y="-18.2">c</text>
+</g>
+<g class="m-edge">
+<title>b&#45;&gt;c</title>
+<path d="M137.47,-22C152.279,-22 172.331,-22 189.606,-22"/>
+<polygon points="189.933,-25.5001 199.933,-22 189.933,-18.5001 189.933,-25.5001"/>
+<text text-anchor="middle" x="169" y="-34" style="font-size: 40.0px;">0</text>
+</g>
+<g class="m-edge">
+<title>c&#45;&gt;c</title>
+<path d="M216.722,-39.0373C214.625,-48.8579 218.051,-58 227,-58 232.593,-58 236.029,-54.4289 237.307,-49.3529"/>
+<polygon points="240.806,-49.0272 237.278,-39.0373 233.806,-49.0473 240.806,-49.0272"/>
+<text text-anchor="middle" x="227" y="-63.2">1</text>
+</g>
+</g>
+</svg>
+</div><div class="m-graph"><svg style="width: 5.750rem; height: 8.625rem;" viewBox="0.00 0.00 92.00 138.00">
+<g transform="scale(1 1) rotate(0) translate(4 134)">
+<title>Colors</title>
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-111" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-107.2">a</text>
+</g>
+<g class="m-node">
+<title>b</title>
+<ellipse cx="27" cy="-19" rx="18.0791" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-15.2">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&gt;b</title>
+<path d="M27,-92.2189C27,-79.564 27,-62.3088 27,-47.8384"/>
+<polygon points="30.5001,-47.4379 27,-37.4379 23.5001,-47.438 30.5001,-47.4379"/>
+<text text-anchor="middle" x="41" y="-61.2">yes</text>
+</g>
+<g class="m-edge">
+<title>b&#45;&gt;b</title>
+<path d="M43.6641,-27.0859C53.625,-28.8828 63,-26.1875 63,-19 63,-14.3955 59.1525,-11.6346 53.7682,-10.7173"/>
+<polygon points="53.594,-7.21994 43.6641,-10.9141 53.7303,-14.2186 53.594,-7.21994"/>
+<text text-anchor="middle" x="73.5" y="-15.2">no</text>
+</g>
+</g>
+</svg>
+</div><figure class="m-figure"><svg class="m-graph" style="width: 5rem;" viewBox="0.00 0.00 62.00 120.00">
+<g transform="scale(1 1) rotate(0) translate(4 116)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-93" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-89.2">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-19" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-15.2">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-74.5708C27,-63.3339 27,-48.7727 27,-37.5182"/>
+</g>
+</g>
+</svg>
+<figcaption>A graph</figcaption></figure><figure class="m-figure"><svg class="m-graph" style="height: 5rem;" viewBox="0.00 0.00 62.00 120.00">
+<g transform="scale(1 1) rotate(0) translate(4 116)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-93" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-89.2">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-19" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-15.2">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-74.5708C27,-63.3339 27,-48.7727 27,-37.5182"/>
+</g>
+</g>
+</svg>
+<figcaption>A graph</figcaption></figure>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/doxygen/test/contents_dot/index-238.html b/doxygen/test/contents_dot/index-238.html
new file mode 100644 (file)
index 0000000..3e7bcdf
--- /dev/null
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+doxygen.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          My Project
+        </h1>
+<p>Note: the test uses DejaVu Sans instead of Source Sans Pro in order to have predictable rendering on the CIs.</p><div class="m-graph"><svg style="width: 16.250rem; height: 5.312rem;" viewBox="0.00 0.00 259.77 84.77">
+<g transform="scale(1 1) rotate(0) translate(4 80.7696)">
+<title>Basics</title>
+<g class="m-node">
+<title>a</title>
+<polygon points="54,-40.3848 0,-40.3848 0,-4.38478 54,-4.38478 54,-40.3848"/>
+<text text-anchor="middle" x="27" y="-18.5848">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="113.385" cy="-22.3848" rx="18.2906" ry="18.2906"/>
+<ellipse cx="113.385" cy="-22.3848" rx="22.2701" ry="22.2701"/>
+<text text-anchor="middle" x="113.385" y="-18.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&gt;b</title>
+<path d="M54.2373,-22.3848C62.63,-22.3848 72.0104,-22.3848 80.7459,-22.3848"/>
+<polygon points="80.9716,-25.8849 90.9716,-22.3848 80.9716,-18.8849 80.9716,-25.8849"/>
+</g>
+<g class="m-node m-flat">
+<title>c</title>
+<ellipse cx="224.77" cy="-22.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="224.77" y="-18.5848">c</text>
+</g>
+<g class="m-edge">
+<title>b&#45;&gt;c</title>
+<path d="M135.996,-22.3848C150.639,-22.3848 170.35,-22.3848 187.383,-22.3848"/>
+<polygon points="187.574,-25.8849 197.574,-22.3848 187.573,-18.8849 187.574,-25.8849"/>
+<text text-anchor="middle" x="166.77" y="-34.3848" style="font-size: 40.0px;">0</text>
+</g>
+<g class="m-edge">
+<title>c&#45;&gt;c</title>
+<path d="M214.492,-39.6042C212.394,-49.5298 215.82,-58.7696 224.77,-58.7696 230.503,-58.7696 233.969,-54.9775 235.168,-49.6425"/>
+<polygon points="238.668,-49.5611 235.047,-39.6042 231.668,-49.6458 238.668,-49.5611"/>
+<text text-anchor="middle" x="224.77" y="-63.9696">1</text>
+</g>
+</g>
+</svg>
+</div><div class="m-graph"><svg style="width: 5.750rem; height: 8.500rem;" viewBox="0.00 0.00 92.38 135.54">
+<g transform="scale(1 1) rotate(0) translate(4 131.539)">
+<title>Colors</title>
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-109.154" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-105.354">a</text>
+</g>
+<g class="m-node">
+<title>b</title>
+<ellipse cx="27" cy="-18.3848" rx="18.2703" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&gt;b</title>
+<path d="M27,-90.6163C27,-78.2149 27,-61.3346 27,-47.1192"/>
+<polygon points="30.5001,-46.8863 27,-36.8864 23.5001,-46.8864 30.5001,-46.8863"/>
+<text text-anchor="middle" x="41" y="-59.9696">yes</text>
+</g>
+<g class="m-edge">
+<title>b&#45;&gt;b</title>
+<path d="M43.8422,-26.1192C53.9096,-27.8379 63.3848,-25.2598 63.3848,-18.3848 63.3848,-13.9805 59.4961,-11.3396 54.0544,-10.4622"/>
+<polygon points="53.776,-6.96664 43.8422,-10.6504 53.905,-13.9655 53.776,-6.96664"/>
+<text text-anchor="middle" x="73.8848" y="-14.5848">no</text>
+</g>
+</g>
+</svg>
+</div>
+<figure class="m-figure"><svg class="m-graph" style="width: 5rem;" viewBox="0.00 0.00 62.00 117.54">
+<g transform="scale(1 1) rotate(0) translate(4 113.539)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-91.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-87.3543">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-18.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-72.6607C27,-61.8548 27,-48.0281 27,-37.1687"/>
+</g>
+</g>
+</svg>
+<figcaption>A graph</figcaption></figure><figure class="m-figure"><svg class="m-graph" style="height: 5rem;" viewBox="0.00 0.00 62.00 117.54">
+<g transform="scale(1 1) rotate(0) translate(4 113.539)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-91.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-87.3543">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-18.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-72.6607C27,-61.8548 27,-48.0281 27,-37.1687"/>
+</g>
+</g>
+</svg>
+<figcaption>A graph</figcaption></figure>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/doxygen/test/contents_dot/index.html b/doxygen/test/contents_dot/index.html
new file mode 100644 (file)
index 0000000..0247b5a
--- /dev/null
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+doxygen.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          My Project
+        </h1>
+<p>Note: the test uses DejaVu Sans instead of Source Sans Pro in order to have predictable rendering on the CIs.</p><div class="m-graph"><svg style="width: 16.250rem; height: 5.312rem;" viewBox="0.00 0.00 259.77 84.77">
+<g transform="scale(1 1) rotate(0) translate(4 80.7696)">
+<title>Basics</title>
+<g class="m-node">
+<title>a</title>
+<polygon points="54,-40.3848 0,-40.3848 0,-4.3848 54,-4.3848 54,-40.3848"/>
+<text text-anchor="middle" x="27" y="-18.5848">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="113.3848" cy="-22.3848" rx="18.2906" ry="18.2906"/>
+<ellipse cx="113.3848" cy="-22.3848" rx="22.2701" ry="22.2701"/>
+<text text-anchor="middle" x="113.3848" y="-18.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&gt;b</title>
+<path d="M54.3327,-22.3848C62.6964,-22.3848 71.9814,-22.3848 80.6653,-22.3848"/>
+<polygon points="80.8574,-25.8849 90.8574,-22.3848 80.8574,-18.8849 80.8574,-25.8849"/>
+</g>
+<g class="m-node m-flat">
+<title>c</title>
+<ellipse cx="224.7696" cy="-22.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="224.7696" y="-18.5848">c</text>
+</g>
+<g class="m-edge">
+<title>b&#45;&gt;c</title>
+<path d="M135.9251,-22.3848C150.665,-22.3848 170.2762,-22.3848 187.3232,-22.3848"/>
+<polygon points="187.5396,-25.8849 197.5396,-22.3848 187.5396,-18.8849 187.5396,-25.8849"/>
+<text text-anchor="middle" x="166.7696" y="-34.3848" style="font-size: 40.0px;">0</text>
+</g>
+<g class="m-edge">
+<title>c&#45;&gt;c</title>
+<path d="M214.4919,-39.6042C212.3945,-49.5298 215.8203,-58.7696 224.7696,-58.7696 230.5026,-58.7696 233.969,-54.9775 235.1685,-49.6425"/>
+<polygon points="238.6679,-49.5611 235.0472,-39.6042 231.6684,-49.6458 238.6679,-49.5611"/>
+<text text-anchor="middle" x="224.7696" y="-63.9696">1</text>
+</g>
+</g>
+</svg>
+</div><div class="m-graph"><svg style="width: 5.688rem; height: 8.500rem;" viewBox="0.00 0.00 91.38 135.54">
+<g transform="scale(1 1) rotate(0) translate(4 131.5391)">
+<title>Colors</title>
+<g class="m-node m-success m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-109.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-105.3543">a</text>
+</g>
+<g class="m-node m-dim">
+<title>b</title>
+<ellipse cx="27" cy="-18.3848" rx="18.2703" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">b</text>
+</g>
+<g class="m-edge m-warning">
+<title>a&#45;&gt;b</title>
+<path d="M27,-90.3468C27,-77.8501 27,-61.2025 27,-47.0671"/>
+<polygon points="30.5001,-46.8576 27,-36.8576 23.5001,-46.8577 30.5001,-46.8576"/>
+<text text-anchor="middle" x="41" y="-59.9696">yes</text>
+</g>
+<g class="m-edge m-primary">
+<title>b&#45;&gt;b</title>
+<path d="M43.8422,-26.1192C53.9096,-27.8379 63.3848,-25.2598 63.3848,-18.3848 63.3848,-13.9805 59.4961,-11.3396 54.0544,-10.4622"/>
+<polygon points="53.776,-6.9666 43.8422,-10.6504 53.905,-13.9655 53.776,-6.9666"/>
+<text text-anchor="middle" x="73.3848" y="-14.5848">no</text>
+</g>
+</g>
+</svg>
+</div><figure class="m-figure"><svg class="m-graph" style="width: 5rem;" viewBox="0.00 0.00 62.00 117.54">
+<g transform="scale(1 1) rotate(0) translate(4 113.5391)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-91.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-87.3543">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-18.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-72.4144C27,-61.4654 27,-47.7036 27,-36.8093"/>
+</g>
+</g>
+</svg>
+<figcaption>A graph</figcaption></figure><figure class="m-figure"><svg class="m-graph" style="height: 5rem;" viewBox="0.00 0.00 62.00 117.54">
+<g transform="scale(1 1) rotate(0) translate(4 113.5391)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-91.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-87.3543">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-18.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&#45;b</title>
+<path d="M27,-72.4144C27,-61.4654 27,-47.7036 27,-36.8093"/>
+</g>
+</g>
+</svg>
+<figcaption>A graph</figcaption></figure>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/doxygen/test/contents_dot/input.dox b/doxygen/test/contents_dot/input.dox
new file mode 100644 (file)
index 0000000..d201260
--- /dev/null
@@ -0,0 +1,36 @@
+/** @mainpage
+
+Note: the test uses DejaVu Sans instead of Source Sans Pro in order to have
+predictable rendering on the CIs.
+
+@dot
+digraph Basics {
+    rankdir=LR
+
+    a [style=filled shape=rect]
+    b [peripheries=2 shape=circle]
+    c [shape=ellipse]
+    a -> b
+    b -> c [label="0" fontsize=40]
+    c -> c [label="1"]
+}
+@enddot
+
+@dotfile colors.dot
+
+@dot "A graph" width=5rem
+strict graph "" {
+    a -- b
+    a -- b
+}
+@enddot
+
+@dotfile ab.dot "A graph" height=5rem
+*/
+
+/** @page warnings
+
+This file doesn't exist:
+
+@dotfile nonexistent.dot
+*/
diff --git a/doxygen/test/contents_dot/warnings.html b/doxygen/test/contents_dot/warnings.html
new file mode 100644 (file)
index 0000000..42e2f66
--- /dev/null
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>warnings | My Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+doxygen.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          warnings
+        </h1>
+<p>This file doesn&#x27;t exist:</p><div class="m-graph"><svg style="width: 0.500rem; height: 0.500rem;" viewBox="0.00 0.00 8.00 8.00">
+<g transform="scale(1 1) rotate(0) translate(4 4)">
+</g>
+</svg>
+</div>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
\ No newline at end of file
index 3e1d843344fe079fc45d13c6ac0737fae5b9f73c..8995678778158da077719f4a5091fc5b7d8be4bb 100644 (file)
@@ -24,6 +24,7 @@
 
 import os
 import pickle
+import re
 import shutil
 import subprocess
 import unittest
@@ -34,6 +35,9 @@ from distutils.version import LooseVersion
 
 from . import BaseTestCase, IntegrationTestCase, doxygen_version
 
+def dot_version():
+    return re.match(".*version (?P<version>\d+\.\d+\.\d+).*", subprocess.check_output(['dot', '-V'], stderr=subprocess.STDOUT).decode('utf-8').strip()).group('version')
+
 class Typography(IntegrationTestCase):
     def __init__(self, *args, **kwargs):
         super().__init__(__file__, 'typography', *args, **kwargs)
@@ -244,6 +248,25 @@ class Custom(IntegrationTestCase):
         self.run_dox2html5(wildcard='math.xml')
         self.assertEqual(*self.actual_expected_contents('math.html'))
 
+    @unittest.skipUnless(LooseVersion(dot_version()) >= LooseVersion("2.40.1"),
+                         "Dot < 2.40.1 has a completely different output.")
+    def test_dot(self):
+        self.run_dox2html5(wildcard='dot.xml')
+        self.assertEqual(*self.actual_expected_contents('dot.html'))
+
+    @unittest.skipUnless(LooseVersion(dot_version()) < LooseVersion("2.40.1") and
+                         LooseVersion(dot_version()) >= LooseVersion("2.38.0"),
+                         "Dot < 2.38 and dot > 2.38 has a completely different output.")
+    def test_dot238(self):
+        self.run_dox2html5(wildcard='dot.xml')
+        self.assertEqual(*self.actual_expected_contents('dot.html', 'dot-238.html'))
+
+    @unittest.skipUnless(LooseVersion(dot_version()) < LooseVersion("2.38.0"),
+                         "Dot > 2.36 has a completely different output.")
+    def test_dot236(self):
+        self.run_dox2html5(wildcard='dot.xml')
+        self.assertEqual(*self.actual_expected_contents('dot.html', 'dot-236.html'))
+
 class ParseError(BaseTestCase):
     def __init__(self, *args, **kwargs):
         super().__init__(__file__, 'parse_error', *args, **kwargs)
@@ -328,3 +351,30 @@ class UnexpectedSections(IntegrationTestCase):
     def test(self):
         self.run_dox2html5(wildcard='File_8h.xml')
         self.assertEqual(*self.actual_expected_contents('File_8h.html'))
+
+class Dot(IntegrationTestCase):
+    def __init__(self, *args, **kwargs):
+        super().__init__(__file__, 'dot', *args, **kwargs)
+
+    @unittest.skipUnless(LooseVersion(dot_version()) >= LooseVersion("2.40.1"),
+                         "Dot < 2.40.1 has a completely different output.")
+    def test(self):
+        self.run_dox2html5(wildcard='indexpage.xml')
+        self.assertEqual(*self.actual_expected_contents('index.html'))
+
+    @unittest.skipUnless(LooseVersion(dot_version()) < LooseVersion("2.40.1") and
+                         LooseVersion(dot_version()) >= LooseVersion("2.38.0"),
+                         "Dot < 2.38 and dot > 2.38 has a completely different output.")
+    def test_238(self):
+        self.run_dox2html5(wildcard='indexpage.xml')
+        self.assertEqual(*self.actual_expected_contents('index.html', 'index-238.html'))
+
+    @unittest.skipUnless(LooseVersion(dot_version()) < LooseVersion("2.38.0"),
+                         "Dot > 2.36 has a completely different output.")
+    def test_236(self):
+        self.run_dox2html5(wildcard='indexpage.xml')
+        self.assertEqual(*self.actual_expected_contents('index.html', 'index-236.html'))
+
+    def test_warnings(self):
+        self.run_dox2html5(wildcard='warnings.xml')
+        self.assertEqual(*self.actual_expected_contents('warnings.html'))
index a86a03112967177320f8ba432840f514cf6fd947..36c33eb1d4ec2bba7520c8d8d820998654e10c2d 100644 (file)
@@ -42,6 +42,8 @@ class Doxyfile(unittest.TestCase):
         state = State()
         parse_doxyfile(state, 'test/doxyfile/Doxyfile')
         self.assertEqual(state.doxyfile, {
+            'DOT_FONTNAME': 'Helvetica',
+            'DOT_FONTSIZE': 10,
             'HTML_EXTRA_FILES': ['css', 'another.png', 'hello'],
             'HTML_EXTRA_STYLESHEET': ['a.css', 'b.css'],
             'HTML_OUTPUT': 'html',
index 5c224bd89a4f5aa77ca8fd051115b27435e18a0e..bece7ad9d95cba60f32f1d617ed33f434d1f84ed 100644 (file)
@@ -34,6 +34,8 @@ _patch_src = re.compile(r"""<\?xml version="1\.0" encoding="UTF-8" standalone="n
 
 _patch_dst = r"""<svg{attribs} style="width: {width:.3f}rem; height: {height:.3f}rem;" viewBox="{viewBox}">
 <g """
+_patch_custom_size_dst = r"""<svg{attribs} style="{size}" viewBox="\g<viewBox>">
+<g """
 
 _comment_src = re.compile(r"""<!--[^-]+-->\n""")
 
@@ -63,7 +65,7 @@ _font_size = 0.0
 # converting to rem here
 def _pt2em(pt): return pt/_font_size
 
-def dot2svg(source, attribs=''):
+def dot2svg(source, size=None, attribs=''):
     try:
         ret = subprocess.run(['dot', '-Tsvg',
             '-Gfontname={}'.format(_font),
@@ -83,12 +85,15 @@ def dot2svg(source, attribs=''):
     svg = _comment_src.sub('', ret.stdout.decode('utf-8'))
 
     # Remove preamble and fixed size
-    def patch_repl(match): return _patch_dst.format(
-        attribs=attribs,
-        width=_pt2em(float(match.group('width'))),
-        height=_pt2em(float(match.group('height'))),
-        viewBox=match.group('viewBox'))
-    svg = _patch_src.sub(patch_repl, svg)
+    if size:
+        svg = _patch_src.sub(_patch_custom_size_dst.format(attribs=attribs, size=size), svg)
+    else:
+        def patch_repl(match): return _patch_dst.format(
+            attribs=attribs,
+            width=_pt2em(float(match.group('width'))),
+            height=_pt2em(float(match.group('height'))),
+            viewBox=match.group('viewBox'))
+        svg = _patch_src.sub(patch_repl, svg)
 
     # Remove unnecessary IDs and attributes, replace classes for elements
     def element_repl(match):