Repository: sphinx-doc/sphinx

Issue:
LaTeX/ImageConverter: Regression with `data:` URIs
### Describe the bug

Images with `data:` URIs are not converted correctly in LaTeX output.

```rst
.. image:: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHv0lEQVR42u2ae2xbZxnGf+f4OLbjOHGcNq2bpqF12tJmZGtJttK1m7Z1XMomVQyp4zYESEMwMNLEKEgIbQJNFKkIVUNogJj2B9u6cYkETBUtl5UNWDt10NGMbiU0G+klpE6aq9PYMX+c5xRjEl8SJ3WcvdKR7ePvXN7ney/P+34fvCWLW4x5eIYHCAGV+v2PcgcgBFTpewB4J/AxYDmQAKLAaeBcOQLQKgW3AymgETgL1AJHgAlgI9ADPAQcB8bLxZ1WAZ3AYaALuBdo0flrgTogLID6gDc0xlMOynuAm4DXgU9plj1Zxt4B/BM4JYAWvPL3AX8VCPnO6A5gRFayoKUFiAOvFTibzcDfgTuvphuYRbjHkPz518BAAVbTCkwCr17NQGgW6T5J+fV1BQCwRFnhcjnEgE/KnDs1s6vEB6aSauDzwN+ALeWSAquV0rqApwXG9wXGBoERBtqkfB8wCqwtJyLkEevbI8Vf1Kcf+JNA2CzljwPfBl4qRyocltIjwBdEfA4ClwA38Czwu1KhwnMpzcAJ4CsCpU5HSTE/aw4D463KMs8shtnOVP5dmv0DQH0pv6yriEonFQRvBR5QKhwS5+8W6Sm7hkgIWA2skDs9CgwCPs38oAqku4EzpQiANYOZ9qu+r1VO3ykKvAL4I/BlBbuvqyFyXkCUhW9/GHhMjG8UeAL4lUx/raK9B7gfeEXjy6Lic7o9J4CngJeB29KaHOnSJo7/jK7pFBCeAtwqXGrKR4BHRG+bs1BYj/i9o3QYeBz4XpbaIPM5PwJ+Pl8gWHnOyEOq+b+J3dCcTvl3Aw+rOHpZ8WJCDDCXbAc+I6sx8rxmXsrhNcBu7LZ2LMu4cY1ZA1xMq/ETOZSJAB8EfgbcJTf7kHoMJSHbsXt3ufw43fdDGYHzpFwns2Z4P3Yr7d/AT4CtiislIxv0go/n4ZOblBnap6gJOoEPCJCIssZBoFfl871XK/BZOdLeJqBBFd25HP2Aj4jxXcj477JYYEQz/B2gQue+IYuJUaLrA+/Ns3HRKjN+aopoH1JNMKEAekIz3kyJrwlEFMmfziOFNWN3haeLE3XAjdgd4I2lpHiuNFghJpdP6XsZODqNKV8EXtDYkjL1bGlwQn56KAcIK4HPYjc5YzmeV3J+ns0C3lCaupDjxR3SEl+I/D6XC+RLRlzYCyMjCw2A2S6MpK/wHGUBLnUXY2XInZbvWYwAFKOztOAB8CplLjoAnC0wg7rXChbYjo+Zmm4IuBnYh935jYoP7AZ+KtITw+4b9k9zj9hsXrznc22pzHMNj7xkzAcAEeBBKXsEey3QqfbukVVMihhtVDWZmR3iwNdmAsJUis8GCGMGM/8tfX9SijtVok//+4DrsbfAtE0BgEOHv1QoAPkoXygIRgGKh+Tj+1TRPZBFAQ/2XsFQxpha7LY5hQJQiPKFgJBvT3CfCp4B+Xsu3j+u42LG+RR2i8w3VzOfeV0uEPIBIKCmSBJ7IeTILGLXiK7/qixkShCjHe0GwP5dx1IUWaId7Vcy3/5dxyaNaEe7S65gKHilgJQeHgZukblGZ6m8Iy3AMezNk6+nvZhbE2Ly3zXLxJ7DqVnXF3t3GC4xVrfc0wHhsgWs0x+OeY4Dw+tvClWc+kPsYVKMKLqf0MyYafFjMg2sfMWhzDUC36vYUIe9cSqgc5PqRhVD/Lr3crm017FIC/iEzNGUifYD55dG/K7Xno/dabqNQ81bamPvuT9Sqxt500AYB8aiHe2jUiyRByAXgV+YLuOev/zywuh1dywLaRLWiUvU6DkUEYA12DvY1qp75ZMOMQt75dZBJAmMAb3V9RXjHr+LyJbQ4NaPNtyoC5ZiN0AtKXsJuxf4L/UNBoB4tKM9ofs5LpUEJvfvOpaq8LuqJ0aTrcvW+X/89pvrtgLX9nWP3mC6jKWhlb7KDICLFQN2qiW3WpbglgXHLQU4x6RTAD0nhxr//MRZ831fbO4Pb6jaZlWY16SlN2/a2GExwR5R4h7R4iFZxLA+B4GRaEf72IsHehqPd5z3N7ZWbxofTS7p7RpZ+/xjby4DjJ17mlPBsHcuCqvdQJMsy9SRAgJWRj1gxN4c47kfdJuJ8ST+kDtkVZhBDZ6cgjs4ceNK7JAbjelzQGD0K+InVrcFm04e6gsaBtuPHugJ9p4etUKNXsPtc+HxW8YUAYw9h1OzCYAXxEitjPc3/i8NJiYmOfvqMKFGHy63QWXQbaRF5NQ05Mmdli6XZJh9QhYQd9igp8qqtDymv/O3fVUrrwkYbXeFWfmOAKZl4queky1L9dkInxHtaL8Cb8/JIZ7de5qmzTVcv3sFwbB3Ng9OZXx3DnPgXNzoOzNqhNdX4QlYWO7cRelMrGDvjtzedOXJ8aEEXUf7adpcQ8vtS/HVzHpx1kg7nNxuAWYw7DXe1hbEH6rIS/l8lZnJeNcNdzc8CGB5TOojfupW+fj9o92MXZrAG7BwWQaWxyy6XZquwmPdC2sMtnUVF6z/cQFHzp0a5jffPQOpFMvXV7Ht4414A3O1pXBmMpVLFGol09YC4fVV3PLpJp77YTfJiRTx4UTJATATZQtqiTW0BLjtvtVUVLp45WAv8aEE5ShZp3VZs5/qervFV2oWMC8AAHOVm0tGTBa5vAXAYgfgP6N1EOJNty18AAAAAElFTkSuQmCC
```

