1 /**
2  * @file   latex.c
3  * @brief
4  *
5  * Copyright (C) 2009 Gummi Developers
6  * All Rights reserved.
7  *
8  * Permission is hereby granted, free of charge, to any person
9  * obtaining a copy of this software and associated documentation
10  * files (the "Software"), to deal in the Software without
11  * restriction, including without limitation the rights to use,
12  * copy, modify, merge, publish, distribute, sublicense, and/or sell
13  * copies of the Software, and to permit persons to whom the
14  * Software is furnished to do so, subject to the following
15  * conditions:
16  *
17  * The above copyright notice and this permission notice shall be
18  * included in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27  * OTHER DEALINGS IN THE SOFTWARE.
28  */
29 
30 #include "latex.h"
31 
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <unistd.h>
36 
37 #include <gtk/gtk.h>
38 #include <glib.h>
39 #include <glib/gstdio.h>
40 
41 #include "configfile.h"
42 #include "constants.h"
43 #include "editor.h"
44 #include "environment.h"
45 #include "external.h"
46 #include "gui/gui-preview.h"
47 #include "utils.h"
48 
49 #include "compile/rubber.h"
50 #include "compile/latexmk.h"
51 #include "compile/texlive.h"
52 
53 extern Gummi* gummi;
54 
latex_init(void)55 GuLatex* latex_init (void) {
56     GuLatex* l = g_new0 (GuLatex, 1);
57     l->compilelog = NULL;
58     l->modified_since_compile = FALSE;
59 
60     l->tex_version = texlive_init ();
61     rubber_init ();
62     latexmk_init ();
63     return l;
64 }
65 
66 
67 
latex_method_active(gchar * method)68 gboolean latex_method_active (gchar* method) {
69     return config_value_as_str_equals ("Compile", "steps", method);
70 }
71 
latex_update_workfile(GuEditor * ec)72 gchar* latex_update_workfile (GuEditor* ec) {
73     gchar *text;
74 
75     text = editor_grab_buffer (ec);
76 
77     // bit of a dirty hack, but only write the buffer content when
78     // there is not a recovery in progress, otherwise the workfile
79     // will be overwritten with empty text
80     if (!STR_EQU (text, "")) {
81         utils_set_file_contents (ec->workfile, text, -1);
82     }
83     return text;
84 }
85 
latex_set_compile_cmd(GuEditor * ec)86 gchar* latex_set_compile_cmd (GuEditor* ec) {
87 
88     const gchar* method = config_get_string ("Compile", "steps");
89     gchar* combined = NULL;
90     gchar* texcmd = NULL;
91 
92     if (rubber_active()) {
93         texcmd = rubber_get_command (method, ec->workfile);
94     }
95     else if (latexmk_active()) {
96         texcmd = latexmk_get_command (method, ec->workfile, ec->basename);
97     }
98     else {
99         texcmd = texlive_get_command (method, ec->workfile, ec->basename);
100     }
101 
102     combined = g_strdup_printf("%s %s", C_TEXSEC, texcmd);
103     g_free(texcmd);
104 
105     return combined;
106 }
107 
latex_analyse_log(gchar * log,gchar * filename,gchar * basename)108 gchar* latex_analyse_log (gchar* log, gchar* filename, gchar* basename) {
109     /* Rubber does not post the pdftex compilation output to tty, so we will
110      * have to open the log file and retrieve it I guess */
111     if (rubber_active()) {
112         gchar* logpath = NULL;
113         if (filename == NULL) {
114             logpath = g_strconcat (basename, ".log", NULL);
115         }
116         else {
117             logpath = g_strconcat (C_TMPDIR, C_DIRSEP,
118                                    g_path_get_basename(basename), ".log", NULL);
119         }
120         g_file_get_contents (logpath, &log, NULL, NULL);
121     }
122     return log;
123 }
124 
125 
126 
127 
latex_analyse_errors(GuLatex * lc)128 void latex_analyse_errors (GuLatex* lc) {
129     gchar* result = NULL;
130     GError* err = NULL;
131     GRegex* match_str = NULL;
132     GMatchInfo* match_info;
133 
134     if (! (match_str = g_regex_new (":(\\d+):", G_REGEX_DOTALL, 0, &err))) {
135         slog (L_ERROR, "g_regex_new (): %s\n", err->message);
136         g_error_free (err);
137         return;
138     }
139 
140     if (lc->compilelog == NULL) printf("null\n");
141 
142     if (g_regex_match (match_str, lc->compilelog, 0, &match_info)) {
143         gint count = 0;
144         while (g_match_info_matches (match_info)) {
145             if (count + 1 == BUFSIZ) break;
146             result = g_match_info_fetch (match_info, 1);
147             lc->errorlines[count++] = atoi (result);
148             g_free (result);
149             g_match_info_next (match_info, NULL);
150         }
151     }
152     if (!lc->errorlines[0])
153         lc->errorlines[0] = -1;
154     g_match_info_free (match_info);
155     g_regex_unref (match_str);
156 }
157 
latex_update_pdffile(GuLatex * lc,GuEditor * ec)158 gboolean latex_update_pdffile (GuLatex* lc, GuEditor* ec) {
159     static glong cerrors = 0;
160     gchar* basename = ec->basename;
161     gchar* filename = ec->filename;
162 
163     if (!lc->modified_since_compile) return cerrors == 0;
164 
165     const gchar* typesetter = config_get_string ("Compile", "typesetter");
166     if (!external_exists (typesetter)) {
167         // Set to default first detected typesetter
168         config_set_string ("Compile", "typesetter", "pdflatex");
169     }
170 
171     /* create compile command */
172     gchar* curdir = g_path_get_dirname (ec->workfile);
173     gchar *command = latex_set_compile_cmd (ec);
174 
175     g_free (lc->compilelog);
176     memset (lc->errorlines, 0, BUFSIZ * sizeof(gint));
177 
178     /* run pdf compilation */
179     Tuple2 cresult = utils_popen_r (command, curdir);
180     cerrors = (glong)cresult.first;
181     gchar* coutput = (gchar*)cresult.second;
182 
183     lc->compilelog = latex_analyse_log (coutput, filename, basename);
184     lc->modified_since_compile = FALSE;
185 
186     /* find error line */
187     if (cerrors && (g_utf8_strlen (lc->compilelog, -1) != 0)) {
188         latex_analyse_errors (lc);
189     }
190 
191     g_free (command);
192 
193     return cerrors == 0;
194 }
195 
latex_update_auxfile(GuEditor * ec)196 void latex_update_auxfile (GuEditor* ec) {
197     gchar* dirname = g_path_get_dirname (ec->workfile);
198     gchar* command = g_strdup_printf ("%s %s "
199                                       "--draftmode "
200                                       "-interaction=nonstopmode "
201                                       "--output-directory=\"%s\" \"%s\"",
202                                       C_TEXSEC,
203                                       config_get_string ("Compile", "typesetter"),
204                                       C_TMPDIR,
205                                       ec->workfile);
206     Tuple2 res = utils_popen_r (command, dirname);
207     g_free (dirname);
208     g_free (res.second);
209     g_free (command);
210 }
211 
latex_remove_auxfile(GuEditor * ec)212 int latex_remove_auxfile (GuEditor* ec) {
213     gchar* auxfile = NULL;
214     int res = -1;
215     // TODO: merge this into function with several other instances.
216     // for instance the lines in analyse_log
217     if (ec->filename == NULL) {
218         auxfile = g_strconcat (ec->basename, ".aux", NULL);
219     }
220     else {
221         auxfile = g_strconcat (C_TMPDIR, C_DIRSEP,
222                   g_path_get_basename(ec->basename), ".aux", NULL);
223         }
224 
225     // TODO: extend for other build files
226     if (g_file_test (auxfile, G_FILE_TEST_EXISTS)) {
227         res = g_remove (auxfile);
228     }
229     g_free (auxfile);
230     return res;
231 }
232 
latex_precompile_check(gchar * editortext)233 gboolean latex_precompile_check (gchar* editortext) {
234     /* both documentclass and documentstyle appear to be valid.
235      * http://pangea.stanford.edu/computing/unix/formatting/parts.php
236      * TOD: Improve and add document scan tags and make compatible with
237      * upcoming master/slave document system */
238 
239     // TODO: see issue #269
240 
241     gboolean class = utils_subinstr("\\documentclass", editortext, FALSE);
242     gboolean style = utils_subinstr("\\documentstyle", editortext, FALSE);
243     gboolean input = utils_subinstr("\\input", editortext, FALSE);
244 
245     return (class || style || input);
246 }
247 
latex_export_pdffile(GuLatex * lc,GuEditor * ec,const gchar * path,gboolean prompt_overrite)248 void latex_export_pdffile (GuLatex* lc, GuEditor* ec, const gchar* path,
249         gboolean prompt_overrite) {
250     gchar* savepath = NULL;
251     GError* err = NULL;
252     gint ret = 0;
253 
254     if (!STR_EQU (path + strlen (path) -4, ".pdf"))
255         savepath = g_strdup_printf ("%s.pdf", path);
256     else
257         savepath = g_strdup (path);
258 
259     if (prompt_overrite && utils_path_exists (savepath)) {
260         ret = utils_yes_no_dialog (_("The file already exists. Overwrite?"));
261         if (GTK_RESPONSE_YES != ret) {
262             g_free (savepath);
263             return;
264         }
265     }
266     if (!utils_copy_file (ec->pdffile, savepath, &err)) {
267         slog (L_G_ERROR, _("Unable to export PDF file.\n\n%s"),
268                             err->message);
269         g_error_free (err);
270     }
271 
272     g_free (savepath);
273 }
274 
latex_run_makeindex(GuEditor * ec)275 gboolean latex_run_makeindex (GuEditor* ec) {
276     int retcode = 1;
277 
278     if (g_find_program_in_path ("makeindex")) {
279 
280         gchar* command = g_strdup_printf ("%s makeindex \"%s.idx\"",
281                                           C_TEXSEC,
282                                           g_path_get_basename(ec->basename));
283 
284         Tuple2 res = utils_popen_r (command, C_TMPDIR);
285         retcode = (glong)res.first;
286         g_free (command);
287     }
288     if (retcode == 0) return TRUE;
289     return FALSE;
290 }
291 
latex_can_synctex(void)292 gboolean latex_can_synctex (void) {
293     if (gummi->latex->tex_version >= 2008) {
294         return TRUE;
295     }
296     return FALSE;
297 }
298 
299 
latex_use_synctex(void)300 gboolean latex_use_synctex (void) {
301     return (config_get_boolean ("Compile", "synctex") &&
302             config_get_boolean ("Preview", "autosync"));
303 }
304 
latex_use_shellescaping(void)305 gboolean latex_use_shellescaping (void) {
306     return config_get_boolean ("Compile", "shellescape");
307 }
308