Completed
Push — master ( fac099...37b9e9 )
by Christophe
02:15
created

get_collection()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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