This is the error when running LaTeX:

```
! LaTeX Error: File `{data:image/png;base64,[...]}' not found.
```

### How to Reproduce

Try to build the LaTeX/PDF from https://github.com/mgeier/rsvgconverter-bug, after disabling the `rsvg_converter_bin` setting in `conf.py` (https://github.com/mgeier/rsvgconverter-bug/blob/8e107a6c7fca594a30c540117007716e967ee83e/conf.py#L5C1-L5C19).

According to `git bisect`, this is the culprit: 0a76199f994ea07ad8429fab3e1798fe01cdde80 (released in Sphinx 7.3.0).

### Environment Information

```text
Sphinx 7.3.0+
```


### Sphinx extensions

```python
['sphinxcontrib.rsvgconverter']
```


### Additional context

See #10073 and #10118, which might be related.


Discussion:
I've been investigating this, after recently [modifying](https://github.com/sphinx-doc/sphinx/pull/12274) some image-download code -- I was wondering if that could be related.  It doesn't seem to be, but I'll report some findings from along the way:

An important part of 0a76199f994ea07ad8429fab3e1798fe01cdde80 seems to be:

```diff
@@ -117,7 +117,7 @@ class DataURIExtractor(BaseImageConverter):
     default_priority = 150
 
     def match(self, node: nodes.image) -> bool:
-        if self.app.builder.supported_remote_images == []:
+        if not self.app.builder.supported_remote_images:
             return False
         if self.app.builder.supported_data_uri_images is True:
             return False
