-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlatex-to-github-app.py
397 lines (282 loc) · 17.9 KB
/
latex-to-github-app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# -*- coding: utf-8 -*-
"""
Created on Sun Aug 2 12:14:50 2020
@author: artmenlope
"""
# Use Tkinter for python 2, tkinter for python 3
import tkinter as tk # Python 3
import urllib # To convert the LaTeX code to HTML
from PIL import Image, ImageTk
from io import BytesIO
from cairosvg import svg2png
from tkinter import messagebox
class Application:
def __init__(self, root):
self.root = root
self.txt_width = 50
self.txt_height = 5
self.latex_width = self.txt_width
self.latex_height = 5*self.txt_height
self.canvas_width = 100 #6*self.txt_width
self.canvas_height = 50 #10*self.latex_height
self.root.title("Convert LaTeX code to HTML readable by Github.")
self.root.resizable(True, True)
self.create_widgets()
self.create_buttons()
self.create_scrollbars()
self.make_grid()
self.configure_rowsCols()
self.make_menu()
self.root.focus_force() # Focus on the main app's window as soon as the app starts running.
def create_widgets(self):
# Create the widgets
# Top LaTeX widget
self.l1 = tk.Label(self.root, text="Write your LaTeX code (math mode):")
self.entry = tk.Text(self.root, width=self.latex_width, height=self.latex_height, wrap=tk.NONE)
self.entry.focus_set() # Allow writing in the LaTeX widget as soon as the app is open.
# HTML output widget
self.l2 = tk.Label(self.root, text="The HTML result is:")
self.output_html = tk.Text(self.root, width=self.txt_width, height=self.txt_height, wrap=tk.CHAR)
# Github url output widget
self.l3 = tk.Label(self.root, text="The GitHub URL result is:")
self.output_gith = tk.Text(self.root, width=self.txt_width, height=self.txt_height, wrap=tk.CHAR)
# Image output canvas
self.l4 = tk.Label(self.root, text="Image:")
self.output_img = tk.Canvas(self.root, bg="white", width=self.canvas_width, height=self.canvas_height)
self.right_click_menus() # Create right click mouse copy-cut-paste menus for the text widgets.
return
def create_buttons(self):
# Create the buttons.
self.button = tk.Button(self.root, text="Convert LaTeX code", width=20)
self.activate_img = tk.IntVar(value=1) # value=1 -> checkbutton checked by default.
self.checkbutton = tk.Checkbutton(self.root, text=" View image\n(requires internet connection).", justify=tk.LEFT,
variable=self.activate_img, onvalue=1, offvalue=0)
# Button activation
self.button.configure(command=self.encodeLaTeX)
return
def create_scrollbars(self):
# Latex widget scrollbar
# Horizontal scroll
self.entry_scroll_x = tk.Scrollbar(self.root, orient="horizontal", command=self.entry.xview)
# Vertical scroll
self.entry_scroll_y = tk.Scrollbar(self.root, orient="vertical", command=self.entry.yview)
# Configure
self.entry.configure(yscrollcommand=self.entry_scroll_y.set, xscrollcommand=self.entry_scroll_x.set)
# HTML widget scrollbar
self.root.scrollbar_html = tk.Scrollbar(self.root)
self.output_html.config(yscrollcommand=self.root.scrollbar_html.set)
self.root.scrollbar_html.config(command=self.output_html.yview)
# Github widget scrollbar
self.root.scrollbar_gith = tk.Scrollbar(self.root)
self.output_gith.config(yscrollcommand=self.root.scrollbar_gith.set)
self.root.scrollbar_gith.config(command=self.output_gith.yview)
# Image scrollbar
# Horizontal scroll
self.output_img_scroll_x = tk.Scrollbar(self.root, orient="horizontal", command=self.output_img.xview)
# Vertical scroll
self.output_img_scroll_y = tk.Scrollbar(self.root, orient="vertical", command=self.output_img.yview)
# Configure
self.output_img.configure(yscrollcommand=self.output_img_scroll_y.set, xscrollcommand=self.output_img_scroll_x.set)
self.output_img.configure(scrollregion=self.output_img.bbox("all"))
return
def make_grid(self):
#Positioning the widgets
# Top LaTeX widget
self.l1.grid(row=1, column=1, padx=20, pady=(20,0), sticky="w")
self.entry.grid(row=2, column=1, rowspan=6, columnspan=2, padx=(20,0), pady=(10,0), sticky="wens")
# Buttons
self.button.grid(row=9, column=1, rowspan=1, columnspan=1, padx=(0,0), pady=(25,0))
self.checkbutton.grid(row=9, column=2, rowspan=1, columnspan=1, padx=(0,40), pady=(25,0))
# HTML output widgetd
self.l2.grid(row=1, column=5, padx=(10,0), pady=(20,0), sticky="w")
self.output_html.grid(row=2, column=5, columnspan=2, padx=(10,0), pady=(10,10), sticky="wens")
# Github url output widget
self.l3.grid(row=3, column=5, padx=(10,0), pady=(10,0), sticky="w")
self.output_gith.grid(row=4, column=5, columnspan=2, padx=(10,0), pady=(0,10), sticky="wens")
# Image output canvas
self.l4.grid(row=5, column=5, padx=(10,0), pady=(5,0), sticky="w")
self.output_img.grid(row=6, column=5, rowspan=4, columnspan=2, padx=(10,0), pady=0, sticky="wens")
# Scrollbars
# Latex widget scrollbar
# Horizontal scroll
self.entry_scroll_x.grid(row=8, column=1, rowspan=1, columnspan=2, padx=(20,0), pady=(0,10), sticky="ewn")
# Vertical scroll
self.entry_scroll_y.grid(row=2, column=3, rowspan=6, columnspan=1, pady=(10,0), sticky="nsw")
# HTML widget scrollbar
self.root.scrollbar_html.grid(row=2, column=7, rowspan=1, columnspan=1, padx=(0,10), pady=(10,10), sticky="nsw")
# Github widget scrollbar
self.root.scrollbar_gith.grid(row=4, column=7, rowspan=1, columnspan=1, padx=(0,10), pady=(0,10), sticky="nsw")
# Image scrollbar
# Horizontal scroll
self.output_img_scroll_x.grid(row=10, column=5, columnspan=2, padx=(10,0), pady=(0,15), sticky="ewn")
# Vertical scroll
self.output_img_scroll_y.grid(row=6, column=7, rowspan=4, padx=(0,10), pady=0, sticky="nsw")
return
def encodeLaTeX(self):
self.string = self.entry.get("1.0",tk.END)
self.encoded_string = urllib.parse.quote(str(self.string).encode("utf-8"), safe="~()*!.\"")
self.github_url = "https://render.githubusercontent.com/render/math?math=" + self.encoded_string
self.result = '<img src="' + self.github_url + '">'
self.output_html.delete('1.0', tk.END)
self.output_html.insert(tk.END, str(self.result))
self.output_gith.delete('1.0', tk.END)
self.output_gith.insert(tk.END, str(self.github_url))
state = self.activate_img.get()
if state == 1:
self.show_image()
else:
self.output_img.delete("all") # Delete all possible images in the output_img canvas.
return
def show_image(self):
try:
self.page = urllib.request.urlopen(self.github_url).read()
self.img_data = self.page
self.img_data = svg2png(bytestring=self.img_data, write_to=None)
self.img_data = Image.open(BytesIO(self.img_data))
#####
self.check_canvas_dimensions()
#####
self.image = ImageTk.PhotoImage(self.img_data)
self.root.image=self.image # to prevent the image garbage collected.
self.output_img.create_image((10,10), image=self.root.image, anchor="nw")
self.output_img.update() # Refresh (optional)
except urllib.error.HTTPError: # Error while rendering the image using Github.
messagebox.showerror(title="HTTPError", message="An error occurred while trying to show the image.\n\nPlease, check for typos in your LaTeX code.")
raise # Show the exception in the terminal too.
return
def check_canvas_dimensions(self):
self.img_width, self.img_height = self.img_data.size
# Reset scrollregion in case that the canvas it is already displaying a big image.
self.output_img.config(scrollregion=(0, 0, self.canvas_width, self.canvas_height))
if self.img_width > self.canvas_width or self.img_height > self.canvas_height:
self.new_width = max([1.1*self.img_width, self.canvas_width])
self.new_height = max([1.2*self.img_height, self.canvas_height])
self.output_img.config(scrollregion=(0, 0, int(self.new_width)+1, int(self.new_height)+1)) # The numbers for scrollregion must be integers.
return
def configure_rowsCols(self):
for i in range(0,7+1):
self.root.grid_columnconfigure(i,weight=1)
# self.root.grid_columnconfigure(3,weight=0) # LaTeX horizontal scrollbar
for i in range(0,10+1):
self.root.grid_rowconfigure(i,weight=1)
# self.root.grid_rowconfigure(8,weight=0) # LaTeX vertical scrollbar
return
def make_menu(self):
# Creating Menubar
self.menubar = tk.Menu(self.root)
self.root.config(menu=self.menubar) # Asign it to the window
# Adding options menu
self.help = tk.Menu(self.menubar, tearoff=0) # tearoff=0 -> once clicked, show menu (even without mouse over it) until clicked again.
self.help.add_command(label ='Documentation', command = self.docs_window)
self.menubar.add_cascade(label ='Help', menu=self.help)
# options.add_separator()
return
def right_click_menus(self):
"""
Create a right click mouse menu on each text widget with
the copy, cut and paste options.
References:
https://stackoverflow.com/q/8449053
https://gist.github.com/kai9987kai/f8b34c5538613d2786be8ab1b273e1ca
"""
# Right click menu on the text entry widget.
rClickMenu_entry = tk.Menu(self.entry, tearoff=0) # Create the menu.
rClickMenu_entry.add_command(label="Copy",
accelerator="Ctrl+C", # Show the command's shortcut together with the label.
command=lambda: self.entry.event_generate('<Control-c>')) # Define the command.
rClickMenu_entry.add_separator() # Add vertical space between the two labels.
rClickMenu_entry.add_command(label="Cut",
accelerator="Ctrl+X", # Show the command's shortcut together with the label.
command=lambda: self.entry.event_generate('<Control-x>')) # Define the command.
rClickMenu_entry.add_separator() # Add vertical space between the two labels.
rClickMenu_entry.add_command(label="Paste",
accelerator="Ctrl+V", # Show the command's shortcut together with the label.
command=lambda: self.entry.event_generate('<Control-v>')) # Define the command.
def show_rClickMenu_entry(event):
rClickMenu_entry.tk_popup(event.x_root, event.y_root, 0)
self.entry.bind("<Button-3>", show_rClickMenu_entry)
# Right click menu on the HTML output widget.
rClickMenu_HTML = tk.Menu(self.output_html, tearoff=0)
rClickMenu_HTML.add_command(label="Copy",
accelerator="Ctrl+C",
command=lambda: self.output_html.event_generate('<Control-c>'))
rClickMenu_HTML.add_separator()
rClickMenu_HTML.add_command(label="Cut",
accelerator="Ctrl+X",
command=lambda: self.output_html.event_generate('<Control-x>'))
rClickMenu_HTML.add_separator()
rClickMenu_HTML.add_command(label="Paste",
accelerator="Ctrl+V",
command=lambda: self.output_html.event_generate('<Control-v>'))
def show_rClickMenu_HTML(event):
rClickMenu_HTML.tk_popup(event.x_root, event.y_root, 0)
self.output_html.bind("<Button-3>", show_rClickMenu_HTML)
# Right click menu on the Github url output widget.
rClickMenu_gith = tk.Menu(self.output_gith, tearoff=0)
rClickMenu_gith.add_command(label="Copy",
accelerator="Ctrl+C",
command=lambda: self.output_gith.event_generate('<Control-c>'))
rClickMenu_gith.add_separator()
rClickMenu_gith.add_command(label="Cut",
accelerator="Ctrl+X",
command=lambda: self.output_gith.event_generate('<Control-x>'))
rClickMenu_gith.add_separator()
rClickMenu_gith.add_command(label="Paste",
accelerator="Ctrl+V",
command=lambda: self.output_gith.event_generate('<Control-v>'))
def show_rClickMenu_gith(event):
rClickMenu_gith.tk_popup(event.x_root, event.y_root, 0)
self.output_gith.bind("<Button-3>", show_rClickMenu_gith)
return
def docs_window(self): # new window definition
self.new_window = tk.Toplevel(self.root)
self.new_window.title('Documentation')
# self.new_window.geometry("500x500")
self.new_window.resizable(True, True)
self.new_window.focus_force() # Focus on the docs window as soon as the window is opened.
self.docs_width = 80
self.docs_height = 30
self.docs_text_widget = tk.Text(self.new_window, width=self.docs_width, height=self.docs_height, wrap=tk.WORD)
self.docs_text_widget.configure(borderwidth=0, background='gray95')
# self.docs_text_widget.configure(font=("Helvetica", 10))
self.docs_text_widget.pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=20, pady=20)
# Blue color text tag.
blue_tag = "color-" + "blue"
self.docs_text_widget.tag_configure(blue_tag, foreground="medium blue")
self.docs_text_widget.delete('1.0', tk.END)
self.docs_text_widget.insert(tk.END, str(
"This is a small app for converting LaTeX code to a format readable by GitHub's Markdown using source URLs."+"\n\n"+\
"It was inspired by the solutions provided by Alexander Rodin (username: a-rodin) at:"+"\n\n"+\
"\thttps://gist.github.com/a-rodin/fef3f543412d6e1ec5b6cf55bf197d7b"+"\n\n"+\
u"\u25A0"+' You can write your LaTeX formula in the frame under the label "Write yout LaTeX code:". '+\
'Pressing the "Convert LaTeX code" left button under the LaTeX input frame a HTML code is generated. This code can be pasted into a GitHub README.md file so the desired formula can be generated. ' "\n"+\
"It is asumed that the input is already in math mode. You can also start writing in the LaTeX frame as soon as the app is opened."+"\n\n"+\
u"\u25A0"+' The frame under the "The HTML result is:" label is where the resulting HTML code will be shown. The code will have the following form:'+"\n\n"
))
self.docs_text_widget.insert(tk.END, str(
'<img src="https://render.githubusercontent.com/render/math?math=--Formatted formula here--">'+"\n\n"
), blue_tag)
self.docs_text_widget.insert(tk.END, str(
u"\u25A0"+' The frame under the "The GitHub URL result is:" label will show the URL for GitHub to render the math formula. '+\
"You can paste this URL in the adress bar of your web browser to view the resulting formula. "+\
"This URL is already inside the HTML code and has the following aspect:"+"\n\n"))
self.docs_text_widget.insert(tk.END, str(
"https://render.githubusercontent.com/render/math?math=--Formatted formula here--"+"\n\n"
), blue_tag)
self.docs_text_widget.insert(tk.END, str(
u"\u25A0"+" In this app you can also view the result from the URL if internet connection is available. "+\
'Keeping the "View image" checkbutton below the input frame checked, the formula can ve viewed on '+\
'the "Image:" canvas on the lower right.'+"\n\n"+\
u"\u274F"+" For further reference on this application see the following GitHub repository:"+"\n\n"+\
"\thttps://github.com/artmenlope/LaTeX-to-GitHub-app"+"\n\n"+\
"This project is licensed under the terms of the MIT license."))
# Docs right y-scrollbar
self.root.docs_scroll_y = tk.Scrollbar(self.new_window, orient='vertical')
self.docs_text_widget.config(yscrollcommand=self.root.docs_scroll_y.set)
self.root.docs_scroll_y.config(command=self.docs_text_widget.yview)
self.root.docs_scroll_y.pack(side=tk.RIGHT, fill=tk.Y)
return
#Setting up the GUI window
root = tk.Tk()
Application(root)
root.mainloop()