Completed
Push — master ( 209477...315c22 )
by Christophe
23s
created

get_variant()   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-version',
32
                    'latex-tip-variant'
33
                )
34
            )
35
        else:
36
            # Get the classes
37
            classes = set(elem.classes)
38
39
            # Loop on all fontsize definition
40
            for definition in doc.defined:
41
42
                # Are the classes correct?
43
                if classes >= definition['classes']:
44
                    return add_latex(elem, definition['latex'])
45
46
def add_latex(elem, latex):
47
    if bool(latex):
48
        # Is it a Span or a Code?
49
        if isinstance(elem, Span) or isinstance(elem, Code):
50
            return [RawInline(latex, 'tex'), elem]
51
52
        # It is a CodeBlock: create a minipage to ensure the tip to be on the same page as the codeblock
53
        elif isinstance(elem, CodeBlock):
54
            return [RawBlock('\\begin{minipage}{\\textwidth}' + latex, 'tex'), elem, RawBlock('\\end{minipage}', 'tex')]
55
        # It is a Div: try to insert an inline raw before the first inline element
56
        else:
57
            inserted = [False]
58
            def insert(elem, doc):
59
                if not inserted[0] and isinstance(elem, Inline) and not isinstance(elem.parent, Inline):
60
                    inserted[0] = True
61
                    return [RawInline(latex, 'tex'), elem]
62
            elem.walk(insert)
63
            if not inserted[0]:
64
                return [RawBlock(latex, 'tex'), elem]
65
66
def latex_code(doc, definition, key_icon, key_position, key_size, key_color, key_version, key_variant):
67
    # Get the default color
68
    color = get_color(doc, definition, key_color)
69
70
    # Get the size
71
    size = get_size(doc, definition, key_size)
72
73
    # Get the prefix
74
    prefix = get_prefix(doc, definition, key_position)
75
76
    # Get the version
77
    version = get_version(doc, definition, key_version)
78
79
    # Get the variant
80
    variant = get_variant(doc, definition, key_variant)
81
82
    # Get the icons
83
    icons = get_icons(doc, definition, key_icon, color, version, variant)
84
85
    # Get the images
86
    images = create_images(doc, icons, size)
87
88
    if bool(images):
89
        # Prepare LaTeX code
90
        latex = [
91
            '{',
92
            '\\makeatletter',
93
            '\\patchcmd{\\@mn@margintest}{\\@tempswafalse}{\\@tempswatrue}{}{}',
94
            '\\patchcmd{\\@mn@margintest}{\\@tempswafalse}{\\@tempswatrue}{}{}',
95
            '\\makeatother',
96
            prefix,
97
            '\\marginnote{'
98
        ] + images + [
99
            '}[0pt]',
100
            '\\vspace{0cm}',
101
            '}',
102
        ]
103
104
        # Return LaTeX code
105
        return ''.join(latex)
106
    else:
107
        return ''
108
109
110
def get_icons(doc, definition, key_icons, color, version, variant):
111
    icons = [{
112
        'name': 'exclamation-circle',
113
        'color': color,
114
        'version': version,
115
        'variant': variant
116
    }]
117
118
    # Test the icons definition
119
    if key_icons in definition:
120
        icons = []
121
        if isinstance(definition[key_icons], str) or isinstance(definition[key_icons], unicode):
122
            check_icon(doc, icons, definition[key_icons], color, version, variant)
123
        elif isinstance(definition[key_icons], list):
124
            for icon in definition[key_icons]:
125
                check_icon(doc, icons, icon, color, version, variant)
126
127
    return icons
128
129
# Fix unicode for python3
130
try:
131
    unicode = unicode
132
except (NameError):
133
    unicode = str
134
135
def check_icon(doc, icons, icon, color, version, variant):
136
    if isinstance(icon, str) or isinstance(icon, unicode):
137
        # Simple icon
138
        name = icon
139
    elif isinstance(icon, dict) and 'color' in icon and 'name' in icon:
140
        # Complex icon with name and color
141
        color = str(icon['color'])
142
        name = str(icon['name'])
143
        if 'version' in icon:
144
            version = str(icon['version'])
145
        if 'variant' in icon:
146
            variant = str(icon['variant'])
147
    else:
148
        # Bad formed icon
149
        debug('[WARNING] pandoc-latex-tip: Bad formed icon')
150
        return
151
152
    add_icon(doc, icons, color, name, version, variant)
153
154
def add_icon(doc, icons, color, name, version, variant):
155
    # Lower the color
156
    lowerColor = color.lower()
157
158
    # Convert the color to black if unexisting
159
    from PIL import ImageColor
160
    if lowerColor not in ImageColor.colormap:
161
        debug('[WARNING] pandoc-latex-tip: ' + lowerColor + ' is not a correct color name; using black')
162
        lowerColor = 'black'
163
164
    # Is the icon correct?
165
    try:
166
        category = version + '-' + variant
167
        if category in doc.get_icon_font:
168
            if name in doc.get_icon_font[category].css_icons:
169
                icons.append({'name': name, 'color': lowerColor, 'version': version, 'variant': variant})
170
            else:
171
                debug('[WARNING] pandoc-latex-tip: ' + name + ' is not a correct icon name')
172
        else:
173
            debug('[WARNING] pandoc-latex-tip: ' + variant + ' does not exist in version ' + version)
174
    except FileNotFoundError:
175
        debug('[WARNING] pandoc-latex-tip: error in accessing to icons definition')
176
177
def get_color(doc, definition, key):
178
    if key in definition:
179
        return str(definition[key])
180
    else:
181
        return 'black'
