Completed
Push — master ( 37f12b...701e77 )
by Christophe
21s
created

check_icon()   C

Complexity

Conditions 8

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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