15
13
from docutils import nodes, utils
14
from docutils.parsers.rst import Directive
16
15
from docutils.parsers.rst import directives, states
17
16
from docutils.nodes import fully_normalize_name, whitespace_normalize_name
18
17
from docutils.parsers.rst.roles import set_classes
20
import Image as PIL # PIL
22
21
except ImportError:
25
align_h_values = ('left', 'center', 'right')
26
align_v_values = ('top', 'middle', 'bottom')
27
align_values = align_v_values + align_h_values
30
return directives.choice(argument, align_values)
32
def image(name, arguments, options, content, lineno,
33
content_offset, block_text, state, state_machine):
34
if options.has_key('align'):
35
# check for align_v values only
36
if isinstance(state, states.SubstitutionDef):
37
if options['align'] not in align_v_values:
38
error = state_machine.reporter.error(
25
class Image(Directive):
27
align_h_values = ('left', 'center', 'right')
28
align_v_values = ('top', 'middle', 'bottom')
29
align_values = align_v_values + align_h_values
32
# This is not callable as self.align. We cannot make it a
33
# staticmethod because we're saving an unbound method in
35
return directives.choice(argument, Image.align_values)
37
required_arguments = 1
38
optional_arguments = 0
39
final_argument_whitespace = True
40
option_spec = {'alt': directives.unchanged,
41
'height': directives.length_or_unitless,
42
'width': directives.length_or_percentage_or_unitless,
43
'scale': directives.nonnegative_int,
45
'target': directives.unchanged_required,
46
'class': directives.class_option}
49
if self.options.has_key('align'):
50
if isinstance(self.state, states.SubstitutionDef):
51
# Check for align_v_values.
52
if self.options['align'] not in self.align_v_values:
54
'Error in "%s" directive: "%s" is not a valid value '
55
'for the "align" option within a substitution '
56
'definition. Valid values for "align" are: "%s".'
57
% (self.name, self.options['align'],
58
'", "'.join(self.align_v_values)))
59
elif self.options['align'] not in self.align_h_values:
39
61
'Error in "%s" directive: "%s" is not a valid value for '
40
'the "align" option within a substitution definition. '
41
'Valid values for "align" are: "%s".'
42
% (name, options['align'], '", "'.join(align_v_values)),
43
nodes.literal_block(block_text, block_text), line=lineno)
45
elif options['align'] not in align_h_values:
46
error = state_machine.reporter.error(
47
'Error in "%s" directive: "%s" is not a valid value for '
48
'the "align" option. Valid values for "align" are: "%s".'
49
% (name, options['align'], '", "'.join(align_h_values)),
50
nodes.literal_block(block_text, block_text), line=lineno)
53
reference = directives.uri(arguments[0])
54
options['uri'] = reference
56
if options.has_key('target'):
57
block = states.escape2null(options['target']).splitlines()
58
block = [line for line in block]
59
target_type, data = state.parse_target(block, block_text, lineno)
60
if target_type == 'refuri':
61
reference_node = nodes.reference(refuri=data)
62
elif target_type == 'refname':
63
reference_node = nodes.reference(
64
refname=fully_normalize_name(data),
65
name=whitespace_normalize_name(data))
66
reference_node.indirect_reference_name = data
67
state.document.note_refname(reference_node)
68
else: # malformed target
69
messages.append(data) # data is a system message
72
image_node = nodes.image(block_text, **options)
74
reference_node += image_node
75
return messages + [reference_node]
77
return messages + [image_node]
79
image.arguments = (1, 0, 1)
80
image.options = {'alt': directives.unchanged,
81
'height': directives.length_or_unitless,
82
'width': directives.length_or_percentage_or_unitless,
83
'scale': directives.nonnegative_int,
85
'target': directives.unchanged_required,
86
'class': directives.class_option}
88
def figure_align(argument):
89
return directives.choice(argument, align_h_values)
91
def figure(name, arguments, options, content, lineno,
92
content_offset, block_text, state, state_machine):
93
figwidth = options.get('figwidth')
95
del options['figwidth']
96
figclasses = options.get('figclass')
98
del options['figclass']
99
align = options.get('align')
102
(image_node,) = image(name, arguments, options, content, lineno,
103
content_offset, block_text, state, state_machine)
104
if isinstance(image_node, nodes.system_message):
106
figure_node = nodes.figure('', image_node)
107
if figwidth == 'image':
108
if Image and state.document.settings.file_insertion_enabled:
109
# PIL doesn't like Unicode paths:
111
i = Image.open(str(image_node['uri']))
112
except (IOError, UnicodeError):
115
state.document.settings.record_dependencies.add(image_node['uri'])
116
figure_node['width'] = i.size[0]
117
elif figwidth is not None:
118
figure_node['width'] = figwidth
120
figure_node['classes'] += figclasses
122
figure_node['align'] = align
124
node = nodes.Element() # anonymous container for parsing
125
state.nested_parse(content, content_offset, node)
127
if isinstance(first_node, nodes.paragraph):
128
caption = nodes.caption(first_node.rawsource, '',
129
*first_node.children)
130
figure_node += caption
131
elif not (isinstance(first_node, nodes.comment)
132
and len(first_node) == 0):
133
error = state_machine.reporter.error(
134
'Figure caption must be a paragraph or empty comment.',
135
nodes.literal_block(block_text, block_text), line=lineno)
136
return [figure_node, error]
138
figure_node += nodes.legend('', *node[1:])
141
def figwidth_value(argument):
142
if argument.lower() == 'image':
145
return directives.nonnegative_int(argument)
147
figure.arguments = (1, 0, 1)
148
figure.options = {'figwidth': figwidth_value,
149
'figclass': directives.class_option}
150
figure.options.update(image.options)
151
figure.options['align'] = figure_align
62
'the "align" option. Valid values for "align" are: "%s".'
63
% (self.name, self.options['align'],
64
'", "'.join(self.align_h_values)))
66
reference = directives.uri(self.arguments[0])
67
self.options['uri'] = reference
69
if self.options.has_key('target'):
70
block = states.escape2null(
71
self.options['target']).splitlines()
72
block = [line for line in block]
73
target_type, data = self.state.parse_target(
74
block, self.block_text, self.lineno)
75
if target_type == 'refuri':
76
reference_node = nodes.reference(refuri=data)
77
elif target_type == 'refname':
78
reference_node = nodes.reference(
79
refname=fully_normalize_name(data),
80
name=whitespace_normalize_name(data))
81
reference_node.indirect_reference_name = data
82
self.state.document.note_refname(reference_node)
83
else: # malformed target
84
messages.append(data) # data is a system message
85
del self.options['target']
86
set_classes(self.options)
87
image_node = nodes.image(self.block_text, **self.options)
89
reference_node += image_node
90
return messages + [reference_node]
92
return messages + [image_node]
98
return directives.choice(argument, Figure.align_h_values)
100
def figwidth_value(argument):
101
if argument.lower() == 'image':
104
return directives.nonnegative_int(argument)
106
option_spec = Image.option_spec.copy()
107
option_spec['figwidth'] = figwidth_value
108
option_spec['figclass'] = directives.class_option
109
option_spec['align'] = align
113
figwidth = self.options.get('figwidth')
115
del self.options['figwidth']
116
figclasses = self.options.get('figclass')
118
del self.options['figclass']
119
align = self.options.get('align')
121
del self.options['align']
122
(image_node,) = Image.run(self)
123
if isinstance(image_node, nodes.system_message):
125
figure_node = nodes.figure('', image_node)
126
if figwidth == 'image':
127
if PIL and self.state.document.settings.file_insertion_enabled:
128
# PIL doesn't like Unicode paths:
130
i = PIL.open(str(image_node['uri']))
131
except (IOError, UnicodeError):
134
self.state.document.settings.record_dependencies.add(
136
figure_node['width'] = i.size[0]
137
elif figwidth is not None:
138
figure_node['width'] = figwidth
140
figure_node['classes'] += figclasses
142
figure_node['align'] = align
144
node = nodes.Element() # anonymous container for parsing
145
self.state.nested_parse(self.content, self.content_offset, node)
147
if isinstance(first_node, nodes.paragraph):
148
caption = nodes.caption(first_node.rawsource, '',
149
*first_node.children)
150
figure_node += caption
151
elif not (isinstance(first_node, nodes.comment)
152
and len(first_node) == 0):
153
error = self.state_machine.reporter.error(
154
'Figure caption must be a paragraph or empty comment.',
155
nodes.literal_block(self.block_text, self.block_text),
157
return [figure_node, error]
159
figure_node += nodes.legend('', *node[1:])