123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- # $Id: images.py 9037 2022-03-05 23:31:10Z milde $
- # Author: David Goodger <goodger@python.org>
- # Copyright: This module has been placed in the public domain.
- """
- Directives for figures and simple images.
- """
- __docformat__ = 'reStructuredText'
- from urllib.request import url2pathname
- try: # check for the Python Imaging Library
- import PIL.Image
- except ImportError:
- try: # sometimes PIL modules are put in PYTHONPATH's root
- import Image
- class PIL: pass # noqa:E701 dummy wrapper
- PIL.Image = Image
- except ImportError:
- PIL = None
- from docutils import nodes
- from docutils.nodes import fully_normalize_name, whitespace_normalize_name
- from docutils.parsers.rst import Directive
- from docutils.parsers.rst import directives, states
- from docutils.parsers.rst.roles import set_classes
- class Image(Directive):
- align_h_values = ('left', 'center', 'right')
- align_v_values = ('top', 'middle', 'bottom')
- align_values = align_v_values + align_h_values
- def align(argument):
- # This is not callable as self.align. We cannot make it a
- # staticmethod because we're saving an unbound method in
- # option_spec below.
- return directives.choice(argument, Image.align_values)
- required_arguments = 1
- optional_arguments = 0
- final_argument_whitespace = True
- option_spec = {'alt': directives.unchanged,
- 'height': directives.length_or_unitless,
- 'width': directives.length_or_percentage_or_unitless,
- 'scale': directives.percentage,
- 'align': align,
- 'target': directives.unchanged_required,
- 'class': directives.class_option,
- 'name': directives.unchanged}
- def run(self):
- if 'align' in self.options:
- if isinstance(self.state, states.SubstitutionDef):
- # Check for align_v_values.
- if self.options['align'] not in self.align_v_values:
- raise self.error(
- 'Error in "%s" directive: "%s" is not a valid value '
- 'for the "align" option within a substitution '
- 'definition. Valid values for "align" are: "%s".'
- % (self.name, self.options['align'],
- '", "'.join(self.align_v_values)))
- elif self.options['align'] not in self.align_h_values:
- raise self.error(
- 'Error in "%s" directive: "%s" is not a valid value for '
- 'the "align" option. Valid values for "align" are: "%s".'
- % (self.name, self.options['align'],
- '", "'.join(self.align_h_values)))
- messages = []
- reference = directives.uri(self.arguments[0])
- self.options['uri'] = reference
- reference_node = None
- if 'target' in self.options:
- block = states.escape2null(
- self.options['target']).splitlines()
- block = [line for line in block]
- target_type, data = self.state.parse_target(
- block, self.block_text, self.lineno)
- if target_type == 'refuri':
- reference_node = nodes.reference(refuri=data)
- elif target_type == 'refname':
- reference_node = nodes.reference(
- refname=fully_normalize_name(data),
- name=whitespace_normalize_name(data))
- reference_node.indirect_reference_name = data
- self.state.document.note_refname(reference_node)
- else: # malformed target
- messages.append(data) # data is a system message
- del self.options['target']
- set_classes(self.options)
- image_node = nodes.image(self.block_text, **self.options)
- self.add_name(image_node)
- if reference_node:
- reference_node += image_node
- return messages + [reference_node]
- else:
- return messages + [image_node]
- class Figure(Image):
- def align(argument):
- return directives.choice(argument, Figure.align_h_values)
- def figwidth_value(argument):
- if argument.lower() == 'image':
- return 'image'
- else:
- return directives.length_or_percentage_or_unitless(argument, 'px')
- option_spec = Image.option_spec.copy()
- option_spec['figwidth'] = figwidth_value
- option_spec['figclass'] = directives.class_option
- option_spec['align'] = align
- has_content = True
- def run(self):
- figwidth = self.options.pop('figwidth', None)
- figclasses = self.options.pop('figclass', None)
- align = self.options.pop('align', None)
- (image_node,) = Image.run(self)
- if isinstance(image_node, nodes.system_message):
- return [image_node]
- figure_node = nodes.figure('', image_node)
- if figwidth == 'image':
- if PIL and self.state.document.settings.file_insertion_enabled:
- imagepath = url2pathname(image_node['uri'])
- try:
- with PIL.Image.open(imagepath) as img:
- figure_node['width'] = '%dpx' % img.size[0]
- except (OSError, UnicodeEncodeError):
- pass # TODO: warn/info?
- else:
- self.state.document.settings.record_dependencies.add(
- imagepath.replace('\\', '/'))
- elif figwidth is not None:
- figure_node['width'] = figwidth
- if figclasses:
- figure_node['classes'] += figclasses
- if align:
- figure_node['align'] = align
- if self.content:
- node = nodes.Element() # anonymous container for parsing
- self.state.nested_parse(self.content, self.content_offset, node)
- first_node = node[0]
- if isinstance(first_node, nodes.paragraph):
- caption = nodes.caption(first_node.rawsource, '',
- *first_node.children)
- caption.source = first_node.source
- caption.line = first_node.line
- figure_node += caption
- elif not (isinstance(first_node, nodes.comment)
- and len(first_node) == 0):
- error = self.reporter.error(
- 'Figure caption must be a paragraph or empty comment.',
- nodes.literal_block(self.block_text, self.block_text),
- line=self.lineno)
- return [figure_node, error]
- if len(node) > 1:
- figure_node += nodes.legend('', *node[1:])
- return [figure_node]
|