pandoc_beamer_arrow._main.finalize()   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 42
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 21
nop 1
dl 0
loc 42
rs 9.376
c 0
b 0
f 0
1
#!/usr/bin/env python
2
3
"""Pandoc filter for adding admonition in LaTeX."""
4
from __future__ import annotations
5
6
import contextlib
7
8
from panflute import (
9
    Doc,
10
    Element,
11
    MetaInlines,
12
    MetaList,
13
    Plain,
14
    RawInline,
15
    convert_text,
16
    debug,
17
    run_filter,
18
)
19
20
21
def x11colors() -> dict[str, str]:  # noqa: CFQ001
22
    """
23
    Get the x11 colors.
24
25
    Returns
26
    -------
27
    dict[str, str]
28
        The x11 colors
29
    """
30
    # See https://www.w3.org/TR/css-color-3/#svg-color
31
    return {
32
        "aliceblue": "F0F8FF",
33
        "antiquewhite": "FAEBD7",
34
        "aqua": "00FFFF",
35
        "aquamarine": "7FFFD4",
36
        "azure": "F0FFFF",
37
        "beige": "F5F5DC",
38
        "bisque": "FFE4C4",
39
        "black": "000000",
40
        "blanchedalmond": "FFEBCD",
41
        "blue": "0000FF",
42
        "blueviolet": "8A2BE2",
43
        "brown": "A52A2A",
44
        "burlywood": "DEB887",
45
        "cadetblue": "5F9EA0",
46
        "chartreuse": "7FFF00",
47
        "chocolate": "D2691E",
48
        "coral": "FF7F50",
49
        "cornflowerblue": "6495ED",
50
        "cornsilk": "FFF8DC",
51
        "crimson": "DC143C",
52
        "cyan": "00FFFF",
53
        "darkblue": "00008B",
54
        "darkcyan": "008B8B",
55
        "darkgoldenrod": "B8860B",
56
        "darkgray": "A9A9A9",
57
        "darkgreen": "006400",
58
        "darkgrey": "A9A9A9",
59
        "darkkhaki": "BDB76B",
60
        "darkmagenta": "8B008B",
61
        "darkolivegreen": "556B2F",
62
        "darkorange": "FF8C00",
63
        "darkorchid": "9932CC",
64
        "darkred": "8B0000",
65
        "darksalmon": "E9967A",
66
        "darkseagreen": "8FBC8F",
67
        "darkslateblue": "483D8B",
68
        "darkslategray": "2F4F4F",
69
        "darkslategrey": "2F4F4F",
70
        "darkturquoise": "00CED1",
71
        "darkviolet": "9400D3",
72
        "deeppink": "FF1493",
73
        "deepskyblue": "00BFFF",
74
        "dimgray": "696969",
75
        "dimgrey": "696969",
76
        "dodgerblue": "1E90FF",
77
        "firebrick": "B22222",
78
        "floralwhite": "FFFAF0",
79
        "forestgreen": "228B22",
80
        "fuchsia": "FF00FF",
81
        "gainsboro": "DCDCDC",
82
        "ghostwhite": "F8F8FF",
83
        "gold": "FFD700",
84
        "goldenrod": "DAA520",
85
        "gray": "808080",
86
        "green": "008000",
87
        "greenyellow": "ADFF2F",
88
        "grey": "808080",
89
        "honeydew": "F0FFF0",
90
        "hotpink": "FF69B4",
91
        "indianred": "CD5C5C",
92
        "indigo": "4B0082",
93
        "ivory": "FFFFF0",
94
        "khaki": "F0E68C",
95
        "lavender": "E6E6FA",
96
        "lavenderblush": "FFF0F5",
97
        "lawngreen": "7CFC00",
98
        "lemonchiffon": "FFFACD",
99
        "lightblue": "ADD8E6",
100
        "lightcoral": "F08080",
101
        "lightcyan": "E0FFFF",
102
        "lightgoldenrodyellow": "FAFAD2",
103
        "lightgray": "D3D3D3",
104
        "lightgreen": "90EE90",
105
        "lightgrey": "D3D3D3",
106
        "lightpink": "FFB6C1",
107
        "lightsalmon": "FFA07A",
108
        "lightseagreen": "20B2AA",
109
        "lightskyblue": "87CEFA",
110
        "lightslategray": "778899",
111
        "lightslategrey": "778899",
112
        "lightsteelblue": "B0C4DE",
113
        "lightyellow": "FFFFE0",
114
        "lime": "00FF00",
115
        "limegreen": "32CD32",
116
        "linen": "FAF0E6",
117
        "magenta": "FF00FF",
118
        "maroon": "800000",
119
        "mediumaquamarine": "66CDAA",
120
        "mediumblue": "0000CD",
121
        "mediumorchid": "BA55D3",
122
        "mediumpurple": "9370DB",
123
        "mediumseagreen": "3CB371",
124
        "mediumslateblue": "7B68EE",
125
        "mediumspringgreen": "00FA9A",
126
        "mediumturquoise": "48D1CC",
127
        "mediumvioletred": "C71585",
128
        "midnightblue": "191970",
129
        "mintcream": "F5FFFA",
130
        "mistyrose": "FFE4E1",
131
        "moccasin": "FFE4B5",
132
        "navajowhite": "FFDEAD",
133
        "navy": "000080",
134
        "oldlace": "FDF5E6",
135
        "olive": "808000",
136
        "olivedrab": "6B8E23",
137
        "orange": "FFA500",
138
        "orangered": "FF4500",
139
        "orchid": "DA70D6",
140
        "palegoldenrod": "EEE8AA",
141
        "palegreen": "98FB98",
142
        "paleturquoise": "AFEEEE",
143
        "palevioletred": "DB7093",
144
        "papayawhip": "FFEFD5",
145
        "peachpuff": "FFDAB9",
146
        "peru": "CD853F",
147
        "pink": "FFC0CB",
148
        "plum": "DDA0DD",
149
        "powderblue": "B0E0E6",
150
        "purple": "800080",
151
        "red": "FF0000",
152
        "rosybrown": "BC8F8F",
153
        "royalblue": "4169E1",
154
        "saddlebrown": "8B4513",
155
        "salmon": "FA8072",
156
        "sandybrown": "F4A460",
157
        "seagreen": "2E8B57",
158
        "seashell": "FFF5EE",
159
        "sienna": "A0522D",
160
        "silver": "C0C0C0",
161
        "skyblue": "87CEEB",
162
        "slateblue": "6A5ACD",
163
        "slategray": "708090",
164
        "slategrey": "708090",
165
        "snow": "FFFAFA",
166
        "springgreen": "00FF7F",
167
        "steelblue": "4682B4",
168
        "tan": "D2B48C",
169
        "teal": "008080",
170
        "thistle": "D8BFD8",
171
        "tomato": "FF6347",
172
        "turquoise": "40E0D0",
173
        "violet": "EE82EE",
174
        "wheat": "F5DEB3",
175
        "white": "FFFFFF",
176
        "whitesmoke": "F5F5F5",
177
        "yellow": "FFFF00",
178
        "yellowgreen": "9ACD32",
179
    }
