Completed
Push — master ( 0e3d3b...3b0afb )
by Christophe
31s
created

get_link()   A

Complexity

Conditions 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
dl 0
loc 4
rs 10
1
#!/usr/bin/env python
2
3
"""
4
Pandoc filter for adding tip in LaTeX
5
"""
6
7
import os
8
9
from panflute import run_filter, convert_text, debug, Span, Code, CodeBlock, Inline, RawBlock, RawInline, Image, Link, Plain, MetaList, MetaInlines
10
11
try:
12
    FileNotFoundError
13
except NameError:
14
    # py2
15
    FileNotFoundError = IOError  # pylint: disable=redefined-builtin
16
17
18
def tip(elem, doc):
19
    # Is it in the right format and is it a Span, Div?
20
    if doc.format in ['latex', 'beamer'] and elem.tag in ['Span', 'Div', 'Code', 'CodeBlock']:
21
22
        # Is there a latex-tip-icon attribute?
23
        if 'latex-tip-icon' in elem.attributes:
24
            return add_latex(
25
                elem,
26
                latex_code(
27
                    doc,
28
                    elem.attributes,
29
                    'latex-tip-icon',
30
                    'latex-tip-position',
31
                    'latex-tip-size',
32
                    'latex-tip-color',
33
                    'latex-tip-collection',
34
                    'latex-tip-version',
35
                    'latex-tip-variant',
36
                    'latex-tip-link'
37
                )
38
            )
39
        else:
40
            # Get the classes
41
            classes = set(elem.classes)
42
43
            # Loop on all fontsize definition
44
            for definition in doc.defined:
45
46
                # Are the classes correct?
47
                if classes >= definition['classes']:
48
                    return add_latex(elem, definition['latex'])
49
50
    return None
51
52
53
def add_latex(elem, latex):
54
    if bool(latex):
55
        # Is it a Span or a Code?
56
        if isinstance(elem, (Span, Code)):
57
            return [RawInline(latex, 'tex'), elem]
58
59
        # It is a CodeBlock: create a minipage to ensure the tip to be on the same page as the codeblock
60
        elif isinstance(elem, CodeBlock):
61
            return [RawBlock('\\begin{minipage}{\\textwidth}' + latex, 'tex'), elem, RawBlock('\\end{minipage}', 'tex')]
62
        # It is a Div: try to insert an inline raw before the first inline element
63
        else:
64
            inserted = [False]
65
66
            def insert(elem, _):
67
                if not inserted[0] and isinstance(elem, Inline) and not isinstance(elem.parent, Inline):
68
                    inserted[0] = True
69
                    return [RawInline(latex, 'tex'), elem]
70
                return None
71
72
            elem.walk(insert)
73
            if not inserted[0]:
74
                return [RawBlock(latex, 'tex'), elem]
75
76
    return None
77
78
79
# pylint: disable=too-many-arguments,too-many-locals
80
def latex_code(doc, definition, key_icon, key_position, key_size, key_color, key_collection, key_version, key_variant, key_link):
81
    # Get the default color
82
    color = get_color(doc, definition, key_color)
83
84
    # Get the size
85
    size = get_size(doc, definition, key_size)
86
87
    # Get the prefix
88
    prefix = get_prefix(doc, definition, key_position)
89
90
    # Get the collection
91
    collection = get_collection(doc, definition, key_collection)
92
93
    # Get the version
94
    version = get_version(doc, definition, key_version)
95
96
    # Get the variant
97
    variant = get_variant(doc, definition, key_variant)
98
99
    # Get the link
100
    link = get_link(doc, definition, key_link)
101
102
    # Get the icons
103
    icons = get_icons(doc, definition, key_icon, color, collection, version, variant, link)
104
105
    # Get the images
106
    images = create_images(doc, icons, size)
107
108
    if bool(images):
109
        # Prepare LaTeX code