182
183
def get_prefix(doc, definition, key):
184
    if key in definition:
185
        if definition[key] == 'right':
186
            return '\\normalmarginpar'
187
        elif definition[key] == 'left':
188
            return '\\reversemarginpar'
189
        else:
190
            debug('[WARNING] pandoc-latex-tip: ' + str(definition[key]) + ' is not a correct position; using left')
191
            return '\\reversemarginpar'
192
    return '\\reversemarginpar'
193
194
def get_version(doc, definition, key):
195
    if key in definition:
196
        return str(definition[key])
197
    else:
198
        return '4.7'
199
200
def get_variant(doc, definition, key):
201
    if key in definition:
202
        return str(definition[key])
203
    else:
204
        return 'regular'
205
206
def get_size(doc, definition, key):
207
   # Get the size
208
    size = '18'
209
    if key in definition:
210
        try:
211
            intValue = int(definition[key])
212
            if intValue > 0:
213
                size = str(intValue)
214
            else:
215
                debug('[WARNING] pandoc-latex-tip: size must be greater than 0; using ' + size)
216
        except ValueError:
217
            debug('[WARNING] pandoc-latex-tip: size must be a number; using ' + size)
218
    return size
219
220
def create_images(doc, icons, size):
221
    # Generate the LaTeX image code
222
    images = []
223
224
    for icon in icons:
225
226
        # Get the apps dirs
227
        from pkg_resources import get_distribution
228
        from appdirs import AppDirs
229
        dirs = AppDirs('pandoc_latex_tip', version = get_distribution('pandoc_latex_tip').version)
230
231
        # Get the image from the App cache folder
232
        image_dir = os.path.join(dirs.user_cache_dir, icon['version'], icon['variant'], icon['color'])
233
        image = os.path.join(image_dir, icon['name'] + '.png')
234
235
        # Create the image if not existing in the cache
236
        try:
237
            if not os.path.isfile(image):
238
                # Create the image in the cache
239
                doc.get_icon_font[icon['version'] + '-' + icon['variant']].export_icon(
240
                    icon['name'],
241
                    512,
242
                    color = icon['color'],
243
                    export_dir = image_dir
244
                )
245
246
            # Add the LaTeX image
247
            images.append('\\includegraphics[width=' + size + 'pt]{' + image + '}')
248
        except TypeError:
249
            debug('[WARNING] pandoc-latex-tip: icon name ' + icon['name'] + ' does not exist in variant ' + icon['variant'])
250
        except FileNotFoundError:
251
            debug('[WARNING] pandoc-latex-tip: error in generating image')
252
253
    return images
254
255
def add_definition(doc, definition):
256
    # Get the classes
257
    classes = definition['classes']
258
259
    # Add a definition if correct
260
    if bool(classes):
261
        latex = latex_code(doc, definition, 'icons', 'position', 'size', 'color', 'version', 'variant')
262
        if latex:
263
            doc.defined.append({'classes' : set(classes), 'latex': latex})
264
265
def prepare(doc):
266
    # Add getIconFont library to doc
267
    import icon_font_to_png
268
    from pkg_resources import get_distribution
269
    from appdirs import AppDirs
270
    dirs = AppDirs('pandoc_latex_tip', version = get_distribution('pandoc_latex_tip').version)
271
    doc.get_icon_font = {
272
        '4.7-regular': icon_font_to_png.IconFont(
273
            os.path.join(dirs.user_data_dir, '4.7', 'font-awesome.css'),
274
            os.path.join(dirs.user_data_dir, '4.7', 'fontawesome-webfont.ttf')
275
        ),
276
        '5.0-brands': icon_font_to_png.IconFont(
277
            os.path.join(dirs.user_data_dir, '5.0', 'fontawesome.css'),
278
            os.path.join(dirs.user_data_dir, '5.0', 'fa-brands-400.ttf')
279
        ),
280
        '5.0-regular': icon_font_to_png.IconFont(
281
            os.path.join(dirs.user_data_dir, '5.0', 'fontawesome.css'),
282
            os.path.join(dirs.user_data_dir, '5.0', 'fa-regular-400.ttf')
283
        ),
284
        '5.0-solid': icon_font_to_png.IconFont(
285
            os.path.join(dirs.user_data_dir, '5.0', 'fontawesome.css'),
286
            os.path.join(dirs.user_data_dir, '5.0', 'fa-solid-900.ttf')
287
        )
288
    }
289
290
    # Prepare the definitions
291
    doc.defined = []
292
293
    # Get the meta data
294
    meta = doc.get_metadata('pandoc-latex-tip')
295
296
    if isinstance(meta, list):
297
298
        # Loop on all definitions
299
        for definition in meta:
300
301
            # Verify the definition
302
            if isinstance(definition, dict) and 'classes' in definition and isinstance(definition['classes'], list):
303
                add_definition(doc, definition)
304
305
def finalize(doc):
306
    # Add header-includes if necessary
307
    if 'header-includes' not in doc.metadata:
308
        doc.metadata['header-includes'] = MetaList()
309
    # Convert header-includes to MetaList if necessary
310
    elif not isinstance(doc.metadata['header-includes'], MetaList):
311
        doc.metadata['header-includes'] = MetaList(doc.metadata['header-includes'])
312
313
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{graphicx,grffile}', 'tex')))
314
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{marginnote}', 'tex')))
315
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{etoolbox}', 'tex')))
316
317
def main(doc = None):
318
    return run_filter(tip, prepare = prepare, finalize = finalize, doc = doc)
319
320
if __name__ == '__main__':
321
    main()
322
323