1 # $Id: images.py 4667 2006-07-12 21:40:56Z wiemann $ |
|
2 # Author: David Goodger <goodger@python.org> |
|
3 # Copyright: This module has been placed in the public domain. |
|
4 |
|
5 """ |
|
6 Directives for figures and simple images. |
|
7 """ |
|
8 |
|
9 __docformat__ = 'reStructuredText' |
|
10 |
|
11 |
|
12 import sys |
|
13 from docutils import nodes, utils |
|
14 from docutils.parsers.rst import Directive |
|
15 from docutils.parsers.rst import directives, states |
|
16 from docutils.nodes import fully_normalize_name, whitespace_normalize_name |
|
17 from docutils.parsers.rst.roles import set_classes |
|
18 |
|
19 try: |
|
20 import Image as PIL # PIL |
|
21 except ImportError: |
|
22 PIL = None |
|
23 |
|
24 |
|
25 class Image(Directive): |
|
26 |
|
27 align_h_values = ('left', 'center', 'right') |
|
28 align_v_values = ('top', 'middle', 'bottom') |
|
29 align_values = align_v_values + align_h_values |
|
30 |
|
31 def align(argument): |
|
32 # This is not callable as self.align. We cannot make it a |
|
33 # staticmethod because we're saving an unbound method in |
|
34 # option_spec below. |
|
35 return directives.choice(argument, Image.align_values) |
|
36 |
|
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, |
|
44 'align': align, |
|
45 'target': directives.unchanged_required, |
|
46 'class': directives.class_option} |
|
47 |
|
48 def run(self): |
|
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: |
|
53 raise self.error( |
|
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: |
|
60 raise self.error( |
|
61 'Error in "%s" directive: "%s" is not a valid value for ' |
|
62 'the "align" option. Valid values for "align" are: "%s".' |
|
63 % (self.name, self.options['align'], |
|
64 '", "'.join(self.align_h_values))) |
|
65 messages = [] |
|
66 reference = directives.uri(self.arguments[0]) |
|
67 self.options['uri'] = reference |
|
68 reference_node = None |
|
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) |
|
88 if reference_node: |
|
89 reference_node += image_node |
|
90 return messages + [reference_node] |
|
91 else: |
|
92 return messages + [image_node] |
|
93 |
|
94 |
|
95 class Figure(Image): |
|
96 |
|
97 def align(argument): |
|
98 return directives.choice(argument, Figure.align_h_values) |
|
99 |
|
100 def figwidth_value(argument): |
|
101 if argument.lower() == 'image': |
|
102 return 'image' |
|
103 else: |
|
104 return directives.nonnegative_int(argument) |
|
105 |
|
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 |
|
110 has_content = True |
|
111 |
|
112 def run(self): |
|
113 figwidth = self.options.get('figwidth') |
|
114 if figwidth: |
|
115 del self.options['figwidth'] |
|
116 figclasses = self.options.get('figclass') |
|
117 if figclasses: |
|
118 del self.options['figclass'] |
|
119 align = self.options.get('align') |
|
120 if align: |
|
121 del self.options['align'] |
|
122 (image_node,) = Image.run(self) |
|
123 if isinstance(image_node, nodes.system_message): |
|
124 return [image_node] |
|
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: |
|
129 try: |
|
130 i = PIL.open(str(image_node['uri'])) |
|
131 except (IOError, UnicodeError): |
|
132 pass |
|
133 else: |
|
134 self.state.document.settings.record_dependencies.add( |
|
135 image_node['uri']) |
|
136 figure_node['width'] = i.size[0] |
|
137 elif figwidth is not None: |
|
138 figure_node['width'] = figwidth |
|
139 if figclasses: |
|
140 figure_node['classes'] += figclasses |
|
141 if align: |
|
142 figure_node['align'] = align |
|
143 if self.content: |
|
144 node = nodes.Element() # anonymous container for parsing |
|
145 self.state.nested_parse(self.content, self.content_offset, node) |
|
146 first_node = node[0] |
|
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), |
|
156 line=self.lineno) |
|
157 return [figure_node, error] |
|
158 if len(node) > 1: |
|
159 figure_node += nodes.legend('', *node[1:]) |
|
160 return [figure_node] |
|