diff --git a/CHANGES b/CHANGES index e1fe0ea2a..1543bec91 100644 --- a/CHANGES +++ b/CHANGES @@ -106,7 +106,8 @@ Features added ``suppress_warnings`` * #2803: Discovery of builders by entry point * #1764, #1676: Allow setting 'rel' and 'title' attributes for stylesheets -* #3589: Support remote images +* #3589: Support remote images on non-HTML builders +* #3589: Support images in Data URI on non-HTML builders Bugs fixed ---------- diff --git a/doc/config.rst b/doc/config.rst index ca27f384f..5e641cbfb 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -227,7 +227,6 @@ General configuration * app.add_generic_role * app.add_source_parser * download.not_readable - * image.data_uri * image.not_readable * ref.term * ref.ref diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 15eaac926..8c9c03f34 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -65,6 +65,7 @@ class Builder(object): #: Image files are searched in the order in which they appear here. supported_image_types = [] # type: List[unicode] supported_remote_images = True + supported_data_uri_images = False def __init__(self, app): # type: (Sphinx) -> None diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 0fd5a43ef..d09886309 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -103,6 +103,7 @@ class StandaloneHTMLBuilder(Builder): html_scaled_image_link = True supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', 'image/jpeg'] + supported_data_uri_images = True searchindex_filename = 'searchindex.js' add_permalinks = True allow_sharp_as_current_path = True diff --git a/sphinx/environment/collectors/asset.py b/sphinx/environment/collectors/asset.py index 36c451765..3a0e1fefd 100644 --- a/sphinx/environment/collectors/asset.py +++ b/sphinx/environment/collectors/asset.py @@ -59,8 +59,6 @@ class ImageCollector(EnvironmentCollector): node['candidates'] = candidates imguri = node['uri'] if imguri.startswith('data:'): - logger.warning('image data URI found. some builders might not support', - location=node, type='image', subtype='data_uri') candidates['?'] = imguri continue elif imguri.find('://') != -1: diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index ab7b75837..d2c6cbe25 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -10,13 +10,14 @@ """ import os +from hashlib import sha1 from six import text_type from docutils import nodes from sphinx.transforms import SphinxTransform from sphinx.util import logging, requests -from sphinx.util.images import guess_mimetype +from sphinx.util.images import guess_mimetype, get_image_extension, parse_data_uri from sphinx.util.osutil import ensuredir if False: @@ -43,6 +44,11 @@ class BaseImageConverter(SphinxTransform): # type: (nodes.Node) -> None pass + @property + def imagedir(self): + # type: () -> unicode + return os.path.join(self.app.doctreedir, 'images') + class ImageDownloader(BaseImageConverter): default_priority = 100 @@ -56,14 +62,13 @@ class ImageDownloader(BaseImageConverter): def handle(self, node): # type: (nodes.Node) -> None - imgdir = os.path.join(self.app.doctreedir, 'images') basename = os.path.basename(node['uri']) if '?' in basename: basename = basename.split('?')[0] dirname = node['uri'].replace('://', '/').translate({ord("?"): u"/", ord("&"): u"/"}) - ensuredir(os.path.join(imgdir, dirname)) - path = os.path.join(imgdir, dirname, basename) + ensuredir(os.path.join(self.imagedir, dirname)) + path = os.path.join(self.imagedir, dirname, basename) try: r = requests.get(node['uri']) if r.status_code != 200: @@ -85,9 +90,43 @@ class ImageDownloader(BaseImageConverter): (node['uri'], text_type(exc))) +class DataURIExtractor(BaseImageConverter): + default_priority = 150 + + def match(self, node): + # type: (nodes.Node) -> bool + if self.app.builder.supported_data_uri_images: + return False + else: + return 'data:' in node['uri'] + + def handle(self, node): + # type: (nodes.Node) -> None + image = parse_data_uri(node['uri']) + ext = get_image_extension(image.mimetype) + if ext is None: + logger.warning('Unknown image format: %s...', node['uri'][:32], + location=node) + return + + ensuredir(os.path.join(self.imagedir, 'embeded')) + digest = sha1(image.data).hexdigest() + path = os.path.join(self.imagedir, 'embeded', digest + ext) + self.app.env.original_image_uri[path] = node['uri'] + + with open(path, 'wb') as f: + f.write(image.data) + + node['candidates'].pop('?') + node['candidates'][image.mimetype] = path + node['uri'] = path + self.app.env.images.add_file(self.env.docname, path) + + def setup(app): # type: (Sphinx) -> Dict[unicode, Any] app.add_post_transform(ImageDownloader) + app.add_post_transform(DataURIExtractor) return { 'version': 'builtin',