1*4882a593Smuzhiyun# -*- coding: utf-8; mode: python -*- 2*4882a593Smuzhiyun# pylint: disable=C0103, R0903, R0912, R0915 3*4882a593Smuzhiyunu""" 4*4882a593Smuzhiyun scalable figure and image handling 5*4882a593Smuzhiyun ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6*4882a593Smuzhiyun 7*4882a593Smuzhiyun Sphinx extension which implements scalable image handling. 8*4882a593Smuzhiyun 9*4882a593Smuzhiyun :copyright: Copyright (C) 2016 Markus Heiser 10*4882a593Smuzhiyun :license: GPL Version 2, June 1991 see Linux/COPYING for details. 11*4882a593Smuzhiyun 12*4882a593Smuzhiyun The build for image formats depend on image's source format and output's 13*4882a593Smuzhiyun destination format. This extension implement methods to simplify image 14*4882a593Smuzhiyun handling from the author's POV. Directives like ``kernel-figure`` implement 15*4882a593Smuzhiyun methods *to* always get the best output-format even if some tools are not 16*4882a593Smuzhiyun installed. For more details take a look at ``convert_image(...)`` which is 17*4882a593Smuzhiyun the core of all conversions. 18*4882a593Smuzhiyun 19*4882a593Smuzhiyun * ``.. kernel-image``: for image handling / a ``.. image::`` replacement 20*4882a593Smuzhiyun 21*4882a593Smuzhiyun * ``.. kernel-figure``: for figure handling / a ``.. figure::`` replacement 22*4882a593Smuzhiyun 23*4882a593Smuzhiyun * ``.. kernel-render``: for render markup / a concept to embed *render* 24*4882a593Smuzhiyun markups (or languages). Supported markups (see ``RENDER_MARKUP_EXT``) 25*4882a593Smuzhiyun 26*4882a593Smuzhiyun - ``DOT``: render embedded Graphviz's **DOC** 27*4882a593Smuzhiyun - ``SVG``: render embedded Scalable Vector Graphics (**SVG**) 28*4882a593Smuzhiyun - ... *developable* 29*4882a593Smuzhiyun 30*4882a593Smuzhiyun Used tools: 31*4882a593Smuzhiyun 32*4882a593Smuzhiyun * ``dot(1)``: Graphviz (https://www.graphviz.org). If Graphviz is not 33*4882a593Smuzhiyun available, the DOT language is inserted as literal-block. 34*4882a593Smuzhiyun 35*4882a593Smuzhiyun * SVG to PDF: To generate PDF, you need at least one of this tools: 36*4882a593Smuzhiyun 37*4882a593Smuzhiyun - ``convert(1)``: ImageMagick (https://www.imagemagick.org) 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun List of customizations: 40*4882a593Smuzhiyun 41*4882a593Smuzhiyun * generate PDF from SVG / used by PDF (LaTeX) builder 42*4882a593Smuzhiyun 43*4882a593Smuzhiyun * generate SVG (html-builder) and PDF (latex-builder) from DOT files. 44*4882a593Smuzhiyun DOT: see https://www.graphviz.org/content/dot-language 45*4882a593Smuzhiyun 46*4882a593Smuzhiyun """ 47*4882a593Smuzhiyun 48*4882a593Smuzhiyunimport os 49*4882a593Smuzhiyunfrom os import path 50*4882a593Smuzhiyunimport subprocess 51*4882a593Smuzhiyunfrom hashlib import sha1 52*4882a593Smuzhiyunimport sys 53*4882a593Smuzhiyun 54*4882a593Smuzhiyunfrom docutils import nodes 55*4882a593Smuzhiyunfrom docutils.statemachine import ViewList 56*4882a593Smuzhiyunfrom docutils.parsers.rst import directives 57*4882a593Smuzhiyunfrom docutils.parsers.rst.directives import images 58*4882a593Smuzhiyunimport sphinx 59*4882a593Smuzhiyun 60*4882a593Smuzhiyunfrom sphinx.util.nodes import clean_astext 61*4882a593Smuzhiyunfrom six import iteritems 62*4882a593Smuzhiyun 63*4882a593Smuzhiyunimport kernellog 64*4882a593Smuzhiyun 65*4882a593SmuzhiyunPY3 = sys.version_info[0] == 3 66*4882a593Smuzhiyun 67*4882a593Smuzhiyunif PY3: 68*4882a593Smuzhiyun _unicode = str 69*4882a593Smuzhiyunelse: 70*4882a593Smuzhiyun _unicode = unicode 71*4882a593Smuzhiyun 72*4882a593Smuzhiyun# Get Sphinx version 73*4882a593Smuzhiyunmajor, minor, patch = sphinx.version_info[:3] 74*4882a593Smuzhiyunif major == 1 and minor > 3: 75*4882a593Smuzhiyun # patches.Figure only landed in Sphinx 1.4 76*4882a593Smuzhiyun from sphinx.directives.patches import Figure # pylint: disable=C0413 77*4882a593Smuzhiyunelse: 78*4882a593Smuzhiyun Figure = images.Figure 79*4882a593Smuzhiyun 80*4882a593Smuzhiyun__version__ = '1.0.0' 81*4882a593Smuzhiyun 82*4882a593Smuzhiyun# simple helper 83*4882a593Smuzhiyun# ------------- 84*4882a593Smuzhiyun 85*4882a593Smuzhiyundef which(cmd): 86*4882a593Smuzhiyun """Searches the ``cmd`` in the ``PATH`` environment. 87*4882a593Smuzhiyun 88*4882a593Smuzhiyun This *which* searches the PATH for executable ``cmd`` . First match is 89*4882a593Smuzhiyun returned, if nothing is found, ``None` is returned. 90*4882a593Smuzhiyun """ 91*4882a593Smuzhiyun envpath = os.environ.get('PATH', None) or os.defpath 92*4882a593Smuzhiyun for folder in envpath.split(os.pathsep): 93*4882a593Smuzhiyun fname = folder + os.sep + cmd 94*4882a593Smuzhiyun if path.isfile(fname): 95*4882a593Smuzhiyun return fname 96*4882a593Smuzhiyun 97*4882a593Smuzhiyundef mkdir(folder, mode=0o775): 98*4882a593Smuzhiyun if not path.isdir(folder): 99*4882a593Smuzhiyun os.makedirs(folder, mode) 100*4882a593Smuzhiyun 101*4882a593Smuzhiyundef file2literal(fname): 102*4882a593Smuzhiyun with open(fname, "r") as src: 103*4882a593Smuzhiyun data = src.read() 104*4882a593Smuzhiyun node = nodes.literal_block(data, data) 105*4882a593Smuzhiyun return node 106*4882a593Smuzhiyun 107*4882a593Smuzhiyundef isNewer(path1, path2): 108*4882a593Smuzhiyun """Returns True if ``path1`` is newer than ``path2`` 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun If ``path1`` exists and is newer than ``path2`` the function returns 111*4882a593Smuzhiyun ``True`` is returned otherwise ``False`` 112*4882a593Smuzhiyun """ 113*4882a593Smuzhiyun return (path.exists(path1) 114*4882a593Smuzhiyun and os.stat(path1).st_ctime > os.stat(path2).st_ctime) 115*4882a593Smuzhiyun 116*4882a593Smuzhiyundef pass_handle(self, node): # pylint: disable=W0613 117*4882a593Smuzhiyun pass 118*4882a593Smuzhiyun 119*4882a593Smuzhiyun# setup conversion tools and sphinx extension 120*4882a593Smuzhiyun# ------------------------------------------- 121*4882a593Smuzhiyun 122*4882a593Smuzhiyun# Graphviz's dot(1) support 123*4882a593Smuzhiyundot_cmd = None 124*4882a593Smuzhiyun 125*4882a593Smuzhiyun# ImageMagick' convert(1) support 126*4882a593Smuzhiyunconvert_cmd = None 127*4882a593Smuzhiyun 128*4882a593Smuzhiyun 129*4882a593Smuzhiyundef setup(app): 130*4882a593Smuzhiyun # check toolchain first 131*4882a593Smuzhiyun app.connect('builder-inited', setupTools) 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun # image handling 134*4882a593Smuzhiyun app.add_directive("kernel-image", KernelImage) 135*4882a593Smuzhiyun app.add_node(kernel_image, 136*4882a593Smuzhiyun html = (visit_kernel_image, pass_handle), 137*4882a593Smuzhiyun latex = (visit_kernel_image, pass_handle), 138*4882a593Smuzhiyun texinfo = (visit_kernel_image, pass_handle), 139*4882a593Smuzhiyun text = (visit_kernel_image, pass_handle), 140*4882a593Smuzhiyun man = (visit_kernel_image, pass_handle), ) 141*4882a593Smuzhiyun 142*4882a593Smuzhiyun # figure handling 143*4882a593Smuzhiyun app.add_directive("kernel-figure", KernelFigure) 144*4882a593Smuzhiyun app.add_node(kernel_figure, 145*4882a593Smuzhiyun html = (visit_kernel_figure, pass_handle), 146*4882a593Smuzhiyun latex = (visit_kernel_figure, pass_handle), 147*4882a593Smuzhiyun texinfo = (visit_kernel_figure, pass_handle), 148*4882a593Smuzhiyun text = (visit_kernel_figure, pass_handle), 149*4882a593Smuzhiyun man = (visit_kernel_figure, pass_handle), ) 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun # render handling 152*4882a593Smuzhiyun app.add_directive('kernel-render', KernelRender) 153*4882a593Smuzhiyun app.add_node(kernel_render, 154*4882a593Smuzhiyun html = (visit_kernel_render, pass_handle), 155*4882a593Smuzhiyun latex = (visit_kernel_render, pass_handle), 156*4882a593Smuzhiyun texinfo = (visit_kernel_render, pass_handle), 157*4882a593Smuzhiyun text = (visit_kernel_render, pass_handle), 158*4882a593Smuzhiyun man = (visit_kernel_render, pass_handle), ) 159*4882a593Smuzhiyun 160*4882a593Smuzhiyun app.connect('doctree-read', add_kernel_figure_to_std_domain) 161*4882a593Smuzhiyun 162*4882a593Smuzhiyun return dict( 163*4882a593Smuzhiyun version = __version__, 164*4882a593Smuzhiyun parallel_read_safe = True, 165*4882a593Smuzhiyun parallel_write_safe = True 166*4882a593Smuzhiyun ) 167*4882a593Smuzhiyun 168*4882a593Smuzhiyun 169*4882a593Smuzhiyundef setupTools(app): 170*4882a593Smuzhiyun u""" 171*4882a593Smuzhiyun Check available build tools and log some *verbose* messages. 172*4882a593Smuzhiyun 173*4882a593Smuzhiyun This function is called once, when the builder is initiated. 174*4882a593Smuzhiyun """ 175*4882a593Smuzhiyun global dot_cmd, convert_cmd # pylint: disable=W0603 176*4882a593Smuzhiyun kernellog.verbose(app, "kfigure: check installed tools ...") 177*4882a593Smuzhiyun 178*4882a593Smuzhiyun dot_cmd = which('dot') 179*4882a593Smuzhiyun convert_cmd = which('convert') 180*4882a593Smuzhiyun 181*4882a593Smuzhiyun if dot_cmd: 182*4882a593Smuzhiyun kernellog.verbose(app, "use dot(1) from: " + dot_cmd) 183*4882a593Smuzhiyun else: 184*4882a593Smuzhiyun kernellog.warn(app, "dot(1) not found, for better output quality install " 185*4882a593Smuzhiyun "graphviz from https://www.graphviz.org") 186*4882a593Smuzhiyun if convert_cmd: 187*4882a593Smuzhiyun kernellog.verbose(app, "use convert(1) from: " + convert_cmd) 188*4882a593Smuzhiyun else: 189*4882a593Smuzhiyun kernellog.warn(app, 190*4882a593Smuzhiyun "convert(1) not found, for SVG to PDF conversion install " 191*4882a593Smuzhiyun "ImageMagick (https://www.imagemagick.org)") 192*4882a593Smuzhiyun 193*4882a593Smuzhiyun 194*4882a593Smuzhiyun# integrate conversion tools 195*4882a593Smuzhiyun# -------------------------- 196*4882a593Smuzhiyun 197*4882a593SmuzhiyunRENDER_MARKUP_EXT = { 198*4882a593Smuzhiyun # The '.ext' must be handled by convert_image(..) function's *in_ext* input. 199*4882a593Smuzhiyun # <name> : <.ext> 200*4882a593Smuzhiyun 'DOT' : '.dot', 201*4882a593Smuzhiyun 'SVG' : '.svg' 202*4882a593Smuzhiyun} 203*4882a593Smuzhiyun 204*4882a593Smuzhiyundef convert_image(img_node, translator, src_fname=None): 205*4882a593Smuzhiyun """Convert a image node for the builder. 206*4882a593Smuzhiyun 207*4882a593Smuzhiyun Different builder prefer different image formats, e.g. *latex* builder 208*4882a593Smuzhiyun prefer PDF while *html* builder prefer SVG format for images. 209*4882a593Smuzhiyun 210*4882a593Smuzhiyun This function handles output image formats in dependence of source the 211*4882a593Smuzhiyun format (of the image) and the translator's output format. 212*4882a593Smuzhiyun """ 213*4882a593Smuzhiyun app = translator.builder.app 214*4882a593Smuzhiyun 215*4882a593Smuzhiyun fname, in_ext = path.splitext(path.basename(img_node['uri'])) 216*4882a593Smuzhiyun if src_fname is None: 217*4882a593Smuzhiyun src_fname = path.join(translator.builder.srcdir, img_node['uri']) 218*4882a593Smuzhiyun if not path.exists(src_fname): 219*4882a593Smuzhiyun src_fname = path.join(translator.builder.outdir, img_node['uri']) 220*4882a593Smuzhiyun 221*4882a593Smuzhiyun dst_fname = None 222*4882a593Smuzhiyun 223*4882a593Smuzhiyun # in kernel builds, use 'make SPHINXOPTS=-v' to see verbose messages 224*4882a593Smuzhiyun 225*4882a593Smuzhiyun kernellog.verbose(app, 'assert best format for: ' + img_node['uri']) 226*4882a593Smuzhiyun 227*4882a593Smuzhiyun if in_ext == '.dot': 228*4882a593Smuzhiyun 229*4882a593Smuzhiyun if not dot_cmd: 230*4882a593Smuzhiyun kernellog.verbose(app, 231*4882a593Smuzhiyun "dot from graphviz not available / include DOT raw.") 232*4882a593Smuzhiyun img_node.replace_self(file2literal(src_fname)) 233*4882a593Smuzhiyun 234*4882a593Smuzhiyun elif translator.builder.format == 'latex': 235*4882a593Smuzhiyun dst_fname = path.join(translator.builder.outdir, fname + '.pdf') 236*4882a593Smuzhiyun img_node['uri'] = fname + '.pdf' 237*4882a593Smuzhiyun img_node['candidates'] = {'*': fname + '.pdf'} 238*4882a593Smuzhiyun 239*4882a593Smuzhiyun 240*4882a593Smuzhiyun elif translator.builder.format == 'html': 241*4882a593Smuzhiyun dst_fname = path.join( 242*4882a593Smuzhiyun translator.builder.outdir, 243*4882a593Smuzhiyun translator.builder.imagedir, 244*4882a593Smuzhiyun fname + '.svg') 245*4882a593Smuzhiyun img_node['uri'] = path.join( 246*4882a593Smuzhiyun translator.builder.imgpath, fname + '.svg') 247*4882a593Smuzhiyun img_node['candidates'] = { 248*4882a593Smuzhiyun '*': path.join(translator.builder.imgpath, fname + '.svg')} 249*4882a593Smuzhiyun 250*4882a593Smuzhiyun else: 251*4882a593Smuzhiyun # all other builder formats will include DOT as raw 252*4882a593Smuzhiyun img_node.replace_self(file2literal(src_fname)) 253*4882a593Smuzhiyun 254*4882a593Smuzhiyun elif in_ext == '.svg': 255*4882a593Smuzhiyun 256*4882a593Smuzhiyun if translator.builder.format == 'latex': 257*4882a593Smuzhiyun if convert_cmd is None: 258*4882a593Smuzhiyun kernellog.verbose(app, 259*4882a593Smuzhiyun "no SVG to PDF conversion available / include SVG raw.") 260*4882a593Smuzhiyun img_node.replace_self(file2literal(src_fname)) 261*4882a593Smuzhiyun else: 262*4882a593Smuzhiyun dst_fname = path.join(translator.builder.outdir, fname + '.pdf') 263*4882a593Smuzhiyun img_node['uri'] = fname + '.pdf' 264*4882a593Smuzhiyun img_node['candidates'] = {'*': fname + '.pdf'} 265*4882a593Smuzhiyun 266*4882a593Smuzhiyun if dst_fname: 267*4882a593Smuzhiyun # the builder needs not to copy one more time, so pop it if exists. 268*4882a593Smuzhiyun translator.builder.images.pop(img_node['uri'], None) 269*4882a593Smuzhiyun _name = dst_fname[len(translator.builder.outdir) + 1:] 270*4882a593Smuzhiyun 271*4882a593Smuzhiyun if isNewer(dst_fname, src_fname): 272*4882a593Smuzhiyun kernellog.verbose(app, 273*4882a593Smuzhiyun "convert: {out}/%s already exists and is newer" % _name) 274*4882a593Smuzhiyun 275*4882a593Smuzhiyun else: 276*4882a593Smuzhiyun ok = False 277*4882a593Smuzhiyun mkdir(path.dirname(dst_fname)) 278*4882a593Smuzhiyun 279*4882a593Smuzhiyun if in_ext == '.dot': 280*4882a593Smuzhiyun kernellog.verbose(app, 'convert DOT to: {out}/' + _name) 281*4882a593Smuzhiyun ok = dot2format(app, src_fname, dst_fname) 282*4882a593Smuzhiyun 283*4882a593Smuzhiyun elif in_ext == '.svg': 284*4882a593Smuzhiyun kernellog.verbose(app, 'convert SVG to: {out}/' + _name) 285*4882a593Smuzhiyun ok = svg2pdf(app, src_fname, dst_fname) 286*4882a593Smuzhiyun 287*4882a593Smuzhiyun if not ok: 288*4882a593Smuzhiyun img_node.replace_self(file2literal(src_fname)) 289*4882a593Smuzhiyun 290*4882a593Smuzhiyun 291*4882a593Smuzhiyundef dot2format(app, dot_fname, out_fname): 292*4882a593Smuzhiyun """Converts DOT file to ``out_fname`` using ``dot(1)``. 293*4882a593Smuzhiyun 294*4882a593Smuzhiyun * ``dot_fname`` pathname of the input DOT file, including extension ``.dot`` 295*4882a593Smuzhiyun * ``out_fname`` pathname of the output file, including format extension 296*4882a593Smuzhiyun 297*4882a593Smuzhiyun The *format extension* depends on the ``dot`` command (see ``man dot`` 298*4882a593Smuzhiyun option ``-Txxx``). Normally you will use one of the following extensions: 299*4882a593Smuzhiyun 300*4882a593Smuzhiyun - ``.ps`` for PostScript, 301*4882a593Smuzhiyun - ``.svg`` or ``svgz`` for Structured Vector Graphics, 302*4882a593Smuzhiyun - ``.fig`` for XFIG graphics and 303*4882a593Smuzhiyun - ``.png`` or ``gif`` for common bitmap graphics. 304*4882a593Smuzhiyun 305*4882a593Smuzhiyun """ 306*4882a593Smuzhiyun out_format = path.splitext(out_fname)[1][1:] 307*4882a593Smuzhiyun cmd = [dot_cmd, '-T%s' % out_format, dot_fname] 308*4882a593Smuzhiyun exit_code = 42 309*4882a593Smuzhiyun 310*4882a593Smuzhiyun with open(out_fname, "w") as out: 311*4882a593Smuzhiyun exit_code = subprocess.call(cmd, stdout = out) 312*4882a593Smuzhiyun if exit_code != 0: 313*4882a593Smuzhiyun kernellog.warn(app, 314*4882a593Smuzhiyun "Error #%d when calling: %s" % (exit_code, " ".join(cmd))) 315*4882a593Smuzhiyun return bool(exit_code == 0) 316*4882a593Smuzhiyun 317*4882a593Smuzhiyundef svg2pdf(app, svg_fname, pdf_fname): 318*4882a593Smuzhiyun """Converts SVG to PDF with ``convert(1)`` command. 319*4882a593Smuzhiyun 320*4882a593Smuzhiyun Uses ``convert(1)`` from ImageMagick (https://www.imagemagick.org) for 321*4882a593Smuzhiyun conversion. Returns ``True`` on success and ``False`` if an error occurred. 322*4882a593Smuzhiyun 323*4882a593Smuzhiyun * ``svg_fname`` pathname of the input SVG file with extension (``.svg``) 324*4882a593Smuzhiyun * ``pdf_name`` pathname of the output PDF file with extension (``.pdf``) 325*4882a593Smuzhiyun 326*4882a593Smuzhiyun """ 327*4882a593Smuzhiyun cmd = [convert_cmd, svg_fname, pdf_fname] 328*4882a593Smuzhiyun # use stdout and stderr from parent 329*4882a593Smuzhiyun exit_code = subprocess.call(cmd) 330*4882a593Smuzhiyun if exit_code != 0: 331*4882a593Smuzhiyun kernellog.warn(app, "Error #%d when calling: %s" % (exit_code, " ".join(cmd))) 332*4882a593Smuzhiyun return bool(exit_code == 0) 333*4882a593Smuzhiyun 334*4882a593Smuzhiyun 335*4882a593Smuzhiyun# image handling 336*4882a593Smuzhiyun# --------------------- 337*4882a593Smuzhiyun 338*4882a593Smuzhiyundef visit_kernel_image(self, node): # pylint: disable=W0613 339*4882a593Smuzhiyun """Visitor of the ``kernel_image`` Node. 340*4882a593Smuzhiyun 341*4882a593Smuzhiyun Handles the ``image`` child-node with the ``convert_image(...)``. 342*4882a593Smuzhiyun """ 343*4882a593Smuzhiyun img_node = node[0] 344*4882a593Smuzhiyun convert_image(img_node, self) 345*4882a593Smuzhiyun 346*4882a593Smuzhiyunclass kernel_image(nodes.image): 347*4882a593Smuzhiyun """Node for ``kernel-image`` directive.""" 348*4882a593Smuzhiyun pass 349*4882a593Smuzhiyun 350*4882a593Smuzhiyunclass KernelImage(images.Image): 351*4882a593Smuzhiyun u"""KernelImage directive 352*4882a593Smuzhiyun 353*4882a593Smuzhiyun Earns everything from ``.. image::`` directive, except *remote URI* and 354*4882a593Smuzhiyun *glob* pattern. The KernelImage wraps a image node into a 355*4882a593Smuzhiyun kernel_image node. See ``visit_kernel_image``. 356*4882a593Smuzhiyun """ 357*4882a593Smuzhiyun 358*4882a593Smuzhiyun def run(self): 359*4882a593Smuzhiyun uri = self.arguments[0] 360*4882a593Smuzhiyun if uri.endswith('.*') or uri.find('://') != -1: 361*4882a593Smuzhiyun raise self.severe( 362*4882a593Smuzhiyun 'Error in "%s: %s": glob pattern and remote images are not allowed' 363*4882a593Smuzhiyun % (self.name, uri)) 364*4882a593Smuzhiyun result = images.Image.run(self) 365*4882a593Smuzhiyun if len(result) == 2 or isinstance(result[0], nodes.system_message): 366*4882a593Smuzhiyun return result 367*4882a593Smuzhiyun (image_node,) = result 368*4882a593Smuzhiyun # wrap image node into a kernel_image node / see visitors 369*4882a593Smuzhiyun node = kernel_image('', image_node) 370*4882a593Smuzhiyun return [node] 371*4882a593Smuzhiyun 372*4882a593Smuzhiyun# figure handling 373*4882a593Smuzhiyun# --------------------- 374*4882a593Smuzhiyun 375*4882a593Smuzhiyundef visit_kernel_figure(self, node): # pylint: disable=W0613 376*4882a593Smuzhiyun """Visitor of the ``kernel_figure`` Node. 377*4882a593Smuzhiyun 378*4882a593Smuzhiyun Handles the ``image`` child-node with the ``convert_image(...)``. 379*4882a593Smuzhiyun """ 380*4882a593Smuzhiyun img_node = node[0][0] 381*4882a593Smuzhiyun convert_image(img_node, self) 382*4882a593Smuzhiyun 383*4882a593Smuzhiyunclass kernel_figure(nodes.figure): 384*4882a593Smuzhiyun """Node for ``kernel-figure`` directive.""" 385*4882a593Smuzhiyun 386*4882a593Smuzhiyunclass KernelFigure(Figure): 387*4882a593Smuzhiyun u"""KernelImage directive 388*4882a593Smuzhiyun 389*4882a593Smuzhiyun Earns everything from ``.. figure::`` directive, except *remote URI* and 390*4882a593Smuzhiyun *glob* pattern. The KernelFigure wraps a figure node into a kernel_figure 391*4882a593Smuzhiyun node. See ``visit_kernel_figure``. 392*4882a593Smuzhiyun """ 393*4882a593Smuzhiyun 394*4882a593Smuzhiyun def run(self): 395*4882a593Smuzhiyun uri = self.arguments[0] 396*4882a593Smuzhiyun if uri.endswith('.*') or uri.find('://') != -1: 397*4882a593Smuzhiyun raise self.severe( 398*4882a593Smuzhiyun 'Error in "%s: %s":' 399*4882a593Smuzhiyun ' glob pattern and remote images are not allowed' 400*4882a593Smuzhiyun % (self.name, uri)) 401*4882a593Smuzhiyun result = Figure.run(self) 402*4882a593Smuzhiyun if len(result) == 2 or isinstance(result[0], nodes.system_message): 403*4882a593Smuzhiyun return result 404*4882a593Smuzhiyun (figure_node,) = result 405*4882a593Smuzhiyun # wrap figure node into a kernel_figure node / see visitors 406*4882a593Smuzhiyun node = kernel_figure('', figure_node) 407*4882a593Smuzhiyun return [node] 408*4882a593Smuzhiyun 409*4882a593Smuzhiyun 410*4882a593Smuzhiyun# render handling 411*4882a593Smuzhiyun# --------------------- 412*4882a593Smuzhiyun 413*4882a593Smuzhiyundef visit_kernel_render(self, node): 414*4882a593Smuzhiyun """Visitor of the ``kernel_render`` Node. 415*4882a593Smuzhiyun 416*4882a593Smuzhiyun If rendering tools available, save the markup of the ``literal_block`` child 417*4882a593Smuzhiyun node into a file and replace the ``literal_block`` node with a new created 418*4882a593Smuzhiyun ``image`` node, pointing to the saved markup file. Afterwards, handle the 419*4882a593Smuzhiyun image child-node with the ``convert_image(...)``. 420*4882a593Smuzhiyun """ 421*4882a593Smuzhiyun app = self.builder.app 422*4882a593Smuzhiyun srclang = node.get('srclang') 423*4882a593Smuzhiyun 424*4882a593Smuzhiyun kernellog.verbose(app, 'visit kernel-render node lang: "%s"' % (srclang)) 425*4882a593Smuzhiyun 426*4882a593Smuzhiyun tmp_ext = RENDER_MARKUP_EXT.get(srclang, None) 427*4882a593Smuzhiyun if tmp_ext is None: 428*4882a593Smuzhiyun kernellog.warn(app, 'kernel-render: "%s" unknown / include raw.' % (srclang)) 429*4882a593Smuzhiyun return 430*4882a593Smuzhiyun 431*4882a593Smuzhiyun if not dot_cmd and tmp_ext == '.dot': 432*4882a593Smuzhiyun kernellog.verbose(app, "dot from graphviz not available / include raw.") 433*4882a593Smuzhiyun return 434*4882a593Smuzhiyun 435*4882a593Smuzhiyun literal_block = node[0] 436*4882a593Smuzhiyun 437*4882a593Smuzhiyun code = literal_block.astext() 438*4882a593Smuzhiyun hashobj = code.encode('utf-8') # str(node.attributes) 439*4882a593Smuzhiyun fname = path.join('%s-%s' % (srclang, sha1(hashobj).hexdigest())) 440*4882a593Smuzhiyun 441*4882a593Smuzhiyun tmp_fname = path.join( 442*4882a593Smuzhiyun self.builder.outdir, self.builder.imagedir, fname + tmp_ext) 443*4882a593Smuzhiyun 444*4882a593Smuzhiyun if not path.isfile(tmp_fname): 445*4882a593Smuzhiyun mkdir(path.dirname(tmp_fname)) 446*4882a593Smuzhiyun with open(tmp_fname, "w") as out: 447*4882a593Smuzhiyun out.write(code) 448*4882a593Smuzhiyun 449*4882a593Smuzhiyun img_node = nodes.image(node.rawsource, **node.attributes) 450*4882a593Smuzhiyun img_node['uri'] = path.join(self.builder.imgpath, fname + tmp_ext) 451*4882a593Smuzhiyun img_node['candidates'] = { 452*4882a593Smuzhiyun '*': path.join(self.builder.imgpath, fname + tmp_ext)} 453*4882a593Smuzhiyun 454*4882a593Smuzhiyun literal_block.replace_self(img_node) 455*4882a593Smuzhiyun convert_image(img_node, self, tmp_fname) 456*4882a593Smuzhiyun 457*4882a593Smuzhiyun 458*4882a593Smuzhiyunclass kernel_render(nodes.General, nodes.Inline, nodes.Element): 459*4882a593Smuzhiyun """Node for ``kernel-render`` directive.""" 460*4882a593Smuzhiyun pass 461*4882a593Smuzhiyun 462*4882a593Smuzhiyunclass KernelRender(Figure): 463*4882a593Smuzhiyun u"""KernelRender directive 464*4882a593Smuzhiyun 465*4882a593Smuzhiyun Render content by external tool. Has all the options known from the 466*4882a593Smuzhiyun *figure* directive, plus option ``caption``. If ``caption`` has a 467*4882a593Smuzhiyun value, a figure node with the *caption* is inserted. If not, a image node is 468*4882a593Smuzhiyun inserted. 469*4882a593Smuzhiyun 470*4882a593Smuzhiyun The KernelRender directive wraps the text of the directive into a 471*4882a593Smuzhiyun literal_block node and wraps it into a kernel_render node. See 472*4882a593Smuzhiyun ``visit_kernel_render``. 473*4882a593Smuzhiyun """ 474*4882a593Smuzhiyun has_content = True 475*4882a593Smuzhiyun required_arguments = 1 476*4882a593Smuzhiyun optional_arguments = 0 477*4882a593Smuzhiyun final_argument_whitespace = False 478*4882a593Smuzhiyun 479*4882a593Smuzhiyun # earn options from 'figure' 480*4882a593Smuzhiyun option_spec = Figure.option_spec.copy() 481*4882a593Smuzhiyun option_spec['caption'] = directives.unchanged 482*4882a593Smuzhiyun 483*4882a593Smuzhiyun def run(self): 484*4882a593Smuzhiyun return [self.build_node()] 485*4882a593Smuzhiyun 486*4882a593Smuzhiyun def build_node(self): 487*4882a593Smuzhiyun 488*4882a593Smuzhiyun srclang = self.arguments[0].strip() 489*4882a593Smuzhiyun if srclang not in RENDER_MARKUP_EXT.keys(): 490*4882a593Smuzhiyun return [self.state_machine.reporter.warning( 491*4882a593Smuzhiyun 'Unknown source language "%s", use one of: %s.' % ( 492*4882a593Smuzhiyun srclang, ",".join(RENDER_MARKUP_EXT.keys())), 493*4882a593Smuzhiyun line=self.lineno)] 494*4882a593Smuzhiyun 495*4882a593Smuzhiyun code = '\n'.join(self.content) 496*4882a593Smuzhiyun if not code.strip(): 497*4882a593Smuzhiyun return [self.state_machine.reporter.warning( 498*4882a593Smuzhiyun 'Ignoring "%s" directive without content.' % ( 499*4882a593Smuzhiyun self.name), 500*4882a593Smuzhiyun line=self.lineno)] 501*4882a593Smuzhiyun 502*4882a593Smuzhiyun node = kernel_render() 503*4882a593Smuzhiyun node['alt'] = self.options.get('alt','') 504*4882a593Smuzhiyun node['srclang'] = srclang 505*4882a593Smuzhiyun literal_node = nodes.literal_block(code, code) 506*4882a593Smuzhiyun node += literal_node 507*4882a593Smuzhiyun 508*4882a593Smuzhiyun caption = self.options.get('caption') 509*4882a593Smuzhiyun if caption: 510*4882a593Smuzhiyun # parse caption's content 511*4882a593Smuzhiyun parsed = nodes.Element() 512*4882a593Smuzhiyun self.state.nested_parse( 513*4882a593Smuzhiyun ViewList([caption], source=''), self.content_offset, parsed) 514*4882a593Smuzhiyun caption_node = nodes.caption( 515*4882a593Smuzhiyun parsed[0].rawsource, '', *parsed[0].children) 516*4882a593Smuzhiyun caption_node.source = parsed[0].source 517*4882a593Smuzhiyun caption_node.line = parsed[0].line 518*4882a593Smuzhiyun 519*4882a593Smuzhiyun figure_node = nodes.figure('', node) 520*4882a593Smuzhiyun for k,v in self.options.items(): 521*4882a593Smuzhiyun figure_node[k] = v 522*4882a593Smuzhiyun figure_node += caption_node 523*4882a593Smuzhiyun 524*4882a593Smuzhiyun node = figure_node 525*4882a593Smuzhiyun 526*4882a593Smuzhiyun return node 527*4882a593Smuzhiyun 528*4882a593Smuzhiyundef add_kernel_figure_to_std_domain(app, doctree): 529*4882a593Smuzhiyun """Add kernel-figure anchors to 'std' domain. 530*4882a593Smuzhiyun 531*4882a593Smuzhiyun The ``StandardDomain.process_doc(..)`` method does not know how to resolve 532*4882a593Smuzhiyun the caption (label) of ``kernel-figure`` directive (it only knows about 533*4882a593Smuzhiyun standard nodes, e.g. table, figure etc.). Without any additional handling 534*4882a593Smuzhiyun this will result in a 'undefined label' for kernel-figures. 535*4882a593Smuzhiyun 536*4882a593Smuzhiyun This handle adds labels of kernel-figure to the 'std' domain labels. 537*4882a593Smuzhiyun """ 538*4882a593Smuzhiyun 539*4882a593Smuzhiyun std = app.env.domains["std"] 540*4882a593Smuzhiyun docname = app.env.docname 541*4882a593Smuzhiyun labels = std.data["labels"] 542*4882a593Smuzhiyun 543*4882a593Smuzhiyun for name, explicit in iteritems(doctree.nametypes): 544*4882a593Smuzhiyun if not explicit: 545*4882a593Smuzhiyun continue 546*4882a593Smuzhiyun labelid = doctree.nameids[name] 547*4882a593Smuzhiyun if labelid is None: 548*4882a593Smuzhiyun continue 549*4882a593Smuzhiyun node = doctree.ids[labelid] 550*4882a593Smuzhiyun 551*4882a593Smuzhiyun if node.tagname == 'kernel_figure': 552*4882a593Smuzhiyun for n in node.next_node(): 553*4882a593Smuzhiyun if n.tagname == 'caption': 554*4882a593Smuzhiyun sectname = clean_astext(n) 555*4882a593Smuzhiyun # add label to std domain 556*4882a593Smuzhiyun labels[name] = docname, labelid, sectname 557*4882a593Smuzhiyun break 558