Completed
Push — master ( 131b28...d5f583 )
by Christophe
29s
created

define_localfootnotes()   A

Complexity

Conditions 3

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
#!/usr/bin/env python
2
3
"""
4
Pandoc filter for adding admonition in LaTeX
5
"""
6
7
from panflute import *
8
import uuid
9
10
def default_environment():
11
    return {
12
        'env': 'env-' + str(uuid.uuid4()),
13
        'color': 'black',
14
        'position': 'left',
15
        'linewidth': 2,
16
        'margin': -4,
17
        'innermargin': 5,
18
        'localfootnotes': False
19
    }
20
21
def x11colors():
22
    # See https://www.w3.org/TR/css-color-3/#svg-color
23
    return {
24
        'aliceblue': 'F0F8FF',
25
        'antiquewhite': 'FAEBD7',
26
        'aqua': '00FFFF',
27
        'aquamarine': '7FFFD4',
28
        'azure': 'F0FFFF',
29
        'beige': 'F5F5DC',
30
        'bisque': 'FFE4C4',
31
        'black': '000000',
32
        'blanchedalmond': 'FFEBCD',
33
        'blue': '0000FF',
34
        'blueviolet': '8A2BE2',
35
        'brown': 'A52A2A',
36
        'burlywood': 'DEB887',
37
        'cadetblue': '5F9EA0',
38
        'chartreuse': '7FFF00',
39
        'chocolate': 'D2691E',
40
        'coral': 'FF7F50',
41
        'cornflowerblue': '6495ED',
42
        'cornsilk': 'FFF8DC',
43
        'crimson': 'DC143C',
44
        'cyan': '00FFFF',
45
        'darkblue': '00008B',
46
        'darkcyan': '008B8B',
47
        'darkgoldenrod': 'B8860B',
48
        'darkgray': 'A9A9A9',
49
        'darkgreen': '006400',
50
        'darkgrey': 'A9A9A9',
51
        'darkkhaki': 'BDB76B',
52
        'darkmagenta': '8B008B',
53
        'darkolivegreen': '556B2F',
54
        'darkorange': 'FF8C00',
55
        'darkorchid': '9932CC',
56
        'darkred': '8B0000',
57
        'darksalmon': 'E9967A',
58
        'darkseagreen': '8FBC8F',
59
        'darkslateblue': '483D8B',
60
        'darkslategray': '2F4F4F',
61
        'darkslategrey': '2F4F4F',
62
        'darkturquoise': '00CED1',
63
        'darkviolet': '9400D3',
64
        'deeppink': 'FF1493',
65
        'deepskyblue': '00BFFF',
66
        'dimgray': '696969',
67
        'dimgrey': '696969',
68
        'dodgerblue': '1E90FF',
69
        'firebrick': 'B22222',
70
        'floralwhite': 'FFFAF0',
71
        'forestgreen': '228B22',
72
        'fuchsia': 'FF00FF',
73
        'gainsboro': 'DCDCDC',
74
        'ghostwhite': 'F8F8FF',
75
        'gold': 'FFD700',
76
        'goldenrod': 'DAA520',
77
        'gray': '808080',
78
        'green': '008000',
79
        'greenyellow': 'ADFF2F',
80
        'grey': '808080',
81
        'honeydew': 'F0FFF0',
82
        'hotpink': 'FF69B4',
83
        'indianred': 'CD5C5C',
84
        'indigo': '4B0082',
85
        'ivory': 'FFFFF0',
86
        'khaki': 'F0E68C',
87
        'lavender': 'E6E6FA',
88
        'lavenderblush': 'FFF0F5',
89
        'lawngreen': '7CFC00',
90
        'lemonchiffon': 'FFFACD',
91
        'lightblue': 'ADD8E6',
92
        'lightcoral': 'F08080',
93
        'lightcyan': 'E0FFFF',
94
        'lightgoldenrodyellow': 'FAFAD2',
95
        'lightgray': 'D3D3D3',
96
        'lightgreen': '90EE90',
97
        'lightgrey': 'D3D3D3',
98
        'lightpink': 'FFB6C1',
99
        'lightsalmon': 'FFA07A',
100
        'lightseagreen': '20B2AA',
101
        'lightskyblue': '87CEFA',
102
        'lightslategray': '778899',
103
        'lightslategrey': '778899',
104
        'lightsteelblue': 'B0C4DE',
105
        'lightyellow': 'FFFFE0',
106
        'lime': '00FF00',
107
        'limegreen': '32CD32',
108
        'linen': 'FAF0E6',
109
        'magenta': 'FF00FF',
110
        'maroon': '800000',
111
        'mediumaquamarine': '66CDAA',
112
        'mediumblue': '0000CD',
113
        'mediumorchid': 'BA55D3',
114
        'mediumpurple': '9370DB',
115
        'mediumseagreen': '3CB371',
116
        'mediumslateblue': '7B68EE',
117
        'mediumspringgreen': '00FA9A',
118
        'mediumturquoise': '48D1CC',
119
        'mediumvioletred': 'C71585',
120
        'midnightblue': '191970',
121
        'mintcream': 'F5FFFA',
122
        'mistyrose': 'FFE4E1',
123
        'moccasin': 'FFE4B5',
124
        'navajowhite': 'FFDEAD',
125
        'navy': '000080',
126
        'oldlace': 'FDF5E6',
127
        'olive': '808000',
128
        'olivedrab': '6B8E23',
129
        'orange': 'FFA500',
130
        'orangered': 'FF4500',
131
        'orchid': 'DA70D6',
132
        'palegoldenrod': 'EEE8AA',
133
        'palegreen': '98FB98',
134
        'paleturquoise': 'AFEEEE',
135
        'palevioletred': 'DB7093',
136
        'papayawhip': 'FFEFD5',
137
        'peachpuff': 'FFDAB9',
138
        'peru': 'CD853F',
139
        'pink': 'FFC0CB',
140
        'plum': 'DDA0DD',
141
        'powderblue': 'B0E0E6',
142
        'purple': '800080',
143
        'red': 'FF0000',
144
        'rosybrown': 'BC8F8F',
145
        'royalblue': '4169E1',
146
        'saddlebrown': '8B4513',
147
        'salmon': 'FA8072',
148
        'sandybrown': 'F4A460',
149
        'seagreen': '2E8B57',
150
        'seashell': 'FFF5EE',
151
        'sienna': 'A0522D',
152
        'silver': 'C0C0C0',
153
        'skyblue': '87CEEB',
154
        'slateblue': '6A5ACD',
155
        'slategray': '708090',
156
        'slategrey': '708090',
157
        'snow': 'FFFAFA',
158
        'springgreen': '00FF7F',
159
        'steelblue': '4682B4',
160
        'tan': 'D2B48C',
161
        'teal': '008080',
162
        'thistle': 'D8BFD8',
163
        'tomato': 'FF6347',
164
        'turquoise': '40E0D0',
165
        'violet': 'EE82EE',
166
        'wheat': 'F5DEB3',
167
        'white': 'FFFFFF',
168
        'whitesmoke': 'F5F5F5',
169
        'yellow': 'FFFF00',
170
        'yellowgreen': '9ACD32'
171
    }
