Add image format handling.

This commit is contained in:
Georg Brandl 2008-06-15 14:31:16 +00:00
parent 66b5f39bc7
commit b05861454c
7 changed files with 95 additions and 32 deletions

View File

@ -38,6 +38,9 @@ New features added
- The directories in the `html_static_path` can now contain - The directories in the `html_static_path` can now contain
subdirectories. subdirectories.
* The image directive now supports specifying the extension as ``.*``,
which makes the builder select the one that matches best.
* The new config value `exclude_trees` can be used to exclude whole * The new config value `exclude_trees` can be used to exclude whole
subtrees from the search for source files. subtrees from the search for source files.

9
TODO
View File

@ -13,12 +13,3 @@ Sphinx
- "often used" combo box in sidebar - "often used" combo box in sidebar
- source file cross-references? - source file cross-references?
Web App
*******
- fix /download
- discuss and debug comments system
- prepare for databases other than sqlite for comments
- add search via Xapian or Nucular (Python indexer - nucular.sf.net)
- optionally have a contents tree view in the sidebar (AJAX based)?

View File

@ -72,7 +72,7 @@ or roman numerals, such as ::
A. First item A. First item
B. Second item B. Second item
Nested lists are possible, but be aware that they must be separated from the Nested lists are possible, but be aware that they must be separated from the
parent list items by blank lines:: parent list items by blank lines::
@ -214,12 +214,27 @@ Images
reST supports an image directive, used like so:: reST supports an image directive, used like so::
.. image:: filename .. image:: gnu.png
(options) (options)
When used within Sphinx, the ``filename`` given must be relative to the source When used within Sphinx, the file name given (here ``gnu.png``) must be relative
file, and Sphinx will automatically copy image files over to a subdirectory of to the source file, and Sphinx will automatically copy image files over to a
the output directory on building. subdirectory of the output directory on building (e.g. the ``_static`` directory
for HTML output.)
Sphinx extends the standard docutils behavior by allowing an asterisk for the
extension::
.. image:: gnu.*
Sphinx then searches for all images matching the provided pattern and determines
their type. Each builder then chooses the best image out of these candidates.
For instance, if the file name ``gnu.*`` was given and two files :file:`gnu.pdf`
and :file:`gnu.png` existed in the source tree, the LaTeX builder would choose
the former, while the HTML builder would prefer the latter.
.. versionchanged:: 0.4
Added the support for file names ending in an asterisk.
Footnotes Footnotes

View File