110
        latex = [
111
            '{',
112
            '\\makeatletter',
113
            '\\patchcmd{\\@mn@margintest}{\\@tempswafalse}{\\@tempswatrue}{}{}',
114
            '\\patchcmd{\\@mn@margintest}{\\@tempswafalse}{\\@tempswatrue}{}{}',
115
            '\\makeatother',
116
            prefix,
117
            '\\marginnote{'
118
        ] + images + [
119
            '}[0pt]',
120
            '\\vspace{0cm}',
121
            '}',
122
        ]
123
124
        # Return LaTeX code
125
        return ''.join(latex)
126
    return ''
127
128
129
def get_icons(doc, definition, key_icons, color, collection, version, variant, link):
130
    icons = [{
131
        'extended-name': 'fa-exclamation-circle',
132
        'name': 'exclamation-circle',
133
        'color': color,
134
        'collection': collection,
135
        'version': version,
136
        'variant': variant,
137
        'link': link
138
    }]
139
140
    # Test the icons definition
141
    if key_icons in definition:
142
        icons = []
143
        # pylint: disable=invalid-name
144
        if isinstance(definition[key_icons], (str, unicode)):
145
            check_icon(doc, icons, definition[key_icons], color, collection, version, variant, link)
146
        elif isinstance(definition[key_icons], list):
147
            for icon in definition[key_icons]:
148
                check_icon(doc, icons, icon, color, collection, version, variant, link)
149
150
    return icons
151
152
153
# Fix unicode for python3
154
# pylint: disable=invalid-name
155
try:
156
    # pylint: disable=redefined-builtin
157
    unicode = unicode
158
except NameError:
159
    unicode = str
160
161
162
def check_icon(doc, icons, icon, color, collection, version, variant, link):
163
    if isinstance(icon, (str, unicode)):
164
        # Simple icon
165
        name = icon
166
    elif isinstance(icon, dict) and 'color' in icon and 'name' in icon:
167
        # Complex icon with name and color
168
        color = str(icon['color'])
169
        name = str(icon['name'])
170
        if 'collection' in icon:
171
            collection = str(icon['collection'])
172
        if 'version' in icon:
173
            version = str(icon['version'])
174
        if 'variant' in icon:
175
            variant = str(icon['variant'])
176
        if 'link' in icon:
177
            link = str(icon['link'])
178
    else:
179
        # Bad formed icon
180
        debug('[WARNING] pandoc-latex-tip: Bad formed icon')
181
        return
182
183
    add_icon(doc, icons, color, name, collection, version, variant, link)
184
185
186
def add_icon(doc, icons, color, name, collection, version, variant, link):
187
    # Lower the color
188
    lower_color = color.lower()
189
190
    # Convert the color to black if unexisting
191
    from PIL import ImageColor
192
    if lower_color not in ImageColor.colormap:
193
        debug('[WARNING] pandoc-latex-tip: ' + lower_color + ' is not a correct color name; using black')
194
        lower_color = 'black'
195
196
    # Is the icon correct?
197
    try:
198
        category = collection + '-' + version + '-' + variant
199
        if category in doc.get_icon_font:
200
            extended_name = doc.get_icon_font[category]['prefix'] + name
201
            if extended_name in doc.get_icon_font[category]['font'].css_icons:
202
                icons.append({
203
                    'name': name,
204
                    'extended-name': extended_name,
205
                    'color': lower_color,
206
                    'collection': collection,
207
                    'version': version,
208
                    'variant': variant,
209
                    'link': link
210
                })
211
            else:
212
                debug('[WARNING] pandoc-latex-tip: ' + name + ' is not a correct icon name')
213
        else:
214
            debug('[WARNING] pandoc-latex-tip: ' + variant + ' does not exist in version ' + version)
215
    except FileNotFoundError:
216
        debug('[WARNING] pandoc-latex-tip: error in accessing to icons definition')
217
218
219
def get_color(_, definition, key):
220
    if key in definition:
221
        return str(definition[key])