```

...in particular because [`LaTeXBuilder.supported_remote_images`](https://github.com/sphinx-doc/sphinx/blob/0a76199f994ea07ad8429fab3e1798fe01cdde80/sphinx/builders/latex/__init__.py#L120) had, and still has, a boolean value (`False`), not a list value.

Before 0a76199f994ea07ad8429fab3e1798fe01cdde80, the `DataURIExtractor` could successfully match data URIs during `latex` builds: the above condition would always fail, allowing fallback to the final [check for the `data:` protocol scheme](https://github.com/sphinx-doc/sphinx/blob/092bac7f9ca21afd3373a64544da40bab2cab4e0/sphinx/transforms/post_transforms/images.py#L122) (v7.3.0 permalink).



Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 675c7f77c0a..39a6d2c6a14 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -73,6 +73,9 @@ Bugs fixed
 * #12459: Add valid-type arguments to the ``linkcheck_rate_limit_timeout``
   configuration setting.
   Patch by James Addison.
+* #12331: Resolve data-URI-image-extraction regression from v7.3.0 affecting
+  builders without native support for data-URIs in their output format.
+  Patch by James Addison.
 
 Improvements
 ------------
diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py
index bfbf116570e..de25ab06a2d 100644
--- a/sphinx/builders/__init__.py
+++ b/sphinx/builders/__init__.py
@@ -71,9 +71,9 @@ class Builder:
     #: The list of MIME types of image formats supported by the builder.
     #: Image files are searched in the order in which they appear here.
     supported_image_types: list[str] = []
-    #: The builder supports remote images or not.
+    #: The builder can produce output documents that may fetch external images when opened.
     supported_remote_images = False
-    #: The builder supports data URIs or not.
+    #: The file format produced by the builder allows images to be embedded using data-URIs.
     supported_data_uri_images = False
 
     def __init__(self, app: Sphinx, env: BuildEnvironment) -> None:
diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py
index 589a8d6abb9..b679481e83d 100644
--- a/sphinx/transforms/post_transforms/images.py
+++ b/sphinx/transforms/post_transforms/images.py
@@ -115,10 +115,8 @@ class DataURIExtractor(BaseImageConverter):
     default_priority = 150
 
     def match(self, node: nodes.image) -> bool:
-        if not self.app.builder.supported_remote_images:
-            return False
         if self.app.builder.supported_data_uri_images is True:
-            return False
+            return False  # do not transform the image; data URIs are valid in the build output
         return node['uri'].startswith('data:')
 
     def handle(self, node: nodes.image) -> None:


Test patch:
diff --git a/tests/roots/test-images/index.rst b/tests/roots/test-images/index.rst
index 9b9aac1e595..f6d7160daad 100644
--- a/tests/roots/test-images/index.rst
+++ b/tests/roots/test-images/index.rst
@@ -27,3 +27,8 @@ test-image
 
 .. non-exist remote image
 .. image:: http://localhost:7777/NOT_EXIST.PNG
+
+.. a self-contained image within a data URI
+   This image was generated using ImageMagick 6.9 with the command ``convert -pointsize 32 -font Noto-Sans-Egyptian-Hieroglyphs-Regular caption:$(printf '\U13080') -trim -border 2 -monochrome eoh.png``
+.. image:: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAjAQAAAADKt6U+AAAAAmJLR0QAAd2KE6QAAAAHdElNRQfoBQIVBgOBlOMTAAAAEGNhTnYAAAAtAAAAOwAAAAEAAAATst46RgAAAJtJREFUCNdNz70KwkAMAOA8iOhjuGh9HB9BCtoTHHwMH0Mc7KWTmx0dHDpovUk6HCil3sUmATHLR/4IAeJA+LEWPmbEeHJMWbTMZDA0CNFn8x1COFPaIHQ55R7hlZGdIjwj2aovRjJbhPvMLNN+r0g2vB7ByIWbHqqVh3LR3lhZWM0qYV8qjU6+lc4J7ZVx4SjEINBKOSinv/+YL1xvsJE6ztdqAAAADHRFWHRjYXB0aW9uAPCTgoD4hdKUAAAAD3RFWHRjYXB0aW9uOmxpbmVzADGoBz2RAAAAAElFTkSuQmCC
+   :alt: The Eye of Horus in a black font on a white background.
diff --git a/tests/test_builders/test_build_epub.py b/tests/test_builders/test_build_epub.py
index 6829f22dd26..461ff8cc619 100644
--- a/tests/test_builders/test_build_epub.py
+++ b/tests/test_builders/test_build_epub.py
@@ -409,6 +409,7 @@ def test_copy_images(app, status, warning):
     images = {image.name for image in images_dir.rglob('*')}
     images.discard('python-logo.png')
     assert images == {
+        # 'ba30773957c3fe046897111afd65a80b81cad089.png',  # epub: image from data:image/png URI in source
         'img.png',
         'rimg.png',
         'rimg1.png',
diff --git a/tests/test_builders/test_build_html_image.py b/tests/test_builders/test_build_html_image.py
index f385d11891e..365b0110ff0 100644
--- a/tests/test_builders/test_build_html_image.py
+++ b/tests/test_builders/test_build_html_image.py
@@ -72,6 +72,7 @@ def test_copy_images(app, status, warning):
     images_dir = Path(app.outdir) / '_images'
     images = {image.name for image in images_dir.rglob('*')}
     assert images == {
+        # 'ba30773957c3fe046897111afd65a80b81cad089.png',  # html: image from data:image/png URI in source
         'img.png',
         'rimg.png',
         'rimg1.png',
diff --git a/tests/test_builders/test_build_latex.py b/tests/test_builders/test_build_latex.py
index 2c3cae18bb5..88c83560b87 100644
--- a/tests/test_builders/test_build_latex.py
+++ b/tests/test_builders/test_build_latex.py
@@ -1711,6 +1711,7 @@ def test_copy_images(app, status, warning):
     }
     images.discard('sphinx.png')
     assert images == {
+        'ba30773957c3fe046897111afd65a80b81cad089.png',  # latex: image from data:image/png URI in source
         'img.pdf',
         'rimg.png',
         'testimäge.png',
diff --git a/tests/test_builders/test_build_texinfo.py b/tests/test_builders/test_build_texinfo.py
index f9effb2c066..3b8e27662ea 100644
--- a/tests/test_builders/test_build_texinfo.py
+++ b/tests/test_builders/test_build_texinfo.py
@@ -124,6 +124,7 @@ def test_copy_images(app, status, warning):
     images = {image.name for image in images_dir.rglob('*')}
     images.discard('python-logo.png')
     assert images == {
+        'ba30773957c3fe046897111afd65a80b81cad089.png',  # texinfo: image from data:image/png URI in source
         'img.png',
         'rimg.png',
         'testimäge.png',