180
181
182
# pylint: disable=inconsistent-return-statements,too-many-branches,too-many-statements
183
def tikz(elem: Element, doc: Doc) -> RawInline | None:
184
    """
185
    Add admonition to elem.
186
187
    Arguments
188
    ---------
189
    elem
190
        The current element
191
    doc
192
        The pandoc document
193
194
    Returns
195
    -------
196
    RawInline | None
197
        The modified element or None
198
    """
199
    # Is it in the right format and is it Div or a CodeBlock?
200
    if doc.format in ("beamer") and elem.tag in ("Span"):
201
        # Is there a latex-admonition-color attribute?
202
        if "beamer-arrow-node" in elem.classes:
203
            text = convert_text(
204
                Plain(*elem.content), input_format="panflute", output_format="latex"
205
            )
206
207
            options = ["anchor=base"]
208
209
            color = get_color(elem, doc)
210
            if color:
211
                options.append(f"fill={color}")
212
213
            (from_value, to_value) = get_range(elem, doc)
214
            if from_value or to_value:
215
                display = f"\\only<{from_value}-{to_value}>"
216
            else:
217
                display = ""
218
219
            return RawInline(
220
                f"\\tikz[baseline]{{"
221
                f"{display}{{\\node[{','.join(options)}] "
222
                f"({elem.identifier}) "
223
                f"{{{text}}}"
224
                f";}}}}",
225
                format="tex",
226
            )
227
228
        if "beamer-arrow-edge" in elem.classes:
229
            angles = []
230
            if "angle_src" in elem.attributes:
231
                try:
232
                    angle = elem.attributes["angle_src"]
233
                    angle = int(angle)
234
                    angles.append(f"out={angle}")
235
                except ValueError:
236
                    debug(f"pandoc-beamer-arrow: angle_src '{angle}' is not correct")
237
238
            if "angle_dest" in elem.attributes:
239
                try:
240
                    angle = elem.attributes["angle_dest"]
241
                    angle = int(angle)
242
                    angles.append(f"in={angle}")
243
                except ValueError:
244
                    debug(f"pandoc-beamer-arrow: angle_dest '{angle}' is not correct")
245
246
            options = ["->"]
247
248
            color = get_color(elem, doc)
249
            if color:
250
                options.append(color)
251
252
            if "linewidth" in elem.attributes:
253
                try:
254
                    linewidth = elem.attributes["linewidth"]
255
                    linewidth = int(linewidth)
256
                    options.append(f"line width={linewidth}pt")
257
                except ValueError:
258
                    debug(
259
                        f"pandoc-beamer-arrow: linewidth '{linewidth}' is not correct"
260
                    )
