2 # Author: David Goodger <goodger@python.org>
3 # Copyright: This module has been placed in the public domain.
6 Directives for figures and simple images.
9 __docformat__
= 'reStructuredText'
14 from docutils
import nodes
, utils
15 from docutils
.parsers
.rst
import Directive
16 from docutils
.parsers
.rst
import directives
, states
17 from docutils
.nodes
import fully_normalize_name
, whitespace_normalize_name
18 from docutils
.parsers
.rst
.roles
import set_classes
19 try: # check for the Python Imaging Library
22 try: # sometimes PIL modules are put in PYTHONPATH's root
24 class PIL(object): pass # dummy wrapper
29 class Image(Directive
):
31 align_h_values
= ('left', 'center', 'right')
32 align_v_values
= ('top', 'middle', 'bottom')
33 align_values
= align_v_values
+ align_h_values
36 # This is not callable as self.align. We cannot make it a
37 # staticmethod because we're saving an unbound method in
39 return directives
.choice(argument
, Image
.align_values
)
41 required_arguments
= 1
42 optional_arguments
= 0
43 final_argument_whitespace
= True
44 option_spec
= {'alt': directives
.unchanged
,
45 'height': directives
.length_or_unitless
,
46 'width': directives
.length_or_percentage_or_unitless
,
47 'scale': directives
.percentage
,
49 'name': directives
.unchanged
,
50 'target': directives
.unchanged_required
,
51 'class': directives
.class_option
}
54 if 'align' in self
.options
:
55 if isinstance(self
.state
, states
.SubstitutionDef
):
56 # Check for align_v_values.
57 if self
.options
['align'] not in self
.align_v_values
:
59 'Error in "%s" directive: "%s" is not a valid value '
60 'for the "align" option within a substitution '
61 'definition. Valid values for "align" are: "%s".'
62 % (self
.name
, self
.options
['align'],
63 '", "'.join(self
.align_v_values
)))
64 elif self
.options
['align'] not in self
.align_h_values
:
66 'Error in "%s" directive: "%s" is not a valid value for '
67 'the "align" option. Valid values for "align" are: "%s".'
68 % (self
.name
, self
.options
['align'],
69 '", "'.join(self
.align_h_values
)))
71 reference
= directives
.uri(self
.arguments
[0])
72 self
.options
['uri'] = reference
74 if 'target' in self
.options
:
75 block
= states
.escape2null(
76 self
.options
['target']).splitlines()
77 block
= [line
for line
in block
]
78 target_type
, data
= self
.state
.parse_target(
79 block
, self
.block_text
, self
.lineno
)
80 if target_type
== 'refuri':
81 reference_node
= nodes
.reference(refuri
=data
)
82 elif target_type
== 'refname':
83 reference_node
= nodes
.reference(
84 refname
=fully_normalize_name(data
),
85 name
=whitespace_normalize_name(data
))
86 reference_node
.indirect_reference_name
= data
87 self
.state
.document
.note_refname(reference_node
)
88 else: # malformed target
89 messages
.append(data
) # data is a system message
90 del self
.options
['target']
91 set_classes(self
.options
)
92 image_node
= nodes
.image(self
.block_text
, **self
.options
)
93 self
.add_name(image_node
)
95 reference_node
+= image_node
96 return messages
+ [reference_node
]
98 return messages
+ [image_node
]
104 return directives
.choice(argument
, Figure
.align_h_values
)
106 def figwidth_value(argument
):
107 if argument
.lower() == 'image':
110 return directives
.length_or_percentage_or_unitless(argument
, 'px')
112 option_spec
= Image
.option_spec
.copy()
113 option_spec
['figwidth'] = figwidth_value
114 option_spec
['figclass'] = directives
.class_option
115 option_spec
['align'] = align
119 figwidth
= self
.options
.pop('figwidth', None)
120 figclasses
= self
.options
.pop('figclass', None)
121 align
= self
.options
.pop('align', None)
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 imagepath
= urllib
.url2pathname(image_node
['uri'])
130 img
= PIL
.Image
.open(
131 imagepath
.encode(sys
.getfilesystemencoding()))
132 except (IOError, UnicodeEncodeError):
135 self
.state
.document
.settings
.record_dependencies
.add(
136 imagepath
.replace('\\', '/'))
137 figure_node
['width'] = '%dpx' % img
.size
[0]
139 elif figwidth
is not None:
140 figure_node
['width'] = figwidth
142 figure_node
['classes'] += figclasses
144 figure_node
['align'] = align
146 node
= nodes
.Element() # anonymous container for parsing
147 self
.state
.nested_parse(self
.content
, self
.content_offset
, node
)
149 if isinstance(first_node
, nodes
.paragraph
):
150 caption
= nodes
.caption(first_node
.rawsource
, '',
151 *first_node
.children
)
152 caption
.source
= first_node
.source
153 caption
.line
= first_node
.line
154 figure_node
+= caption
155 elif not (isinstance(first_node
, nodes
.comment
)
156 and len(first_node
) == 0):
157 error
= self
.state_machine
.reporter
.error(
158 'Figure caption must be a paragraph or empty comment.',
159 nodes
.literal_block(self
.block_text
, self
.block_text
),
161 return [figure_node
, error
]
163 figure_node
+= nodes
.legend('', *node
[1:])