1"""
2    sphinx.ext.imgconverter
3    ~~~~~~~~~~~~~~~~~~~~~~~
4
5    Image converter extension for Sphinx
6
7    :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
8    :license: BSD, see LICENSE for details.
9"""
10
11import subprocess
12import sys
13from subprocess import PIPE, CalledProcessError
14from typing import Any, Dict
15
16from sphinx.application import Sphinx
17from sphinx.errors import ExtensionError
18from sphinx.locale import __
19from sphinx.transforms.post_transforms.images import ImageConverter
20from sphinx.util import logging
21
22logger = logging.getLogger(__name__)
23
24
25class ImagemagickConverter(ImageConverter):
26    conversion_rules = [
27        ('image/svg+xml', 'image/png'),
28        ('image/gif', 'image/png'),
29        ('application/pdf', 'image/png'),
30        ('application/illustrator', 'image/png'),
31    ]
32
33    def is_available(self) -> bool:
34        """Confirms the converter is available or not."""
35        try:
36            args = [self.config.image_converter, '-version']
37            logger.debug('Invoking %r ...', args)
38            subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True)
39            return True
40        except OSError:
41            logger.warning(__('convert command %r cannot be run, '
42                              'check the image_converter setting'),
43                           self.config.image_converter)
44            return False
45        except CalledProcessError as exc:
46            logger.warning(__('convert exited with error:\n'
47                              '[stderr]\n%r\n[stdout]\n%r'),
48                           exc.stderr, exc.stdout)
49            return False
50
51    def convert(self, _from: str, _to: str) -> bool:
52        """Converts the image to expected one."""
53        try:
54            # append an index 0 to source filename to pick up the first frame
55            # (or first page) of image (ex. Animation GIF, PDF)
56            _from += '[0]'
57
58            args = ([self.config.image_converter] +
59                    self.config.image_converter_args +
60                    [_from, _to])
61            logger.debug('Invoking %r ...', args)
62            subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True)
63            return True
64        except OSError:
65            logger.warning(__('convert command %r cannot be run, '
66                              'check the image_converter setting'),
67                           self.config.image_converter)
68            return False
69        except CalledProcessError as exc:
70            raise ExtensionError(__('convert exited with error:\n'
71                                    '[stderr]\n%r\n[stdout]\n%r') %
72                                 (exc.stderr, exc.stdout)) from exc
73
74
75def setup(app: Sphinx) -> Dict[str, Any]:
76    app.add_post_transform(ImagemagickConverter)
77    if sys.platform == 'win32':
78        # On Windows, we use Imagemagik v7 by default to avoid the trouble for
79        # convert.exe bundled with Windows.
80        app.add_config_value('image_converter', 'magick', 'env')
81        app.add_config_value('image_converter_args', ['convert'], 'env')
82    else:
83        # On other platform, we use Imagemagick v6 by default.  Especially,
84        # Debian/Ubuntu are still based of v6.  So we can't use "magick" command
85        # for these platforms.
86        app.add_config_value('image_converter', 'convert', 'env')
87        app.add_config_value('image_converter_args', [], 'env')
88
89    return {
90        'version': 'builtin',
91        'parallel_read_safe': True,
92        'parallel_write_safe': True,
93    }
94