172
173
def admonition(elem, doc):
174
    # Is it in the right format and is it Div or a CodeBlock?
175
    if doc.format == 'latex' and elem.tag in ['Div', 'CodeBlock']:
176
177
        # Is there a latex-admonition-color attribute?
178
        if 'latex-admonition-color' in elem.attributes:
179
            environment = define_environment(
180
                doc,
181
                elem.attributes,
182
                'latex-admonition-color',
183
                'latex-admonition-position',
184
                'latex-admonition-linewidth',
185
                'latex-admonition-margin',
186
                'latex-admonition-innermargin',
187
                'latex-admonition-localfootnotes'
188
            )
189
            doc.added.append(environment)
190
            return add_latex(elem, environment)
191
        else:
192
            # Get the classes
193
            classes = set(elem.classes)
194
195
            # Loop on all fontsize definition
196
            for environment in doc.defined:
197
198
                # Are the classes correct?
199
                if classes >= environment['classes']:
200
                    return add_latex(elem,  environment)
201
202
def add_latex(elem,  environment):
203
    images = []
204
    def extract_images(elem, doc):
205
        # Extract image which is alone with a title
206
        if isinstance(elem, Para) and len(elem.content) == 1 and isinstance(elem.content[0], Image) and bool(elem.content[0].content):
207
            images.append(elem)
208
            return []
209
    # The images need to be placed after the framed environment
210
    return [
211
        RawBlock('\\begin{' + environment['env'] + '}', 'tex'),
212
        elem.walk(extract_images),
213
        RawBlock('\\end{' + environment['env'] + '}', 'tex')
214
    ] + images
215
216
def prepare(doc):
217
    doc.x11colors = x11colors()
218
219
   # Prepare the definitions
220
    doc.defined = []
221
    doc.added = []
222
223
    # Get the meta data
224
    meta = doc.get_metadata('pandoc-latex-admonition')
225
226
    if isinstance(meta, list):
227
228
        # Loop on all definitions
229
        for definition in meta:
230
231
            # Verify the definition
232
            if isinstance(definition, dict) and 'classes' in definition and isinstance(definition['classes'], list):
233
                environment = define_environment(doc, definition, 'color', 'position', 'linewidth', 'margin', 'innermargin', 'localfootnotes')
234
                environment['classes'] = set(definition['classes'])
235
                doc.defined.append(environment)
236
237
def define_environment(doc, definition, key_color, key_position, key_linewidth, key_margin, key_innermargin, key_localfootnotes):
238
    # Get the default environment
239
    environment = default_environment()
240
    define_color(doc, environment, definition, key_color)
241
    define_position(doc, environment, definition, key_position)
242
    define_linewidth(doc, environment, definition, key_linewidth)
243
    define_margin(doc, environment, definition, key_margin)
244
    define_innermargin(doc, environment, definition, key_innermargin)
245
    define_localfootnotes(doc, environment, definition, key_localfootnotes)
246
    return environment
247
248
def define_color(doc, environment, definition, key_color):
249
    # Get the color
