summaryrefslogtreecommitdiff
path: root/Documentation/sphinx/kfigure.py
diff options
context:
space:
mode:
Diffstat (limited to 'Documentation/sphinx/kfigure.py')
-rw-r--r--Documentation/sphinx/kfigure.py217
1 files changed, 157 insertions, 60 deletions
diff --git a/Documentation/sphinx/kfigure.py b/Documentation/sphinx/kfigure.py
index fbfe6693bb60..f1a7f13c9c60 100644
--- a/Documentation/sphinx/kfigure.py
+++ b/Documentation/sphinx/kfigure.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; mode: python -*-
# pylint: disable=C0103, R0903, R0912, R0915
-u"""
+"""
scalable figure and image handling
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -29,19 +29,22 @@ u"""
Used tools:
- * ``dot(1)``: Graphviz (http://www.graphviz.org). If Graphviz is not
+ * ``dot(1)``: Graphviz (https://www.graphviz.org). If Graphviz is not
available, the DOT language is inserted as literal-block.
+ For conversion to PDF, ``rsvg-convert(1)`` of librsvg
+ (https://gitlab.gnome.org/GNOME/librsvg) is used when available.
* SVG to PDF: To generate PDF, you need at least one of this tools:
- ``convert(1)``: ImageMagick (https://www.imagemagick.org)
+ - ``inkscape(1)``: Inkscape (https://inkscape.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
+ DOT: see https://www.graphviz.org/content/dot-language
"""
@@ -49,36 +52,21 @@ import os
from os import path
import subprocess
from hashlib import sha1
-import sys
-
+import re
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
-
-import kernellog
-
-PY3 = sys.version_info[0] == 3
+from sphinx.util import logging
-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
+Figure = images.Figure
__version__ = '1.0.0'
+logger = logging.getLogger('kfigure')
+
# simple helper
# -------------
@@ -121,10 +109,20 @@ def pass_handle(self, node): # pylint: disable=W0613
# Graphviz's dot(1) support
dot_cmd = None
+# dot(1) -Tpdf should be used
+dot_Tpdf = False
# ImageMagick' convert(1) support
convert_cmd = None
+# librsvg's rsvg-convert(1) support
+rsvg_convert_cmd = None
+
+# Inkscape's inkscape(1) support
+inkscape_cmd = None
+# Inkscape prior to 1.0 uses different command options
+inkscape_ver_one = False
+
def setup(app):
# check toolchain first
@@ -167,28 +165,69 @@ def setup(app):
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
- kernellog.verbose(app, "kfigure: check installed tools ...")
+ global dot_cmd, dot_Tpdf, convert_cmd, rsvg_convert_cmd # pylint: disable=W0603
+ global inkscape_cmd, inkscape_ver_one # pylint: disable=W0603
+ logger.verbose("kfigure: check installed tools ...")
dot_cmd = which('dot')
convert_cmd = which('convert')
+ rsvg_convert_cmd = which('rsvg-convert')
+ inkscape_cmd = which('inkscape')
if dot_cmd:
- kernellog.verbose(app, "use dot(1) from: " + dot_cmd)
+ logger.verbose("use dot(1) from: " + dot_cmd)
+
+ try:
+ dot_Thelp_list = subprocess.check_output([dot_cmd, '-Thelp'],
+ stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as err:
+ dot_Thelp_list = err.output
+ pass
+
+ dot_Tpdf_ptn = b'pdf'
+ dot_Tpdf = re.search(dot_Tpdf_ptn, dot_Thelp_list)
else:
- kernellog.warn(app, "dot(1) not found, for better output quality install "
- "graphviz from http://www.graphviz.org")
- if convert_cmd:
- kernellog.verbose(app, "use convert(1) from: " + convert_cmd)
+ logger.warning(
+ "dot(1) not found, for better output quality install graphviz from https://www.graphviz.org"
+ )
+ if inkscape_cmd:
+ logger.verbose("use inkscape(1) from: " + inkscape_cmd)
+ inkscape_ver = subprocess.check_output([inkscape_cmd, '--version'],
+ stderr=subprocess.DEVNULL)
+ ver_one_ptn = b'Inkscape 1'
+ inkscape_ver_one = re.search(ver_one_ptn, inkscape_ver)
+ convert_cmd = None
+ rsvg_convert_cmd = None
+ dot_Tpdf = False
+
else:
- kernellog.warn(app,
- "convert(1) not found, for SVG to PDF conversion install "
- "ImageMagick (https://www.imagemagick.org)")
+ if convert_cmd:
+ logger.verbose("use convert(1) from: " + convert_cmd)
+ else:
+ logger.verbose(
+ "Neither inkscape(1) nor convert(1) found.\n"
+ "For SVG to PDF conversion, install either Inkscape (https://inkscape.org/) (preferred) or\n"
+ "ImageMagick (https://www.imagemagick.org)"
+ )
+
+ if rsvg_convert_cmd:
+ logger.verbose("use rsvg-convert(1) from: " + rsvg_convert_cmd)
+ logger.verbose("use 'dot -Tsvg' and rsvg-convert(1) for DOT -> PDF conversion")
+ dot_Tpdf = False
+ else:
+ logger.verbose(
+ "rsvg-convert(1) not found.\n"
+ " SVG rendering of convert(1) is done by ImageMagick-native renderer."
+ )
+ if dot_Tpdf:
+ logger.verbose("use 'dot -Tpdf' for DOT -> PDF conversion")
+ else:
+ logger.verbose("use 'dot -Tsvg' and convert(1) for DOT -> PDF conversion")
# integrate conversion tools
@@ -222,13 +261,12 @@ def convert_image(img_node, translator, src_fname=None):
# in kernel builds, use 'make SPHINXOPTS=-v' to see verbose messages
- kernellog.verbose(app, 'assert best format for: ' + img_node['uri'])
+ logger.verbose('assert best format for: ' + img_node['uri'])
if in_ext == '.dot':
if not dot_cmd:
- kernellog.verbose(app,
- "dot from graphviz not available / include DOT raw.")
+ logger.verbose("dot from graphviz not available / include DOT raw.")
img_node.replace_self(file2literal(src_fname))
elif translator.builder.format == 'latex':
@@ -254,9 +292,12 @@ def convert_image(img_node, translator, src_fname=None):
elif in_ext == '.svg':
if translator.builder.format == 'latex':
- if convert_cmd is None:
- kernellog.verbose(app,
- "no SVG to PDF conversion available / include SVG raw.")
+ if not inkscape_cmd and convert_cmd is None:
+ logger.warning(
+ "no SVG to PDF conversion available / include SVG raw.\n"
+ "Including large raw SVGs can cause xelatex error.\n"
+ "Install Inkscape (preferred) or ImageMagick."
+ )
img_node.replace_self(file2literal(src_fname))
else:
dst_fname = path.join(translator.builder.outdir, fname + '.pdf')
@@ -266,22 +307,28 @@ def convert_image(img_node, translator, src_fname=None):
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:]
+ _name = dst_fname[len(str(translator.builder.outdir)) + 1:]
if isNewer(dst_fname, src_fname):
- kernellog.verbose(app,
- "convert: {out}/%s already exists and is newer" % _name)
+ logger.verbose("convert: {out}/%s already exists and is newer" % _name)
else:
ok = False
mkdir(path.dirname(dst_fname))
if in_ext == '.dot':
- kernellog.verbose(app, 'convert DOT to: {out}/' + _name)
- ok = dot2format(app, src_fname, dst_fname)
+ logger.verbose('convert DOT to: {out}/' + _name)
+ if translator.builder.format == 'latex' and not dot_Tpdf:
+ svg_fname = path.join(translator.builder.outdir, fname + '.svg')
+ ok1 = dot2format(app, src_fname, svg_fname)
+ ok2 = svg2pdf_by_rsvg(app, svg_fname, dst_fname)
+ ok = ok1 and ok2
+
+ else:
+ ok = dot2format(app, src_fname, dst_fname)
elif in_ext == '.svg':
- kernellog.verbose(app, 'convert SVG to: {out}/' + _name)
+ logger.verbose('convert SVG to: {out}/' + _name)
ok = svg2pdf(app, src_fname, dst_fname)
if not ok:
@@ -310,27 +357,77 @@ def dot2format(app, dot_fname, out_fname):
with open(out_fname, "w") as out:
exit_code = subprocess.call(cmd, stdout = out)
if exit_code != 0:
- kernellog.warn(app,
+ logger.warning(
"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.
+ """Converts SVG to PDF with ``inkscape(1)`` or ``convert(1)`` command.
- Uses ``convert(1)`` from ImageMagick (https://www.imagemagick.org) for
- conversion. Returns ``True`` on success and ``False`` if an error occurred.
+ Uses ``inkscape(1)`` from Inkscape (https://inkscape.org/) or ``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)
+ cmd_name = 'convert(1)'
+
+ if inkscape_cmd:
+ cmd_name = 'inkscape(1)'
+ if inkscape_ver_one:
+ cmd = [inkscape_cmd, '-o', pdf_fname, svg_fname]
+ else:
+ cmd = [inkscape_cmd, '-z', '--export-pdf=%s' % pdf_fname, svg_fname]
+
+ try:
+ warning_msg = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+ exit_code = 0
+ except subprocess.CalledProcessError as err:
+ warning_msg = err.output
+ exit_code = err.returncode
+ pass
+
if exit_code != 0:
- kernellog.warn(app, "Error #%d when calling: %s" % (exit_code, " ".join(cmd)))
+ logger.warning("Error #%d when calling: %s" %
+ (exit_code, " ".join(cmd)))
+ if warning_msg:
+ logger.warning( "Warning msg from %s: %s" %
+ (cmd_name, str(warning_msg, 'utf-8')))
+ elif warning_msg:
+ logger.verbose("Warning msg from %s (likely harmless):\n%s" %
+ (cmd_name, str(warning_msg, 'utf-8')))
+
return bool(exit_code == 0)
+def svg2pdf_by_rsvg(app, svg_fname, pdf_fname):
+ """Convert SVG to PDF with ``rsvg-convert(1)`` command.
+
+ * ``svg_fname`` pathname of input SVG file, including extension ``.svg``
+ * ``pdf_fname`` pathname of output PDF file, including extension ``.pdf``
+
+ Input SVG file should be the one generated by ``dot2format()``.
+ SVG -> PDF conversion is done by ``rsvg-convert(1)``.
+
+ If ``rsvg-convert(1)`` is unavailable, fall back to ``svg2pdf()``.
+
+ """
+
+ if rsvg_convert_cmd is None:
+ ok = svg2pdf(app, svg_fname, pdf_fname)
+ else:
+ cmd = [rsvg_convert_cmd, '--format=pdf', '-o', pdf_fname, svg_fname]
+ # use stdout and stderr from parent
+ exit_code = subprocess.call(cmd)
+ if exit_code != 0:
+ logger.warning("Error #%d when calling: %s" %
+ (exit_code, " ".join(cmd)))
+ ok = bool(exit_code == 0)
+
+ return ok
+
# image handling
# ---------------------
@@ -348,7 +445,7 @@ class kernel_image(nodes.image):
pass
class KernelImage(images.Image):
- u"""KernelImage directive
+ """KernelImage directive
Earns everything from ``.. image::`` directive, except *remote URI* and
*glob* pattern. The KernelImage wraps a image node into a
@@ -384,7 +481,7 @@ class kernel_figure(nodes.figure):
"""Node for ``kernel-figure`` directive."""
class KernelFigure(Figure):
- u"""KernelImage directive
+ """KernelImage directive
Earns everything from ``.. figure::`` directive, except *remote URI* and
*glob* pattern. The KernelFigure wraps a figure node into a kernel_figure
@@ -421,15 +518,15 @@ def visit_kernel_render(self, node):
app = self.builder.app
srclang = node.get('srclang')
- kernellog.verbose(app, 'visit kernel-render node lang: "%s"' % (srclang))
+ logger.verbose('visit kernel-render node lang: "%s"' % srclang)
tmp_ext = RENDER_MARKUP_EXT.get(srclang, None)
if tmp_ext is None:
- kernellog.warn(app, 'kernel-render: "%s" unknown / include raw.' % (srclang))
+ logger.warning( 'kernel-render: "%s" unknown / include raw.' % srclang)
return
if not dot_cmd and tmp_ext == '.dot':
- kernellog.verbose(app, "dot from graphviz not available / include raw.")
+ logger.verbose("dot from graphviz not available / include raw.")
return
literal_block = node[0]
@@ -460,7 +557,7 @@ class kernel_render(nodes.General, nodes.Inline, nodes.Element):
pass
class KernelRender(Figure):
- u"""KernelRender directive
+ """KernelRender directive
Render content by external tool. Has all the options known from the
*figure* directive, plus option ``caption``. If ``caption`` has a
@@ -540,7 +637,7 @@ def add_kernel_figure_to_std_domain(app, doctree):
docname = app.env.docname
labels = std.data["labels"]
- for name, explicit in iteritems(doctree.nametypes):
+ for name, explicit in doctree.nametypes.items():
if not explicit:
continue
labelid = doctree.nameids[name]