You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
6.7 KiB
164 lines
6.7 KiB
# $Id: images.py 7753 2014-06-24 14:52:59Z 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' |
|
|
|
|
|
import sys |
|
import urllib |
|
from docutils import nodes, utils |
|
from docutils.parsers.rst import Directive |
|
from docutils.parsers.rst import directives, states |
|
from docutils.nodes import fully_normalize_name, whitespace_normalize_name |
|
from docutils.parsers.rst.roles import set_classes |
|
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(object): pass # dummy wrapper |
|
PIL.Image = Image |
|
except ImportError: |
|
PIL = None |
|
|
|
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, |
|
'name': directives.unchanged, |
|
'target': directives.unchanged_required, |
|
'class': directives.class_option} |
|
|
|
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 = urllib.url2pathname(image_node['uri']) |
|
try: |
|
img = PIL.Image.open( |
|
imagepath.encode(sys.getfilesystemencoding())) |
|
except (IOError, UnicodeEncodeError): |
|
pass # TODO: warn? |
|
else: |
|
self.state.document.settings.record_dependencies.add( |
|
imagepath.replace('\\', '/')) |
|
figure_node['width'] = '%dpx' % img.size[0] |
|
del img |
|
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.state_machine.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]
|
|
|