@ -62,6 +62,9 @@ class Builder(object):
self.info = app.info self.info = app.info
self.config = app.config self.config = app.config
# images that need to be copied over (source -> dest)
self.images = {}
# if None, this is set in load_env() # if None, this is set in load_env()
self.env = env self.env = env
self.freshenv = freshenv self.freshenv = freshenv
@ -118,6 +121,28 @@ class Builder(object):
if l == 0: if l == 0:
self.info() self.info()
supported_image_types = []
def post_process_images(self, doctree):
"""
Pick the best candidate for all image URIs.
"""
for node in doctree.traverse(nodes.image):
uri = node['candidates'].get('*', None)
if not uri:
for imgtype in self.supported_image_types:
uri = node['candidates'].get(imgtype, None)
if uri:
node['uri'] = uri
break
else:
self.warn('%s:%s: %s' %
(node.source, node.lineno,
'No matching candidate for uri: %(uri)s' % node))
continue
if uri in self.env.images:
self.images[uri] = self.env.images[uri][1]
# build methods # build methods
def load_env(self): def load_env(self):
@ -271,6 +296,8 @@ class StandaloneHTMLBuilder(Builder):
copysource = True copysource = True
out_suffix = '.html' out_suffix = '.html'
indexer_format = 'json' indexer_format = 'json'
supported_image_types = ['image/svg+xml', 'image/png', 'image/gif',
'image/jpeg']
def init(self): def init(self):
"""Load templates.""" """Load templates."""
@ -323,7 +350,7 @@ class StandaloneHTMLBuilder(Builder):
favicon = self.config.html_favicon and \ favicon = self.config.html_favicon and \
path.basename(self.config.html_favicon) or '' path.basename(self.config.html_favicon) or ''
if os.path.splitext(favicon)[1] != '.ico': if favicon and os.path.splitext(favicon)[1] != '.ico':
self.warn('html_favicon is not an .ico file') self.warn('html_favicon is not an .ico file')
if not isinstance(self.config.html_use_opensearch, basestring): if not isinstance(self.config.html_use_opensearch, basestring):
@ -407,6 +434,7 @@ class StandaloneHTMLBuilder(Builder):
) )
def write_doc(self, docname, doctree): def write_doc(self, docname, doctree):
self.post_process_images(doctree)
destination = StringOutput(encoding='utf-8') destination = StringOutput(encoding='utf-8')
doctree.settings = self.docsettings doctree.settings = self.docsettings
@ -504,10 +532,10 @@ class StandaloneHTMLBuilder(Builder):
self.info() self.info()
# copy image files # copy image files
if self.env.images: if self.images:
self.info(bold('copying images...'), nonl=1) self.info(bold('copying images...'), nonl=1)
ensuredir(path.join(self.outdir, '_images')) ensuredir(path.join(self.outdir, '_images'))
for src, (_, dest) in self.env.images.iteritems(): for src, dest in self.images.iteritems():
self.info(' '+src, nonl=1) self.info(' '+src, nonl=1)
shutil.copyfile(path.join(self.srcdir, src), shutil.copyfile(path.join(self.srcdir, src),
path.join(self.outdir, '_images', dest)) path.join(self.outdir, '_images', dest))
@ -636,6 +664,8 @@ class PickleHTMLBuilder(StandaloneHTMLBuilder):
name = 'pickle' name = 'pickle'
out_suffix = '.fpickle' out_suffix = '.fpickle'
indexer_format = 'pickle' indexer_format = 'pickle'
supported_image_types = ('image/svg+xml', 'image/png', 'image/gif',
'image/jpeg')
def init(self): def init(self):
self.init_translator_class() self.init_translator_class()
@ -711,6 +741,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
# don't copy the reST source # don't copy the reST source
copysource = False copysource = False
supported_image_types = ['image/png', 'image/gif', 'image/jpeg']
def init(self): def init(self):
StandaloneHTMLBuilder.init(self) StandaloneHTMLBuilder.init(self)
@ -726,6 +757,8 @@ class LaTeXBuilder(Builder):
Builds LaTeX output to create PDF. Builds LaTeX output to create PDF.
""" """
name = 'latex' name = 'latex'
supported_image_types = ['application/pdf', 'image/png', 'image/gif',
'image/jpeg']
def init(self): def init(self):
self.docnames = [] self.docnames = []
@ -787,6 +820,7 @@ class LaTeXBuilder(Builder):
self.info("processing " + targetname + "... ", nonl=1) self.info("processing " + targetname + "... ", nonl=1)
doctree = self.assemble_doctree(docname, toctree_only, doctree = self.assemble_doctree(docname, toctree_only,
appendices=(docclass == 'manual') and appendices or []) appendices=(docclass == 'manual') and appendices or [])
self.post_process_images(doctree)
self.info("writing... ", nonl=1) self.info("writing... ", nonl=1)
doctree.settings = docsettings doctree.settings = docsettings
doctree.settings.author = author doctree.settings.author = author
@ -852,9 +886,9 @@ class LaTeXBuilder(Builder):
def finish(self): def finish(self):
# copy image files # copy image files
if self.env.images: if self.images:
self.info(bold('copying images...'), nonl=1) self.info(bold('copying images...'), nonl=1)
for src, (_, dest) in self.env.images.iteritems(): for src, dest in self.images.iteritems():
self.info(' '+src, nonl=1) self.info(' '+src, nonl=1)
shutil.copyfile(path.join(self.srcdir, src), shutil.copyfile(path.join(self.srcdir, src),
path.join(self.outdir, dest)) path.join(self.outdir, dest))

View File

