Passed
Push — main ( 5a74ad...67d186 )
by Christophe
02:19 queued 01:12
created

_main.main()   A

Complexity

Conditions 1

Size

Total Lines 15
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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