222
    return 'black'
223
224
225
def get_prefix(_, definition, key):
226
    if key in definition:
227
        if definition[key] == 'right':
228
            return '\\normalmarginpar'
229
        elif definition[key] == 'left':
230
            return '\\reversemarginpar'
231
        debug('[WARNING] pandoc-latex-tip: ' + str(definition[key]) + ' is not a correct position; using left')
232
233
    return '\\reversemarginpar'
234
235
236
def get_version(_, definition, key):
237
    if key in definition:
238
        return str(definition[key])
239
    return '4.7'
240
241
242
def get_collection(_, definition, key):
243
    if key in definition:
244
        return str(definition[key])
245
    return 'fontawesome'
246
247
248
def get_variant(_, definition, key):
249
    if key in definition:
250
        return str(definition[key])
251
    return 'regular'
252
253
254
def get_link(_, definition, key):
255
    if key in definition:
256
        return str(definition[key])
257
    return None
258
259
260
def get_size(_, definition, key):
261
    # Get the size
262
    size = '18'
263
    if key in definition:
264
        try:
265
            intValue = int(definition[key])
266
            if intValue > 0:
267
                size = str(intValue)
268
            else:
269
                debug('[WARNING] pandoc-latex-tip: size must be greater than 0; using ' + size)
270
        except ValueError:
271
            debug('[WARNING] pandoc-latex-tip: size must be a number; using ' + size)
272
    return size
273
274
275
def create_images(doc, icons, size):
276
    # Generate the LaTeX image code
277
    images = []
278
279
    for icon in icons:
280
281
        # Get the apps dirs
282
        from pkg_resources import get_distribution
283
        from appdirs import AppDirs
284
        dirs = AppDirs('pandoc_latex_tip', version=get_distribution('pandoc_latex_tip').version)
285
286
        # Get the image from the App cache folder
287
        image_dir = os.path.join(
288
            dirs.user_cache_dir,
289
            icon['collection'],
290
            icon['version'],
291
            icon['variant'],
292
            icon['color']
293
        )
294
        image = os.path.join(image_dir, icon['extended-name'] + '.png')
295
296
        # Create the image if not existing in the cache
297
        try:
298
            if not os.path.isfile(image):
299
                # Create the image in the cache
300
                category = icon['collection'] + '-' + icon['version'] + '-' + icon['variant']
301
                doc.get_icon_font[category]['font'].export_icon(
302
                    icon['extended-name'],
303
                    512,
304
                    color=icon['color'],
305
                    export_dir=image_dir
306
                )
307
308
            # Add the LaTeX image
309
            image = Image(url=image, attributes={'width': size + 'pt', 'height': size + 'pt'})
310
            if icon['link'] is None:
311
                elem = image
312
            else:
313
                elem = Link(image, url=icon['link'])
314
            images.append(convert_text(Plain(elem), input_format='panflute', output_format='latex'))
315
        except TypeError:
316
            debug('[WARNING] pandoc-latex-tip: icon name ' + icon['name'] + ' does not exist in variant ' + icon['variant'] + ' for collection ' + icon['collection'] + '-' + icon['version'])
317
        except FileNotFoundError:
318
            debug('[WARNING] pandoc-latex-tip: error in generating image')
319
320
    return images
321
322
323
def add_definition(doc, definition):
324
    # Get the classes
325
    classes = definition['classes']
326
327
    # Add a definition if correct
328
    if bool(classes):
329
        latex = latex_code(
330
            doc,
331
            definition,
332
            'icons',
333
            'position',
334
            'size',
335
            'color',
336
            'collection',
337
            'version',
338
            'variant',
339
            'link'
340
        )
341
        if latex:
342
            doc.defined.append({'classes': set(classes), 'latex': latex})
343
344
345
def prepare(doc):
346
    # Add getIconFont library to doc
347
    import icon_font_to_png
348
    from pkg_resources import get_distribution
349
    from appdirs import AppDirs