@ -14,9 +14,11 @@ import os
import time import time
import heapq import heapq
import types import types
import imghdr
import difflib import difflib
import cPickle as pickle import cPickle as pickle
from os import path from os import path
from glob import glob
from string import uppercase from string import uppercase
from itertools import izip, groupby from itertools import izip, groupby
try: try:
@ -511,25 +513,43 @@ class BuildEnvironment:
existing_names = set(v[1] for v in self.images.itervalues()) existing_names = set(v[1] for v in self.images.itervalues())
docdir = path.dirname(self.doc2path(docname, base=None)) docdir = path.dirname(self.doc2path(docname, base=None))
for node in doctree.traverse(nodes.image): for node in doctree.traverse(nodes.image):
# Map the mimetype to the corresponding image. The writer may
# choose the best image from these candidates. The special key * is
# set if there is only single candiate to be used by a writer.
node['candidates'] = candidates = {}
imguri = node['uri'] imguri = node['uri']
if imguri.find('://') != -1: if imguri.find('://') != -1:
self.warn(docname, 'Nonlocal image URI found: %s' % imguri, node.line) self.warn(docname, 'Nonlocal image URI found: %s' % imguri, node.line)
candidates['*'] = imguri
continue
imgpath = path.normpath(path.join(docdir, imguri))
if imgpath.endswith(os.extsep + '*'):
for filename in glob(imgpath):
basename, ext = os.path.splitext(filename)
if ext == '.pdf':
candidates['application/pdf'] = filename
elif ext == '.svg':
candidates['image/svg+xml'] = filename
else:
imgtype = imghdr.what(filename)
if imgtype:
candidates['image/' + imgtype] = filename
else: else:
imgpath = path.normpath(path.join(docdir, imguri)) candidates['*'] = imgpath
node['uri'] = imgpath for img in candidates.itervalues():
self.dependencies.setdefault(docname, set()).add(imgpath) self.dependencies.setdefault(docname, set()).add(img)
if not os.access(path.join(self.srcdir, imgpath), os.R_OK): if not os.access(path.join(self.srcdir, img), os.R_OK):
self.warn(docname, 'Image file not readable: %s' % imguri, node.line) self.warn(docname, 'Image file not readable: %s' % img, node.line)
if imgpath in self.images: if img in self.images:
self.images[imgpath][0].add(docname) self.images[img][0].add(docname)
continue continue
uniquename = path.basename(imgpath) uniquename = path.basename(img)
base, ext = path.splitext(uniquename) base, ext = path.splitext(uniquename)
i = 0 i = 0
while uniquename in existing_names: while uniquename in existing_names:
i += 1 i += 1
uniquename = '%s%s%s' % (base, i, ext) uniquename = '%s%s%s' % (base, i, ext)
self.images[imgpath] = (set([docname]), uniquename) self.images[img] = (set([docname]), uniquename)
existing_names.add(uniquename) existing_names.add(uniquename)
def process_metadata(self, docname, doctree): def process_metadata(self, docname, doctree):

View File

@ -250,9 +250,9 @@ class HTMLTranslator(BaseTranslator):
def visit_image(self, node): def visit_image(self, node):
olduri = node['uri'] olduri = node['uri']
# rewrite the URI if the environment knows about it # rewrite the URI if the environment knows about it
if olduri in self.builder.env.images: if olduri in self.builder.images:
node['uri'] = posixpath.join(self.builder.imgpath, node['uri'] = posixpath.join(self.builder.imgpath,
self.builder.env.images[olduri][1]) self.builder.images[olduri])
BaseTranslator.visit_image(self, node) BaseTranslator.visit_image(self, node)
def visit_toctree(self, node): def visit_toctree(self, node):

View File

@ -630,8 +630,8 @@ class LaTeXTranslator(nodes.NodeVisitor):
pre.append('\n') pre.append('\n')
post.append('\n') post.append('\n')
pre.reverse() pre.reverse()
if node['uri'] in self.builder.env.images: if node['uri'] in self.builder.images:
uri = self.builder.env.images[node['uri']][1] uri = self.builder.images[node['uri']]
else: else:
uri = node['uri'] uri = node['uri']
if uri.find('://') != -1: if uri.find('://') != -1: