162306a36Sopenharmony_ci# -*- coding: utf-8; mode: python -*- 262306a36Sopenharmony_ci# pylint: disable=C0103, R0903, R0912, R0915 362306a36Sopenharmony_ciu""" 462306a36Sopenharmony_ci scalable figure and image handling 562306a36Sopenharmony_ci ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci Sphinx extension which implements scalable image handling. 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci :copyright: Copyright (C) 2016 Markus Heiser 1062306a36Sopenharmony_ci :license: GPL Version 2, June 1991 see Linux/COPYING for details. 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci The build for image formats depend on image's source format and output's 1362306a36Sopenharmony_ci destination format. This extension implement methods to simplify image 1462306a36Sopenharmony_ci handling from the author's POV. Directives like ``kernel-figure`` implement 1562306a36Sopenharmony_ci methods *to* always get the best output-format even if some tools are not 1662306a36Sopenharmony_ci installed. For more details take a look at ``convert_image(...)`` which is 1762306a36Sopenharmony_ci the core of all conversions. 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci * ``.. kernel-image``: for image handling / a ``.. image::`` replacement 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci * ``.. kernel-figure``: for figure handling / a ``.. figure::`` replacement 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci * ``.. kernel-render``: for render markup / a concept to embed *render* 2462306a36Sopenharmony_ci markups (or languages). Supported markups (see ``RENDER_MARKUP_EXT``) 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci - ``DOT``: render embedded Graphviz's **DOC** 2762306a36Sopenharmony_ci - ``SVG``: render embedded Scalable Vector Graphics (**SVG**) 2862306a36Sopenharmony_ci - ... *developable* 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci Used tools: 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci * ``dot(1)``: Graphviz (https://www.graphviz.org). If Graphviz is not 3362306a36Sopenharmony_ci available, the DOT language is inserted as literal-block. 3462306a36Sopenharmony_ci For conversion to PDF, ``rsvg-convert(1)`` of librsvg 3562306a36Sopenharmony_ci (https://gitlab.gnome.org/GNOME/librsvg) is used when available. 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci * SVG to PDF: To generate PDF, you need at least one of this tools: 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci - ``convert(1)``: ImageMagick (https://www.imagemagick.org) 4062306a36Sopenharmony_ci - ``inkscape(1)``: Inkscape (https://inkscape.org/) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci List of customizations: 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci * generate PDF from SVG / used by PDF (LaTeX) builder 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci * generate SVG (html-builder) and PDF (latex-builder) from DOT files. 4762306a36Sopenharmony_ci DOT: see https://www.graphviz.org/content/dot-language 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci """ 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ciimport os 5262306a36Sopenharmony_cifrom os import path 5362306a36Sopenharmony_ciimport subprocess 5462306a36Sopenharmony_cifrom hashlib import sha1 5562306a36Sopenharmony_ciimport re 5662306a36Sopenharmony_cifrom docutils import nodes 5762306a36Sopenharmony_cifrom docutils.statemachine import ViewList 5862306a36Sopenharmony_cifrom docutils.parsers.rst import directives 5962306a36Sopenharmony_cifrom docutils.parsers.rst.directives import images 6062306a36Sopenharmony_ciimport sphinx 6162306a36Sopenharmony_cifrom sphinx.util.nodes import clean_astext 6262306a36Sopenharmony_ciimport kernellog 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci# Get Sphinx version 6562306a36Sopenharmony_cimajor, minor, patch = sphinx.version_info[:3] 6662306a36Sopenharmony_ciif major == 1 and minor > 3: 6762306a36Sopenharmony_ci # patches.Figure only landed in Sphinx 1.4 6862306a36Sopenharmony_ci from sphinx.directives.patches import Figure # pylint: disable=C0413 6962306a36Sopenharmony_cielse: 7062306a36Sopenharmony_ci Figure = images.Figure 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci__version__ = '1.0.0' 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci# simple helper 7562306a36Sopenharmony_ci# ------------- 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cidef which(cmd): 7862306a36Sopenharmony_ci """Searches the ``cmd`` in the ``PATH`` environment. 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci This *which* searches the PATH for executable ``cmd`` . First match is 8162306a36Sopenharmony_ci returned, if nothing is found, ``None` is returned. 8262306a36Sopenharmony_ci """ 8362306a36Sopenharmony_ci envpath = os.environ.get('PATH', None) or os.defpath 8462306a36Sopenharmony_ci for folder in envpath.split(os.pathsep): 8562306a36Sopenharmony_ci fname = folder + os.sep + cmd 8662306a36Sopenharmony_ci if path.isfile(fname): 8762306a36Sopenharmony_ci return fname 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cidef mkdir(folder, mode=0o775): 9062306a36Sopenharmony_ci if not path.isdir(folder): 9162306a36Sopenharmony_ci os.makedirs(folder, mode) 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cidef file2literal(fname): 9462306a36Sopenharmony_ci with open(fname, "r") as src: 9562306a36Sopenharmony_ci data = src.read() 9662306a36Sopenharmony_ci node = nodes.literal_block(data, data) 9762306a36Sopenharmony_ci return node 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cidef isNewer(path1, path2): 10062306a36Sopenharmony_ci """Returns True if ``path1`` is newer than ``path2`` 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci If ``path1`` exists and is newer than ``path2`` the function returns 10362306a36Sopenharmony_ci ``True`` is returned otherwise ``False`` 10462306a36Sopenharmony_ci """ 10562306a36Sopenharmony_ci return (path.exists(path1) 10662306a36Sopenharmony_ci and os.stat(path1).st_ctime > os.stat(path2).st_ctime) 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cidef pass_handle(self, node): # pylint: disable=W0613 10962306a36Sopenharmony_ci pass 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci# setup conversion tools and sphinx extension 11262306a36Sopenharmony_ci# ------------------------------------------- 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci# Graphviz's dot(1) support 11562306a36Sopenharmony_cidot_cmd = None 11662306a36Sopenharmony_ci# dot(1) -Tpdf should be used 11762306a36Sopenharmony_cidot_Tpdf = False 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci# ImageMagick' convert(1) support 12062306a36Sopenharmony_ciconvert_cmd = None 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci# librsvg's rsvg-convert(1) support 12362306a36Sopenharmony_cirsvg_convert_cmd = None 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci# Inkscape's inkscape(1) support 12662306a36Sopenharmony_ciinkscape_cmd = None 12762306a36Sopenharmony_ci# Inkscape prior to 1.0 uses different command options 12862306a36Sopenharmony_ciinkscape_ver_one = False 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cidef setup(app): 13262306a36Sopenharmony_ci # check toolchain first 13362306a36Sopenharmony_ci app.connect('builder-inited', setupTools) 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci # image handling 13662306a36Sopenharmony_ci app.add_directive("kernel-image", KernelImage) 13762306a36Sopenharmony_ci app.add_node(kernel_image, 13862306a36Sopenharmony_ci html = (visit_kernel_image, pass_handle), 13962306a36Sopenharmony_ci latex = (visit_kernel_image, pass_handle), 14062306a36Sopenharmony_ci texinfo = (visit_kernel_image, pass_handle), 14162306a36Sopenharmony_ci text = (visit_kernel_image, pass_handle), 14262306a36Sopenharmony_ci man = (visit_kernel_image, pass_handle), ) 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci # figure handling 14562306a36Sopenharmony_ci app.add_directive("kernel-figure", KernelFigure) 14662306a36Sopenharmony_ci app.add_node(kernel_figure, 14762306a36Sopenharmony_ci html = (visit_kernel_figure, pass_handle), 14862306a36Sopenharmony_ci latex = (visit_kernel_figure, pass_handle), 14962306a36Sopenharmony_ci texinfo = (visit_kernel_figure, pass_handle), 15062306a36Sopenharmony_ci text = (visit_kernel_figure, pass_handle), 15162306a36Sopenharmony_ci man = (visit_kernel_figure, pass_handle), ) 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci # render handling 15462306a36Sopenharmony_ci app.add_directive('kernel-render', KernelRender) 15562306a36Sopenharmony_ci app.add_node(kernel_render, 15662306a36Sopenharmony_ci html = (visit_kernel_render, pass_handle), 15762306a36Sopenharmony_ci latex = (visit_kernel_render, pass_handle), 15862306a36Sopenharmony_ci texinfo = (visit_kernel_render, pass_handle), 15962306a36Sopenharmony_ci text = (visit_kernel_render, pass_handle), 16062306a36Sopenharmony_ci man = (visit_kernel_render, pass_handle), ) 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci app.connect('doctree-read', add_kernel_figure_to_std_domain) 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci return dict( 16562306a36Sopenharmony_ci version = __version__, 16662306a36Sopenharmony_ci parallel_read_safe = True, 16762306a36Sopenharmony_ci parallel_write_safe = True 16862306a36Sopenharmony_ci ) 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cidef setupTools(app): 17262306a36Sopenharmony_ci u""" 17362306a36Sopenharmony_ci Check available build tools and log some *verbose* messages. 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci This function is called once, when the builder is initiated. 17662306a36Sopenharmony_ci """ 17762306a36Sopenharmony_ci global dot_cmd, dot_Tpdf, convert_cmd, rsvg_convert_cmd # pylint: disable=W0603 17862306a36Sopenharmony_ci global inkscape_cmd, inkscape_ver_one # pylint: disable=W0603 17962306a36Sopenharmony_ci kernellog.verbose(app, "kfigure: check installed tools ...") 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci dot_cmd = which('dot') 18262306a36Sopenharmony_ci convert_cmd = which('convert') 18362306a36Sopenharmony_ci rsvg_convert_cmd = which('rsvg-convert') 18462306a36Sopenharmony_ci inkscape_cmd = which('inkscape') 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci if dot_cmd: 18762306a36Sopenharmony_ci kernellog.verbose(app, "use dot(1) from: " + dot_cmd) 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci try: 19062306a36Sopenharmony_ci dot_Thelp_list = subprocess.check_output([dot_cmd, '-Thelp'], 19162306a36Sopenharmony_ci stderr=subprocess.STDOUT) 19262306a36Sopenharmony_ci except subprocess.CalledProcessError as err: 19362306a36Sopenharmony_ci dot_Thelp_list = err.output 19462306a36Sopenharmony_ci pass 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci dot_Tpdf_ptn = b'pdf' 19762306a36Sopenharmony_ci dot_Tpdf = re.search(dot_Tpdf_ptn, dot_Thelp_list) 19862306a36Sopenharmony_ci else: 19962306a36Sopenharmony_ci kernellog.warn(app, "dot(1) not found, for better output quality install " 20062306a36Sopenharmony_ci "graphviz from https://www.graphviz.org") 20162306a36Sopenharmony_ci if inkscape_cmd: 20262306a36Sopenharmony_ci kernellog.verbose(app, "use inkscape(1) from: " + inkscape_cmd) 20362306a36Sopenharmony_ci inkscape_ver = subprocess.check_output([inkscape_cmd, '--version'], 20462306a36Sopenharmony_ci stderr=subprocess.DEVNULL) 20562306a36Sopenharmony_ci ver_one_ptn = b'Inkscape 1' 20662306a36Sopenharmony_ci inkscape_ver_one = re.search(ver_one_ptn, inkscape_ver) 20762306a36Sopenharmony_ci convert_cmd = None 20862306a36Sopenharmony_ci rsvg_convert_cmd = None 20962306a36Sopenharmony_ci dot_Tpdf = False 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci else: 21262306a36Sopenharmony_ci if convert_cmd: 21362306a36Sopenharmony_ci kernellog.verbose(app, "use convert(1) from: " + convert_cmd) 21462306a36Sopenharmony_ci else: 21562306a36Sopenharmony_ci kernellog.verbose(app, 21662306a36Sopenharmony_ci "Neither inkscape(1) nor convert(1) found.\n" 21762306a36Sopenharmony_ci "For SVG to PDF conversion, " 21862306a36Sopenharmony_ci "install either Inkscape (https://inkscape.org/) (preferred) or\n" 21962306a36Sopenharmony_ci "ImageMagick (https://www.imagemagick.org)") 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci if rsvg_convert_cmd: 22262306a36Sopenharmony_ci kernellog.verbose(app, "use rsvg-convert(1) from: " + rsvg_convert_cmd) 22362306a36Sopenharmony_ci kernellog.verbose(app, "use 'dot -Tsvg' and rsvg-convert(1) for DOT -> PDF conversion") 22462306a36Sopenharmony_ci dot_Tpdf = False 22562306a36Sopenharmony_ci else: 22662306a36Sopenharmony_ci kernellog.verbose(app, 22762306a36Sopenharmony_ci "rsvg-convert(1) not found.\n" 22862306a36Sopenharmony_ci " SVG rendering of convert(1) is done by ImageMagick-native renderer.") 22962306a36Sopenharmony_ci if dot_Tpdf: 23062306a36Sopenharmony_ci kernellog.verbose(app, "use 'dot -Tpdf' for DOT -> PDF conversion") 23162306a36Sopenharmony_ci else: 23262306a36Sopenharmony_ci kernellog.verbose(app, "use 'dot -Tsvg' and convert(1) for DOT -> PDF conversion") 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci# integrate conversion tools 23662306a36Sopenharmony_ci# -------------------------- 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ciRENDER_MARKUP_EXT = { 23962306a36Sopenharmony_ci # The '.ext' must be handled by convert_image(..) function's *in_ext* input. 24062306a36Sopenharmony_ci # <name> : <.ext> 24162306a36Sopenharmony_ci 'DOT' : '.dot', 24262306a36Sopenharmony_ci 'SVG' : '.svg' 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cidef convert_image(img_node, translator, src_fname=None): 24662306a36Sopenharmony_ci """Convert a image node for the builder. 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci Different builder prefer different image formats, e.g. *latex* builder 24962306a36Sopenharmony_ci prefer PDF while *html* builder prefer SVG format for images. 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci This function handles output image formats in dependence of source the 25262306a36Sopenharmony_ci format (of the image) and the translator's output format. 25362306a36Sopenharmony_ci """ 25462306a36Sopenharmony_ci app = translator.builder.app 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci fname, in_ext = path.splitext(path.basename(img_node['uri'])) 25762306a36Sopenharmony_ci if src_fname is None: 25862306a36Sopenharmony_ci src_fname = path.join(translator.builder.srcdir, img_node['uri']) 25962306a36Sopenharmony_ci if not path.exists(src_fname): 26062306a36Sopenharmony_ci src_fname = path.join(translator.builder.outdir, img_node['uri']) 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci dst_fname = None 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci # in kernel builds, use 'make SPHINXOPTS=-v' to see verbose messages 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci kernellog.verbose(app, 'assert best format for: ' + img_node['uri']) 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci if in_ext == '.dot': 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci if not dot_cmd: 27162306a36Sopenharmony_ci kernellog.verbose(app, 27262306a36Sopenharmony_ci "dot from graphviz not available / include DOT raw.") 27362306a36Sopenharmony_ci img_node.replace_self(file2literal(src_fname)) 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci elif translator.builder.format == 'latex': 27662306a36Sopenharmony_ci dst_fname = path.join(translator.builder.outdir, fname + '.pdf') 27762306a36Sopenharmony_ci img_node['uri'] = fname + '.pdf' 27862306a36Sopenharmony_ci img_node['candidates'] = {'*': fname + '.pdf'} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci elif translator.builder.format == 'html': 28262306a36Sopenharmony_ci dst_fname = path.join( 28362306a36Sopenharmony_ci translator.builder.outdir, 28462306a36Sopenharmony_ci translator.builder.imagedir, 28562306a36Sopenharmony_ci fname + '.svg') 28662306a36Sopenharmony_ci img_node['uri'] = path.join( 28762306a36Sopenharmony_ci translator.builder.imgpath, fname + '.svg') 28862306a36Sopenharmony_ci img_node['candidates'] = { 28962306a36Sopenharmony_ci '*': path.join(translator.builder.imgpath, fname + '.svg')} 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci else: 29262306a36Sopenharmony_ci # all other builder formats will include DOT as raw 29362306a36Sopenharmony_ci img_node.replace_self(file2literal(src_fname)) 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci elif in_ext == '.svg': 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci if translator.builder.format == 'latex': 29862306a36Sopenharmony_ci if not inkscape_cmd and convert_cmd is None: 29962306a36Sopenharmony_ci kernellog.warn(app, 30062306a36Sopenharmony_ci "no SVG to PDF conversion available / include SVG raw." 30162306a36Sopenharmony_ci "\nIncluding large raw SVGs can cause xelatex error." 30262306a36Sopenharmony_ci "\nInstall Inkscape (preferred) or ImageMagick.") 30362306a36Sopenharmony_ci img_node.replace_self(file2literal(src_fname)) 30462306a36Sopenharmony_ci else: 30562306a36Sopenharmony_ci dst_fname = path.join(translator.builder.outdir, fname + '.pdf') 30662306a36Sopenharmony_ci img_node['uri'] = fname + '.pdf' 30762306a36Sopenharmony_ci img_node['candidates'] = {'*': fname + '.pdf'} 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if dst_fname: 31062306a36Sopenharmony_ci # the builder needs not to copy one more time, so pop it if exists. 31162306a36Sopenharmony_ci translator.builder.images.pop(img_node['uri'], None) 31262306a36Sopenharmony_ci _name = dst_fname[len(translator.builder.outdir) + 1:] 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci if isNewer(dst_fname, src_fname): 31562306a36Sopenharmony_ci kernellog.verbose(app, 31662306a36Sopenharmony_ci "convert: {out}/%s already exists and is newer" % _name) 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci else: 31962306a36Sopenharmony_ci ok = False 32062306a36Sopenharmony_ci mkdir(path.dirname(dst_fname)) 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if in_ext == '.dot': 32362306a36Sopenharmony_ci kernellog.verbose(app, 'convert DOT to: {out}/' + _name) 32462306a36Sopenharmony_ci if translator.builder.format == 'latex' and not dot_Tpdf: 32562306a36Sopenharmony_ci svg_fname = path.join(translator.builder.outdir, fname + '.svg') 32662306a36Sopenharmony_ci ok1 = dot2format(app, src_fname, svg_fname) 32762306a36Sopenharmony_ci ok2 = svg2pdf_by_rsvg(app, svg_fname, dst_fname) 32862306a36Sopenharmony_ci ok = ok1 and ok2 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci else: 33162306a36Sopenharmony_ci ok = dot2format(app, src_fname, dst_fname) 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci elif in_ext == '.svg': 33462306a36Sopenharmony_ci kernellog.verbose(app, 'convert SVG to: {out}/' + _name) 33562306a36Sopenharmony_ci ok = svg2pdf(app, src_fname, dst_fname) 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci if not ok: 33862306a36Sopenharmony_ci img_node.replace_self(file2literal(src_fname)) 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_cidef dot2format(app, dot_fname, out_fname): 34262306a36Sopenharmony_ci """Converts DOT file to ``out_fname`` using ``dot(1)``. 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci * ``dot_fname`` pathname of the input DOT file, including extension ``.dot`` 34562306a36Sopenharmony_ci * ``out_fname`` pathname of the output file, including format extension 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci The *format extension* depends on the ``dot`` command (see ``man dot`` 34862306a36Sopenharmony_ci option ``-Txxx``). Normally you will use one of the following extensions: 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci - ``.ps`` for PostScript, 35162306a36Sopenharmony_ci - ``.svg`` or ``svgz`` for Structured Vector Graphics, 35262306a36Sopenharmony_ci - ``.fig`` for XFIG graphics and 35362306a36Sopenharmony_ci - ``.png`` or ``gif`` for common bitmap graphics. 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci """ 35662306a36Sopenharmony_ci out_format = path.splitext(out_fname)[1][1:] 35762306a36Sopenharmony_ci cmd = [dot_cmd, '-T%s' % out_format, dot_fname] 35862306a36Sopenharmony_ci exit_code = 42 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci with open(out_fname, "w") as out: 36162306a36Sopenharmony_ci exit_code = subprocess.call(cmd, stdout = out) 36262306a36Sopenharmony_ci if exit_code != 0: 36362306a36Sopenharmony_ci kernellog.warn(app, 36462306a36Sopenharmony_ci "Error #%d when calling: %s" % (exit_code, " ".join(cmd))) 36562306a36Sopenharmony_ci return bool(exit_code == 0) 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_cidef svg2pdf(app, svg_fname, pdf_fname): 36862306a36Sopenharmony_ci """Converts SVG to PDF with ``inkscape(1)`` or ``convert(1)`` command. 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci Uses ``inkscape(1)`` from Inkscape (https://inkscape.org/) or ``convert(1)`` 37162306a36Sopenharmony_ci from ImageMagick (https://www.imagemagick.org) for conversion. 37262306a36Sopenharmony_ci Returns ``True`` on success and ``False`` if an error occurred. 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci * ``svg_fname`` pathname of the input SVG file with extension (``.svg``) 37562306a36Sopenharmony_ci * ``pdf_name`` pathname of the output PDF file with extension (``.pdf``) 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci """ 37862306a36Sopenharmony_ci cmd = [convert_cmd, svg_fname, pdf_fname] 37962306a36Sopenharmony_ci cmd_name = 'convert(1)' 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci if inkscape_cmd: 38262306a36Sopenharmony_ci cmd_name = 'inkscape(1)' 38362306a36Sopenharmony_ci if inkscape_ver_one: 38462306a36Sopenharmony_ci cmd = [inkscape_cmd, '-o', pdf_fname, svg_fname] 38562306a36Sopenharmony_ci else: 38662306a36Sopenharmony_ci cmd = [inkscape_cmd, '-z', '--export-pdf=%s' % pdf_fname, svg_fname] 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci try: 38962306a36Sopenharmony_ci warning_msg = subprocess.check_output(cmd, stderr=subprocess.STDOUT) 39062306a36Sopenharmony_ci exit_code = 0 39162306a36Sopenharmony_ci except subprocess.CalledProcessError as err: 39262306a36Sopenharmony_ci warning_msg = err.output 39362306a36Sopenharmony_ci exit_code = err.returncode 39462306a36Sopenharmony_ci pass 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci if exit_code != 0: 39762306a36Sopenharmony_ci kernellog.warn(app, "Error #%d when calling: %s" % (exit_code, " ".join(cmd))) 39862306a36Sopenharmony_ci if warning_msg: 39962306a36Sopenharmony_ci kernellog.warn(app, "Warning msg from %s: %s" 40062306a36Sopenharmony_ci % (cmd_name, str(warning_msg, 'utf-8'))) 40162306a36Sopenharmony_ci elif warning_msg: 40262306a36Sopenharmony_ci kernellog.verbose(app, "Warning msg from %s (likely harmless):\n%s" 40362306a36Sopenharmony_ci % (cmd_name, str(warning_msg, 'utf-8'))) 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci return bool(exit_code == 0) 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_cidef svg2pdf_by_rsvg(app, svg_fname, pdf_fname): 40862306a36Sopenharmony_ci """Convert SVG to PDF with ``rsvg-convert(1)`` command. 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci * ``svg_fname`` pathname of input SVG file, including extension ``.svg`` 41162306a36Sopenharmony_ci * ``pdf_fname`` pathname of output PDF file, including extension ``.pdf`` 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci Input SVG file should be the one generated by ``dot2format()``. 41462306a36Sopenharmony_ci SVG -> PDF conversion is done by ``rsvg-convert(1)``. 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci If ``rsvg-convert(1)`` is unavailable, fall back to ``svg2pdf()``. 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci """ 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci if rsvg_convert_cmd is None: 42162306a36Sopenharmony_ci ok = svg2pdf(app, svg_fname, pdf_fname) 42262306a36Sopenharmony_ci else: 42362306a36Sopenharmony_ci cmd = [rsvg_convert_cmd, '--format=pdf', '-o', pdf_fname, svg_fname] 42462306a36Sopenharmony_ci # use stdout and stderr from parent 42562306a36Sopenharmony_ci exit_code = subprocess.call(cmd) 42662306a36Sopenharmony_ci if exit_code != 0: 42762306a36Sopenharmony_ci kernellog.warn(app, "Error #%d when calling: %s" % (exit_code, " ".join(cmd))) 42862306a36Sopenharmony_ci ok = bool(exit_code == 0) 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci return ok 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci# image handling 43462306a36Sopenharmony_ci# --------------------- 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_cidef visit_kernel_image(self, node): # pylint: disable=W0613 43762306a36Sopenharmony_ci """Visitor of the ``kernel_image`` Node. 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci Handles the ``image`` child-node with the ``convert_image(...)``. 44062306a36Sopenharmony_ci """ 44162306a36Sopenharmony_ci img_node = node[0] 44262306a36Sopenharmony_ci convert_image(img_node, self) 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ciclass kernel_image(nodes.image): 44562306a36Sopenharmony_ci """Node for ``kernel-image`` directive.""" 44662306a36Sopenharmony_ci pass 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ciclass KernelImage(images.Image): 44962306a36Sopenharmony_ci u"""KernelImage directive 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci Earns everything from ``.. image::`` directive, except *remote URI* and 45262306a36Sopenharmony_ci *glob* pattern. The KernelImage wraps a image node into a 45362306a36Sopenharmony_ci kernel_image node. See ``visit_kernel_image``. 45462306a36Sopenharmony_ci """ 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci def run(self): 45762306a36Sopenharmony_ci uri = self.arguments[0] 45862306a36Sopenharmony_ci if uri.endswith('.*') or uri.find('://') != -1: 45962306a36Sopenharmony_ci raise self.severe( 46062306a36Sopenharmony_ci 'Error in "%s: %s": glob pattern and remote images are not allowed' 46162306a36Sopenharmony_ci % (self.name, uri)) 46262306a36Sopenharmony_ci result = images.Image.run(self) 46362306a36Sopenharmony_ci if len(result) == 2 or isinstance(result[0], nodes.system_message): 46462306a36Sopenharmony_ci return result 46562306a36Sopenharmony_ci (image_node,) = result 46662306a36Sopenharmony_ci # wrap image node into a kernel_image node / see visitors 46762306a36Sopenharmony_ci node = kernel_image('', image_node) 46862306a36Sopenharmony_ci return [node] 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci# figure handling 47162306a36Sopenharmony_ci# --------------------- 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_cidef visit_kernel_figure(self, node): # pylint: disable=W0613 47462306a36Sopenharmony_ci """Visitor of the ``kernel_figure`` Node. 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci Handles the ``image`` child-node with the ``convert_image(...)``. 47762306a36Sopenharmony_ci """ 47862306a36Sopenharmony_ci img_node = node[0][0] 47962306a36Sopenharmony_ci convert_image(img_node, self) 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ciclass kernel_figure(nodes.figure): 48262306a36Sopenharmony_ci """Node for ``kernel-figure`` directive.""" 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ciclass KernelFigure(Figure): 48562306a36Sopenharmony_ci u"""KernelImage directive 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci Earns everything from ``.. figure::`` directive, except *remote URI* and 48862306a36Sopenharmony_ci *glob* pattern. The KernelFigure wraps a figure node into a kernel_figure 48962306a36Sopenharmony_ci node. See ``visit_kernel_figure``. 49062306a36Sopenharmony_ci """ 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci def run(self): 49362306a36Sopenharmony_ci uri = self.arguments[0] 49462306a36Sopenharmony_ci if uri.endswith('.*') or uri.find('://') != -1: 49562306a36Sopenharmony_ci raise self.severe( 49662306a36Sopenharmony_ci 'Error in "%s: %s":' 49762306a36Sopenharmony_ci ' glob pattern and remote images are not allowed' 49862306a36Sopenharmony_ci % (self.name, uri)) 49962306a36Sopenharmony_ci result = Figure.run(self) 50062306a36Sopenharmony_ci if len(result) == 2 or isinstance(result[0], nodes.system_message): 50162306a36Sopenharmony_ci return result 50262306a36Sopenharmony_ci (figure_node,) = result 50362306a36Sopenharmony_ci # wrap figure node into a kernel_figure node / see visitors 50462306a36Sopenharmony_ci node = kernel_figure('', figure_node) 50562306a36Sopenharmony_ci return [node] 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci# render handling 50962306a36Sopenharmony_ci# --------------------- 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_cidef visit_kernel_render(self, node): 51262306a36Sopenharmony_ci """Visitor of the ``kernel_render`` Node. 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci If rendering tools available, save the markup of the ``literal_block`` child 51562306a36Sopenharmony_ci node into a file and replace the ``literal_block`` node with a new created 51662306a36Sopenharmony_ci ``image`` node, pointing to the saved markup file. Afterwards, handle the 51762306a36Sopenharmony_ci image child-node with the ``convert_image(...)``. 51862306a36Sopenharmony_ci """ 51962306a36Sopenharmony_ci app = self.builder.app 52062306a36Sopenharmony_ci srclang = node.get('srclang') 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci kernellog.verbose(app, 'visit kernel-render node lang: "%s"' % (srclang)) 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci tmp_ext = RENDER_MARKUP_EXT.get(srclang, None) 52562306a36Sopenharmony_ci if tmp_ext is None: 52662306a36Sopenharmony_ci kernellog.warn(app, 'kernel-render: "%s" unknown / include raw.' % (srclang)) 52762306a36Sopenharmony_ci return 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci if not dot_cmd and tmp_ext == '.dot': 53062306a36Sopenharmony_ci kernellog.verbose(app, "dot from graphviz not available / include raw.") 53162306a36Sopenharmony_ci return 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci literal_block = node[0] 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci code = literal_block.astext() 53662306a36Sopenharmony_ci hashobj = code.encode('utf-8') # str(node.attributes) 53762306a36Sopenharmony_ci fname = path.join('%s-%s' % (srclang, sha1(hashobj).hexdigest())) 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci tmp_fname = path.join( 54062306a36Sopenharmony_ci self.builder.outdir, self.builder.imagedir, fname + tmp_ext) 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci if not path.isfile(tmp_fname): 54362306a36Sopenharmony_ci mkdir(path.dirname(tmp_fname)) 54462306a36Sopenharmony_ci with open(tmp_fname, "w") as out: 54562306a36Sopenharmony_ci out.write(code) 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci img_node = nodes.image(node.rawsource, **node.attributes) 54862306a36Sopenharmony_ci img_node['uri'] = path.join(self.builder.imgpath, fname + tmp_ext) 54962306a36Sopenharmony_ci img_node['candidates'] = { 55062306a36Sopenharmony_ci '*': path.join(self.builder.imgpath, fname + tmp_ext)} 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci literal_block.replace_self(img_node) 55362306a36Sopenharmony_ci convert_image(img_node, self, tmp_fname) 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ciclass kernel_render(nodes.General, nodes.Inline, nodes.Element): 55762306a36Sopenharmony_ci """Node for ``kernel-render`` directive.""" 55862306a36Sopenharmony_ci pass 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_ciclass KernelRender(Figure): 56162306a36Sopenharmony_ci u"""KernelRender directive 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci Render content by external tool. Has all the options known from the 56462306a36Sopenharmony_ci *figure* directive, plus option ``caption``. If ``caption`` has a 56562306a36Sopenharmony_ci value, a figure node with the *caption* is inserted. If not, a image node is 56662306a36Sopenharmony_ci inserted. 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci The KernelRender directive wraps the text of the directive into a 56962306a36Sopenharmony_ci literal_block node and wraps it into a kernel_render node. See 57062306a36Sopenharmony_ci ``visit_kernel_render``. 57162306a36Sopenharmony_ci """ 57262306a36Sopenharmony_ci has_content = True 57362306a36Sopenharmony_ci required_arguments = 1 57462306a36Sopenharmony_ci optional_arguments = 0 57562306a36Sopenharmony_ci final_argument_whitespace = False 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci # earn options from 'figure' 57862306a36Sopenharmony_ci option_spec = Figure.option_spec.copy() 57962306a36Sopenharmony_ci option_spec['caption'] = directives.unchanged 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci def run(self): 58262306a36Sopenharmony_ci return [self.build_node()] 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci def build_node(self): 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci srclang = self.arguments[0].strip() 58762306a36Sopenharmony_ci if srclang not in RENDER_MARKUP_EXT.keys(): 58862306a36Sopenharmony_ci return [self.state_machine.reporter.warning( 58962306a36Sopenharmony_ci 'Unknown source language "%s", use one of: %s.' % ( 59062306a36Sopenharmony_ci srclang, ",".join(RENDER_MARKUP_EXT.keys())), 59162306a36Sopenharmony_ci line=self.lineno)] 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci code = '\n'.join(self.content) 59462306a36Sopenharmony_ci if not code.strip(): 59562306a36Sopenharmony_ci return [self.state_machine.reporter.warning( 59662306a36Sopenharmony_ci 'Ignoring "%s" directive without content.' % ( 59762306a36Sopenharmony_ci self.name), 59862306a36Sopenharmony_ci line=self.lineno)] 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci node = kernel_render() 60162306a36Sopenharmony_ci node['alt'] = self.options.get('alt','') 60262306a36Sopenharmony_ci node['srclang'] = srclang 60362306a36Sopenharmony_ci literal_node = nodes.literal_block(code, code) 60462306a36Sopenharmony_ci node += literal_node 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci caption = self.options.get('caption') 60762306a36Sopenharmony_ci if caption: 60862306a36Sopenharmony_ci # parse caption's content 60962306a36Sopenharmony_ci parsed = nodes.Element() 61062306a36Sopenharmony_ci self.state.nested_parse( 61162306a36Sopenharmony_ci ViewList([caption], source=''), self.content_offset, parsed) 61262306a36Sopenharmony_ci caption_node = nodes.caption( 61362306a36Sopenharmony_ci parsed[0].rawsource, '', *parsed[0].children) 61462306a36Sopenharmony_ci caption_node.source = parsed[0].source 61562306a36Sopenharmony_ci caption_node.line = parsed[0].line 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci figure_node = nodes.figure('', node) 61862306a36Sopenharmony_ci for k,v in self.options.items(): 61962306a36Sopenharmony_ci figure_node[k] = v 62062306a36Sopenharmony_ci figure_node += caption_node 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci node = figure_node 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci return node 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_cidef add_kernel_figure_to_std_domain(app, doctree): 62762306a36Sopenharmony_ci """Add kernel-figure anchors to 'std' domain. 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci The ``StandardDomain.process_doc(..)`` method does not know how to resolve 63062306a36Sopenharmony_ci the caption (label) of ``kernel-figure`` directive (it only knows about 63162306a36Sopenharmony_ci standard nodes, e.g. table, figure etc.). Without any additional handling 63262306a36Sopenharmony_ci this will result in a 'undefined label' for kernel-figures. 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci This handle adds labels of kernel-figure to the 'std' domain labels. 63562306a36Sopenharmony_ci """ 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci std = app.env.domains["std"] 63862306a36Sopenharmony_ci docname = app.env.docname 63962306a36Sopenharmony_ci labels = std.data["labels"] 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci for name, explicit in doctree.nametypes.items(): 64262306a36Sopenharmony_ci if not explicit: 64362306a36Sopenharmony_ci continue 64462306a36Sopenharmony_ci labelid = doctree.nameids[name] 64562306a36Sopenharmony_ci if labelid is None: 64662306a36Sopenharmony_ci continue 64762306a36Sopenharmony_ci node = doctree.ids[labelid] 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci if node.tagname == 'kernel_figure': 65062306a36Sopenharmony_ci for n in node.next_node(): 65162306a36Sopenharmony_ci if n.tagname == 'caption': 65262306a36Sopenharmony_ci sectname = clean_astext(n) 65362306a36Sopenharmony_ci # add label to std domain 65462306a36Sopenharmony_ci labels[name] = docname, labelid, sectname 65562306a36Sopenharmony_ci break 656