261
262
            (from_value, to_value) = get_range(elem, doc)
263
            if from_value or to_value:
264
                display = f"<{from_value}-{to_value}>"
265
            else:
266
                display = ""
267
268
            return RawInline(
269
                f"\\begin{{tikzpicture}}[overlay]"
270
                f"\\path[{','.join(options)}]{display} "
271
                f"({elem.attributes['src']}) "
272
                f"edge "
273
                f"[{','.join(angles)}] "
274
                f"({elem.attributes['dest']});"
275
                f"\\end{{tikzpicture}}",
276
                format="tex",
277
            )
278
    return None
279
280
281
def get_range(elem: Element, _) -> tuple[str, str]:
282
    """
283
    Get the range of display for an element.
284
285
    Arguments
286
    ---------
287
    elem
288
        A pandoc element.
289
290
    Returns
291
    -------
292
    tuple[str, str]
293
        A range represented by a couple.
294
    """
295
    from_value = elem.attributes.get("from", "")
296
    if bool(from_value):
297
        try:
298
            from_value = max(1, int(from_value))
299
        except ValueError:
300
            debug(f"pandoc-beamer-arrow: from value '{from_value}' is not " f"correct")
301
            from_value = ""
302
    else:
303
        from_value = ""
304
305
    to_value = elem.attributes.get("to", "")
306
    if bool(to_value):
307
        try:
308
            to_value = max(1, int(to_value))
309
        except ValueError:
310
            debug(f"pandoc-beamer-arrow: to value '{to_value}' is not " f"correct")
311
            to_value = ""
312
    else:
313
        to_value = ""
314
315
    with contextlib.suppress(TypeError):
316
        if to_value < from_value:
317
            debug(
318
                f"pandoc-beamer-arrow: from value '{from_value}' and "
319
                f" to value '{to_value}' are incompatible"
320
            )
321
            from_value = ""
322
            to_value = ""
323
324
    return (from_value, to_value)
325
326
327
def get_color(elem: Element, doc: Doc) -> str:
328
    """
329
    Get the color of display for an element.
330
331
    Arguments
332
    ---------
333
    elem
334
        A pandoc element
335
    doc
336
        The pandoc document.
337
338
    Returns
339
    -------
340
    str
341
        The color.
342
    """
343
    if "color" in elem.attributes:
344
        color = elem.attributes["color"]
345
        if color in doc.x11colors:
346
            return color  # type: ignore
347
        debug(f"pandoc-beamer-arrow: color '{color}' is not correct")
348
    return ""
349
350
351
def prepare(doc: Doc) -> None:
352
    """
353
    Prepare the document.
354
355
    Arguments
356
    ---------
357
    doc
358
        The pandoc document
359
    """
360
    doc.x11colors = x11colors()
361
362
363
def finalize(doc: Doc) -> None:
364
    """
365
    Finalize the pandoc document.
366
367
    Arguments
368
    ---------
369
    doc
370
        The pandoc document
371
    """
372
    # Add header-includes if necessary
373
    if "header-includes" not in doc.metadata:
374
        doc.metadata["header-includes"] = MetaList()
375
    # Convert header-includes to MetaList if necessary
376
    elif not isinstance(doc.metadata["header-includes"], MetaList):
377
        doc.metadata["header-includes"] = MetaList(doc.metadata["header-includes"])
378
379
    # Add useful LaTexPackage
380
    doc.metadata["header-includes"].append(
381
        MetaInlines(RawInline("\\usepackage{tikz}", "tex"))
382
    )
383
    doc.metadata["header-includes"].append(
384
        MetaInlines(RawInline("\\tikzstyle{every picture}+=[remember picture]", "tex"))
385
    )
386
    doc.metadata["header-includes"].append(
387
        MetaInlines(RawInline("\\usetikzlibrary{positioning}", "tex"))
388
    )
389
    doc.metadata["header-includes"].append(
390
        MetaInlines(
391
            RawInline(
392
                "\\tikzset{onslide/.code args={<#1>#2}{\\only<#1>{\\pgfkeysalso{#2}}}}",
393
                "tex",
394
            )
395
        )
396
    )
397
398
    # Define x11 colors
399
    tex = [
400
        f"\\definecolor{{{name.lower()}}}{{HTML}}{{{color}}}"
401
        for name, color in doc.x11colors.items()
402
    ]
403
    doc.metadata["header-includes"].append(
404
        MetaInlines(RawInline("\n".join(tex), "tex"))
405
    )
406
407
408
def main(doc: Doc | None = None) -> Doc:
409
    """
410
    Convert the document.
411
412
    Arguments
413
    ---------
414
    doc
415
        The pandoc document
416
417
    Returns
418
    -------
419
    Doc
420
        The modified pandoc document
421
    """
422
    return run_filter(tikz, prepare=prepare, finalize=finalize, doc=doc)
423
424
425
if __name__ == "__main__":
426
    main()
427