|
@@ -0,0 +1,551 @@
|
|
|
+# -*- coding: utf-8; mode: python -*-
|
|
|
+# pylint: disable=C0103, R0903, R0912, R0915
|
|
|
+u"""
|
|
|
+ scalable figure and image handling
|
|
|
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
+
|
|
|
+ Sphinx extension which implements scalable image handling.
|
|
|
+
|
|
|
+ :copyright: Copyright (C) 2016 Markus Heiser
|
|
|
+ :license: GPL Version 2, June 1991 see Linux/COPYING for details.
|
|
|
+
|
|
|
+ The build for image formats depend on image's source format and output's
|
|
|
+ destination format. This extension implement methods to simplify image
|
|
|
+ handling from the author's POV. Directives like ``kernel-figure`` implement
|
|
|
+ methods *to* always get the best output-format even if some tools are not
|
|
|
+ installed. For more details take a look at ``convert_image(...)`` which is
|
|
|
+ the core of all conversions.
|
|
|
+
|
|
|
+ * ``.. kernel-image``: for image handling / a ``.. image::`` replacement
|
|
|
+
|
|
|
+ * ``.. kernel-figure``: for figure handling / a ``.. figure::`` replacement
|
|
|
+
|
|
|
+ * ``.. kernel-render``: for render markup / a concept to embed *render*
|
|
|
+ markups (or languages). Supported markups (see ``RENDER_MARKUP_EXT``)
|
|
|
+
|
|
|
+ - ``DOT``: render embedded Graphviz's **DOC**
|
|
|
+ - ``SVG``: render embedded Scalable Vector Graphics (**SVG**)
|
|
|
+ - ... *developable*
|
|
|
+
|
|
|
+ Used tools:
|
|
|
+
|
|
|
+ * ``dot(1)``: Graphviz (http://www.graphviz.org). If Graphviz is not
|
|
|
+ available, the DOT language is inserted as literal-block.
|
|
|
+
|
|
|
+ * SVG to PDF: To generate PDF, you need at least one of this tools:
|
|
|
+
|
|
|
+ - ``convert(1)``: ImageMagick (https://www.imagemagick.org)
|
|
|
+
|
|
|
+ List of customizations:
|
|
|
+
|
|
|
+ * generate PDF from SVG / used by PDF (LaTeX) builder
|
|
|
+
|
|
|
+ * generate SVG (html-builder) and PDF (latex-builder) from DOT files.
|
|
|
+ DOT: see http://www.graphviz.org/content/dot-language
|
|
|
+
|
|
|
+ """
|
|
|
+
|
|
|
+import os
|
|
|
+from os import path
|
|
|
+import subprocess
|
|
|
+from hashlib import sha1
|
|
|
+import sys
|
|
|
+
|
|
|
+from docutils import nodes
|
|
|
+from docutils.statemachine import ViewList
|
|
|
+from docutils.parsers.rst import directives
|
|
|
+from docutils.parsers.rst.directives import images
|
|
|
+import sphinx
|
|
|
+
|
|
|
+from sphinx.util.nodes import clean_astext
|
|
|
+from six import iteritems
|
|
|
+
|
|
|
+PY3 = sys.version_info[0] == 3
|
|
|
+
|
|
|
+if PY3:
|
|
|
+ _unicode = str
|
|
|
+else:
|
|
|
+ _unicode = unicode
|
|
|
+
|
|
|
+# Get Sphinx version
|
|
|
+major, minor, patch = sphinx.version_info[:3]
|
|
|
+if major == 1 and minor > 3:
|
|
|
+ # patches.Figure only landed in Sphinx 1.4
|
|
|
+ from sphinx.directives.patches import Figure # pylint: disable=C0413
|
|
|
+else:
|
|
|
+ Figure = images.Figure
|
|
|
+
|
|
|
+__version__ = '1.0.0'
|
|
|
+
|
|
|
+# simple helper
|
|
|
+# -------------
|
|
|
+
|
|
|
+def which(cmd):
|
|
|
+ """Searches the ``cmd`` in the ``PATH`` enviroment.
|
|
|
+
|
|
|
+ This *which* searches the PATH for executable ``cmd`` . First match is
|
|
|
+ returned, if nothing is found, ``None` is returned.
|
|
|
+ """
|
|
|
+ envpath = os.environ.get('PATH', None) or os.defpath
|
|
|
+ for folder in envpath.split(os.pathsep):
|
|
|
+ fname = folder + os.sep + cmd
|
|
|
+ if path.isfile(fname):
|
|
|
+ return fname
|
|
|
+
|
|
|
+def mkdir(folder, mode=0o775):
|
|
|
+ if not path.isdir(folder):
|
|
|
+ os.makedirs(folder, mode)
|
|
|
+
|
|
|
+def file2literal(fname):
|
|
|
+ with open(fname, "r") as src:
|
|
|
+ data = src.read()
|
|
|
+ node = nodes.literal_block(data, data)
|
|
|
+ return node
|
|
|
+
|
|
|
+def isNewer(path1, path2):
|
|
|
+ """Returns True if ``path1`` is newer than ``path2``
|
|
|
+
|
|
|
+ If ``path1`` exists and is newer than ``path2`` the function returns
|
|
|
+ ``True`` is returned otherwise ``False``
|
|
|
+ """
|
|
|
+ return (path.exists(path1)
|
|
|
+ and os.stat(path1).st_ctime > os.stat(path2).st_ctime)
|
|
|
+
|
|
|
+def pass_handle(self, node): # pylint: disable=W0613
|
|
|
+ pass
|
|
|
+
|
|
|
+# setup conversion tools and sphinx extension
|
|
|
+# -------------------------------------------
|
|
|
+
|
|
|
+# Graphviz's dot(1) support
|
|
|
+dot_cmd = None
|
|
|
+
|
|
|
+# ImageMagick' convert(1) support
|
|
|
+convert_cmd = None
|
|
|
+
|
|
|
+
|
|
|
+def setup(app):
|
|
|
+ # check toolchain first
|
|
|
+ app.connect('builder-inited', setupTools)
|
|
|
+
|
|
|
+ # image handling
|
|
|
+ app.add_directive("kernel-image", KernelImage)
|
|
|
+ app.add_node(kernel_image,
|
|
|
+ html = (visit_kernel_image, pass_handle),
|
|
|
+ latex = (visit_kernel_image, pass_handle),
|
|
|
+ texinfo = (visit_kernel_image, pass_handle),
|
|
|
+ text = (visit_kernel_image, pass_handle),
|
|
|
+ man = (visit_kernel_image, pass_handle), )
|
|
|
+
|
|
|
+ # figure handling
|
|
|
+ app.add_directive("kernel-figure", KernelFigure)
|
|
|
+ app.add_node(kernel_figure,
|
|
|
+ html = (visit_kernel_figure, pass_handle),
|
|
|
+ latex = (visit_kernel_figure, pass_handle),
|
|
|
+ texinfo = (visit_kernel_figure, pass_handle),
|
|
|
+ text = (visit_kernel_figure, pass_handle),
|
|
|
+ man = (visit_kernel_figure, pass_handle), )
|
|
|
+
|
|
|
+ # render handling
|
|
|
+ app.add_directive('kernel-render', KernelRender)
|
|
|
+ app.add_node(kernel_render,
|
|
|
+ html = (visit_kernel_render, pass_handle),
|
|
|
+ latex = (visit_kernel_render, pass_handle),
|
|
|
+ texinfo = (visit_kernel_render, pass_handle),
|
|
|
+ text = (visit_kernel_render, pass_handle),
|
|
|
+ man = (visit_kernel_render, pass_handle), )
|
|
|
+
|
|
|
+ app.connect('doctree-read', add_kernel_figure_to_std_domain)
|
|
|
+
|
|
|
+ return dict(
|
|
|
+ version = __version__,
|
|
|
+ parallel_read_safe = True,
|
|
|
+ parallel_write_safe = True
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def setupTools(app):
|
|
|
+ u"""
|
|
|
+ Check available build tools and log some *verbose* messages.
|
|
|
+
|
|
|
+ This function is called once, when the builder is initiated.
|
|
|
+ """
|
|
|
+ global dot_cmd, convert_cmd # pylint: disable=W0603
|
|
|
+ app.verbose("kfigure: check installed tools ...")
|
|
|
+
|
|
|
+ dot_cmd = which('dot')
|
|
|
+ convert_cmd = which('convert')
|
|
|
+
|
|
|
+ if dot_cmd:
|
|
|
+ app.verbose("use dot(1) from: " + dot_cmd)
|
|
|
+ else:
|
|
|
+ app.warn("dot(1) not found, for better output quality install "
|
|
|
+ "graphviz from http://www.graphviz.org")
|
|
|
+ if convert_cmd:
|
|
|
+ app.verbose("use convert(1) from: " + convert_cmd)
|
|
|
+ else:
|
|
|
+ app.warn(
|
|
|
+ "convert(1) not found, for SVG to PDF conversion install "
|
|
|
+ "ImageMagick (https://www.imagemagick.org)")
|
|
|
+
|
|
|
+
|
|
|
+# integrate conversion tools
|
|
|
+# --------------------------
|
|
|
+
|
|
|
+RENDER_MARKUP_EXT = {
|
|
|
+ # The '.ext' must be handled by convert_image(..) function's *in_ext* input.
|
|
|
+ # <name> : <.ext>
|
|
|
+ 'DOT' : '.dot',
|
|
|
+ 'SVG' : '.svg'
|
|
|
+}
|
|
|
+
|
|
|
+def convert_image(img_node, translator, src_fname=None):
|
|
|
+ """Convert a image node for the builder.
|
|
|
+
|
|
|
+ Different builder prefer different image formats, e.g. *latex* builder
|
|
|
+ prefer PDF while *html* builder prefer SVG format for images.
|
|
|
+
|
|
|
+ This function handles output image formats in dependence of source the
|
|
|
+ format (of the image) and the translator's output format.
|
|
|
+ """
|
|
|
+ app = translator.builder.app
|
|
|
+
|
|
|
+ fname, in_ext = path.splitext(path.basename(img_node['uri']))
|
|
|
+ if src_fname is None:
|
|
|
+ src_fname = path.join(translator.builder.srcdir, img_node['uri'])
|
|
|
+ if not path.exists(src_fname):
|
|
|
+ src_fname = path.join(translator.builder.outdir, img_node['uri'])
|
|
|
+
|
|
|
+ dst_fname = None
|
|
|
+
|
|
|
+ # in kernel builds, use 'make SPHINXOPTS=-v' to see verbose messages
|
|
|
+
|
|
|
+ app.verbose('assert best format for: ' + img_node['uri'])
|
|
|
+
|
|
|
+ if in_ext == '.dot':
|
|
|
+
|
|
|
+ if not dot_cmd:
|
|
|
+ app.verbose("dot from graphviz not available / include DOT raw.")
|
|
|
+ img_node.replace_self(file2literal(src_fname))
|
|
|
+
|
|
|
+ elif translator.builder.format == 'latex':
|
|
|
+ dst_fname = path.join(translator.builder.outdir, fname + '.pdf')
|
|
|
+ img_node['uri'] = fname + '.pdf'
|
|
|
+ img_node['candidates'] = {'*': fname + '.pdf'}
|
|
|
+
|
|
|
+
|
|
|
+ elif translator.builder.format == 'html':
|
|
|
+ dst_fname = path.join(
|
|
|
+ translator.builder.outdir,
|
|
|
+ translator.builder.imagedir,
|
|
|
+ fname + '.svg')
|
|
|
+ img_node['uri'] = path.join(
|
|
|
+ translator.builder.imgpath, fname + '.svg')
|
|
|
+ img_node['candidates'] = {
|
|
|
+ '*': path.join(translator.builder.imgpath, fname + '.svg')}
|
|
|
+
|
|
|
+ else:
|
|
|
+ # all other builder formats will include DOT as raw
|
|
|
+ img_node.replace_self(file2literal(src_fname))
|
|
|
+
|
|
|
+ elif in_ext == '.svg':
|
|
|
+
|
|
|
+ if translator.builder.format == 'latex':
|
|
|
+ if convert_cmd is None:
|
|
|
+ app.verbose("no SVG to PDF conversion available / include SVG raw.")
|
|
|
+ img_node.replace_self(file2literal(src_fname))
|
|
|
+ else:
|
|
|
+ dst_fname = path.join(translator.builder.outdir, fname + '.pdf')
|
|
|
+ img_node['uri'] = fname + '.pdf'
|
|
|
+ img_node['candidates'] = {'*': fname + '.pdf'}
|
|
|
+
|
|
|
+ if dst_fname:
|
|
|
+ # the builder needs not to copy one more time, so pop it if exists.
|
|
|
+ translator.builder.images.pop(img_node['uri'], None)
|
|
|
+ _name = dst_fname[len(translator.builder.outdir) + 1:]
|
|
|
+
|
|
|
+ if isNewer(dst_fname, src_fname):
|
|
|
+ app.verbose("convert: {out}/%s already exists and is newer" % _name)
|
|
|
+
|
|
|
+ else:
|
|
|
+ ok = False
|
|
|
+ mkdir(path.dirname(dst_fname))
|
|
|
+
|
|
|
+ if in_ext == '.dot':
|
|
|
+ app.verbose('convert DOT to: {out}/' + _name)
|
|
|
+ ok = dot2format(app, src_fname, dst_fname)
|
|
|
+
|
|
|
+ elif in_ext == '.svg':
|
|
|
+ app.verbose('convert SVG to: {out}/' + _name)
|
|
|
+ ok = svg2pdf(app, src_fname, dst_fname)
|
|
|
+
|
|
|
+ if not ok:
|
|
|
+ img_node.replace_self(file2literal(src_fname))
|
|
|
+
|
|
|
+
|
|
|
+def dot2format(app, dot_fname, out_fname):
|
|
|
+ """Converts DOT file to ``out_fname`` using ``dot(1)``.
|
|
|
+
|
|
|
+ * ``dot_fname`` pathname of the input DOT file, including extension ``.dot``
|
|
|
+ * ``out_fname`` pathname of the output file, including format extension
|
|
|
+
|
|
|
+ The *format extension* depends on the ``dot`` command (see ``man dot``
|
|
|
+ option ``-Txxx``). Normally you will use one of the following extensions:
|
|
|
+
|
|
|
+ - ``.ps`` for PostScript,
|
|
|
+ - ``.svg`` or ``svgz`` for Structured Vector Graphics,
|
|
|
+ - ``.fig`` for XFIG graphics and
|
|
|
+ - ``.png`` or ``gif`` for common bitmap graphics.
|
|
|
+
|
|
|
+ """
|
|
|
+ out_format = path.splitext(out_fname)[1][1:]
|
|
|
+ cmd = [dot_cmd, '-T%s' % out_format, dot_fname]
|
|
|
+ exit_code = 42
|
|
|
+
|
|
|
+ with open(out_fname, "w") as out:
|
|
|
+ exit_code = subprocess.call(cmd, stdout = out)
|
|
|
+ if exit_code != 0:
|
|
|
+ app.warn("Error #%d when calling: %s" % (exit_code, " ".join(cmd)))
|
|
|
+ return bool(exit_code == 0)
|
|
|
+
|
|
|
+def svg2pdf(app, svg_fname, pdf_fname):
|
|
|
+ """Converts SVG to PDF with ``convert(1)`` command.
|
|
|
+
|
|
|
+ Uses ``convert(1)`` from ImageMagick (https://www.imagemagick.org) for
|
|
|
+ conversion. Returns ``True`` on success and ``False`` if an error occurred.
|
|
|
+
|
|
|
+ * ``svg_fname`` pathname of the input SVG file with extension (``.svg``)
|
|
|
+ * ``pdf_name`` pathname of the output PDF file with extension (``.pdf``)
|
|
|
+
|
|
|
+ """
|
|
|
+ cmd = [convert_cmd, svg_fname, pdf_fname]
|
|
|
+ # use stdout and stderr from parent
|
|
|
+ exit_code = subprocess.call(cmd)
|
|
|
+ if exit_code != 0:
|
|
|
+ app.warn("Error #%d when calling: %s" % (exit_code, " ".join(cmd)))
|
|
|
+ return bool(exit_code == 0)
|
|
|
+
|
|
|
+
|
|
|
+# image handling
|
|
|
+# ---------------------
|
|
|
+
|
|
|
+def visit_kernel_image(self, node): # pylint: disable=W0613
|
|
|
+ """Visitor of the ``kernel_image`` Node.
|
|
|
+
|
|
|
+ Handles the ``image`` child-node with the ``convert_image(...)``.
|
|
|
+ """
|
|
|
+ img_node = node[0]
|
|
|
+ convert_image(img_node, self)
|
|
|
+
|
|
|
+class kernel_image(nodes.image):
|
|
|
+ """Node for ``kernel-image`` directive."""
|
|
|
+ pass
|
|
|
+
|
|
|
+class KernelImage(images.Image):
|
|
|
+ u"""KernelImage directive
|
|
|
+
|
|
|
+ Earns everything from ``.. image::`` directive, except *remote URI* and
|
|
|
+ *glob* pattern. The KernelImage wraps a image node into a
|
|
|
+ kernel_image node. See ``visit_kernel_image``.
|
|
|
+ """
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ uri = self.arguments[0]
|
|
|
+ if uri.endswith('.*') or uri.find('://') != -1:
|
|
|
+ raise self.severe(
|
|
|
+ 'Error in "%s: %s": glob pattern and remote images are not allowed'
|
|
|
+ % (self.name, uri))
|
|
|
+ result = images.Image.run(self)
|
|
|
+ if len(result) == 2 or isinstance(result[0], nodes.system_message):
|
|
|
+ return result
|
|
|
+ (image_node,) = result
|
|
|
+ # wrap image node into a kernel_image node / see visitors
|
|
|
+ node = kernel_image('', image_node)
|
|
|
+ return [node]
|
|
|
+
|
|
|
+# figure handling
|
|
|
+# ---------------------
|
|
|
+
|
|
|
+def visit_kernel_figure(self, node): # pylint: disable=W0613
|
|
|
+ """Visitor of the ``kernel_figure`` Node.
|
|
|
+
|
|
|
+ Handles the ``image`` child-node with the ``convert_image(...)``.
|
|
|
+ """
|
|
|
+ img_node = node[0][0]
|
|
|
+ convert_image(img_node, self)
|
|
|
+
|
|
|
+class kernel_figure(nodes.figure):
|
|
|
+ """Node for ``kernel-figure`` directive."""
|
|
|
+
|
|
|
+class KernelFigure(Figure):
|
|
|
+ u"""KernelImage directive
|
|
|
+
|
|
|
+ Earns everything from ``.. figure::`` directive, except *remote URI* and
|
|
|
+ *glob* pattern. The KernelFigure wraps a figure node into a kernel_figure
|
|
|
+ node. See ``visit_kernel_figure``.
|
|
|
+ """
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ uri = self.arguments[0]
|
|
|
+ if uri.endswith('.*') or uri.find('://') != -1:
|
|
|
+ raise self.severe(
|
|
|
+ 'Error in "%s: %s":'
|
|
|
+ ' glob pattern and remote images are not allowed'
|
|
|
+ % (self.name, uri))
|
|
|
+ result = Figure.run(self)
|
|
|
+ if len(result) == 2 or isinstance(result[0], nodes.system_message):
|
|
|
+ return result
|
|
|
+ (figure_node,) = result
|
|
|
+ # wrap figure node into a kernel_figure node / see visitors
|
|
|
+ node = kernel_figure('', figure_node)
|
|
|
+ return [node]
|
|
|
+
|
|
|
+
|
|
|
+# render handling
|
|
|
+# ---------------------
|
|
|
+
|
|
|
+def visit_kernel_render(self, node):
|
|
|
+ """Visitor of the ``kernel_render`` Node.
|
|
|
+
|
|
|
+ If rendering tools available, save the markup of the ``literal_block`` child
|
|
|
+ node into a file and replace the ``literal_block`` node with a new created
|
|
|
+ ``image`` node, pointing to the saved markup file. Afterwards, handle the
|
|
|
+ image child-node with the ``convert_image(...)``.
|
|
|
+ """
|
|
|
+ app = self.builder.app
|
|
|
+ srclang = node.get('srclang')
|
|
|
+
|
|
|
+ app.verbose('visit kernel-render node lang: "%s"' % (srclang))
|
|
|
+
|
|
|
+ tmp_ext = RENDER_MARKUP_EXT.get(srclang, None)
|
|
|
+ if tmp_ext is None:
|
|
|
+ app.warn('kernel-render: "%s" unknow / include raw.' % (srclang))
|
|
|
+ return
|
|
|
+
|
|
|
+ if not dot_cmd and tmp_ext == '.dot':
|
|
|
+ app.verbose("dot from graphviz not available / include raw.")
|
|
|
+ return
|
|
|
+
|
|
|
+ literal_block = node[0]
|
|
|
+
|
|
|
+ code = literal_block.astext()
|
|
|
+ hashobj = code.encode('utf-8') # str(node.attributes)
|
|
|
+ fname = path.join('%s-%s' % (srclang, sha1(hashobj).hexdigest()))
|
|
|
+
|
|
|
+ tmp_fname = path.join(
|
|
|
+ self.builder.outdir, self.builder.imagedir, fname + tmp_ext)
|
|
|
+
|
|
|
+ if not path.isfile(tmp_fname):
|
|
|
+ mkdir(path.dirname(tmp_fname))
|
|
|
+ with open(tmp_fname, "w") as out:
|
|
|
+ out.write(code)
|
|
|
+
|
|
|
+ img_node = nodes.image(node.rawsource, **node.attributes)
|
|
|
+ img_node['uri'] = path.join(self.builder.imgpath, fname + tmp_ext)
|
|
|
+ img_node['candidates'] = {
|
|
|
+ '*': path.join(self.builder.imgpath, fname + tmp_ext)}
|
|
|
+
|
|
|
+ literal_block.replace_self(img_node)
|
|
|
+ convert_image(img_node, self, tmp_fname)
|
|
|
+
|
|
|
+
|
|
|
+class kernel_render(nodes.General, nodes.Inline, nodes.Element):
|
|
|
+ """Node for ``kernel-render`` directive."""
|
|
|
+ pass
|
|
|
+
|
|
|
+class KernelRender(Figure):
|
|
|
+ u"""KernelRender directive
|
|
|
+
|
|
|
+ Render content by external tool. Has all the options known from the
|
|
|
+ *figure* directive, plus option ``caption``. If ``caption`` has a
|
|
|
+ value, a figure node with the *caption* is inserted. If not, a image node is
|
|
|
+ inserted.
|
|
|
+
|
|
|
+ The KernelRender directive wraps the text of the directive into a
|
|
|
+ literal_block node and wraps it into a kernel_render node. See
|
|
|
+ ``visit_kernel_render``.
|
|
|
+ """
|
|
|
+ has_content = True
|
|
|
+ required_arguments = 1
|
|
|
+ optional_arguments = 0
|
|
|
+ final_argument_whitespace = False
|
|
|
+
|
|
|
+ # earn options from 'figure'
|
|
|
+ option_spec = Figure.option_spec.copy()
|
|
|
+ option_spec['caption'] = directives.unchanged
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ return [self.build_node()]
|
|
|
+
|
|
|
+ def build_node(self):
|
|
|
+
|
|
|
+ srclang = self.arguments[0].strip()
|
|
|
+ if srclang not in RENDER_MARKUP_EXT.keys():
|
|
|
+ return [self.state_machine.reporter.warning(
|
|
|
+ 'Unknow source language "%s", use one of: %s.' % (
|
|
|
+ srclang, ",".join(RENDER_MARKUP_EXT.keys())),
|
|
|
+ line=self.lineno)]
|
|
|
+
|
|
|
+ code = '\n'.join(self.content)
|
|
|
+ if not code.strip():
|
|
|
+ return [self.state_machine.reporter.warning(
|
|
|
+ 'Ignoring "%s" directive without content.' % (
|
|
|
+ self.name),
|
|
|
+ line=self.lineno)]
|
|
|
+
|
|
|
+ node = kernel_render()
|
|
|
+ node['alt'] = self.options.get('alt','')
|
|
|
+ node['srclang'] = srclang
|
|
|
+ literal_node = nodes.literal_block(code, code)
|
|
|
+ node += literal_node
|
|
|
+
|
|
|
+ caption = self.options.get('caption')
|
|
|
+ if caption:
|
|
|
+ # parse caption's content
|
|
|
+ parsed = nodes.Element()
|
|
|
+ self.state.nested_parse(
|
|
|
+ ViewList([caption], source=''), self.content_offset, parsed)
|
|
|
+ caption_node = nodes.caption(
|
|
|
+ parsed[0].rawsource, '', *parsed[0].children)
|
|
|
+ caption_node.source = parsed[0].source
|
|
|
+ caption_node.line = parsed[0].line
|
|
|
+
|
|
|
+ figure_node = nodes.figure('', node)
|
|
|
+ for k,v in self.options.items():
|
|
|
+ figure_node[k] = v
|
|
|
+ figure_node += caption_node
|
|
|
+
|
|
|
+ node = figure_node
|
|
|
+
|
|
|
+ return node
|
|
|
+
|
|
|
+def add_kernel_figure_to_std_domain(app, doctree):
|
|
|
+ """Add kernel-figure anchors to 'std' domain.
|
|
|
+
|
|
|
+ The ``StandardDomain.process_doc(..)`` method does not know how to resolve
|
|
|
+ the caption (label) of ``kernel-figure`` directive (it only knows about
|
|
|
+ standard nodes, e.g. table, figure etc.). Without any additional handling
|
|
|
+ this will result in a 'undefined label' for kernel-figures.
|
|
|
+
|
|
|
+ This handle adds labels of kernel-figure to the 'std' domain labels.
|
|
|
+ """
|
|
|
+
|
|
|
+ std = app.env.domains["std"]
|
|
|
+ docname = app.env.docname
|
|
|
+ labels = std.data["labels"]
|
|
|
+
|
|
|
+ for name, explicit in iteritems(doctree.nametypes):
|
|
|
+ if not explicit:
|
|
|
+ continue
|
|
|
+ labelid = doctree.nameids[name]
|
|
|
+ if labelid is None:
|
|
|
+ continue
|
|
|
+ node = doctree.ids[labelid]
|
|
|
+
|
|
|
+ if node.tagname == 'kernel_figure':
|
|
|
+ for n in node.next_node():
|
|
|
+ if n.tagname == 'caption':
|
|
|
+ sectname = clean_astext(n)
|
|
|
+ # add label to std domain
|
|
|
+ labels[name] = docname, labelid, sectname
|
|
|
+ break
|