├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .readthedocs.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.rst ├── docs ├── Makefile ├── _static │ └── custom.css ├── conf.py ├── img.jpg ├── index.rst ├── make.bat └── rtd-requirements.txt ├── pyproject.toml ├── release-to-pypi.sh ├── sphinxcontrib └── images.py ├── sphinxcontrib_images_lightbox2 ├── __init__.py ├── lightbox2-customize │ ├── jquery-noconflict.js │ └── pointer.css └── lightbox2.py └── tox.ini /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test Python ${{ matrix.python-version }} 8 | 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | python-version: 15 | - '3.9' 16 | - '3.10' 17 | - '3.11' 18 | - '3.12' 19 | - '3.13' 20 | - pypy-3.10 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | submodules: true 26 | 27 | - name: Set up Python ${{ matrix.python-version }} 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | 32 | - name: Cache 33 | uses: actions/cache@v4 34 | with: 35 | path: ~/.cache/pip 36 | key: pip-test-python-${{ matrix.python-version }}-${{ hashFiles('**/tox.ini', '**/pyproject.toml') }} 37 | restore-keys: | 38 | pip-test-python-${{ matrix.python-version }}-${{ hashFiles('**/tox.ini', '**/pyproject.toml') }} 39 | pip-test-python-${{ matrix.python-version }} 40 | pip-test- 41 | 42 | - name: Install dependencies 43 | run: | 44 | python -m pip install --upgrade pip setuptools wheel 45 | python -m pip install --upgrade tox tox-gh-actions 46 | 47 | - name: Tox tests 48 | run: | 49 | tox 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /docs/_images 2 | /docs/_build 3 | /.tox 4 | *~ 5 | *pyc 6 | *egg* 7 | build 8 | dist 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "sphinxcontrib_images_lightbox2/lightbox2"] 2 | path = sphinxcontrib_images_lightbox2/lightbox2 3 | url = https://github.com/lokesh/lightbox2.git 4 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | # Build documentation with MkDocs 13 | #mkdocs: 14 | # configuration: mkdocs.yml 15 | 16 | # Optionally build your docs in additional formats such as PDF and ePub 17 | formats: 18 | - htmlzip 19 | 20 | # Optionally set the version of Python and requirements required to build your docs 21 | python: 22 | version: 3.7 23 | install: 24 | - requirements: docs/rtd-requirements.txt 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.9.4] - 2021-08-09 9 | 10 | ### Changed 11 | 12 | - Updated the included Lightbox2 backend to version 2.11.3 and with it jQuery to 13 | version 3.4.1. 14 | 15 | ### Fixed 16 | 17 | - Issue where explicit targets for thumbnails were ignored. 18 | `.. _my target:` before a thumbnail directive would not make the thumbnail 19 | referenceable by `:ref:Title `. 20 | 21 | 22 | 23 | ## [0.9.3] - 2021-04-27 24 | 25 | ### Fixed 26 | 27 | - Issue where adding package configuration options to conf.py 28 | (e.g. `images_config={'show_caption'=True}`) would cause incremental builds 29 | to stop working. 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | sphinxcontrib-images 2 | ==================== 3 | 4 | sphinxcontrib-images (formerly `sphinxcontrib-fancybox 5 | `_). 6 | 7 | Easy thumbnails in Sphinx documentation (focused on HTML). 8 | 9 | * `Documentation `_ 10 | * `Repository (GitHub) `_ 11 | * `PyPI `_ 12 | * `GitHub actions `_ 13 | 14 | .. image:: https://github.com/sphinx-contrib/images/actions/workflows/ci.yml/badge.svg 15 | :target: https://github.com/sphinx-contrib/images/actions/workflows/ci.yml 16 | :alt: GitHub Actions 17 | 18 | Features 19 | -------- 20 | 21 | * Show thumbnails instead of full size images inside documentation (HTML). 22 | * Ability to zoom/enlarge picture using LightBox2 (HTML). 23 | * Ability to group pictures 24 | * Download remote pictures and keep it in cache (if requested) 25 | * Support for other formats (latex, epub, ... - fallback to image directive) 26 | * Easy to extend (add own backend in only few lines of code) 27 | 28 | * Add other HTML "preview" solution than LightBox2 29 | * Add better support to non-HTML outputs 30 | * Preprocess images 31 | 32 | TODO 33 | ^^^^ 34 | 35 | * Make proper thumbnails (scale down images) 36 | 37 | How to install? 38 | --------------- 39 | 40 | Instalation through pip: :: 41 | 42 | pip install sphinxcontrib-images 43 | 44 | or through the GitHub: :: 45 | 46 | pip install git+https://github.com/sphinx-contrib/images 47 | 48 | Next, you have to add extension to ``conf.py`` in your Sphinx project. :: 49 | 50 | extensions = [ 51 | … 52 | 'sphinxcontrib.images', 53 | … 54 | ] 55 | 56 | 57 | How to use it? 58 | -------------- 59 | 60 | Example: :: 61 | 62 | .. thumbnail:: picture.png 63 | 64 | 65 | You can also override the default ``image`` directive provided by Sphinx. 66 | Check the documentation for all configuration options. 67 | 68 | 69 | Questions and suggestions 70 | ------------------------- 71 | 72 | If you have any suggstions, patches, problems - please use 73 | `GitHub Issues `_. 74 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | .framed img { border: 2px solid black;} 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'sphinxcontrib-images' 21 | copyright = u'2014, Tomasz Czyż' 22 | author = u'Tomasz Czyż' 23 | 24 | from sphinxcontrib import images 25 | version = images.__version__ 26 | 27 | # The full version, including alpha/beta/rc tags 28 | release = '1' 29 | 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | 37 | source_suffix = '.rst' 38 | master_doc = 'index' 39 | 40 | extensions = [ 41 | 'sphinxcontrib.images', 42 | 'sphinx.ext.viewcode', 43 | ] 44 | 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # List of patterns, relative to source directory, that match files and 50 | # directories to ignore when looking for source files. 51 | # This pattern also affects html_static_path and html_extra_path. 52 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 53 | 54 | 55 | # -- Options for HTML output ------------------------------------------------- 56 | 57 | # The theme to use for HTML and HTML Help pages. See the documentation for 58 | # a list of builtin themes. 59 | # 60 | html_theme = 'sphinxdoc' 61 | 62 | #html_theme_options = { } 63 | # Add any paths that contain custom static files (such as style sheets) here, 64 | # relative to this directory. They are copied after the builtin static files, 65 | # so a file named "default.css" will overwrite the builtin "default.css". 66 | html_static_path = ['_static'] 67 | html_css_files = ['custom.css'] 68 | 69 | images_config = dict(backend='LightBox2', 70 | default_image_width='200px' 71 | ) 72 | -------------------------------------------------------------------------------- /docs/img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sphinx-contrib/images/ecb052f6b3b893707fbce879f7db52e3c92c1f50/docs/img.jpg -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | sphinxcontrib-images |version| 2 | ============================== 3 | 4 | Project home: ``_ 5 | 6 | PyPI: ``_ 7 | 8 | 9 | How to install? 10 | --------------- 11 | 12 | :: 13 | 14 | pip install sphinxcontrib-images 15 | 16 | Add extension to ``conf.py`` in your sphinx project. :: 17 | 18 | extensions = [ 19 | … 20 | 'sphinxcontrib.images', 21 | … 22 | ] 23 | 24 | How to configure? 25 | ----------------- 26 | 27 | You can configure behaviour using a dictionary with options placed in ``conf.py``:: 28 | 29 | images_config = { 30 | … 31 | } 32 | 33 | All available options with comments: 34 | 35 | :backend: (default: 'LightBox2') 36 | 37 | ``string`` or ``callable`` 38 | 39 | If ``string`` it has to be the name of the 40 | ``entrypoint`` registered in the 41 | ``sphinxcontrib.images.backend`` group (look at the source 42 | of ``setup.py`` in this project). 43 | 44 | Callable can be a function or a class which 45 | returns an instance of a backend to render images. 46 | The first argument is Sphinx's app instance. Go to 47 | LightBox2 backend to see how to implement that. 48 | 49 | Each backend should implement rendering ``image_node`` on specific outputs. 50 | If methods are not implemented, default ``image`` directive is a fallback. 51 | 52 | By default ``LightBox2`` backends natively support only HTML (other 53 | outputs are supported by fallback-image-directive). 54 | 55 | :override_image_directive: (default: ``False``) 56 | 57 | ``True`` or ``False``. It overrides the default Sphinx ``image`` directive 58 | with the ``thumbnail`` directive provided by this extension. 59 | 60 | :cache_path: (default :``_images``) 61 | 62 | Path, where to keep downloaded images. Relative to 63 | source (actually relative to ``conf.py``) directory or absolute path. 64 | 65 | :requests_kwargs: (default: ``{}``) 66 | 67 | Remote images are downloaded by `requests 68 | `_. This 69 | ``dict`` keeps the keyword arguments that will be 70 | passed to the ``get`` method. 71 | 72 | :default_image_width: (default: ``100%``) 73 | 74 | Default width of an image. Backend can use this 75 | setting to set width properly. 76 | 77 | :default_image_height: (default: ``auto``) 78 | 79 | Default height of an image. Backend can use this 80 | setting to set width properly. 81 | 82 | :default_group: (default: ``None``) 83 | 84 | This setting sets the default group for images without 85 | a defined group. If ``None`` each image is placed in a 86 | separate group, which means that images are not 87 | grouped by default. Otherwise you can set a group 88 | name like ``default`` to group all ungrouped images 89 | together. 90 | 91 | :default_show_title: (default: ``False``) 92 | 93 | Defines whether a caption below the picture should be visible or not. 94 | 95 | .. warning:: 96 | 97 | Currently this options does not work, I have no idea how to 98 | enable this feature in lightbox2. If you have any idea please do 99 | a pull request. 100 | 101 | 102 | :download: (default: ``True``) 103 | 104 | Download remote images. 105 | 106 | 107 | 108 | Thumbnail directive 109 | ------------------- 110 | 111 | You can use it like:: 112 | 113 | .. thumbnail:: path/to/image.jpg 114 | 115 | or:: 116 | 117 | .. thumbnail:: http://remote/image.jpg 118 | 119 | You can pass options like regular Sphinxs' directives:: 120 | 121 | .. thumbnail:: http://remote/image.jpg 122 | :download: true 123 | 124 | All available arguments: 125 | 126 | :group: 127 | 128 | If you set the same group for different images the backend 129 | can *group* them. 130 | 131 | :class: 132 | 133 | This can be used by the backend to apply some style. 134 | 135 | The straightforward use case is to define HTML classes here (LightBox2 136 | backend puts these classes on outer ``a`` element, not inner ``img``). 137 | 138 | :width: 139 | 140 | Backend can use this option to set the width of the 141 | image. This overrides ``default_image_width`` from configuration. 142 | 143 | Values like: 144 | 145 | * percentage ``100%`` 146 | * length with unit ``100px`` 147 | * ``auto`` 148 | 149 | are accepted. 150 | 151 | :height: 152 | 153 | Backend can use this option to set the height of the 154 | image. This overrides ``default_image_height`` from configuration. 155 | 156 | Values like: 157 | 158 | * length with unit: ``100px`` 159 | * ``auto`` 160 | 161 | are accepted. 162 | 163 | :alt: 164 | 165 | If image cannot be displayed, this text will be shown. 166 | 167 | :download: 168 | 169 | This overrides ``download`` from configuration. You can set 170 | for particular image to download it or not. Works only for remote images. 171 | 172 | :title: 173 | 174 | * If you do not define it, ``default_show_title`` configuration option will 175 | be used (it will define whether to show title or not). 176 | 177 | * If you define this option but leave it empty, the content of the 178 | directive will be used as the title:: 179 | 180 | .. thumbnail:: image.jpg 181 | :title: 182 | 183 | This will be a title 184 | 185 | * If you define this option as text, it will be used as title:: 186 | 187 | .. thumbnail:: image.jpg 188 | :title: This is title 189 | 190 | This is description 191 | 192 | It's up to the backend, how this will be displayed. 193 | 194 | Currently I have a problem with LightBox2 to make captions below thumbnails. 195 | If you have any idea how to solve it please write. 196 | 197 | :align: (default: '') 198 | 199 | Align the picture. 200 | 201 | LightBox2 backend uses ``align-`` Sphinx html classes. 202 | By default alignment is not used at all. 203 | 204 | Values like: 205 | 206 | * ``left`` 207 | * ``center`` 208 | * ``right`` 209 | 210 | are accepted. 211 | 212 | .. note:: 213 | 214 | You may want to wrap aligned element with:: 215 | 216 | .. container:: clearfix 217 | 218 | to fix document flow. 219 | 220 | :show_caption: (default: ``False``) 221 | 222 | Show the title as a caption below the image. 223 | 224 | .. warning:: 225 | 226 | Enabling the caption nests the clickable image inside an HTML ``figure`` 227 | which gets the class if defined. 228 | 229 | This mays break existing styles. 230 | 231 | To solve styles compatibility issues, you may use the *legacy_class* argument. 232 | 233 | :legacy_class: 234 | 235 | Only applicable when *show_caption* is ``True``. 236 | 237 | The classese specified are added to the clickable image. 238 | 239 | The ``figure`` HTML element still gets the classes specified by the *class* argument. 240 | 241 | Examples 242 | -------- 243 | 244 | Thumbnail 245 | ^^^^^^^^^ 246 | 247 | .. sourcecode:: rst 248 | 249 | .. thumbnail:: img.jpg 250 | 251 | .. thumbnail:: img.jpg 252 | 253 | Remote image (http) 254 | ^^^^^^^^^^^^^^^^^^^^ 255 | 256 | .. sourcecode:: rst 257 | 258 | .. thumbnail:: http://upload.wikimedia.org/wikipedia/meta/0/08/Wikipedia-logo-v2_1x.png 259 | :download: false 260 | 261 | .. thumbnail:: http://upload.wikimedia.org/wikipedia/meta/0/08/Wikipedia-logo-v2_1x.png 262 | :download: false 263 | 264 | Remote image (https) 265 | ^^^^^^^^^^^^^^^^^^^^ 266 | 267 | .. sourcecode:: rst 268 | 269 | .. thumbnail:: https://upload.wikimedia.org/wikipedia/meta/0/08/Wikipedia-logo-v2_1x.png 270 | :download: false 271 | 272 | .. thumbnail:: https://upload.wikimedia.org/wikipedia/meta/0/08/Wikipedia-logo-v2_1x.png 273 | :download: false 274 | 275 | Remote image (download http) 276 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 277 | 278 | The image is downloaded and placed in `_build/html/_images` 279 | (for html build) making it availble locally. 280 | 281 | .. sourcecode:: rst 282 | 283 | .. thumbnail:: http://upload.wikimedia.org/wikipedia/meta/0/08/Wikipedia-logo-v2_1x.png 284 | :download: true 285 | 286 | .. thumbnail:: http://upload.wikimedia.org/wikipedia/meta/0/08/Wikipedia-logo-v2_1x.png 287 | :download: true 288 | 289 | 290 | Remote image (download https) 291 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 292 | 293 | The image is downloaded and placed in `_build/html/_images` 294 | (for html build) making it availble locally. 295 | 296 | .. sourcecode:: rst 297 | 298 | .. thumbnail:: https://upload.wikimedia.org/wikipedia/meta/0/08/Wikipedia-logo-v2_1x.png 299 | :download: true 300 | 301 | .. thumbnail:: https://upload.wikimedia.org/wikipedia/meta/0/08/Wikipedia-logo-v2_1x.png 302 | :download: true 303 | 304 | Image with forced dimensions 305 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 306 | 307 | .. sourcecode:: rst 308 | 309 | .. thumbnail:: img.jpg 310 | :width: 300px 311 | :height: 100px 312 | 313 | .. thumbnail:: img.jpg 314 | :width: 300px 315 | :height: 100px 316 | 317 | Image with additional class 318 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 319 | 320 | .. sourcecode:: rst 321 | 322 | .. thumbnail:: img.jpg 323 | :class: framed 324 | 325 | .. thumbnail:: img.jpg 326 | :class: framed 327 | 328 | .. note:: 329 | Requires a custom `custom css file`_ with a rule like: 330 | 331 | .. sourcecode:: css 332 | 333 | .framed img { border: 2px solid black;} 334 | 335 | .. _custom css file: https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_css_files 336 | 337 | Image with description 338 | ^^^^^^^^^^^^^^^^^^^^^^ 339 | 340 | .. sourcecode:: rst 341 | 342 | .. thumbnail:: img.jpg 343 | 344 | Descriptive description. 345 | 346 | .. thumbnail:: img.jpg 347 | 348 | Descriptive description. 349 | 350 | .. seealso:: :ref:`sec-caption-title` 351 | 352 | Image alternative text 353 | ^^^^^^^^^^^^^^^^^^^^^^ 354 | 355 | .. sourcecode:: rst 356 | 357 | .. thumbnail:: http://a.b/non_existing_image.png 358 | :alt: Cannot load this photo, but believe me it is nice. 359 | 360 | .. thumbnail:: http://a.b/non_existing_image.png 361 | :alt: Cannot load this photo, but believe me it's nice. 362 | 363 | Group images 364 | ^^^^^^^^^^^^ 365 | 366 | .. sourcecode:: rst 367 | 368 | .. thumbnail:: img.jpg 369 | :group: group1 370 | 371 | .. thumbnail:: https://upload.wikimedia.org/wikipedia/meta/0/08/Wikipedia-logo-v2_1x.png 372 | :group: group1 373 | 374 | .. thumbnail:: img.jpg 375 | :group: group1 376 | 377 | .. thumbnail:: https://upload.wikimedia.org/wikipedia/meta/0/08/Wikipedia-logo-v2_1x.png 378 | :group: group1 379 | 380 | 381 | Alignment 382 | ^^^^^^^^^ 383 | 384 | .. sourcecode:: rst 385 | 386 | .. thumbnail:: img.jpg 387 | :align: center 388 | 389 | .. thumbnail:: img.jpg 390 | :align: center 391 | 392 | 393 | .. _sec-caption-title: 394 | 395 | Caption / title 396 | ^^^^^^^^^^^^^^^ 397 | .. sourcecode:: rst 398 | 399 | .. thumbnail:: img.jpg 400 | :title: Some title / caption. 401 | 402 | .. thumbnail:: img.jpg 403 | :title: Some title / caption. 404 | :show_caption: True 405 | 406 | .. thumbnail:: img.jpg 407 | :title: Some title / caption. 408 | 409 | .. thumbnail:: img.jpg 410 | :title: Some nice title to the picture. 411 | :show_caption: True 412 | 413 | 414 | Reference via explicit target 415 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 416 | 417 | Thumbnails can not, at present, be numbered like figures, 418 | hence they can not be referenced by `:numref:`, but they can be referenced 419 | by `:ref:`. 420 | 421 | .. sourcecode:: rst 422 | 423 | This is a reference to a thumbnail: :ref:`A thumbnail`. 424 | Scroll until the image is not visible before clicking the link 425 | to see the effect. 426 | 427 | .. _my target: 428 | 429 | .. thumbnail:: img.jpg 430 | 431 | 432 | 433 | This is a reference to a thumbnail: :ref:`A thumbnail`. 434 | Scroll until the image is not visible before clicking the link 435 | to see the effect. 436 | 437 | .. _my target: 438 | 439 | .. thumbnail:: img.jpg 440 | 441 | 442 | 443 | 444 | Indices and tables 445 | ================== 446 | 447 | * :ref:`genindex` 448 | * :ref:`modindex` 449 | * :ref:`search` 450 | 451 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/rtd-requirements.txt: -------------------------------------------------------------------------------- 1 | sphinxcontrib-images 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 77.0.3"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "sphinxcontrib-images" 7 | version = "0.9.4" 8 | requires-python = ">= 3.9" 9 | 10 | authors = [ 11 | {name = "Tomasz Czyż", email = "tomasz.czyz@gmail.com"}, 12 | ] 13 | classifiers = [ 14 | "Development Status :: 4 - Beta", 15 | "Environment :: Console", 16 | "Environment :: Web Environment", 17 | "Intended Audience :: Developers", 18 | "Operating System :: OS Independent", 19 | "Programming Language :: Python", 20 | "Programming Language :: Python :: 3 :: Only", 21 | "Programming Language :: Python :: 3.9", 22 | "Programming Language :: Python :: 3.10", 23 | "Programming Language :: Python :: 3.11", 24 | "Programming Language :: Python :: 3.12", 25 | "Programming Language :: Python :: 3.13", 26 | "Topic :: Documentation", 27 | ] 28 | dependencies = [ 29 | "requests>2.2,<3", 30 | "sphinx>=5.0", 31 | ] 32 | description = "Sphinx extension for thumbnails" 33 | license = "Apache-2.0" 34 | license-files = ["LICENSE.txt"] 35 | readme = "README.rst" 36 | 37 | [project.entry-points."sphinxcontrib.images.backend"] 38 | FakeBackend = "sphinxcontrib_images_lightbox2:LightBox2" 39 | LightBox2 = "sphinxcontrib_images_lightbox2:LightBox2" 40 | 41 | [project.scripts] 42 | sphinxcontrib-images = "sphinxcontrib.images:main" 43 | 44 | [project.urls] 45 | Documentation = "https://sphinxcontrib-images.readthedocs.io/" 46 | Download = "https://pypi.python.org/pypi/sphinxcontrib-images" 47 | Issues = "https://github.com/sphinx-contrib/images/issues" 48 | Repository = "https://github.com/sphinx-contrib/images" 49 | 50 | [tool.setuptools.packages.find] 51 | include = ["sphinxcontrib*"] 52 | 53 | [tool.setuptools.package-data] 54 | sphinxcontrib_images_lightbox2 = [ 55 | "lightbox2/dist/css/lightbox.min.css", 56 | "lightbox2/dist/images/*", 57 | "lightbox2/dist/js/lightbox-plus-jquery.min.*", 58 | "lightbox2-customize/*", 59 | ] 60 | -------------------------------------------------------------------------------- /release-to-pypi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | V1=$(sed -nE "s/^.*version.*=.*([0-9]+\.[0-9]+\.[0-9]+(\.[a-z0-9]+)?).*$/\1/p" pyproject.toml) 4 | V2=$(sed -nE "s/^.*version.*=.*([0-9]+\.[0-9]+\.[0-9]+(\.[a-z0-9]+)?).*$/\1/p" sphinxcontrib/images.py) 5 | SUB=$(git submodule status sphinxcontrib_images_lightbox2/lightbox2 | cut -c1) 6 | 7 | printf "\n## RELEASE SPHINXCONTRIB-IMAGES TO PYPI ##\n\n" 8 | 9 | printf "# VERSION CHECK #\n" 10 | printf "pyproject.toml: %s\n" "$V1" 11 | printf "sphinxcontrib/images.py: %s\n" "$V2" 12 | 13 | if [ "$V1" != "$V2" ]; then 14 | printf "ERROR: versions do _not_ match\n\n" 15 | else 16 | printf "SUCCESS: Versions match\n\n" 17 | fi 18 | 19 | printf "# SUBMODULE STATUS #\n" 20 | if [ "$SUB" = "-" ]; then 21 | echo "ERROR: lightbox2 submodule _not_ initialized" 22 | else 23 | echo "SUCCESS: lightbox2 submodule initialized" 24 | fi 25 | 26 | read -p "Press any key to proceed to instructions ..." -n1 -s 27 | 28 | cat < 0.9.3.pre1 and 100 | 0.9.3 > 0.9.3.pre2 101 | 102 | STEPS 103 | -------------------------------------------------------------------------------- /sphinxcontrib/images.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __version__ = '0.9.4' 3 | __author__ = 'Tomasz Czyż ' 4 | __license__ = "Apache 2" 5 | 6 | 7 | import os 8 | import sys 9 | import copy 10 | import uuid 11 | import hashlib 12 | import importlib.metadata 13 | import argparse 14 | import functools 15 | 16 | import sphinx 17 | from sphinx.util.osutil import copyfile 18 | 19 | try: 20 | from sphinx.util import logging 21 | 22 | logger = logging.getLogger(__name__) 23 | except ImportError: 24 | logger = None 25 | 26 | try: 27 | from sphinx.util.compat import Directive 28 | except: 29 | from docutils.parsers.rst import Directive 30 | 31 | try: 32 | from sphinx.util.display import status_iterator 33 | except ImportError: 34 | try: 35 | from sphinx.util import status_iterator 36 | except ImportError: 37 | pass 38 | 39 | from sphinx.util.console import brown 40 | from sphinx.util.osutil import ensuredir 41 | 42 | from docutils import nodes 43 | from docutils.parsers.rst import directives 44 | 45 | import requests 46 | 47 | 48 | STATICS_DIR_NAME = '_static' 49 | 50 | 51 | DEFAULT_CONFIG = dict( 52 | backend='LightBox2', 53 | default_image_width='100%', 54 | default_image_height='auto', 55 | default_group=None, 56 | default_show_title=False, 57 | download=True, 58 | requests_kwargs={}, 59 | cache_path='_images', 60 | override_image_directive=False, 61 | show_caption=False, 62 | ) 63 | 64 | def get_entry_points(): 65 | group = 'sphinxcontrib.images.backend' 66 | return ( 67 | importlib.metadata.entry_points(group=group) 68 | if sys.version_info > (3, 10) 69 | else importlib.metadata.entry_points()[group] 70 | ) 71 | 72 | 73 | class Backend(object): 74 | STATIC_FILES = () 75 | 76 | def __init__(self, app): 77 | self._app = app 78 | 79 | def visit_image_node_fallback(self, writer, node): 80 | writer.visit_image(node) 81 | 82 | def depart_image_node_fallback(self, writer, node): 83 | writer.depart_image(node) 84 | 85 | 86 | class image_node(nodes.image, nodes.General, nodes.Element): 87 | pass 88 | 89 | 90 | class gallery_node(nodes.image, nodes.General, nodes.Element): 91 | pass 92 | 93 | 94 | def directive_boolean(value): 95 | if not value.strip(): 96 | raise ValueError("No argument provided but required") 97 | if value.lower().strip() in ["yes", "1", 1, "true", "ok"]: 98 | return True 99 | elif value.lower().strip() in ['no', '0', 0, 'false', 'none']: 100 | return False 101 | else: 102 | raise ValueError(u"Please use on of: yes, true, no, false. " 103 | u"Do not use `{}` as boolean.".format(value)) 104 | 105 | 106 | class ImageDirective(Directive): 107 | ''' 108 | Directive which overrides default sphinx directive. 109 | It's backward compatibile and it's adding more cool stuff. 110 | ''' 111 | 112 | align_values = ('left', 'center', 'right') 113 | 114 | def align(argument): 115 | # This is not callable as self.align. We cannot make it a 116 | # staticmethod because we're saving an unbound method in 117 | # option_spec below. 118 | return directives.choice(argument, ImageDirective.align_values) 119 | 120 | has_content = True 121 | required_arguments = True 122 | 123 | option_spec = { 124 | 'width': directives.length_or_percentage_or_unitless, 125 | 'height': directives.length_or_unitless, 126 | 'strech': directives.choice, 127 | 128 | 'group': directives.unchanged, 129 | 'class': directives.class_option, # or str? 130 | 'alt': directives.unchanged, 131 | 'download': directive_boolean, 132 | 'title': directives.unchanged, 133 | 'align': align, 134 | 'show_caption': directive_boolean, 135 | 'legacy_class': directives.class_option, 136 | } 137 | 138 | def run(self): 139 | env = self.state.document.settings.env 140 | conf = env.app.config.images_config 141 | 142 | #TODO get defaults from config 143 | group = self.options.get('group', 144 | conf['default_group'] if conf['default_group'] else uuid.uuid4()) 145 | classes = self.options.get('class', '') 146 | width = self.options.get('width', conf['default_image_width']) 147 | height = self.options.get('height', conf['default_image_height']) 148 | alt = self.options.get('alt', '') 149 | title = self.options.get('title', '' if conf['default_show_title'] else None) 150 | align = self.options.get('align', '') 151 | show_caption = self.options.get('show_caption', False) 152 | legacy_classes = self.options.get('legacy_class', '') 153 | 154 | #TODO get default from config 155 | download = self.options.get('download', conf['download']) 156 | 157 | # parse nested content 158 | #TODO: something is broken here, not parsed as expected 159 | description = nodes.paragraph() 160 | content = nodes.paragraph() 161 | content += [nodes.Text(u"%s" % x) for x in self.content] 162 | self.state.nested_parse(content, 163 | 0, 164 | description) 165 | 166 | img = image_node() 167 | 168 | if self.is_remote(self.arguments[0]): 169 | img['remote'] = True 170 | if download: 171 | img['uri'] = os.path.join('_images', hashlib.sha1(self.arguments[0].encode()).hexdigest()) 172 | img['remote_uri'] = self.arguments[0] 173 | env.remote_images[img['remote_uri']] = img['uri'] 174 | env.images.add_file('', img['uri']) 175 | else: 176 | img['uri'] = self.arguments[0] 177 | img['remote_uri'] = self.arguments[0] 178 | else: 179 | img['uri'] = self.arguments[0] 180 | img['remote'] = False 181 | env.images.add_file('', img['uri']) 182 | 183 | img['content'] = description.astext() 184 | 185 | if title is None: 186 | img['title'] = '' 187 | elif title: 188 | img['title'] = title 189 | else: 190 | img['title'] = img['content'] 191 | img['content'] = '' 192 | 193 | img['show_caption'] = show_caption 194 | img['legacy_classes'] = legacy_classes 195 | img['group'] = group 196 | img['size'] = (width, height) 197 | img['classes'] += classes 198 | img['alt'] = alt 199 | img['align'] = align 200 | return [img] 201 | 202 | def is_remote(self, uri): 203 | uri = uri.strip() 204 | env = self.state.document.settings.env 205 | app_directory = os.path.dirname(os.path.abspath(self.state.document.settings._source)) 206 | if sphinx.__version__.startswith('1.1'): 207 | app_directory = app_directory.decode('utf-8') 208 | 209 | if uri[0] == '/': 210 | return False 211 | if uri[0:7] == 'file://': 212 | return False 213 | if os.path.isfile(os.path.join(env.srcdir, uri)): 214 | return False 215 | if os.path.isfile(os.path.join(app_directory, uri)): 216 | return False 217 | if '://' in uri: 218 | return True 219 | raise ValueError('Image URI `{}` have to be local relative or ' 220 | 'absolute path to image, or remote address.' 221 | .format(uri)) 222 | 223 | 224 | def install_backend_static_files(app, env): 225 | STATICS_DIR_PATH = os.path.join(app.builder.outdir, STATICS_DIR_NAME) 226 | dest_path = os.path.join(STATICS_DIR_PATH, 'sphinxcontrib-images', 227 | app.sphinxcontrib_images_backend.__class__.__name__) 228 | files_to_copy = app.sphinxcontrib_images_backend.STATIC_FILES 229 | 230 | for source_file_path in (app.builder.status_iterator 231 | if hasattr(app.builder, 'status_iterator') else status_iterator)( 232 | files_to_copy, 233 | 'Copying static files for sphinxcontrib-images...', 234 | brown, len(files_to_copy)): 235 | 236 | dest_file_path = os.path.join(dest_path, source_file_path) 237 | 238 | if not os.path.exists(os.path.dirname(dest_file_path)): 239 | ensuredir(os.path.dirname(dest_file_path)) 240 | 241 | source_file_path = os.path.join(os.path.dirname( 242 | sys.modules[app.sphinxcontrib_images_backend.__class__.__module__].__file__), 243 | source_file_path) 244 | 245 | copyfile(source_file_path, dest_file_path) 246 | 247 | if dest_file_path.endswith('.js'): 248 | app.add_js_file(os.path.relpath(dest_file_path, STATICS_DIR_PATH)) 249 | elif dest_file_path.endswith('.css'): 250 | app.add_css_file(os.path.relpath(dest_file_path, STATICS_DIR_PATH)) 251 | 252 | 253 | def download_images(app, env): 254 | conf = app.config.images_config 255 | 256 | for src in (app.builder.status_iterator 257 | if hasattr(app.builder, 'status_iterator') else status_iterator)( 258 | env.remote_images, 259 | 'Downloading remote images...', 260 | brown, 261 | len(env.remote_images)): 262 | 263 | dst = os.path.join(env.srcdir, env.remote_images[src]) 264 | if not os.path.isfile(dst): 265 | logger.info('{} -> {} (downloading)' 266 | .format(src, dst)) 267 | with open(dst, 'wb') as f: 268 | # TODO: apply reuqests_kwargs 269 | try: 270 | f.write(requests.get(src, 271 | **conf['requests_kwargs']).content) 272 | except requests.ConnectionError: 273 | logger.info("Cannot download `{}`".format(src)) 274 | else: 275 | logger.info('{} -> {} (already in cache)' 276 | .format(src, dst)) 277 | 278 | 279 | def update_config(app, config): 280 | '''Ensure all config values are defined''' 281 | 282 | merged = copy.deepcopy(DEFAULT_CONFIG) 283 | merged.update(config.images_config) 284 | config.images_config = merged 285 | 286 | def configure_backend(app): 287 | config = app.config.images_config 288 | ensuredir(os.path.join(app.env.srcdir, config['cache_path'])) 289 | 290 | # html builder 291 | # self.relfn2path(imguri, docname) 292 | 293 | backend_name_or_callable = config['backend'] 294 | if isinstance(backend_name_or_callable, str): 295 | try: 296 | backend = next( 297 | i for i in get_entry_points() if i.name == backend_name_or_callable 298 | ).load() 299 | except StopIteration: 300 | raise IndexError("Cannot find sphinxcontrib-images backend " 301 | "with name `{}`.".format(backend_name_or_callable)) 302 | elif callable(backend_name_or_callable): 303 | backend = backend_name_or_callable 304 | else: 305 | raise TypeError("sphinxcontrib-images backend is configured " 306 | "improperly. It has to be a string (name of " 307 | "installed backend) or callable which returns " 308 | "backend instance but is `{}` (type:`{}`). Please read " 309 | "sphinxcontrib-images documentation for " 310 | "more informations." 311 | .format(backend_name_or_callable, 312 | type(backend_name_or_callable))) 313 | 314 | try: 315 | backend = backend(app) 316 | except TypeError as error: 317 | logger.info('Cannot instantiate sphinxcontrib-images backend `{}`. ' 318 | 'Please, select correct backend. Available backends: {}.' 319 | .format(config['backend'], ', '.join(ep.name for ep in get_entry_points()))) 320 | raise SystemExit(1) 321 | 322 | # remember the chosen backend for processing. Env and config cannot be used 323 | # because sphinx try to make a pickle from it. 324 | app.sphinxcontrib_images_backend = backend 325 | 326 | logger.info('Initiated sphinxcontrib-images backend: ', nonl=True) 327 | logger.info('`{}`'.format(str(backend.__class__.__module__ + 328 | ':' + backend.__class__.__name__))) 329 | 330 | def backend_methods(node, output_type): 331 | def backend_method(f): 332 | @functools.wraps(f) 333 | def inner_wrapper(writer, node): 334 | return f(writer, node) 335 | return inner_wrapper 336 | signature = '_{}_{}'.format(node.__name__, output_type) 337 | return (backend_method(getattr(backend, 'visit' + signature, 338 | getattr(backend, 'visit_' + node.__name__ + '_fallback'))), 339 | backend_method(getattr(backend, 'depart' + signature, 340 | getattr(backend, 'depart_' + node.__name__ + '_fallback')))) 341 | 342 | 343 | # add new node to the stack 344 | # connect backend processing methods to this node 345 | app.add_node(image_node, 346 | **{output_type: backend_methods(image_node, output_type) 347 | for output_type in ('html', 'latex', 'man', 'texinfo', 348 | 'text', 'epub')}) 349 | 350 | app.add_directive('thumbnail', ImageDirective) 351 | if config['override_image_directive']: 352 | app.add_directive('image', ImageDirective) 353 | app.env.remote_images = {} 354 | 355 | def setup(app): 356 | app.require_sphinx('1.0') 357 | app.add_config_value('images_config', {}, 'env') 358 | app.connect('config-inited', update_config) 359 | app.connect('builder-inited', configure_backend) 360 | app.connect('env-updated', download_images) 361 | app.connect('env-updated', install_backend_static_files) 362 | 363 | global logger 364 | 365 | if logger is None: 366 | logger = app 367 | 368 | return {'version': sphinx.__version__, 'parallel_read_safe': True} 369 | 370 | 371 | def main(args=sys.argv[1:]): 372 | ap = argparse.ArgumentParser() 373 | ap.add_argument("command", 374 | choices=['show-backends']) 375 | args = ap.parse_args(args) 376 | if args.command == 'show-backends': 377 | if backends := get_entry_points(): 378 | for backend in backends: 379 | print ('- {0.name} (from package `{0.dist.name}`)'.format(backend)) 380 | else: 381 | print ('No backends installed') 382 | 383 | 384 | if __name__ == '__main__': 385 | main() 386 | -------------------------------------------------------------------------------- /sphinxcontrib_images_lightbox2/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .lightbox2 import LightBox2 3 | -------------------------------------------------------------------------------- /sphinxcontrib_images_lightbox2/lightbox2-customize/jquery-noconflict.js: -------------------------------------------------------------------------------- 1 | jQuery.noConflict(true); 2 | -------------------------------------------------------------------------------- /sphinxcontrib_images_lightbox2/lightbox2-customize/pointer.css: -------------------------------------------------------------------------------- 1 | /* change the lightbox cursor */ 2 | a[data-lightbox] {cursor: zoom-in;} -------------------------------------------------------------------------------- /sphinxcontrib_images_lightbox2/lightbox2.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from sphinxcontrib import images 4 | 5 | class LightBox2(images.Backend): 6 | STATIC_FILES = ( 7 | 'lightbox2/dist/images/close.png', 8 | 'lightbox2/dist/images/next.png', 9 | 'lightbox2/dist/images/prev.png', 10 | 'lightbox2/dist/images/loading.gif', 11 | 'lightbox2/dist/js/lightbox-plus-jquery.min.js', 12 | 'lightbox2/dist/js/lightbox-plus-jquery.min.map', 13 | 'lightbox2/dist/css/lightbox.min.css', 14 | 'lightbox2-customize/jquery-noconflict.js', 15 | 'lightbox2-customize/pointer.css' 16 | ) 17 | 18 | def visit_image_node_html(self, writer, node): 19 | # make links local (for local images only) 20 | builder = self._app.builder 21 | if node['uri'] in builder.images: 22 | node['uri'] = '/'.join([builder.imgpath, 23 | builder.images[node['uri']]]) 24 | 25 | if node['show_caption'] is True: 26 | writer.body.append( 27 | u'''
28 | '''.format(cls=' '.join(node['classes']),)) 29 | if node['legacy_classes']: 30 | writer.body.append( 31 | u''''''.format( group='group-%s' % node['group'] if node['group'] else node['uri'], 45 | href=node['uri'], 46 | title=node['title'] + node['content'], 47 | # Only one id attribute is meaningful 48 | id = 'id="' + node['ids'][0] + '"' if len(node['ids'])>0 else '' 49 | )) 50 | writer.body.append( 51 | '''{alt} 56 | '''.format(src=node['uri'], 57 | cls='align-%s' % node['align'] if node['align'] else '', 58 | width=node['size'][0], 59 | height=node['size'][1], 60 | alt=node['alt'], 61 | title=node['title'])) 62 | 63 | 64 | def depart_image_node_html(self, writer, node): 65 | writer.body.append('') 66 | if node['show_caption'] is True: 67 | writer.body.append(u'''
{title}
68 | '''.format(title=node['title'],)) 69 | writer.body.append('
') 70 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | {py39,py310,py311,py312,py313,pypy3}-sphinx{621,747,latest}, 4 | wheel 5 | 6 | [testenv] 7 | minversion = 3.15.0 8 | allowlist_externals = make 9 | commands = 10 | make -C docs html 11 | deps = 12 | sphinx-rtd-theme==0.1.6 13 | sphinxlatest: sphinx 14 | # last release for old versions 15 | sphinx747: sphinx==7.4.7 16 | sphinx621: sphinx==6.2.1 17 | 18 | [testenv:wheel] 19 | minversion = 3.15.0 20 | commands = 21 | python -m build --wheel . 22 | skip_install = true 23 | deps = 24 | build 25 | 26 | [gh-actions] 27 | python = 28 | 3.9: py39 29 | 3.10: py310 30 | 3.11: py311 31 | 3.12: py312 32 | 3.13: py313, wheel 33 | pypy-3.10: pypy3 34 | --------------------------------------------------------------------------------