350
    dirs = AppDirs('pandoc_latex_tip', version=get_distribution('pandoc_latex_tip').version)
351
    doc.get_icon_font = {
352
        'fontawesome-4.7-regular': {
353
            'font': icon_font_to_png.IconFont(
354
                os.path.join(dirs.user_data_dir, 'fontawesome', '4.7', 'font-awesome.css'),
355
                os.path.join(dirs.user_data_dir, 'fontawesome', '4.7', 'fontawesome-webfont.ttf'),
356
                True
357
            ),
358
            'prefix': 'fa-'
359
        },
360
        'fontawesome-5.0-brands': {
361
            'font': icon_font_to_png.IconFont(
362
                os.path.join(dirs.user_data_dir, 'fontawesome', '5.0', 'fontawesome.css'),
363
                os.path.join(dirs.user_data_dir, 'fontawesome', '5.0', 'fa-brands-400.ttf'),
364
                True
365
            ),
366
            'prefix': 'fa-'
367
        },
368
        'fontawesome-5.0-regular': {
369
            'font': icon_font_to_png.IconFont(
370
                os.path.join(dirs.user_data_dir, 'fontawesome', '5.0', 'fontawesome.css'),
371
                os.path.join(dirs.user_data_dir, 'fontawesome', '5.0', 'fa-regular-400.ttf'),
372
                True
373
            ),
374
            'prefix': 'fa-'
375
        },
376
        'fontawesome-5.0-solid': {
377
            'font': icon_font_to_png.IconFont(
378
                os.path.join(dirs.user_data_dir, 'fontawesome', '5.0', 'fontawesome.css'),
379
                os.path.join(dirs.user_data_dir, 'fontawesome', '5.0', 'fa-solid-900.ttf'),
380
                True
381
            ),
382
            'prefix': 'fa-'
383
        },
384
        'glyphicons-3.3-regular': {
385
            'font': icon_font_to_png.IconFont(
386
                os.path.join(dirs.user_data_dir, 'glyphicons', '3.3', 'bootstrap-modified.css'),
387
                os.path.join(dirs.user_data_dir, 'glyphicons', '3.3', 'glyphicons-halflings-regular.ttf'),
388
                True
389
            ),
390
            'prefix': 'glyphicon-'
391
        },
392
        'materialdesign-2.4-regular': {
393
            'font': icon_font_to_png.IconFont(
394
                os.path.join(dirs.user_data_dir, 'materialdesign', '2.4', 'materialdesignicons.css'),
395
                os.path.join(dirs.user_data_dir, 'materialdesign', '2.4', 'materialdesignicons-webfont.ttf'),
396
                True
397
            ),
398
            'prefix': 'mdi-'
399
        }
400
    }
401
402
    # Prepare the definitions
403
    doc.defined = []
404
405
    # Get the meta data
406
    meta = doc.get_metadata('pandoc-latex-tip')
407
408
    if isinstance(meta, list):
409
410
        # Loop on all definitions
411
        for definition in meta:
412
413
            # Verify the definition
414
            if isinstance(definition, dict) and 'classes' in definition and isinstance(definition['classes'], list):
415
                add_definition(doc, definition)
416
417
418
def finalize(doc):
419
    # Add header-includes if necessary
420
    if 'header-includes' not in doc.metadata:
421
        doc.metadata['header-includes'] = MetaList()
422
    # Convert header-includes to MetaList if necessary
423
    elif not isinstance(doc.metadata['header-includes'], MetaList):
424
        doc.metadata['header-includes'] = MetaList(doc.metadata['header-includes'])
425
426
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{graphicx,grffile}', 'tex')))
427
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{marginnote}', 'tex')))
428
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{etoolbox}', 'tex')))
429
430
431
def main(doc=None):
432
    return run_filter(tip, prepare=prepare, finalize=finalize, doc=doc)
433
434
435
if __name__ == '__main__':
436
    main()
437