250
    if key_color in definition:
251
        color = str(definition[key_color]).lower()
252
        if color in doc.x11colors:
253
            environment['color'] = color
254
        else:
255
            # color must be a valid x11 color (https://www.w3.org/TR/css-color-3/#svg-color)
256
            debug('[WARNING] pandoc-latex-admonition: ' + color + ' is not a valid x11 color; using ' + environment['color'])
257
258
def define_position(doc, environment, definition, key_position):
259
    # Get the position
260
    if key_position in definition:
261
        environment['position'] = str(definition[key_position])
262
263
def define_linewidth(doc, environment, definition, key_linewidth):
264
    # Get the line width
265
    if key_linewidth in definition:
266
        try:
267
            linewidth = int(str(definition[key_linewidth]))
268
            if linewidth <= 0:
269
                debug('[WARNING] pandoc-latex-admonition: linewidth must be a positivie integer; using ' + str(environment['linewidth']))
270
            else:
271
                environment['linewidth'] = linewidth
272
        except ValueError:
273
            debug('[WARNING] pandoc-latex-admonition: linewidth is not a valid; using ' + str(environment['linewidth']))
274
275
def define_margin(doc, environment, definition, key_margin):
276
    # Get the margin
277
    if key_margin in definition:
278
        try:
279
            environment['margin'] = int(str(definition[key_margin]))
280
        except ValueError:
281
            debug('[WARNING] pandoc-latex-admonition: margin is not a valid; using ' + str(environment['margin']))
282
283
def define_innermargin(doc, environment, definition, key_innermargin):
284
    # Get the inner margin
285
    if key_innermargin in definition:
286
        try:
287
            environment['innermargin'] = int(str(definition[key_innermargin]))
288
        except ValueError:
289
            debug('[WARNING] pandoc-latex-admonition: innermargin is not a valid; using ' + str(environment['innermargin']))
290
291
def define_localfootnotes(doc, environment, definition, key_localfootnotes):
292
    # Get the local footnotes
293
    if key_localfootnotes in definition:
294
        try:
295
            environment['localfootnotes'] = bool(str(definition[key_localfootnotes]))
296
        except ValueError:
297
            debug('[WARNING] pandoc-latex-admonition: localfootnotes is not a valid; using ' + str(environment['localfootnotes']))
298
299
def environment_option(position, linewidth, innermargin, margin, color):
300
    if position == 'right':
301
       pos = 'right'
302
       inv = 'left'
303
    else:
304
       pos = 'left'
305
       inv = 'right'
306
307
    properties = [
308
        'topline=false',
309
        'bottomline=false',
310
        inv + 'line=false',
311
        'linewidth=' + str(linewidth) + 'pt',
312
        'inner' + pos + 'margin=' + str(innermargin) +'pt',
313
        pos + 'margin=' + str(margin) +'pt',
314
        'inner' + inv + 'margin=0pt',
315
        'linecolor=' + color,
316
        'skipabove=\\topskip'
317
    ]
318
    return '[' + ','.join(properties) + ']'
319
320
def new_environment(environment):
321
    if environment['localfootnotes']:
322
        return '\n'.join([
323
            '\\newenvironment{' + environment['env'] + '}',
324
            '{',
325
            '    \\begin{mdframed}' + environment_option(environment['position'], environment['linewidth'], environment['innermargin'], environment['margin'], environment['color']),
326
            '}',
327
            '{',
328
            '    \\end{mdframed}',
329
            '}'
330
        ])
331
    else:
332
        return '\n'.join([
333
            '\\newenvironment{' + environment['env'] + '}',
334
            '{',
335
            '    \\savenotes',
336
            '    \\begin{mdframed}' + environment_option(environment['position'], environment['linewidth'], environment['innermargin'], environment['margin'], environment['color']),
337
            '    \\let\\thempfootnote=\\thefootnote',
338
            '    \\let\\oldfootnote\\footnote',
339
            '    \\renewcommand{\\footnote}[1]{\\stepcounter{footnote}\\oldfootnote{##1}}',
340
            '}',
341
            '{',
342
            '    \\end{mdframed}',
343
            '    \\spewnotes',
344
            '}'
345
        ])
346
347
def finalize(doc):
348
    # Add header-includes if necessary
349
    if 'header-includes' not in doc.metadata:
350
        doc.metadata['header-includes'] = []
351
352
    # Add usefull LaTexPackage
353
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{mdframed}', 'tex')))
354
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{xcolor}', 'tex')))
355
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{footnote}', 'tex')))
356
357
    # Define x11 colors
358
    tex = []
359
    for name, color in doc.x11colors.items():
360
        tex.append('\\definecolor{' + name.lower() + '}{HTML}{' + color + '}')
361
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\n'.join(tex), 'tex')))
362
363
    # Define specific environments
364
    for environment in doc.defined + doc.added:
365
        doc.metadata['header-includes'].append(MetaInlines(RawInline(new_environment(environment), 'tex')))
366
367
def main(doc = None):
368
    run_filter(admonition, prepare = prepare, finalize = finalize, doc = doc)
369
370
if __name__ == '__main__':
371
    main()
372
373