[3] | 1 | # Author: David Goodger |
---|
| 2 | # Contact: goodger@users.sourceforge.net |
---|
| 3 | # Revision: $Revision: 4234 $ |
---|
| 4 | # Date: $Date: 2005-12-29 02:14:21 +0100 (Thu, 29 Dec 2005) $ |
---|
| 5 | # Copyright: This module has been placed in the public domain. |
---|
| 6 | |
---|
| 7 | """ |
---|
| 8 | Directives for figures and simple images. |
---|
| 9 | """ |
---|
| 10 | |
---|
| 11 | __docformat__ = 'reStructuredText' |
---|
| 12 | |
---|
| 13 | |
---|
| 14 | import sys |
---|
| 15 | from docutils import nodes, utils |
---|
| 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 | |
---|
| 20 | try: |
---|
| 21 | import Image # PIL |
---|
| 22 | except ImportError: |
---|
| 23 | Image = None |
---|
| 24 | |
---|
| 25 | align_h_values = ('left', 'center', 'right') |
---|
| 26 | align_v_values = ('top', 'middle', 'bottom') |
---|
| 27 | align_values = align_v_values + align_h_values |
---|
| 28 | |
---|
| 29 | def align(argument): |
---|
| 30 | return directives.choice(argument, align_values) |
---|
| 31 | |
---|
| 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( |
---|
| 39 | '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) |
---|
| 44 | return [error] |
---|
| 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) |
---|
| 51 | return [error] |
---|
| 52 | messages = [] |
---|
| 53 | reference = directives.uri(arguments[0]) |
---|
| 54 | options['uri'] = reference |
---|
| 55 | reference_node = None |
---|
| 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 |
---|
| 70 | del options['target'] |
---|
| 71 | set_classes(options) |
---|
| 72 | image_node = nodes.image(block_text, **options) |
---|
| 73 | if reference_node: |
---|
| 74 | reference_node += image_node |
---|
| 75 | return messages + [reference_node] |
---|
| 76 | else: |
---|
| 77 | return messages + [image_node] |
---|
| 78 | |
---|
| 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, |
---|
| 84 | 'align': align, |
---|
| 85 | 'target': directives.unchanged_required, |
---|
| 86 | 'class': directives.class_option} |
---|
| 87 | |
---|
| 88 | def figure_align(argument): |
---|
| 89 | return directives.choice(argument, align_h_values) |
---|
| 90 | |
---|
| 91 | def figure(name, arguments, options, content, lineno, |
---|
| 92 | content_offset, block_text, state, state_machine): |
---|
| 93 | figwidth = options.get('figwidth') |
---|
| 94 | if figwidth: |
---|
| 95 | del options['figwidth'] |
---|
| 96 | figclasses = options.get('figclass') |
---|
| 97 | if figclasses: |
---|
| 98 | del options['figclass'] |
---|
| 99 | align = options.get('align') |
---|
| 100 | if align: |
---|
| 101 | del options['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): |
---|
| 105 | return [image_node] |
---|
| 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: |
---|
| 110 | try: |
---|
| 111 | i = Image.open(str(image_node['uri'])) |
---|
| 112 | except (IOError, UnicodeError): |
---|
| 113 | pass |
---|
| 114 | else: |
---|
| 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 |
---|
| 119 | if figclasses: |
---|
| 120 | figure_node['classes'] += figclasses |
---|
| 121 | if align: |
---|
| 122 | figure_node['align'] = align |
---|
| 123 | if content: |
---|
| 124 | node = nodes.Element() # anonymous container for parsing |
---|
| 125 | state.nested_parse(content, content_offset, node) |
---|
| 126 | first_node = node[0] |
---|
| 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] |
---|
| 137 | if len(node) > 1: |
---|
| 138 | figure_node += nodes.legend('', *node[1:]) |
---|
| 139 | return [figure_node] |
---|
| 140 | |
---|
| 141 | def figwidth_value(argument): |
---|
| 142 | if argument.lower() == 'image': |
---|
| 143 | return 'image' |
---|
| 144 | else: |
---|
| 145 | return directives.nonnegative_int(argument) |
---|
| 146 | |
---|
| 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 |
---|
| 152 | figure.content = 1 |
---|