]> git.lyx.org Git - features.git/blob - src/tex2lyx/Preamble.cpp
Split osf options to families
[features.git] / src / tex2lyx / Preamble.cpp
1 /**
2  * \file Preamble.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  * \author Uwe Stöhr
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 // {[(
13
14 #include <config.h>
15
16 #include "Preamble.h"
17 #include "tex2lyx.h"
18
19 #include "Encoding.h"
20 #include "LayoutFile.h"
21 #include "Layout.h"
22 #include "Lexer.h"
23 #include "TextClass.h"
24 #include "version.h"
25
26 #include "support/convert.h"
27 #include "support/FileName.h"
28 #include "support/filetools.h"
29 #include "support/lstrings.h"
30
31 #include "support/regex.h"
32
33 #include <algorithm>
34 #include <iostream>
35
36 using namespace std;
37 using namespace lyx::support;
38
39
40 namespace lyx {
41
42 Preamble preamble;
43
44 namespace {
45
46 // CJK languages are handled in text.cpp, polyglossia languages are listed
47 // further down.
48 /**
49  * known babel language names (including synonyms)
50  * not in standard babel: arabic, arabtex, armenian, belarusian, serbian-latin, thai
51  * please keep this in sync with known_coded_languages line by line!
52  */
53 const char * const known_languages[] = {"acadian", "afrikaans", "albanian",
54 "american", "arabic", "arabtex", "australian", "austrian", "azerbaijani", "bahasa", "bahasai",
55 "bahasam", "basque", "belarusian", "bosnian", "brazil", "brazilian", "breton", "british",
56 "bulgarian", "canadian", "canadien", "catalan", "croatian", "czech", "danish",
57 "dutch", "english", "esperanto", "estonian", "farsi", "finnish", "francais",
58 "french", "frenchb", "frenchle", "frenchpro", "friulan", "galician", "german", "germanb",
59 "georgian", "greek", "hebrew", "hungarian", "icelandic", "indon", "indonesian",
60 "interlingua", "irish", "italian", "japanese", "kazakh", "kurmanji", "latin",
61 "latvian", "lithuanian", "lowersorbian", "lsorbian", "macedonian", "magyar", "malay", "meyalu",
62 "mongolian", "naustrian", "newzealand", "ngerman", "ngermanb", "norsk", "nswissgerman",
63 "nynorsk", "piedmontese", "polutonikogreek", "polish", "portuges", "portuguese",
64 "romanian", "romansh", "russian", "russianb", "samin", "scottish", "serbian", "serbian-latin",
65 "slovak", "slovene", "spanish", "swedish", "swissgerman", "thai", "turkish", "turkmen",
66 "ukraineb", "ukrainian", "uppersorbian", "UKenglish", "USenglish", "usorbian",
67 "vietnam", "welsh",
68 0};
69
70 /**
71  * the same as known_languages with .lyx names
72  * please keep this in sync with known_languages line by line!
73  */
74 const char * const known_coded_languages[] = {"french", "afrikaans", "albanian",
75 "american", "arabic_arabi", "arabic_arabtex", "australian", "austrian", "azerbaijani", "bahasa", "bahasa",
76 "bahasam", "basque", "belarusian", "bosnian", "brazilian", "brazilian", "breton", "british",
77 "bulgarian", "canadian", "canadien", "catalan", "croatian", "czech", "danish",
78 "dutch", "english", "esperanto", "estonian", "farsi", "finnish", "french",
79 "french", "french", "french", "french", "friulan", "galician", "german", "german",
80 "georgian", "greek", "hebrew", "magyar", "icelandic", "bahasa", "bahasa",
81 "interlingua", "irish", "italian", "japanese", "kazakh", "kurmanji", "latin",
82 "latvian", "lithuanian", "lowersorbian", "lowersorbian", "macedonian", "magyar", "bahasam", "bahasam",
83 "mongolian", "naustrian", "newzealand", "ngerman", "ngerman", "norsk", "german-ch",
84 "nynorsk", "piedmontese", "polutonikogreek", "polish", "portuguese", "portuguese",
85 "romanian", "romansh", "russian", "russian", "samin", "scottish", "serbian", "serbian-latin",
86 "slovak", "slovene", "spanish", "swedish", "german-ch-old", "thai", "turkish", "turkmen",
87 "ukrainian", "ukrainian", "uppersorbian", "english", "english", "uppersorbian",
88 "vietnamese", "welsh",
89 0};
90
91 /// languages with british quotes (.lyx names)
92 const char * const known_british_quotes_languages[] = {"british", "welsh", 0};
93
94 /// languages with cjk quotes (.lyx names)
95 const char * const known_cjk_quotes_languages[] = {"chinese-traditional",
96 "japanese", "japanese-cjk", 0};
97
98 /// languages with cjk-angle quotes (.lyx names)
99 const char * const known_cjkangle_quotes_languages[] = {"korean", 0};
100
101 /// languages with danish quotes (.lyx names)
102 const char * const known_danish_quotes_languages[] = {"danish", 0};
103
104 /// languages with english quotes (.lyx names)
105 const char * const known_english_quotes_languages[] = {"american", "australian",
106 "bahasa", "bahasam", "bengali", "brazilian", "canadian", "chinese-simplified", "english",
107 "esperanto", "farsi", "interlingua", "irish", "newzealand", "scottish",
108 "thai", "turkish", "vietnamese", 0};
109
110 /// languages with french quotes (.lyx names)
111 const char * const known_french_quotes_languages[] = {"ancientgreek",
112 "arabic_arabi", "arabic_arabtex", "asturian", "belarusian", "breton",
113 "canadien", "catalan", "french", "friulan", "galician", "italian", "occitan",
114 "piedmontese", "portuguese", "spanish", "spanish-mexico", 0};
115
116 /// languages with german quotes (.lyx names)
117 const char * const known_german_quotes_languages[] = {"austrian", "bulgarian",
118 "czech", "estonian", "georgian", "german", "icelandic", "latvian", "lithuanian",
119 "lowersorbian", "macedonian", "naustrian", "ngerman", "romansh", "slovak", "slovene",
120 "uppersorbian", 0};
121
122 /// languages with polish quotes (.lyx names)
123 const char * const known_polish_quotes_languages[] = {"afrikaans", "bosnian", "croatian",
124 "dutch", "magyar", "polish", "romanian", "serbian", "serbian-latin", 0};
125
126 /// languages with russian quotes (.lyx names)
127 const char * const known_russian_quotes_languages[] = {"azerbaijani", "oldrussian",
128 "russian", "ukrainian", 0};
129
130 /// languages with swedish quotes (.lyx names)
131 const char * const known_swedish_quotes_languages[] = {"finnish", "swedish", 0};
132
133 /// languages with swiss quotes (.lyx names)
134 const char * const known_swiss_quotes_languages[] = {"albanian",
135 "armenian", "basque", "churchslavonic", "german-ch", "german-ch-old",
136 "norsk", "nynorsk", "turkmen", "ukrainian", "vietnamese", 0};
137
138 /// known language packages from the times before babel
139 const char * const known_old_language_packages[] = {"french", "frenchle",
140 "frenchpro", "german", "ngerman", "pmfrench", 0};
141
142 char const * const known_fontsizes[] = { "10pt", "11pt", "12pt", 0 };
143
144 const char * const known_roman_font_packages[] = { "ae", "beraserif", "bookman",
145 "ccfonts", "chancery", "charter", "cmr", "cochineal", "crimson", "DejaVuSerif", "DejaVuSerifCondensed", "fourier",
146 "garamondx", "libertine", "libertineRoman", "libertine-type1", "lmodern", "mathdesign", "mathpazo",
147 "mathptmx", "MinionPro", "newcent", "noto", "noto-serif", "PTSerif", "tgbonum", "tgchorus",
148 "tgpagella", "tgschola", "tgtermes", "utopia", "xcharter", 0 };
149
150 const char * const known_sans_font_packages[] = { "avant", "berasans", "biolinum",
151 "biolinum-type1", "cmbr", "cmss", "DejaVuSans", "DejaVuSansCondensed", "helvet", "iwona", "iwonac", "iwonal", "iwonalc",
152 "kurier", "kurierc", "kurierl", "kurierlc", "lmss", "noto", "noto-sans", "PTSans",
153 "tgadventor", "tgheros", "uop", 0 };
154
155 const char * const known_typewriter_font_packages[] = { "beramono", "cmtl", "cmtt",
156 "courier", "DejaVuSansMono", "lmtt", "luximono", "fourier", "libertineMono", "libertineMono-type1", "lmodern",
157 "mathpazo", "mathptmx", "newcent", "noto", "noto-mono", "PTMono", "tgcursor", "txtt", 0 };
158
159 const char * const known_math_font_packages[] = { "eulervm", "newtxmath", 0};
160
161 const char * const known_paper_sizes[] = { "a0paper", "b0paper", "c0paper",
162 "a1paper", "b1paper", "c1paper", "a2paper", "b2paper", "c2paper", "a3paper",
163 "b3paper", "c3paper", "a4paper", "b4paper", "c4paper", "a5paper", "b5paper",
164 "c5paper", "a6paper", "b6paper", "c6paper", "executivepaper", "legalpaper",
165 "letterpaper", "b0j", "b1j", "b2j", "b3j", "b4j", "b5j", "b6j", 0};
166
167 const char * const known_class_paper_sizes[] = { "a4paper", "a5paper",
168 "executivepaper", "legalpaper", "letterpaper", 0};
169
170 const char * const known_paper_margins[] = { "lmargin", "tmargin", "rmargin",
171 "bmargin", "headheight", "headsep", "footskip", "columnsep", 0};
172
173 const char * const known_coded_paper_margins[] = { "leftmargin", "topmargin",
174 "rightmargin", "bottommargin", "headheight", "headsep", "footskip",
175 "columnsep", 0};
176
177 /// commands that can start an \if...\else...\endif sequence
178 const char * const known_if_commands[] = {"if", "ifarydshln", "ifbraket",
179 "ifcancel", "ifcolortbl", "ifeurosym", "ifmarginnote", "ifmmode", "ifpdf",
180 "ifsidecap", "ifupgreek", 0};
181
182 const char * const known_basic_colors[] = {"black", "blue", "brown", "cyan",
183         "darkgray", "gray", "green", "lightgray", "lime", "magenta", "orange", "olive",
184         "pink", "purple", "red", "teal", "violet", "white", "yellow", 0};
185
186 const char * const known_basic_color_codes[] = {"#000000", "#0000ff", "#964B00", "#00ffff",
187         "#a9a9a9", "#808080", "#00ff00", "#d3d3d3", "#bfff00", "#ff00ff", "#ff7f00", "#808000",
188         "#ffc0cb", "#800080", "#ff0000", "#008080", "#8f00ff", "#ffffff", "#ffff00", 0};
189
190 /// conditional commands with three arguments like \@ifundefined{}{}{}
191 const char * const known_if_3arg_commands[] = {"@ifundefined", "IfFileExists",
192 0};
193
194 /*!
195  * Known file extensions for TeX files as used by \\includeonly
196  */
197 char const * const known_tex_extensions[] = {"tex", 0};
198
199 /// packages that work only in xetex
200 /// polyglossia is handled separately
201 const char * const known_xetex_packages[] = {"arabxetex", "fixlatvian",
202 "fontbook", "fontwrap", "mathspec", "philokalia", "unisugar",
203 "xeCJK", "xecolor", "xecyr", "xeindex", "xepersian", "xunicode", 0};
204
205 /// packages that are automatically skipped if loaded by LyX
206 const char * const known_lyx_packages[] = {"amsbsy", "amsmath", "amssymb",
207 "amstext", "amsthm", "array", "babel", "booktabs", "calc", "CJK", "color",
208 "float", "fontspec", "framed", "graphicx", "hhline", "ifthen", "longtable",
209 "makeidx", "minted", "multirow", "nomencl", "pdfpages", "prettyref", "refstyle",
210 "rotating", "rotfloat", "splitidx", "setspace", "subscript", "tabularx","textcomp", "tipa",
211 "tipx", "tone", "ulem", "url", "varioref", "verbatim", "wrapfig", "xcolor", "xltabular",
212 "xunicode", 0};
213
214 // codes used to remove packages that are loaded automatically by LyX.
215 // Syntax: package_beg_sep<name>package_mid_sep<package loading code>package_end_sep
216 const char package_beg_sep = '\001';
217 const char package_mid_sep = '\002';
218 const char package_end_sep = '\003';
219
220
221 // returns true if at least one of the options in what has been found
222 bool handle_opt(vector<string> & opts, char const * const * what, string & target)
223 {
224         if (opts.empty())
225                 return false;
226
227         bool found = false;
228         // the last language option is the document language (for babel and LyX)
229         // the last size option is the document font size
230         vector<string>::iterator it;
231         vector<string>::iterator position = opts.begin();
232         for (; *what; ++what) {
233                 it = find(opts.begin(), opts.end(), *what);
234                 if (it != opts.end()) {
235                         if (it >= position) {
236                                 found = true;
237                                 target = *what;
238                                 position = it;
239                         }
240                 }
241         }
242         return found;
243 }
244
245
246 void delete_opt(vector<string> & opts, char const * const * what)
247 {
248         if (opts.empty())
249                 return;
250
251         // remove found options from the list
252         // do this after handle_opt to avoid potential memory leaks
253         vector<string>::iterator it;
254         for (; *what; ++what) {
255                 it = find(opts.begin(), opts.end(), *what);
256                 if (it != opts.end())
257                         opts.erase(it);
258         }
259 }
260
261
262 /*!
263  * Split a package options string (keyval format) into a vector.
264  * Example input:
265  *   authorformat=smallcaps,
266  *   commabeforerest,
267  *   titleformat=colonsep,
268  *   bibformat={tabular,ibidem,numbered}
269  */
270 vector<string> split_options(string const & input)
271 {
272         vector<string> options;
273         string option;
274         Parser p(input);
275         while (p.good()) {
276                 Token const & t = p.get_token();
277                 if (t.asInput() == ",") {
278                         options.push_back(trimSpaceAndEol(option));
279                         option.erase();
280                 } else if (t.asInput() == "=") {
281                         option += '=';
282                         p.skip_spaces(true);
283                         if (p.next_token().asInput() == "{")
284                                 option += '{' + p.getArg('{', '}') + '}';
285                 } else if (t.cat() != catSpace && t.cat() != catComment)
286                         option += t.asInput();
287         }
288
289         if (!option.empty())
290                 options.push_back(trimSpaceAndEol(option));
291
292         return options;
293 }
294
295
296 /*!
297  * Retrieve a keyval option "name={value with=sign}" named \p name from
298  * \p options and return the value.
299  * The found option is also removed from \p options.
300  */
301 string process_keyval_opt(vector<string> & options, string name)
302 {
303         for (size_t i = 0; i < options.size(); ++i) {
304                 vector<string> option;
305                 split(options[i], option, '=');
306                 if (option.size() < 2)
307                         continue;
308                 if (option[0] == name) {
309                         options.erase(options.begin() + i);
310                         option.erase(option.begin());
311                         return join(option, "=");
312                 }
313         }
314         return "";
315 }
316
317 } // anonymous namespace
318
319
320 /**
321  * known polyglossia language names (including variants)
322  * FIXME: support spelling=old for german variants (german vs. ngerman LyX names etc)
323  */
324 const char * const Preamble::polyglossia_languages[] = {
325 "albanian", "american", "amharic", "ancient", "arabic", "armenian", "asturian", "australian",
326 "bahasai", "bahasam", "basque", "bengali", "brazil", "brazilian", "breton", "british", "bulgarian",
327 "catalan", "churchslavonic", "coptic", "croatian", "czech", "danish", "divehi", "dutch",
328 "english", "esperanto", "estonian", "farsi", "finnish", "french", "friulan",
329 "galician", "greek", "monotonic", "hebrew", "hindi",
330 "icelandic", "interlingua", "irish", "italian", "kannada", "khmer", "korean",
331 "lao", "latin", "latvian", "lithuanian", "lsorbian", "magyar", "malayalam", "marathi",
332 "austrian", "newzealand", "german", "norsk", "nynorsk", "occitan", "oldrussian",
333 "piedmontese", "polish", "polytonic", "portuges", "romanian", "romansh", "russian",
334 "samin", "sanskrit", "scottish", "serbian", "slovak", "slovenian", "spanish", "swedish", "syriac",
335 "tamil", "telugu", "thai", "tibetan", "turkish", "turkmen",
336 "ukrainian", "urdu", "usorbian", "vietnamese", "welsh", 0};
337 // not yet supported by LyX: "korean", "nko"
338
339 /**
340  * the same as polyglossia_languages with .lyx names
341  * please keep this in sync with polyglossia_languages line by line!
342  */
343 const char * const Preamble::coded_polyglossia_languages[] = {
344 "albanian", "american", "amharic", "ancientgreek", "arabic_arabi", "armenian", "asturian", "australian",
345 "bahasa", "bahasam", "basque", "bengali", "brazilian", "brazilian", "breton", "british", "bulgarian",
346 "catalan", "churchslavonic", "coptic", "croatian", "czech", "danish", "divehi", "dutch",
347 "english", "esperanto", "estonian", "farsi", "finnish", "french", "friulan",
348 "galician", "greek", "greek", "hebrew", "hindi",
349 "icelandic", "interlingua", "irish", "italian", "kannada", "khmer", "korean",
350 "lao", "latin", "latvian", "lithuanian", "lowersorbian", "magyar", "malayalam", "marathi",
351 "naustrian","newzealand", "ngerman", "norsk", "nynorsk", "occitan", "oldrussian",
352 "piedmontese", "polish", "polutonikogreek", "portuges", "romanian", "romansh", "russian",
353 "samin", "sanskrit", "scottish", "serbian", "slovak", "slovene", "spanish", "swedish", "syriac",
354 "tamil", "telugu", "thai", "tibetan", "turkish", "turkmen",
355 "ukrainian", "urdu", "uppersorbian", "vietnamese", "welsh", 0};
356 // not yet supported by LyX: "korean-polyglossia", "nko"
357
358
359 bool Preamble::usePolyglossia() const
360 {
361         return h_use_non_tex_fonts && h_language_package == "default";
362 }
363
364
365 bool Preamble::indentParagraphs() const
366 {
367         return h_paragraph_separation == "indent";
368 }
369
370
371 bool Preamble::isPackageUsed(string const & package) const
372 {
373         return used_packages.find(package) != used_packages.end();
374 }
375
376
377 bool Preamble::isPackageAutoLoaded(string const & package) const
378 {
379         return auto_packages.find(package) != auto_packages.end();
380 }
381
382
383 vector<string> Preamble::getPackageOptions(string const & package) const
384 {
385         map<string, vector<string> >::const_iterator it = used_packages.find(package);
386         if (it != used_packages.end())
387                 return it->second;
388         return vector<string>();
389 }
390
391
392 void Preamble::registerAutomaticallyLoadedPackage(std::string const & package)
393 {
394         auto_packages.insert(package);
395 }
396
397
398 void Preamble::addModule(string const & module)
399 {
400         for (auto const & m : used_modules) {
401                 if (m == module)
402                         return;
403         }
404         used_modules.push_back(module);
405 }
406
407
408 void Preamble::suppressDate(bool suppress)
409 {
410         if (suppress)
411                 h_suppress_date = "true";
412         else
413                 h_suppress_date = "false";
414 }
415
416
417 void Preamble::registerAuthor(std::string const & name)
418 {
419         Author author(from_utf8(name), empty_docstring());
420         author.setUsed(true);
421         authors_.record(author);
422         h_tracking_changes = "true";
423         h_output_changes = "true";
424 }
425
426
427 Author const & Preamble::getAuthor(std::string const & name) const
428 {
429         Author author(from_utf8(name), empty_docstring());
430         for (AuthorList::Authors::const_iterator it = authors_.begin();
431              it != authors_.end(); ++it)
432                 if (*it == author)
433                         return *it;
434         static Author const dummy;
435         return dummy;
436 }
437
438
439 int Preamble::getSpecialTableColumnArguments(char c) const
440 {
441         map<char, int>::const_iterator it = special_columns_.find(c);
442         if (it == special_columns_.end())
443                 return -1;
444         return it->second;
445 }
446
447
448 void Preamble::add_package(string const & name, vector<string> & options)
449 {
450         // every package inherits the global options
451         if (used_packages.find(name) == used_packages.end())
452                 used_packages[name] = split_options(h_options);
453
454         // Insert options passed via PassOptionsToPackage
455         for (auto const & p : extra_package_options_) {
456                 if (p.first == name) {
457                         vector<string> eo = getVectorFromString(p.second);
458                         for (auto const & eoi : eo)
459                                 options.push_back(eoi);
460                 }
461
462         }
463         vector<string> & v = used_packages[name];
464         v.insert(v.end(), options.begin(), options.end());
465         if (name == "jurabib") {
466                 // Don't output the order argument (see the cite command
467                 // handling code in text.cpp).
468                 vector<string>::iterator end =
469                         remove(options.begin(), options.end(), "natbiborder");
470                 end = remove(options.begin(), end, "jurabiborder");
471                 options.erase(end, options.end());
472         }
473 }
474
475
476 namespace {
477
478 // Given is a string like "scaled=0.9" or "Scale=0.9", return 0.9 * 100
479 bool scale_as_percentage(string const & scale, string & percentage)
480 {
481         string::size_type pos = scale.find('=');
482         if (pos != string::npos) {
483                 string value = scale.substr(pos + 1);
484                 if (isStrDbl(value)) {
485                         percentage = convert<string>(
486                                 static_cast<int>(100 * convert<double>(value)));
487                         return true;
488                 }
489         }
490         return false;
491 }
492
493
494 string remove_braces(string const & value)
495 {
496         if (value.empty())
497                 return value;
498         if (value[0] == '{' && value[value.length()-1] == '}')
499                 return value.substr(1, value.length()-2);
500         return value;
501 }
502
503 } // anonymous namespace
504
505
506 Preamble::Preamble() : one_language(true), explicit_babel(false),
507         title_layout_found(false), index_number(0), h_font_cjk_set(false)
508 {
509         //h_backgroundcolor;
510         //h_boxbgcolor;
511         h_biblio_style            = "plain";
512         h_bibtex_command          = "default";
513         h_cite_engine             = "basic";
514         h_cite_engine_type        = "default";
515         h_color                   = "#008000";
516         h_defskip                 = "medskip";
517         h_dynamic_quotes          = false;
518         //h_float_placement;
519         //h_fontcolor;
520         h_fontencoding            = "default";
521         h_font_roman[0]           = "default";
522         h_font_roman[1]           = "default";
523         h_font_sans[0]            = "default";
524         h_font_sans[1]            = "default";
525         h_font_typewriter[0]      = "default";
526         h_font_typewriter[1]      = "default";
527         h_font_math[0]            = "auto";
528         h_font_math[1]            = "auto";
529         h_font_default_family     = "default";
530         h_use_non_tex_fonts       = false;
531         h_font_sc                 = "false";
532         h_font_roman_osf          = "false";
533         h_font_sans_osf           = "false";
534         h_font_typewriter_osf     = "false";
535         h_font_sf_scale[0]        = "100";
536         h_font_sf_scale[1]        = "100";
537         h_font_tt_scale[0]        = "100";
538         h_font_tt_scale[1]        = "100";
539         // h_font_roman_opts;
540         // h_font_sans_opts;
541         // h_font_typewriter_opts;
542         //h_font_cjk
543         h_is_mathindent           = "0";
544         h_math_numbering_side     = "default";
545         h_graphics                = "default";
546         h_default_output_format   = "default";
547         h_html_be_strict          = "false";
548         h_html_css_as_file        = "0";
549         h_html_math_output        = "0";
550         h_index[0]                = "Index";
551         h_index_command           = "default";
552         h_inputencoding           = "auto-legacy";
553         h_justification           = "true";
554         h_language                = "english";
555         h_language_package        = "none";
556         //h_listings_params;
557         h_maintain_unincluded_children = "false";
558         //h_margins;
559         //h_notefontcolor;
560         //h_options;
561         h_output_changes          = "false";
562         h_output_sync             = "0";
563         //h_output_sync_macro
564         h_papercolumns            = "1";
565         h_paperfontsize           = "default";
566         h_paperorientation        = "portrait";
567         h_paperpagestyle          = "default";
568         //h_papersides;
569         h_papersize               = "default";
570         h_paragraph_indentation   = "default";
571         h_paragraph_separation    = "indent";
572         //h_pdf_title;
573         //h_pdf_author;
574         //h_pdf_subject;
575         //h_pdf_keywords;
576         h_pdf_bookmarks           = "0";
577         h_pdf_bookmarksnumbered   = "0";
578         h_pdf_bookmarksopen       = "0";
579         h_pdf_bookmarksopenlevel  = "1";
580         h_pdf_breaklinks          = "0";
581         h_pdf_pdfborder           = "0";
582         h_pdf_colorlinks          = "0";
583         h_pdf_backref             = "section";
584         h_pdf_pdfusetitle         = "0";
585         //h_pdf_pagemode;
586         //h_pdf_quoted_options;
587         h_quotes_style         = "english";
588         h_secnumdepth             = "3";
589         h_shortcut[0]             = "idx";
590         h_spacing                 = "single";
591         h_save_transient_properties = "true";
592         h_suppress_date           = "false";
593         h_textclass               = "article";
594         h_tocdepth                = "3";
595         h_tracking_changes        = "false";
596         h_use_bibtopic            = "false";
597         h_use_dash_ligatures      = "true";
598         h_use_indices             = "false";
599         h_use_geometry            = "false";
600         h_use_default_options     = "false";
601         h_use_hyperref            = "false";
602         h_use_microtype           = "false";
603         h_use_lineno              = "false";
604         h_use_refstyle            = false;
605         h_use_minted              = false;
606         h_use_packages["amsmath"]    = "1";
607         h_use_packages["amssymb"]    = "0";
608         h_use_packages["cancel"]     = "0";
609         h_use_packages["esint"]      = "1";
610         h_use_packages["mhchem"]     = "0";
611         h_use_packages["mathdots"]   = "0";
612         h_use_packages["mathtools"]  = "0";
613         h_use_packages["stackrel"]   = "0";
614         h_use_packages["stmaryrd"]   = "0";
615         h_use_packages["undertilde"] = "0";
616 }
617
618
619 void Preamble::handle_hyperref(vector<string> & options)
620 {
621         // FIXME swallow inputencoding changes that might surround the
622         //       hyperref setup if it was written by LyX
623         h_use_hyperref = "true";
624         // swallow "unicode=true", since LyX does always write that
625         vector<string>::iterator it =
626                 find(options.begin(), options.end(), "unicode=true");
627         if (it != options.end())
628                 options.erase(it);
629         it = find(options.begin(), options.end(), "pdfusetitle");
630         if (it != options.end()) {
631                 h_pdf_pdfusetitle = "1";
632                 options.erase(it);
633         }
634         string bookmarks = process_keyval_opt(options, "bookmarks");
635         if (bookmarks == "true")
636                 h_pdf_bookmarks = "1";
637         else if (bookmarks == "false")
638                 h_pdf_bookmarks = "0";
639         if (h_pdf_bookmarks == "1") {
640                 string bookmarksnumbered =
641                         process_keyval_opt(options, "bookmarksnumbered");
642                 if (bookmarksnumbered == "true")
643                         h_pdf_bookmarksnumbered = "1";
644                 else if (bookmarksnumbered == "false")
645                         h_pdf_bookmarksnumbered = "0";
646                 string bookmarksopen =
647                         process_keyval_opt(options, "bookmarksopen");
648                 if (bookmarksopen == "true")
649                         h_pdf_bookmarksopen = "1";
650                 else if (bookmarksopen == "false")
651                         h_pdf_bookmarksopen = "0";
652                 if (h_pdf_bookmarksopen == "1") {
653                         string bookmarksopenlevel =
654                                 process_keyval_opt(options, "bookmarksopenlevel");
655                         if (!bookmarksopenlevel.empty())
656                                 h_pdf_bookmarksopenlevel = bookmarksopenlevel;
657                 }
658         }
659         string breaklinks = process_keyval_opt(options, "breaklinks");
660         if (breaklinks == "true")
661                 h_pdf_breaklinks = "1";
662         else if (breaklinks == "false")
663                 h_pdf_breaklinks = "0";
664         string pdfborder = process_keyval_opt(options, "pdfborder");
665         if (pdfborder == "{0 0 0}")
666                 h_pdf_pdfborder = "1";
667         else if (pdfborder == "{0 0 1}")
668                 h_pdf_pdfborder = "0";
669         string backref = process_keyval_opt(options, "backref");
670         if (!backref.empty())
671                 h_pdf_backref = backref;
672         string colorlinks = process_keyval_opt(options, "colorlinks");
673         if (colorlinks == "true")
674                 h_pdf_colorlinks = "1";
675         else if (colorlinks == "false")
676                 h_pdf_colorlinks = "0";
677         string pdfpagemode = process_keyval_opt(options, "pdfpagemode");
678         if (!pdfpagemode.empty())
679                 h_pdf_pagemode = pdfpagemode;
680         string pdftitle = process_keyval_opt(options, "pdftitle");
681         if (!pdftitle.empty()) {
682                 h_pdf_title = remove_braces(pdftitle);
683         }
684         string pdfauthor = process_keyval_opt(options, "pdfauthor");
685         if (!pdfauthor.empty()) {
686                 h_pdf_author = remove_braces(pdfauthor);
687         }
688         string pdfsubject = process_keyval_opt(options, "pdfsubject");
689         if (!pdfsubject.empty())
690                 h_pdf_subject = remove_braces(pdfsubject);
691         string pdfkeywords = process_keyval_opt(options, "pdfkeywords");
692         if (!pdfkeywords.empty())
693                 h_pdf_keywords = remove_braces(pdfkeywords);
694         if (!options.empty()) {
695                 if (!h_pdf_quoted_options.empty())
696                         h_pdf_quoted_options += ',';
697                 h_pdf_quoted_options += join(options, ",");
698                 options.clear();
699         }
700 }
701
702
703 void Preamble::handle_geometry(vector<string> & options)
704 {
705         h_use_geometry = "true";
706         vector<string>::iterator it;
707         // paper orientation
708         if ((it = find(options.begin(), options.end(), "landscape")) != options.end()) {
709                 h_paperorientation = "landscape";
710                 options.erase(it);
711         }
712         // paper size
713         // keyval version: "paper=letter"
714         string paper = process_keyval_opt(options, "paper");
715         if (!paper.empty())
716                 h_papersize = paper + "paper";
717         // alternative version: "letterpaper"
718         handle_opt(options, known_paper_sizes, h_papersize);
719         delete_opt(options, known_paper_sizes);
720         // page margins
721         char const * const * margin = known_paper_margins;
722         for (; *margin; ++margin) {
723                 string value = process_keyval_opt(options, *margin);
724                 if (!value.empty()) {
725                         int k = margin - known_paper_margins;
726                         string name = known_coded_paper_margins[k];
727                         h_margins += '\\' + name + ' ' + value + '\n';
728                 }
729         }
730 }
731
732
733 void Preamble::handle_package(Parser &p, string const & name,
734                               string const & opts, bool in_lyx_preamble,
735                               bool detectEncoding)
736 {
737         vector<string> options = split_options(opts);
738         add_package(name, options);
739
740         if (is_known(name, known_xetex_packages)) {
741                 xetex = true;
742                 h_use_non_tex_fonts = true;
743                 registerAutomaticallyLoadedPackage("fontspec");
744                 if (h_inputencoding == "auto-legacy")
745                         p.setEncoding("UTF-8");
746         }
747
748         // roman fonts
749         if (is_known(name, known_roman_font_packages))
750                 h_font_roman[0] = name;
751
752         if (name == "fourier") {
753                 h_font_roman[0] = "utopia";
754                 // when font uses real small capitals
755                 if (opts == "expert")
756                         h_font_sc = "true";
757         }
758
759         if (name == "garamondx") {
760                 h_font_roman[0] = "garamondx";
761                 if (opts == "osfI")
762                         h_font_roman_osf = "true";
763         }
764
765         if (name == "libertine") {
766                 h_font_roman[0] = "libertine";
767                 // this automatically invokes biolinum
768                 h_font_sans[0] = "biolinum";
769                 // as well as libertineMono
770                 h_font_typewriter[0] = "libertine-mono";
771                 if (opts == "osf")
772                         h_font_roman_osf = "true";
773                 else if (opts == "lining")
774                         h_font_roman_osf = "false";
775         }
776
777         if (name == "libertineRoman" || name == "libertine-type1") {
778                 h_font_roman[0] = "libertine";
779                 // NOTE: contrary to libertine.sty, libertineRoman
780                 // and libertine-type1 do not automatically invoke
781                 // biolinum and libertineMono
782                 if (opts == "lining")
783                         h_font_roman_osf = "false";
784                 else if (opts == "osf")
785                         h_font_roman_osf = "true";
786         }
787
788         if (name == "MinionPro") {
789                 h_font_roman[0] = "minionpro";
790                 vector<string> allopts = getVectorFromString(opts);
791                 string xopts;
792                 h_font_roman_osf = "true";
793                 h_font_math[0] = "auto";
794                 for (auto const & opt : allopts) {
795                         if (opt == "lf") {
796                                 h_font_roman_osf = "false";
797                                 continue;
798                         }
799                         if (opt == "onlytext") {
800                                 h_font_math[0] = "default";
801                                 continue;
802                         }
803                         if (!xopts.empty())
804                                 xopts += ", ";
805                         xopts += opt;
806                 }
807                 if (!xopts.empty())
808                         h_font_roman_opts = xopts;
809                 options.clear();
810         }
811
812         if (name == "mathdesign") {
813                 if (opts.find("charter") != string::npos)
814                         h_font_roman[0] = "md-charter";
815                 if (opts.find("garamond") != string::npos)
816                         h_font_roman[0] = "md-garamond";
817                 if (opts.find("utopia") != string::npos)
818                         h_font_roman[0] = "md-utopia";
819                 if (opts.find("expert") != string::npos) {
820                         h_font_sc = "true";
821                         h_font_roman_osf = "true";
822                 }
823         }
824
825         else if (name == "mathpazo")
826                 h_font_roman[0] = "palatino";
827
828         else if (name == "mathptmx")
829                 h_font_roman[0] = "times";
830
831         if (name == "crimson")
832                 h_font_roman[0] = "cochineal";
833
834         if (name == "cochineal") {
835                 h_font_roman[0] = "cochineal";
836                 // cochineal can have several options, e.g. [proportional,osf]
837                 string::size_type pos = opts.find("osf");
838                 if (pos != string::npos)
839                         h_font_roman_osf = "true";
840         }
841
842         if (name == "noto") {
843                 // noto can have several options
844                 if (opts.empty())
845                         h_font_roman[0] = "NotoSerif-TLF";
846                 string::size_type pos = opts.find("rm");
847                 if (pos != string::npos)
848                         h_font_roman[0] = "NotoSerif-TLF";
849                 pos = opts.find("sf");
850                 if (pos != string::npos)
851                         h_font_sans[0] = "NotoSans-TLF";
852                 pos = opts.find("nott");
853                 if (pos != string::npos) {
854                         h_font_roman[0] = "NotoSerif-TLF";
855                         h_font_sans[0] = "NotoSans-TLF";
856                 }
857                 // noto as typewriter is handled in handling of \ttdefault
858                 // special cases are handled in handling of \rmdefault and \sfdefault
859                 vector<string> allopts = getVectorFromString(opts);
860                 string xopts;
861                 for (auto const & opt : allopts) {
862                         if (opt == "rm")
863                                 continue;
864                         if (opt == "sf")
865                                 continue;
866                         if (opt == "nott")
867                                 continue;
868                         if (opt == "osf") {
869                                 h_font_roman_osf = "true";
870                                 continue;
871                         }
872                         if (!xopts.empty())
873                                 xopts += ", ";
874                         xopts += opt;
875                 }
876         }
877
878         if (name == "paratype") {
879                 // in this case all fonts are ParaType
880                 h_font_roman[0] = "PTSerif-TLF";
881                 h_font_sans[0] = "default";
882                 h_font_typewriter[0] = "default";
883         }
884
885         if (name == "PTSerif")
886                 h_font_roman[0] = "PTSerif-TLF";
887
888         if (name == "XCharter") {
889                 h_font_roman[0] = "xcharter";
890                 if (opts == "osf")
891                         h_font_roman_osf = "true";
892         }
893
894         if (name == "plex-serif") {
895                 if (opts.empty())
896                         h_font_roman[0] = "IBMPlexSerif";
897                 else if (opts.find("thin") != string::npos)
898                         h_font_roman[0] = "IBMPlexSerifThin";
899                 else if (opts.find("extralight") != string::npos)
900                         h_font_roman[0] = "IBMPlexSerifExtraLight";
901                 else if (opts.find("light") != string::npos)
902                         h_font_roman[0] = "IBMPlexSerifLight";
903                 else if (opts.find("semibold") != string::npos)
904                         h_font_roman[0] = "IBMPlexSerifSemibold";
905                 vector<string> allopts = getVectorFromString(opts);
906                 string xopts;
907                 for (auto const & opt : allopts) {
908                         if (opt == "thin")
909                                 continue;
910                         if (opt == "extralight")
911                                 continue;
912                         if (opt == "light")
913                                 continue;
914                         if (opt == "semibold")
915                                 continue;
916                         if (!xopts.empty())
917                                 xopts += ", ";
918                         xopts += opt;
919                 }
920                 if (!xopts.empty())
921                         h_font_roman_opts = xopts;
922                 options.clear();
923         }
924         if (name == "noto-serif") {
925                 h_font_roman[0] = "NotoSerifRegular";
926                 if (!opts.empty()) {
927                         if (opts.find("thin") != string::npos)
928                                 h_font_roman[0] = "NotoSerifThin";
929                         else if (opts.find("medium") != string::npos)
930                                 h_font_roman[0] = "NotoSerifMedium";
931                         else if (opts.find("extralight") != string::npos)
932                                 h_font_roman[0] = "NotoSerifExtralight";
933                         else if (opts.find("light") != string::npos)
934                                 h_font_roman[0] = "NotoSerifLight";
935                 }
936                 vector<string> allopts = getVectorFromString(opts);
937                 string xopts;
938                 for (auto const & opt : allopts) {
939                         if (opt == "regular")
940                                 continue;
941                         if (opt == "thin")
942                                 continue;
943                         if (opt == "extralight")
944                                 continue;
945                         if (opt == "light")
946                                 continue;
947                         if (opt == "semibold")
948                                 continue;
949                         if (!xopts.empty())
950                                 xopts += ", ";
951                         xopts += opt;
952                 }
953                 if (!xopts.empty())
954                         h_font_roman_opts = xopts;
955                 options.clear();
956         }
957
958         if (name == "sourceserifpro") {
959                 h_font_roman[0] = "ADOBESourceSerifPro";
960                 vector<string> allopts = getVectorFromString(opts);
961                 string xopts;
962                 for (auto const & opt : allopts) {
963                         if (opt == "osf") {
964                                 h_font_roman_osf = "true";
965                                 continue;
966                         }
967                         if (!xopts.empty())
968                                 xopts += ", ";
969                         xopts += opt;
970                 }
971                 if (!xopts.empty())
972                         h_font_roman_opts = xopts;
973                 options.clear();
974         }
975
976         // sansserif fonts
977         if (is_known(name, known_sans_font_packages)) {
978                 h_font_sans[0] = name;
979                 if (options.size() >= 1) {
980                         if (scale_as_percentage(opts, h_font_sf_scale[0]))
981                                 options.clear();
982                 }
983         }
984
985         if (name == "biolinum" || name == "biolinum-type1") {
986                 h_font_sans[0] = "biolinum";
987                 // biolinum can have several options, e.g. [osf,scaled=0.97]
988                 string::size_type pos = opts.find("osf");
989                 if (pos != string::npos)
990                         h_font_sans_osf = "true";
991         }
992
993         if (name == "PTSans") {
994                 h_font_sans[0] = "PTSans-TLF";
995                 if (options.size() >= 1) {
996                         if (scale_as_percentage(opts, h_font_sf_scale[0]))
997                                 options.clear();
998                 }
999         }
1000
1001         if (name == "plex-sans") {
1002                 if (opts.find("condensed") != string::npos)
1003                         h_font_sans[0] = "IBMPlexSansCondensed";
1004                 else if (opts.find("thin") != string::npos)
1005                         h_font_sans[0] = "IBMPlexSansThin";
1006                 else if (opts.find("extralight") != string::npos)
1007                         h_font_sans[0] = "IBMPlexSansExtraLight";
1008                 else if (opts.find("light") != string::npos)
1009                         h_font_sans[0] = "IBMPlexSansLight";
1010                 else if (opts.find("semibold") != string::npos)
1011                         h_font_sans[0] = "IBMPlexSansSemibold";
1012                 else
1013                         h_font_sans[0] = "IBMPlexSans";
1014                 vector<string> allopts = getVectorFromString(opts);
1015                 string xopts;
1016                 for (auto const & opt : allopts) {
1017                         if (opt == "thin")
1018                                 continue;
1019                         if (opt == "extralight")
1020                                 continue;
1021                         if (opt == "light")
1022                                 continue;
1023                         if (opt == "semibold")
1024                                 continue;
1025                         if (prefixIs(opt, "scale=")) {
1026                                 scale_as_percentage(opt, h_font_sf_scale[0]);
1027                                 continue;
1028                         }
1029                         if (!xopts.empty())
1030                                 xopts += ", ";
1031                         xopts += opt;
1032                 }
1033                 if (!xopts.empty())
1034                         h_font_sans_opts = xopts;
1035                 options.clear();
1036         }
1037         if (name == "noto-sans") {
1038                 h_font_sans[0] = "NotoSansRegular";
1039                 if (!opts.empty()) {
1040                         if (opts.find("medium") != string::npos)
1041                                 h_font_sans[0] = "NotoSansMedium";
1042                         else if (opts.find("thin") != string::npos)
1043                                 h_font_sans[0] = "NotoSansThin";
1044                         else if (opts.find("extralight") != string::npos)
1045                                 h_font_sans[0] = "NotoSansExtralight";
1046                         else if (opts.find("light") != string::npos)
1047                                 h_font_sans[0] = "NotoSansLight";
1048                 }
1049                 vector<string> allopts = getVectorFromString(opts);
1050                 string xopts;
1051                 for (auto const & opt : allopts) {
1052                         if (opt == "regular")
1053                                 continue;
1054                         if (opt == "thin")
1055                                 continue;
1056                         if (opt == "extralight")
1057                                 continue;
1058                         if (opt == "light")
1059                                 continue;
1060                         if (opt == "semibold")
1061                                 continue;
1062                         if (opt == "osf") {
1063                                 h_font_sans_osf = "true";
1064                                 continue;
1065                         }
1066                         if (!xopts.empty())
1067                                 xopts += ", ";
1068                         xopts += opt;
1069                 }
1070                 if (!xopts.empty())
1071                         h_font_sans_opts = xopts;
1072                 options.clear();
1073         }
1074
1075         if (name == "sourcesanspro") {
1076                 h_font_sans[0] = "ADOBESourceSansPro";
1077                 vector<string> allopts = getVectorFromString(opts);
1078                 string xopts;
1079                 for (auto const & opt : allopts) {
1080                         if (prefixIs(opt, "scaled=")) {
1081                                 scale_as_percentage(opt, h_font_sf_scale[0]);
1082                                 continue;
1083                         }
1084                         if (opt == "osf") {
1085                                 h_font_sans_osf = "true";
1086                                 continue;
1087                         }
1088                         if (!xopts.empty())
1089                                 xopts += ", ";
1090                         xopts += opt;
1091                 }
1092                 if (!xopts.empty())
1093                         h_font_sans_opts = xopts;
1094                 options.clear();
1095         }
1096
1097         // typewriter fonts
1098         if (is_known(name, known_typewriter_font_packages)) {
1099                 // fourier can be set as roman font _only_
1100                 // fourier as typewriter is handled in handling of \ttdefault
1101                 if (name != "fourier") {
1102                         h_font_typewriter[0] = name;
1103                         if (options.size() >= 1) {
1104                                 if (scale_as_percentage(opts, h_font_tt_scale[0]))
1105                                         options.clear();
1106                         }
1107                 }
1108         }
1109
1110         if (name == "libertineMono" || name == "libertineMono-type1")
1111                 h_font_typewriter[0] = "libertine-mono";
1112
1113         if (name == "PTMono") {
1114                 h_font_typewriter[0] = "PTMono-TLF";
1115                 if (options.size() >= 1) {
1116                         if (scale_as_percentage(opts, h_font_tt_scale[0]))
1117                                 options.clear();
1118                 }
1119         }
1120
1121         if (name == "plex-mono") {
1122                 if (opts.find("thin") != string::npos)
1123                         h_font_typewriter[0] = "IBMPlexMonoThin";
1124                 else if (opts.find("extralight") != string::npos)
1125                         h_font_typewriter[0] = "IBMPlexMonoExtraLight";
1126                 else if (opts.find("light") != string::npos)
1127                         h_font_typewriter[0] = "IBMPlexMonoLight";
1128                 else if (opts.find("semibold") != string::npos)
1129                         h_font_typewriter[0] = "IBMPlexMonoSemibold";
1130                 else
1131                         h_font_typewriter[0] = "IBMPlexMono";
1132                 vector<string> allopts = getVectorFromString(opts);
1133                 string xopts;
1134                 for (auto const & opt : allopts) {
1135                         if (opt == "thin")
1136                                 continue;
1137                         if (opt == "extralight")
1138                                 continue;
1139                         if (opt == "light")
1140                                 continue;
1141                         if (opt == "semibold")
1142                                 continue;
1143                         if (prefixIs(opt, "scale=")) {
1144                                 scale_as_percentage(opt, h_font_tt_scale[0]);
1145                                 continue;
1146                         }
1147                         if (!xopts.empty())
1148                                 xopts += ", ";
1149                         xopts += opt;
1150                 }
1151                 if (!xopts.empty())
1152                         h_font_typewriter_opts = xopts;
1153                 options.clear();
1154         }
1155
1156         if (name == "noto-mono") {
1157                 h_font_typewriter[0] = "NotoMonoRegular";
1158                 vector<string> allopts = getVectorFromString(opts);
1159                 string xopts;
1160                 for (auto const & opt : allopts) {
1161                         if (opt == "regular")
1162                                 continue;
1163                         if (!xopts.empty())
1164                                 xopts += ", ";
1165                         xopts += opt;
1166                 }
1167                 if (!xopts.empty())
1168                         h_font_typewriter_opts = xopts;
1169                 options.clear();
1170         }
1171
1172         if (name == "sourcecodepro") {
1173                 h_font_typewriter[0] = "ADOBESourceCodePro";
1174                 vector<string> allopts = getVectorFromString(opts);
1175                 string xopts;
1176                 for (auto const & opt : allopts) {
1177                         if (prefixIs(opt, "scaled=")) {
1178                                 scale_as_percentage(opt, h_font_tt_scale[0]);
1179                                 continue;
1180                         }
1181                         if (opt == "osf") {
1182                                 h_font_typewriter_osf = "true";
1183                                 continue;
1184                         }
1185                         if (!xopts.empty())
1186                                 xopts += ", ";
1187                         xopts += opt;
1188                 }
1189                 if (!xopts.empty())
1190                         h_font_typewriter_opts = xopts;
1191                 options.clear();
1192         }
1193
1194         // font uses old-style figure
1195         if (name == "eco")
1196                 h_font_roman_osf = "true";
1197
1198         // math fonts
1199         if (is_known(name, known_math_font_packages))
1200                 h_font_math[0] = name;
1201
1202         if (name == "newtxmath") {
1203                 if (opts.empty())
1204                         h_font_math[0] = "newtxmath";
1205                 else if (opts == "garamondx")
1206                         h_font_math[0] = "garamondx-ntxm";
1207                 else if (opts == "libertine")
1208                         h_font_math[0] = "libertine-ntxm";
1209                 else if (opts == "minion")
1210                         h_font_math[0] = "minion-ntxm";
1211                 else if (opts == "cochineal")
1212                         h_font_math[0] = "cochineal-ntxm";
1213         }
1214
1215         if (name == "iwona")
1216                 if (opts == "math")
1217                         h_font_math[0] = "iwona-math";
1218
1219         if (name == "kurier")
1220                 if (opts == "math")
1221                         h_font_math[0] = "kurier-math";
1222
1223         // after the detection and handling of special cases, we can remove the
1224         // fonts, otherwise they would appear in the preamble, see bug #7856
1225         if (is_known(name, known_roman_font_packages) || is_known(name, known_sans_font_packages)
1226                 ||      is_known(name, known_typewriter_font_packages) || is_known(name, known_math_font_packages))
1227                 ;
1228         //"On". See the enum Package in BufferParams.h if you thought that "2" should have been "42"
1229         else if (name == "amsmath" || name == "amssymb" || name == "cancel" ||
1230                  name == "esint" || name == "mhchem" || name == "mathdots" ||
1231                  name == "mathtools" || name == "stackrel" ||
1232                  name == "stmaryrd" || name == "undertilde") {
1233                 h_use_packages[name] = "2";
1234                 registerAutomaticallyLoadedPackage(name);
1235         }
1236
1237         else if (name == "babel") {
1238                 h_language_package = "default";
1239                 // One might think we would have to do nothing if babel is loaded
1240                 // without any options to prevent pollution of the preamble with this
1241                 // babel call in every roundtrip.
1242                 // But the user could have defined babel-specific things afterwards. So
1243                 // we need to keep it in the preamble to prevent cases like bug #7861.
1244                 if (!opts.empty()) {
1245                         // check if more than one option was used - used later for inputenc
1246                         if (options.begin() != options.end() - 1)
1247                                 one_language = false;
1248                         // babel takes the last language of the option of its \usepackage
1249                         // call as document language. If there is no such language option, the
1250                         // last language in the documentclass options is used.
1251                         handle_opt(options, known_languages, h_language);
1252                         // translate the babel name to a LyX name
1253                         h_language = babel2lyx(h_language);
1254                         if (h_language == "japanese") {
1255                                 // For Japanese, the encoding isn't indicated in the source
1256                                 // file, and there's really not much we can do. We could
1257                                 // 1) offer a list of possible encodings to choose from, or
1258                                 // 2) determine the encoding of the file by inspecting it.
1259                                 // For the time being, we leave the encoding alone so that
1260                                 // we don't get iconv errors when making a wrong guess, and
1261                                 // we will output a note at the top of the document
1262                                 // explaining what to do.
1263                                 Encoding const * const enc = encodings.fromIconvName(
1264                                         p.getEncoding(), Encoding::japanese, false);
1265                                 if (enc)
1266                                         h_inputencoding = enc->name();
1267                                 is_nonCJKJapanese = true;
1268                                 // in this case babel can be removed from the preamble
1269                                 registerAutomaticallyLoadedPackage("babel");
1270                         } else {
1271                                 // If babel is called with options, LyX puts them by default into the
1272                                 // document class options. This works for most languages, except
1273                                 // for Latvian, Lithuanian, Mongolian, Turkmen and Vietnamese and
1274                                 // perhaps in future others.
1275                                 // Therefore keep the babel call as it is as the user might have
1276                                 // reasons for it.
1277                                 string const babelcall = "\\usepackage[" + opts + "]{babel}\n";
1278                                 if (!contains(h_preamble.str(), babelcall))
1279                                         h_preamble << babelcall;
1280                         }
1281                         delete_opt(options, known_languages);
1282                 } else {
1283                         if (!contains(h_preamble.str(), "\\usepackage{babel}\n"))
1284                                 h_preamble << "\\usepackage{babel}\n";
1285                         explicit_babel = true;
1286                 }
1287         }
1288
1289         else if (name == "polyglossia") {
1290                 h_language_package = "default";
1291                 h_default_output_format = "pdf4";
1292                 h_use_non_tex_fonts = true;
1293                 xetex = true;
1294                 registerAutomaticallyLoadedPackage("xunicode");
1295                 if (h_inputencoding == "auto-legacy")
1296                         p.setEncoding("UTF-8");
1297         }
1298
1299         else if (name == "CJK") {
1300                 // set the encoding to "auto-legacy" because it might be set to "auto-legacy-plain" by the babel handling
1301                 // and this would not be correct for CJK
1302                 if (h_inputencoding == "auto-legacy-plain")
1303                         h_inputencoding = "auto-legacy";
1304                 registerAutomaticallyLoadedPackage("CJK");
1305         }
1306
1307         else if (name == "CJKutf8") {
1308                 h_inputencoding = "utf8-cjk";
1309                 p.setEncoding("UTF-8");
1310                 registerAutomaticallyLoadedPackage("CJKutf8");
1311         }
1312
1313         else if (name == "fontenc") {
1314                 h_fontencoding = getStringFromVector(options, ",");
1315                 options.clear();
1316         }
1317
1318         else if (name == "inputenc" || name == "luainputenc") {
1319                 // h_inputencoding is only set when there is not more than one
1320                 // inputenc option because otherwise h_inputencoding must be
1321                 // set to "auto-legacy" (the default encodings of the document's languages)
1322                 // Therefore check that exactly one option is passed to inputenc.
1323                 // It is also only set when there is not more than one babel
1324                 // language option.
1325                 if (!options.empty()) {
1326                         string const encoding = options.back();
1327                         Encoding const * const enc = encodings.fromLaTeXName(
1328                                 encoding, Encoding::inputenc, true);
1329                         if (!enc) {
1330                                 if (!detectEncoding)
1331                                         cerr << "Unknown encoding " << encoding
1332                                              << ". Ignoring." << std::endl;
1333                         } else {
1334                                 if (!enc->unsafe() && options.size() == 1 && one_language == true)
1335                                         h_inputencoding = enc->name();
1336                                 p.setEncoding(enc->iconvName());
1337                         }
1338                         options.clear();
1339                 }
1340         }
1341
1342         else if (name == "srcltx") {
1343                 h_output_sync = "1";
1344                 if (!opts.empty()) {
1345                         h_output_sync_macro = "\\usepackage[" + opts + "]{srcltx}";
1346                         options.clear();
1347                 } else
1348                         h_output_sync_macro = "\\usepackage{srcltx}";
1349         }
1350
1351         else if (is_known(name, known_old_language_packages)) {
1352                 // known language packages from the times before babel
1353                 // if they are found and not also babel, they will be used as
1354                 // custom language package
1355                 h_language_package = "\\usepackage{" + name + "}";
1356         }
1357
1358         else if (name == "lyxskak") {
1359                 // ignore this and its options
1360                 const char * const o[] = {"ps", "mover", 0};
1361                 delete_opt(options, o);
1362         }
1363
1364         else if (is_known(name, known_lyx_packages) && options.empty()) {
1365                 if (name == "splitidx")
1366                         h_use_indices = "true";
1367                 else if (name == "minted")
1368                         h_use_minted = true;
1369                 else if (name == "refstyle")
1370                         h_use_refstyle = true;
1371                 else if (name == "prettyref")
1372                         h_use_refstyle = false;
1373                 if (!in_lyx_preamble) {
1374                         h_preamble << package_beg_sep << name
1375                                    << package_mid_sep << "\\usepackage{"
1376                                    << name << '}';
1377                         if (p.next_token().cat() == catNewline ||
1378                             (p.next_token().cat() == catSpace &&
1379                              p.next_next_token().cat() == catNewline))
1380                                 h_preamble << '\n';
1381                         h_preamble << package_end_sep;
1382                 }
1383         }
1384
1385         else if (name == "geometry")
1386                 handle_geometry(options);
1387
1388         else if (name == "subfig")
1389                 ; // ignore this FIXME: Use the package separator mechanism instead
1390
1391         else if (char const * const * where = is_known(name, known_languages))
1392                 h_language = known_coded_languages[where - known_languages];
1393
1394         else if (name == "natbib") {
1395                 h_biblio_style = "plainnat";
1396                 h_cite_engine = "natbib";
1397                 h_cite_engine_type = "authoryear";
1398                 vector<string>::iterator it =
1399                         find(options.begin(), options.end(), "authoryear");
1400                 if (it != options.end())
1401                         options.erase(it);
1402                 else {
1403                         it = find(options.begin(), options.end(), "numbers");
1404                         if (it != options.end()) {
1405                                 h_cite_engine_type = "numerical";
1406                                 options.erase(it);
1407                         }
1408                 }
1409                 if (!options.empty())
1410                         h_biblio_options = join(options, ",");
1411         }
1412
1413         else if (name == "biblatex") {
1414                 h_biblio_style = "plainnat";
1415                 h_cite_engine = "biblatex";
1416                 h_cite_engine_type = "authoryear";
1417                 string opt;
1418                 vector<string>::iterator it =
1419                         find(options.begin(), options.end(), "natbib");
1420                 if (it != options.end()) {
1421                         options.erase(it);
1422                         h_cite_engine = "biblatex-natbib";
1423                 } else {
1424                         opt = process_keyval_opt(options, "natbib");
1425                         if (opt == "true")
1426                                 h_cite_engine = "biblatex-natbib";
1427                 }
1428                 opt = process_keyval_opt(options, "style");
1429                 if (!opt.empty()) {
1430                         h_biblatex_citestyle = opt;
1431                         h_biblatex_bibstyle = opt;
1432                 } else {
1433                         opt = process_keyval_opt(options, "citestyle");
1434                         if (!opt.empty())
1435                                 h_biblatex_citestyle = opt;
1436                         opt = process_keyval_opt(options, "bibstyle");
1437                         if (!opt.empty())
1438                                 h_biblatex_bibstyle = opt;
1439                 }
1440                 opt = process_keyval_opt(options, "refsection");
1441                 if (!opt.empty()) {
1442                         if (opt == "none" || opt == "part"
1443                             || opt == "chapter" || opt == "section"
1444                             || opt == "subsection")
1445                                 h_multibib = opt;
1446                         else
1447                                 cerr << "Ignoring unkown refesection value '"
1448                                      << opt << "'.";
1449                 }
1450                 opt = process_keyval_opt(options, "bibencoding");
1451                 if (!opt.empty())
1452                         bibencoding = opt;
1453                 if (!options.empty()) {
1454                         h_biblio_options = join(options, ",");
1455                         options.clear();
1456                 }
1457         }
1458
1459         else if (name == "jurabib") {
1460                 h_biblio_style = "jurabib";
1461                 h_cite_engine = "jurabib";
1462                 h_cite_engine_type = "authoryear";
1463                 if (!options.empty())
1464                         h_biblio_options = join(options, ",");
1465         }
1466
1467         else if (name == "bibtopic")
1468                 h_use_bibtopic = "true";
1469
1470         else if (name == "chapterbib")
1471                 h_multibib = "child";
1472
1473         else if (name == "hyperref")
1474                 handle_hyperref(options);
1475
1476         else if (name == "algorithm2e") {
1477                 // Load "algorithm2e" module
1478                 addModule("algorithm2e");
1479                 // Add the package options to the global document options
1480                 if (!options.empty()) {
1481                         if (h_options.empty())
1482                                 h_options = join(options, ",");
1483                         else
1484                                 h_options += ',' + join(options, ",");
1485                 }
1486         }
1487         else if (name == "microtype") {
1488                 //we internally support only microtype without params
1489                 if (options.empty())
1490                         h_use_microtype = "true";
1491                 else
1492                         h_preamble << "\\usepackage[" << opts << "]{microtype}";
1493         }
1494
1495         else if (name == "lineno") {
1496                 h_use_lineno = "true";
1497                 if (!options.empty()) {
1498                         h_lineno_options = join(options, ",");
1499                         options.clear();
1500                 }
1501         }
1502
1503         else if (!in_lyx_preamble) {
1504                 if (options.empty())
1505                         h_preamble << "\\usepackage{" << name << '}';
1506                 else {
1507                         h_preamble << "\\usepackage[" << opts << "]{"
1508                                    << name << '}';
1509                         options.clear();
1510                 }
1511                 if (p.next_token().cat() == catNewline ||
1512                     (p.next_token().cat() == catSpace &&
1513                      p.next_next_token().cat() == catNewline))
1514                         h_preamble << '\n';
1515         }
1516
1517         // We need to do something with the options...
1518         if (!options.empty() && !detectEncoding)
1519                 cerr << "Ignoring options '" << join(options, ",")
1520                      << "' of package " << name << '.' << endl;
1521
1522         // remove the whitespace
1523         p.skip_spaces();
1524 }
1525
1526
1527 void Preamble::handle_if(Parser & p, bool in_lyx_preamble)
1528 {
1529         while (p.good()) {
1530                 Token t = p.get_token();
1531                 if (t.cat() == catEscape &&
1532                     is_known(t.cs(), known_if_commands))
1533                         handle_if(p, in_lyx_preamble);
1534                 else {
1535                         if (!in_lyx_preamble)
1536                                 h_preamble << t.asInput();
1537                         if (t.cat() == catEscape && t.cs() == "fi")
1538                                 return;
1539                 }
1540         }
1541 }
1542
1543
1544 bool Preamble::writeLyXHeader(ostream & os, bool subdoc, string const & outfiledir)
1545 {
1546         if (contains(h_float_placement, "H"))
1547                 registerAutomaticallyLoadedPackage("float");
1548         if (h_spacing != "single" && h_spacing != "default")
1549                 registerAutomaticallyLoadedPackage("setspace");
1550         if (h_use_packages["amsmath"] == "2") {
1551                 // amsbsy and amstext are already provided by amsmath
1552                 registerAutomaticallyLoadedPackage("amsbsy");
1553                 registerAutomaticallyLoadedPackage("amstext");
1554         }
1555
1556         // output the LyX file settings
1557         // Important: Keep the version formatting in sync with LyX and
1558         //            lyx2lyx (bug 7951)
1559         string const origin = roundtripMode() ? "roundtrip" : outfiledir;
1560         os << "#LyX file created by tex2lyx " << lyx_version_major << '.'
1561            << lyx_version_minor << '\n'
1562            << "\\lyxformat " << LYX_FORMAT << '\n'
1563            << "\\begin_document\n"
1564            << "\\begin_header\n"
1565            << "\\save_transient_properties " << h_save_transient_properties << "\n"
1566            << "\\origin " << origin << "\n"
1567            << "\\textclass " << h_textclass << "\n";
1568         string const raw = subdoc ? empty_string() : h_preamble.str();
1569         if (!raw.empty()) {
1570                 os << "\\begin_preamble\n";
1571                 for (string::size_type i = 0; i < raw.size(); ++i) {
1572                         if (raw[i] == package_beg_sep) {
1573                                 // Here follows some package loading code that
1574                                 // must be skipped if the package is loaded
1575                                 // automatically.
1576                                 string::size_type j = raw.find(package_mid_sep, i);
1577                                 if (j == string::npos)
1578                                         return false;
1579                                 string::size_type k = raw.find(package_end_sep, j);
1580                                 if (k == string::npos)
1581                                         return false;
1582                                 string const package = raw.substr(i + 1, j - i - 1);
1583                                 string const replacement = raw.substr(j + 1, k - j - 1);
1584                                 if (auto_packages.find(package) == auto_packages.end())
1585                                         os << replacement;
1586                                 i = k;
1587                         } else
1588                                 os.put(raw[i]);
1589                 }
1590                 os << "\n\\end_preamble\n";
1591         }
1592         if (!h_options.empty())
1593                 os << "\\options " << h_options << "\n";
1594         os << "\\use_default_options " << h_use_default_options << "\n";
1595         if (!used_modules.empty()) {
1596                 os << "\\begin_modules\n";
1597                 vector<string>::const_iterator const end = used_modules.end();
1598                 vector<string>::const_iterator it = used_modules.begin();
1599                 for (; it != end; ++it)
1600                         os << *it << '\n';
1601                 os << "\\end_modules\n";
1602         }
1603         if (!h_includeonlys.empty()) {
1604                 os << "\\begin_includeonly\n";
1605                 for (auto const & iofile : h_includeonlys)
1606                         os << iofile << '\n';
1607                 os << "\\end_includeonly\n";
1608         }
1609         os << "\\maintain_unincluded_children " << h_maintain_unincluded_children << "\n"
1610            << "\\language " << h_language << "\n"
1611            << "\\language_package " << h_language_package << "\n"
1612            << "\\inputencoding " << h_inputencoding << "\n"
1613            << "\\fontencoding " << h_fontencoding << "\n"
1614            << "\\font_roman \"" << h_font_roman[0]
1615            << "\" \"" << h_font_roman[1] << "\"\n"
1616            << "\\font_sans \"" << h_font_sans[0] << "\" \"" << h_font_sans[1] << "\"\n"
1617            << "\\font_typewriter \"" << h_font_typewriter[0]
1618            << "\" \"" << h_font_typewriter[1] << "\"\n"
1619            << "\\font_math \"" << h_font_math[0] << "\" \"" << h_font_math[1] << "\"\n"
1620            << "\\font_default_family " << h_font_default_family << "\n"
1621            << "\\use_non_tex_fonts " << (h_use_non_tex_fonts ? "true" : "false") << '\n'
1622            << "\\font_sc " << h_font_sc << "\n"
1623            << "\\font_roman_osf " << h_font_roman_osf << "\n"
1624            << "\\font_sans_osf " << h_font_sans_osf << "\n"
1625            << "\\font_typewriter_osf " << h_font_typewriter_osf << "\n";
1626         if (!h_font_roman_opts.empty())
1627                 os << "\\font_roman_opts \"" << h_font_roman_opts << "\"" << '\n';
1628         os << "\\font_sf_scale " << h_font_sf_scale[0]
1629            << ' ' << h_font_sf_scale[1] << '\n';
1630         if (!h_font_sans_opts.empty())
1631                 os << "\\font_sans_opts \"" << h_font_sans_opts << "\"" << '\n';
1632         os << "\\font_tt_scale " << h_font_tt_scale[0]
1633            << ' ' << h_font_tt_scale[1] << '\n';
1634         if (!h_font_cjk.empty())
1635                 os << "\\font_cjk " << h_font_cjk << '\n';
1636         if (!h_font_typewriter_opts.empty())
1637                 os << "\\font_typewriter_opts \"" << h_font_typewriter_opts << "\"" << '\n';
1638         os << "\\use_microtype " << h_use_microtype << '\n'
1639            << "\\use_dash_ligatures " << h_use_dash_ligatures << '\n'
1640            << "\\graphics " << h_graphics << '\n'
1641            << "\\default_output_format " << h_default_output_format << "\n"
1642            << "\\output_sync " << h_output_sync << "\n";
1643         if (h_output_sync == "1")
1644                 os << "\\output_sync_macro \"" << h_output_sync_macro << "\"\n";
1645         os << "\\bibtex_command " << h_bibtex_command << "\n"
1646            << "\\index_command " << h_index_command << "\n";
1647         if (!h_float_placement.empty())
1648                 os << "\\float_placement " << h_float_placement << "\n";
1649         os << "\\paperfontsize " << h_paperfontsize << "\n"
1650            << "\\spacing " << h_spacing << "\n"
1651            << "\\use_hyperref " << h_use_hyperref << '\n';
1652         if (h_use_hyperref == "true") {
1653                 if (!h_pdf_title.empty())
1654                         os << "\\pdf_title " << Lexer::quoteString(h_pdf_title) << '\n';
1655                 if (!h_pdf_author.empty())
1656                         os << "\\pdf_author " << Lexer::quoteString(h_pdf_author) << '\n';
1657                 if (!h_pdf_subject.empty())
1658                         os << "\\pdf_subject " << Lexer::quoteString(h_pdf_subject) << '\n';
1659                 if (!h_pdf_keywords.empty())
1660                         os << "\\pdf_keywords " << Lexer::quoteString(h_pdf_keywords) << '\n';
1661                 os << "\\pdf_bookmarks " << h_pdf_bookmarks << "\n"
1662                       "\\pdf_bookmarksnumbered " << h_pdf_bookmarksnumbered << "\n"
1663                       "\\pdf_bookmarksopen " << h_pdf_bookmarksopen << "\n"
1664                       "\\pdf_bookmarksopenlevel " << h_pdf_bookmarksopenlevel << "\n"
1665                       "\\pdf_breaklinks " << h_pdf_breaklinks << "\n"
1666                       "\\pdf_pdfborder " << h_pdf_pdfborder << "\n"
1667                       "\\pdf_colorlinks " << h_pdf_colorlinks << "\n"
1668                       "\\pdf_backref " << h_pdf_backref << "\n"
1669                       "\\pdf_pdfusetitle " << h_pdf_pdfusetitle << '\n';
1670                 if (!h_pdf_pagemode.empty())
1671                         os << "\\pdf_pagemode " << h_pdf_pagemode << '\n';
1672                 if (!h_pdf_quoted_options.empty())
1673                         os << "\\pdf_quoted_options " << Lexer::quoteString(h_pdf_quoted_options) << '\n';
1674         }
1675         os << "\\papersize " << h_papersize << "\n"
1676            << "\\use_geometry " << h_use_geometry << '\n';
1677         for (map<string, string>::const_iterator it = h_use_packages.begin();
1678              it != h_use_packages.end(); ++it)
1679                 os << "\\use_package " << it->first << ' ' << it->second << '\n';
1680         os << "\\cite_engine " << h_cite_engine << '\n'
1681            << "\\cite_engine_type " << h_cite_engine_type << '\n'
1682            << "\\biblio_style " << h_biblio_style << "\n"
1683            << "\\use_bibtopic " << h_use_bibtopic << "\n";
1684         if (!h_biblio_options.empty())
1685                 os << "\\biblio_options " << h_biblio_options << "\n";
1686         if (!h_biblatex_bibstyle.empty())
1687                 os << "\\biblatex_bibstyle " << h_biblatex_bibstyle << "\n";
1688         if (!h_biblatex_citestyle.empty())
1689                 os << "\\biblatex_citestyle " << h_biblatex_citestyle << "\n";
1690         if (!h_multibib.empty())
1691                 os << "\\multibib " << h_multibib << "\n";
1692         os << "\\use_indices " << h_use_indices << "\n"
1693            << "\\paperorientation " << h_paperorientation << '\n'
1694            << "\\suppress_date " << h_suppress_date << '\n'
1695            << "\\justification " << h_justification << '\n'
1696            << "\\use_refstyle " << h_use_refstyle << '\n'
1697            << "\\use_minted " << h_use_minted << '\n'
1698            << "\\use_lineno " << h_use_lineno << '\n';
1699         if (!h_lineno_options.empty())
1700                 os << "\\lineno_options " << h_lineno_options << '\n';
1701         if (!h_fontcolor.empty())
1702                 os << "\\fontcolor " << h_fontcolor << '\n';
1703         if (!h_notefontcolor.empty())
1704                 os << "\\notefontcolor " << h_notefontcolor << '\n';
1705         if (!h_backgroundcolor.empty())
1706                 os << "\\backgroundcolor " << h_backgroundcolor << '\n';
1707         if (!h_boxbgcolor.empty())
1708                 os << "\\boxbgcolor " << h_boxbgcolor << '\n';
1709         if (index_number != 0)
1710                 for (int i = 0; i < index_number; i++) {
1711                         os << "\\index " << h_index[i] << '\n'
1712                            << "\\shortcut " << h_shortcut[i] << '\n'
1713                            << "\\color " << h_color << '\n'
1714                            << "\\end_index\n";
1715                 }
1716         else {
1717                 os << "\\index " << h_index[0] << '\n'
1718                    << "\\shortcut " << h_shortcut[0] << '\n'
1719                    << "\\color " << h_color << '\n'
1720                    << "\\end_index\n";
1721         }
1722         os << h_margins
1723            << "\\secnumdepth " << h_secnumdepth << "\n"
1724            << "\\tocdepth " << h_tocdepth << "\n"
1725            << "\\paragraph_separation " << h_paragraph_separation << "\n";
1726         if (h_paragraph_separation == "skip")
1727                 os << "\\defskip " << h_defskip << "\n";
1728         else
1729                 os << "\\paragraph_indentation " << h_paragraph_indentation << "\n";
1730         os << "\\is_math_indent " << h_is_mathindent << "\n";
1731         if (!h_mathindentation.empty())
1732                 os << "\\math_indentation " << h_mathindentation << "\n";
1733         os << "\\math_numbering_side " << h_math_numbering_side << "\n";
1734         os << "\\quotes_style " << h_quotes_style << "\n"
1735            << "\\dynamic_quotes " << h_dynamic_quotes << "\n"
1736            << "\\papercolumns " << h_papercolumns << "\n"
1737            << "\\papersides " << h_papersides << "\n"
1738            << "\\paperpagestyle " << h_paperpagestyle << "\n";
1739         if (!h_listings_params.empty())
1740                 os << "\\listings_params " << h_listings_params << "\n";
1741         os << "\\tracking_changes " << h_tracking_changes << "\n"
1742            << "\\output_changes " << h_output_changes << "\n"
1743            << "\\html_math_output " << h_html_math_output << "\n"
1744            << "\\html_css_as_file " << h_html_css_as_file << "\n"
1745            << "\\html_be_strict " << h_html_be_strict << "\n"
1746            << authors_
1747            << "\\end_header\n\n"
1748            << "\\begin_body\n";
1749         return true;
1750 }
1751
1752
1753 void Preamble::parse(Parser & p, string const & forceclass,
1754                      TeX2LyXDocClass & tc)
1755 {
1756         // initialize fixed types
1757         special_columns_['D'] = 3;
1758         parse(p, forceclass, false, tc);
1759 }
1760
1761
1762 void Preamble::parse(Parser & p, string const & forceclass,
1763                      bool detectEncoding, TeX2LyXDocClass & tc)
1764 {
1765         bool is_full_document = false;
1766         bool is_lyx_file = false;
1767         bool in_lyx_preamble = false;
1768
1769         // determine whether this is a full document or a fragment for inclusion
1770         while (p.good()) {
1771                 Token const & t = p.get_token();
1772
1773                 if (t.cat() == catEscape && t.cs() == "documentclass") {
1774                         is_full_document = true;
1775                         break;
1776                 }
1777         }
1778         p.reset();
1779
1780         if (detectEncoding && !is_full_document)
1781                 return;
1782
1783         while (is_full_document && p.good()) {
1784                 if (detectEncoding && h_inputencoding != "auto-legacy" &&
1785                     h_inputencoding != "auto-legacy-plain")
1786                         return;
1787
1788                 Token const & t = p.get_token();
1789
1790 #ifdef FILEDEBUG
1791                 if (!detectEncoding)
1792                         cerr << "t: " << t << '\n';
1793 #endif
1794
1795                 //
1796                 // cat codes
1797                 //
1798                 if (!in_lyx_preamble &&
1799                     (t.cat() == catLetter ||
1800                      t.cat() == catSuper ||
1801                      t.cat() == catSub ||
1802                      t.cat() == catOther ||
1803                      t.cat() == catMath ||
1804                      t.cat() == catActive ||
1805                      t.cat() == catBegin ||
1806                      t.cat() == catEnd ||
1807                      t.cat() == catAlign ||
1808                      t.cat() == catParameter)) {
1809                         h_preamble << t.cs();
1810                         continue;
1811                 }
1812
1813                 if (!in_lyx_preamble &&
1814                     (t.cat() == catSpace || t.cat() == catNewline)) {
1815                         h_preamble << t.asInput();
1816                         continue;
1817                 }
1818
1819                 if (t.cat() == catComment) {
1820                         static regex const islyxfile("%% LyX .* created this file");
1821                         static regex const usercommands("User specified LaTeX commands");
1822
1823                         string const comment = t.asInput();
1824
1825                         // magically switch encoding default if it looks like XeLaTeX
1826                         static string const magicXeLaTeX =
1827                                 "% This document must be compiled with XeLaTeX ";
1828                         if (comment.size() > magicXeLaTeX.size()
1829                                   && comment.substr(0, magicXeLaTeX.size()) == magicXeLaTeX
1830                                   && h_inputencoding == "auto-legacy") {
1831                                 if (!detectEncoding)
1832                                         cerr << "XeLaTeX comment found, switching to UTF8\n";
1833                                 h_inputencoding = "utf8";
1834                         }
1835                         smatch sub;
1836                         if (regex_search(comment, sub, islyxfile)) {
1837                                 is_lyx_file = true;
1838                                 in_lyx_preamble = true;
1839                         } else if (is_lyx_file
1840                                    && regex_search(comment, sub, usercommands))
1841                                 in_lyx_preamble = false;
1842                         else if (!in_lyx_preamble)
1843                                 h_preamble << t.asInput();
1844                         continue;
1845                 }
1846
1847                 if (t.cs() == "PassOptionsToPackage") {
1848                         string const poptions = p.getArg('{', '}');
1849                         string const package = p.verbatim_item();
1850                         extra_package_options_.insert(make_pair(package, poptions));
1851                         continue;
1852                 }
1853
1854                 if (t.cs() == "pagestyle") {
1855                         h_paperpagestyle = p.verbatim_item();
1856                         continue;
1857                 }
1858
1859                 if (t.cs() == "setdefaultlanguage") {
1860                         xetex = true;
1861                         // We don't yet care about non-language variant options
1862                         // because LyX doesn't support this yet, see bug #8214
1863                         if (p.hasOpt()) {
1864                                 string langopts = p.getOpt();
1865                                 // check if the option contains a variant, if yes, extract it
1866                                 string::size_type pos_var = langopts.find("variant");
1867                                 string::size_type i = langopts.find(',', pos_var);
1868                                 string::size_type k = langopts.find('=', pos_var);
1869                                 if (pos_var != string::npos){
1870                                         string variant;
1871                                         if (i == string::npos)
1872                                                 variant = langopts.substr(k + 1, langopts.length() - k - 2);
1873                                         else
1874                                                 variant = langopts.substr(k + 1, i - k - 1);
1875                                         h_language = variant;
1876                                 }
1877                                 p.verbatim_item();
1878                         } else
1879                                 h_language = p.verbatim_item();
1880                         //finally translate the poyglossia name to a LyX name
1881                         h_language = polyglossia2lyx(h_language);
1882                         continue;
1883                 }
1884
1885                 if (t.cs() == "setotherlanguage") {
1886                         // We don't yet care about the option because LyX doesn't
1887                         // support this yet, see bug #8214
1888                         p.hasOpt() ? p.getOpt() : string();
1889                         p.verbatim_item();
1890                         continue;
1891                 }
1892
1893                 if (t.cs() == "setmainfont") {
1894                         string fontopts = p.hasOpt() ? p.getArg('[', ']') : string();
1895                         h_font_roman[1] = p.getArg('{', '}');
1896                         if (!fontopts.empty()) {
1897                                 vector<string> opts = getVectorFromString(fontopts);
1898                                 fontopts.clear();
1899                                 for (auto const & opt : opts) {
1900                                         if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
1901                                                 // ignore
1902                                                 continue;
1903                                         if (!fontopts.empty())
1904                                                 fontopts += ", ";
1905                                         fontopts += opt;
1906                                 }
1907                                 h_font_roman_opts = fontopts;
1908                         }
1909                         continue;
1910                 }
1911
1912                 if (t.cs() == "setsansfont" || t.cs() == "setmonofont") {
1913                         // LyX currently only supports the scale option
1914                         string scale, fontopts;
1915                         if (p.hasOpt()) {
1916                                 fontopts = p.getArg('[', ']');
1917                                 if (!fontopts.empty()) {
1918                                         vector<string> opts = getVectorFromString(fontopts);
1919                                         fontopts.clear();
1920                                         for (auto const & opt : opts) {
1921                                                 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
1922                                                         // ignore
1923                                                         continue;
1924                                                 if (prefixIs(opt, "Scale=")) {
1925                                                         scale_as_percentage(opt, scale);
1926                                                         continue;
1927                                                 }
1928                                                 if (!fontopts.empty())
1929                                                         fontopts += ", ";
1930                                                 fontopts += opt;
1931                                         }
1932                                 }
1933                         }
1934                         if (t.cs() == "setsansfont") {
1935                                 if (!scale.empty())
1936                                         h_font_sf_scale[1] = scale;
1937                                 h_font_sans[1] = p.getArg('{', '}');
1938                                 if (!fontopts.empty())
1939                                         h_font_sans_opts = fontopts;
1940                         } else {
1941                                 if (!scale.empty())
1942                                         h_font_tt_scale[1] = scale;
1943                                 h_font_typewriter[1] = p.getArg('{', '}');
1944                                 if (!fontopts.empty())
1945                                         h_font_typewriter_opts = fontopts;
1946                         }
1947                         continue;
1948                 }
1949
1950                 if (t.cs() == "babelfont") {
1951                         xetex = true;
1952                         h_use_non_tex_fonts = true;
1953                         h_language_package = "babel";
1954                         if (h_inputencoding == "auto-legacy")
1955                         p.setEncoding("UTF-8");
1956                         // we don't care about the lang option
1957                         string const lang = p.hasOpt() ? p.getArg('[', ']') : string();
1958                         string const family = p.getArg('{', '}');
1959                         string fontopts = p.hasOpt() ? p.getArg('[', ']') : string();
1960                         string const fontname = p.getArg('{', '}');
1961                         if (lang.empty() && family == "rm") {
1962                                 h_font_roman[1] = fontname;
1963                                 if (!fontopts.empty()) {
1964                                         vector<string> opts = getVectorFromString(fontopts);
1965                                         fontopts.clear();
1966                                         for (auto const & opt : opts) {
1967                                                 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
1968                                                         // ignore
1969                                                         continue;
1970                                                 if (!fontopts.empty())
1971                                                         fontopts += ", ";
1972                                                 fontopts += opt;
1973                                         }
1974                                         h_font_roman_opts = fontopts;
1975                                 }
1976                                 continue;
1977                         } else if (lang.empty() && (family == "sf" || family == "tt")) {
1978                                 // LyX currently only supports the scale option
1979                                 string scale;
1980                                 if (!fontopts.empty()) {
1981                                         vector<string> opts = getVectorFromString(fontopts);
1982                                         fontopts.clear();
1983                                         for (auto const & opt : opts) {
1984                                                 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
1985                                                         // ignore
1986                                                         continue;
1987                                                 if (prefixIs(opt, "Scale=")) {
1988                                                         scale_as_percentage(opt, scale);
1989                                                         continue;
1990                                                 }
1991                                                 if (!fontopts.empty())
1992                                                         fontopts += ", ";
1993                                                 fontopts += opt;
1994                                         }
1995                                 }
1996                                 if (family == "sf") {
1997                                         if (!scale.empty())
1998                                                 h_font_sf_scale[1] = scale;
1999                                         h_font_sans[1] = fontname;
2000                                         if (!fontopts.empty())
2001                                                 h_font_sans_opts = fontopts;
2002                                 } else {
2003                                         if (!scale.empty())
2004                                                 h_font_tt_scale[1] = scale;
2005                                         h_font_typewriter[1] = fontname;
2006                                         if (!fontopts.empty())
2007                                                 h_font_typewriter_opts = fontopts;
2008                                 }
2009                                 continue;
2010                         } else {
2011                                 // not rm, sf or tt or lang specific
2012                                 h_preamble << '\\' << t.cs();
2013                                 if (!lang.empty())
2014                                         h_preamble << '[' << lang << ']';
2015                                 h_preamble << '{' << family << '}';
2016                                 if (!fontopts.empty())
2017                                         h_preamble << '[' << fontopts << ']';
2018                                 h_preamble << '{' << fontname << '}' << '\n';
2019                                 continue;
2020                         }
2021                 }
2022
2023                 if (t.cs() == "date") {
2024                         string argument = p.getArg('{', '}');
2025                         if (argument.empty())
2026                                 h_suppress_date = "true";
2027                         else
2028                                 h_preamble << t.asInput() << '{' << argument << '}';
2029                         continue;
2030                 }
2031
2032                 if (t.cs() == "color") {
2033                         string const space =
2034                                 (p.hasOpt() ? p.getOpt() : string());
2035                         string argument = p.getArg('{', '}');
2036                         // check the case that a standard color is used
2037                         if (space.empty() && is_known(argument, known_basic_colors)) {
2038                                 h_fontcolor = rgbcolor2code(argument);
2039                                 registerAutomaticallyLoadedPackage("color");
2040                         } else if (space.empty() && argument == "document_fontcolor")
2041                                 registerAutomaticallyLoadedPackage("color");
2042                         // check the case that LyX's document_fontcolor is defined
2043                         // but not used for \color
2044                         else {
2045                                 h_preamble << t.asInput();
2046                                 if (!space.empty())
2047                                         h_preamble << space;
2048                                 h_preamble << '{' << argument << '}';
2049                                 // the color might already be set because \definecolor
2050                                 // is parsed before this
2051                                 h_fontcolor = "";
2052                         }
2053                         continue;
2054                 }
2055
2056                 if (t.cs() == "pagecolor") {
2057                         string argument = p.getArg('{', '}');
2058                         // check the case that a standard color is used
2059                         if (is_known(argument, known_basic_colors)) {
2060                                 h_backgroundcolor = rgbcolor2code(argument);
2061                         } else if (argument == "page_backgroundcolor")
2062                                 registerAutomaticallyLoadedPackage("color");
2063                         // check the case that LyX's page_backgroundcolor is defined
2064                         // but not used for \pagecolor
2065                         else {
2066                                 h_preamble << t.asInput() << '{' << argument << '}';
2067                                 // the color might already be set because \definecolor
2068                                 // is parsed before this
2069                                 h_backgroundcolor = "";
2070                         }
2071                         continue;
2072                 }
2073
2074                 if (t.cs() == "makeatletter") {
2075                         // LyX takes care of this
2076                         p.setCatcode('@', catLetter);
2077                         continue;
2078                 }
2079
2080                 if (t.cs() == "makeatother") {
2081                         // LyX takes care of this
2082                         p.setCatcode('@', catOther);
2083                         continue;
2084                 }
2085
2086                 if (t.cs() == "makeindex") {
2087                         // LyX will re-add this if a print index command is found
2088                         p.skip_spaces();
2089                         continue;
2090                 }
2091
2092                 if (t.cs() == "newindex") {
2093                         string const indexname = p.getArg('[', ']');
2094                         string const shortcut = p.verbatim_item();
2095                         if (!indexname.empty())
2096                                 h_index[index_number] = indexname;
2097                         else
2098                                 h_index[index_number] = shortcut;
2099                         h_shortcut[index_number] = shortcut;
2100                         index_number += 1;
2101                         p.skip_spaces();
2102                         continue;
2103                 }
2104
2105                 if (t.cs() == "addbibresource") {
2106                         string const options =  p.getArg('[', ']');
2107                         string const arg = removeExtension(p.getArg('{', '}'));
2108                         if (!options.empty()) {
2109                                 // check if the option contains a bibencoding, if yes, extract it
2110                                 string::size_type pos = options.find("bibencoding=");
2111                                 string encoding;
2112                                 if (pos != string::npos) {
2113                                         string::size_type i = options.find(',', pos);
2114                                         if (i == string::npos)
2115                                                 encoding = options.substr(pos + 1);
2116                                         else
2117                                                 encoding = options.substr(pos, i - pos);
2118                                         pos = encoding.find('=');
2119                                         if (pos == string::npos)
2120                                                 encoding.clear();
2121                                         else
2122                                                 encoding = encoding.substr(pos + 1);
2123                                 }
2124                                 if (!encoding.empty())
2125                                         biblatex_encodings.push_back(normalize_filename(arg) + ' ' + encoding);
2126                         }
2127                         biblatex_bibliographies.push_back(arg);
2128                         continue;
2129                 }
2130
2131                 if (t.cs() == "bibliography") {
2132                         vector<string> bibs = getVectorFromString(p.getArg('{', '}'));
2133                         biblatex_bibliographies.insert(biblatex_bibliographies.end(), bibs.begin(), bibs.end());
2134                         continue;
2135                 }
2136
2137                 if (t.cs() == "RS@ifundefined") {
2138                         string const name = p.verbatim_item();
2139                         string const body1 = p.verbatim_item();
2140                         string const body2 = p.verbatim_item();
2141                         // only non-lyxspecific stuff
2142                         if (in_lyx_preamble &&
2143                             (name == "subsecref" || name == "thmref" || name == "lemref"))
2144                                 p.skip_spaces();
2145                         else {
2146                                 ostringstream ss;
2147                                 ss << '\\' << t.cs();
2148                                 ss << '{' << name << '}'
2149                                    << '{' << body1 << '}'
2150                                    << '{' << body2 << '}';
2151                                 h_preamble << ss.str();
2152                         }
2153                         continue;
2154                 }
2155
2156                 if (t.cs() == "AtBeginDocument") {
2157                         string const name = p.verbatim_item();
2158                         // only non-lyxspecific stuff
2159                         if (in_lyx_preamble &&
2160                             (name == "\\providecommand\\partref[1]{\\ref{part:#1}}"
2161                                 || name == "\\providecommand\\chapref[1]{\\ref{chap:#1}}"
2162                                 || name == "\\providecommand\\secref[1]{\\ref{sec:#1}}"
2163                                 || name == "\\providecommand\\subsecref[1]{\\ref{subsec:#1}}"
2164                                 || name == "\\providecommand\\parref[1]{\\ref{par:#1}}"
2165                                 || name == "\\providecommand\\figref[1]{\\ref{fig:#1}}"
2166                                 || name == "\\providecommand\\tabref[1]{\\ref{tab:#1}}"
2167                                 || name == "\\providecommand\\algref[1]{\\ref{alg:#1}}"
2168                                 || name == "\\providecommand\\fnref[1]{\\ref{fn:#1}}"
2169                                 || name == "\\providecommand\\enuref[1]{\\ref{enu:#1}}"
2170                                 || name == "\\providecommand\\eqref[1]{\\ref{eq:#1}}"
2171                                 || name == "\\providecommand\\lemref[1]{\\ref{lem:#1}}"
2172                                 || name == "\\providecommand\\thmref[1]{\\ref{thm:#1}}"
2173                                 || name == "\\providecommand\\corref[1]{\\ref{cor:#1}}"
2174                                 || name == "\\providecommand\\propref[1]{\\ref{prop:#1}}"))
2175                                 p.skip_spaces();
2176                         else {
2177                                 ostringstream ss;
2178                                 ss << '\\' << t.cs();
2179                                 ss << '{' << name << '}';
2180                                 h_preamble << ss.str();
2181                         }
2182                         continue;
2183                 }
2184
2185                 if (t.cs() == "newcommand" || t.cs() == "newcommandx"
2186                     || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
2187                     || t.cs() == "providecommand" || t.cs() == "providecommandx"
2188                     || t.cs() == "DeclareRobustCommand"
2189                     || t.cs() == "DeclareRobustCommandx"
2190                     || t.cs() == "ProvideTextCommandDefault"
2191                     || t.cs() == "DeclareMathAccent") {
2192                         bool star = false;
2193                         if (p.next_token().character() == '*') {
2194                                 p.get_token();
2195                                 star = true;
2196                         }
2197                         string const name = p.verbatim_item();
2198                         string const opt1 = p.getFullOpt();
2199                         string const opt2 = p.getFullOpt();
2200                         string const body = p.verbatim_item();
2201                         // store the in_lyx_preamble setting
2202                         bool const was_in_lyx_preamble = in_lyx_preamble;
2203                         // font settings
2204                         if (name == "\\rmdefault")
2205                                 if (is_known(body, known_roman_font_packages)) {
2206                                         h_font_roman[0] = body;
2207                                         p.skip_spaces();
2208                                         in_lyx_preamble = true;
2209                                 }
2210                         if (name == "\\sfdefault")
2211                                 if (is_known(body, known_sans_font_packages)) {
2212                                         h_font_sans[0] = body;
2213                                         p.skip_spaces();
2214                                         in_lyx_preamble = true;
2215                                 }
2216                         if (name == "\\ttdefault")
2217                                 if (is_known(body, known_typewriter_font_packages)) {
2218                                         h_font_typewriter[0] = body;
2219                                         p.skip_spaces();
2220                                         in_lyx_preamble = true;
2221                                 }
2222                         if (name == "\\familydefault") {
2223                                 string family = body;
2224                                 // remove leading "\"
2225                                 h_font_default_family = family.erase(0,1);
2226                                 p.skip_spaces();
2227                                 in_lyx_preamble = true;
2228                         }
2229
2230                         // remove LyX-specific definitions that are re-added by LyX
2231                         // if necessary
2232                         // \lyxline is an ancient command that is converted by tex2lyx into
2233                         // a \rule therefore remove its preamble code
2234                         if (name == "\\lyxdot" || name == "\\lyxarrow"
2235                             || name == "\\lyxline" || name == "\\LyX") {
2236                                 p.skip_spaces();
2237                                 in_lyx_preamble = true;
2238                         }
2239
2240                         // Add the command to the known commands
2241                         add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
2242
2243                         // only non-lyxspecific stuff
2244                         if (!in_lyx_preamble) {
2245                                 ostringstream ss;
2246                                 ss << '\\' << t.cs();
2247                                 if (star)
2248                                         ss << '*';
2249                                 ss << '{' << name << '}' << opt1 << opt2
2250                                    << '{' << body << "}";
2251                                 if (prefixIs(t.cs(), "renew") || !contains(h_preamble.str(), ss.str()))
2252                                         h_preamble << ss.str();
2253 /*
2254                                 ostream & out = in_preamble ? h_preamble : os;
2255                                 out << "\\" << t.cs() << "{" << name << "}"
2256                                     << opts << "{" << body << "}";
2257 */
2258                         }
2259                         // restore the in_lyx_preamble setting
2260                         in_lyx_preamble = was_in_lyx_preamble;
2261                         continue;
2262                 }
2263
2264                 if (t.cs() == "documentclass") {
2265                         vector<string>::iterator it;
2266                         vector<string> opts = split_options(p.getArg('[', ']'));
2267                         handle_opt(opts, known_fontsizes, h_paperfontsize);
2268                         delete_opt(opts, known_fontsizes);
2269                         // delete "pt" at the end
2270                         string::size_type i = h_paperfontsize.find("pt");
2271                         if (i != string::npos)
2272                                 h_paperfontsize.erase(i);
2273                         // The documentclass options are always parsed before the options
2274                         // of the babel call so that a language cannot overwrite the babel
2275                         // options.
2276                         handle_opt(opts, known_languages, h_language);
2277                         delete_opt(opts, known_languages);
2278
2279                         // math indentation
2280                         if ((it = find(opts.begin(), opts.end(), "fleqn"))
2281                                  != opts.end()) {
2282                                 h_is_mathindent = "1";
2283                                 opts.erase(it);
2284                         }
2285                         // formula numbering side
2286                         if ((it = find(opts.begin(), opts.end(), "leqno"))
2287                                  != opts.end()) {
2288                                 h_math_numbering_side = "left";
2289                                 opts.erase(it);
2290                         }
2291                         else if ((it = find(opts.begin(), opts.end(), "reqno"))
2292                                  != opts.end()) {
2293                                 h_math_numbering_side = "right";
2294                                 opts.erase(it);
2295                         }
2296
2297                         // paper orientation
2298                         if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
2299                                 h_paperorientation = "landscape";
2300                                 opts.erase(it);
2301                         }
2302                         // paper sides
2303                         if ((it = find(opts.begin(), opts.end(), "oneside"))
2304                                  != opts.end()) {
2305                                 h_papersides = "1";
2306                                 opts.erase(it);
2307                         }
2308                         if ((it = find(opts.begin(), opts.end(), "twoside"))
2309                                  != opts.end()) {
2310                                 h_papersides = "2";
2311                                 opts.erase(it);
2312                         }
2313                         // paper columns
2314                         if ((it = find(opts.begin(), opts.end(), "onecolumn"))
2315                                  != opts.end()) {
2316                                 h_papercolumns = "1";
2317                                 opts.erase(it);
2318                         }
2319                         if ((it = find(opts.begin(), opts.end(), "twocolumn"))
2320                                  != opts.end()) {
2321                                 h_papercolumns = "2";
2322                                 opts.erase(it);
2323                         }
2324                         // paper sizes
2325                         // some size options are known to any document classes, other sizes
2326                         // are handled by the \geometry command of the geometry package
2327                         handle_opt(opts, known_class_paper_sizes, h_papersize);
2328                         delete_opt(opts, known_class_paper_sizes);
2329                         // the remaining options
2330                         h_options = join(opts, ",");
2331                         // FIXME This does not work for classes that have a
2332                         //       different name in LyX than in LaTeX
2333                         h_textclass = p.getArg('{', '}');
2334                         p.skip_spaces();
2335                         continue;
2336                 }
2337
2338                 if (t.cs() == "usepackage") {
2339                         string const options = p.getArg('[', ']');
2340                         string const name = p.getArg('{', '}');
2341                         vector<string> vecnames;
2342                         split(name, vecnames, ',');
2343                         vector<string>::const_iterator it  = vecnames.begin();
2344                         vector<string>::const_iterator end = vecnames.end();
2345                         for (; it != end; ++it)
2346                                 handle_package(p, trimSpaceAndEol(*it), options,
2347                                                in_lyx_preamble, detectEncoding);
2348                         continue;
2349                 }
2350
2351                 if (t.cs() == "inputencoding") {
2352                         string const encoding = p.getArg('{','}');
2353                         Encoding const * const enc = encodings.fromLaTeXName(
2354                                 encoding, Encoding::inputenc, true);
2355                         if (!enc) {
2356                                 if (!detectEncoding)
2357                                         cerr << "Unknown encoding " << encoding
2358                                              << ". Ignoring." << std::endl;
2359                         } else {
2360                                 if (!enc->unsafe())
2361                                         h_inputencoding = enc->name();
2362                                 p.setEncoding(enc->iconvName());
2363                         }
2364                         continue;
2365                 }
2366
2367                 if (t.cs() == "newenvironment") {
2368                         string const name = p.getArg('{', '}');
2369                         string const opt1 = p.getFullOpt();
2370                         string const opt2 = p.getFullOpt();
2371                         string const beg = p.verbatim_item();
2372                         string const end = p.verbatim_item();
2373                         if (!in_lyx_preamble) {
2374                                 h_preamble << "\\newenvironment{" << name
2375                                            << '}' << opt1 << opt2 << '{'
2376                                            << beg << "}{" << end << '}';
2377                         }
2378                         add_known_environment(name, opt1, !opt2.empty(),
2379                                               from_utf8(beg), from_utf8(end));
2380                         continue;
2381                 }
2382
2383                 if (t.cs() == "newtheorem") {
2384                         bool star = false;
2385                         if (p.next_token().character() == '*') {
2386                                 p.get_token();
2387                                 star = true;
2388                         }
2389                         string const name = p.getArg('{', '}');
2390                         string const opt1 = p.getFullOpt();
2391                         string const opt2 = p.getFullOpt();
2392                         string const body = p.verbatim_item();
2393                         string const opt3 = p.getFullOpt();
2394                         string const cmd = star ? "\\newtheorem*" : "\\newtheorem";
2395
2396                         string const complete = cmd + "{" + name + '}' +
2397                                           opt1 + opt2 + '{' + body + '}' + opt3;
2398
2399                         add_known_theorem(name, opt1, !opt2.empty(), from_utf8(complete));
2400
2401                         if (!in_lyx_preamble)
2402                                 h_preamble << complete;
2403                         continue;
2404                 }
2405
2406                 if (t.cs() == "def") {
2407                         string name = p.get_token().cs();
2408                         // In fact, name may be more than the name:
2409                         // In the test case of bug 8116
2410                         // name == "csname SF@gobble@opt \endcsname".
2411                         // Therefore, we need to use asInput() instead of cs().
2412                         while (p.next_token().cat() != catBegin)
2413                                 name += p.get_token().asInput();
2414                         if (!in_lyx_preamble)
2415                                 h_preamble << "\\def\\" << name << '{'
2416                                            << p.verbatim_item() << "}";
2417                         continue;
2418                 }
2419
2420                 if (t.cs() == "newcolumntype") {
2421                         string const name = p.getArg('{', '}');
2422                         trimSpaceAndEol(name);
2423                         int nargs = 0;
2424                         string opts = p.getOpt();
2425                         if (!opts.empty()) {
2426                                 istringstream is(string(opts, 1));
2427                                 is >> nargs;
2428                         }
2429                         special_columns_[name[0]] = nargs;
2430                         h_preamble << "\\newcolumntype{" << name << "}";
2431                         if (nargs)
2432                                 h_preamble << "[" << nargs << "]";
2433                         h_preamble << "{" << p.verbatim_item() << "}";
2434                         continue;
2435                 }
2436
2437                 if (t.cs() == "setcounter") {
2438                         string const name = p.getArg('{', '}');
2439                         string const content = p.getArg('{', '}');
2440                         if (name == "secnumdepth")
2441                                 h_secnumdepth = content;
2442                         else if (name == "tocdepth")
2443                                 h_tocdepth = content;
2444                         else
2445                                 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
2446                         continue;
2447                 }
2448
2449                 if (t.cs() == "setlength") {
2450                         string const name = p.verbatim_item();
2451                         string const content = p.verbatim_item();
2452                         // the paragraphs are only not indented when \parindent is set to zero
2453                         if (name == "\\parindent" && content != "") {
2454                                 if (content[0] == '0')
2455                                         h_paragraph_separation = "skip";
2456                                 else
2457                                         h_paragraph_indentation = translate_len(content);
2458                         } else if (name == "\\parskip") {
2459                                 if (content == "\\smallskipamount")
2460                                         h_defskip = "smallskip";
2461                                 else if (content == "\\medskipamount")
2462                                         h_defskip = "medskip";
2463                                 else if (content == "\\bigskipamount")
2464                                         h_defskip = "bigskip";
2465                                 else
2466                                         h_defskip = translate_len(content);
2467                         } else if (name == "\\mathindent") {
2468                                 h_mathindentation = translate_len(content);
2469                         } else
2470                                 h_preamble << "\\setlength{" << name << "}{" << content << "}";
2471                         continue;
2472                 }
2473
2474                 if (t.cs() == "onehalfspacing") {
2475                         h_spacing = "onehalf";
2476                         continue;
2477                 }
2478
2479                 if (t.cs() == "doublespacing") {
2480                         h_spacing = "double";
2481                         continue;
2482                 }
2483
2484                 if (t.cs() == "setstretch") {
2485                         h_spacing = "other " + p.verbatim_item();
2486                         continue;
2487                 }
2488
2489                 if (t.cs() == "synctex") {
2490                         // the scheme is \synctex=value
2491                         // where value can only be "1" or "-1"
2492                         h_output_sync = "1";
2493                         // there can be any character behind the value (e.g. a linebreak or a '\'
2494                         // therefore we extract it char by char
2495                         p.get_token();
2496                         string value = p.get_token().asInput();
2497                         if (value == "-")
2498                                 value += p.get_token().asInput();
2499                         h_output_sync_macro = "\\synctex=" + value;
2500                         continue;
2501                 }
2502
2503                 if (t.cs() == "begin") {
2504                         string const name = p.getArg('{', '}');
2505                         if (name == "document")
2506                                 break;
2507                         h_preamble << "\\begin{" << name << "}";
2508                         continue;
2509                 }
2510
2511                 if (t.cs() == "geometry") {
2512                         vector<string> opts = split_options(p.getArg('{', '}'));
2513                         handle_geometry(opts);
2514                         continue;
2515                 }
2516
2517                 if (t.cs() == "definecolor") {
2518                         string const color = p.getArg('{', '}');
2519                         string const space = p.getArg('{', '}');
2520                         string const value = p.getArg('{', '}');
2521                         if (color == "document_fontcolor" && space == "rgb") {
2522                                 RGBColor c(RGBColorFromLaTeX(value));
2523                                 h_fontcolor = X11hexname(c);
2524                         } else if (color == "note_fontcolor" && space == "rgb") {
2525                                 RGBColor c(RGBColorFromLaTeX(value));
2526                                 h_notefontcolor = X11hexname(c);
2527                         } else if (color == "page_backgroundcolor" && space == "rgb") {
2528                                 RGBColor c(RGBColorFromLaTeX(value));
2529                                 h_backgroundcolor = X11hexname(c);
2530                         } else if (color == "shadecolor" && space == "rgb") {
2531                                 RGBColor c(RGBColorFromLaTeX(value));
2532                                 h_boxbgcolor = X11hexname(c);
2533                         } else {
2534                                 h_preamble << "\\definecolor{" << color
2535                                            << "}{" << space << "}{" << value
2536                                            << '}';
2537                         }
2538                         continue;
2539                 }
2540
2541                 if (t.cs() == "bibliographystyle") {
2542                         h_biblio_style = p.verbatim_item();
2543                         continue;
2544                 }
2545
2546                 if (t.cs() == "jurabibsetup") {
2547                         // FIXME p.getArg('{', '}') is most probably wrong (it
2548                         //       does not handle nested braces).
2549                         //       Use p.verbatim_item() instead.
2550                         vector<string> jurabibsetup =
2551                                 split_options(p.getArg('{', '}'));
2552                         // add jurabibsetup to the jurabib package options
2553                         add_package("jurabib", jurabibsetup);
2554                         if (!jurabibsetup.empty()) {
2555                                 h_preamble << "\\jurabibsetup{"
2556                                            << join(jurabibsetup, ",") << '}';
2557                         }
2558                         continue;
2559                 }
2560
2561                 if (t.cs() == "hypersetup") {
2562                         vector<string> hypersetup =
2563                                 split_options(p.verbatim_item());
2564                         // add hypersetup to the hyperref package options
2565                         handle_hyperref(hypersetup);
2566                         if (!hypersetup.empty()) {
2567                                 h_preamble << "\\hypersetup{"
2568                                            << join(hypersetup, ",") << '}';
2569                         }
2570                         continue;
2571                 }
2572
2573                 if (t.cs() == "includeonly") {
2574                         vector<string> includeonlys = getVectorFromString(p.getArg('{', '}'));
2575                         for (auto & iofile : includeonlys) {
2576                                 string filename(normalize_filename(iofile));
2577                                 string const path = getMasterFilePath(true);
2578                                 // We want to preserve relative/absolute filenames,
2579                                 // therefore path is only used for testing
2580                                 if (!makeAbsPath(filename, path).exists()) {
2581                                         // The file extension is probably missing.
2582                                         // Now try to find it out.
2583                                         string const tex_name =
2584                                                 find_file(filename, path,
2585                                                           known_tex_extensions);
2586                                         if (!tex_name.empty())
2587                                                 filename = tex_name;
2588                                 }
2589                                 string outname;
2590                                 if (makeAbsPath(filename, path).exists())
2591                                         fix_child_filename(filename);
2592                                 else
2593                                         cerr << "Warning: Could not find included file '"
2594                                              << filename << "'." << endl;
2595                                 outname = changeExtension(filename, "lyx");
2596                                 h_includeonlys.push_back(outname);
2597                         }
2598                         continue;
2599                 }
2600
2601                 if (is_known(t.cs(), known_if_3arg_commands)) {
2602                         // prevent misparsing of \usepackage if it is used
2603                         // as an argument (see e.g. our own output of
2604                         // \@ifundefined above)
2605                         string const arg1 = p.verbatim_item();
2606                         string const arg2 = p.verbatim_item();
2607                         string const arg3 = p.verbatim_item();
2608                         // test case \@ifundefined{date}{}{\date{}}
2609                         if (t.cs() == "@ifundefined" && arg1 == "date" &&
2610                             arg2.empty() && arg3 == "\\date{}") {
2611                                 h_suppress_date = "true";
2612                         // older tex2lyx versions did output
2613                         // \@ifundefined{definecolor}{\usepackage{color}}{}
2614                         } else if (t.cs() == "@ifundefined" &&
2615                                    arg1 == "definecolor" &&
2616                                    arg2 == "\\usepackage{color}" &&
2617                                    arg3.empty()) {
2618                                 if (!in_lyx_preamble)
2619                                         h_preamble << package_beg_sep
2620                                                    << "color"
2621                                                    << package_mid_sep
2622                                                    << "\\@ifundefined{definecolor}{color}{}"
2623                                                    << package_end_sep;
2624                         // test for case
2625                         //\@ifundefined{showcaptionsetup}{}{%
2626                         // \PassOptionsToPackage{caption=false}{subfig}}
2627                         // that LyX uses for subfloats
2628                         } else if (t.cs() == "@ifundefined" &&
2629                                    arg1 == "showcaptionsetup" && arg2.empty()
2630                                 && arg3 == "%\n \\PassOptionsToPackage{caption=false}{subfig}") {
2631                                 ; // do nothing
2632                         } else if (!in_lyx_preamble) {
2633                                 h_preamble << t.asInput()
2634                                            << '{' << arg1 << '}'
2635                                            << '{' << arg2 << '}'
2636                                            << '{' << arg3 << '}';
2637                         }
2638                         continue;
2639                 }
2640
2641                 if (is_known(t.cs(), known_if_commands)) {
2642                         // must not parse anything in conditional code, since
2643                         // LyX would output the parsed contents unconditionally
2644                         if (!in_lyx_preamble)
2645                                 h_preamble << t.asInput();
2646                         handle_if(p, in_lyx_preamble);
2647                         continue;
2648                 }
2649
2650                 if (!t.cs().empty() && !in_lyx_preamble) {
2651                         h_preamble << '\\' << t.cs();
2652                         continue;
2653                 }
2654         }
2655
2656         // remove the whitespace
2657         p.skip_spaces();
2658
2659         // Force textclass if the user wanted it
2660         if (!forceclass.empty())
2661                 h_textclass = forceclass;
2662         tc.setName(h_textclass);
2663         if (!LayoutFileList::get().haveClass(h_textclass) || !tc.load()) {
2664                 cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
2665                 exit(EXIT_FAILURE);
2666         }
2667         if (h_papersides.empty()) {
2668                 ostringstream ss;
2669                 ss << tc.sides();
2670                 h_papersides = ss.str();
2671         }
2672
2673         // If the CJK package is used we cannot set the document language from
2674         // the babel options. Instead, we guess which language is used most
2675         // and set this one.
2676         default_language = h_language;
2677         if (is_full_document &&
2678             (auto_packages.find("CJK") != auto_packages.end() ||
2679              auto_packages.find("CJKutf8") != auto_packages.end())) {
2680                 p.pushPosition();
2681                 h_language = guessLanguage(p, default_language);
2682                 p.popPosition();
2683                 if (explicit_babel && h_language != default_language) {
2684                         // We set the document language to a CJK language,
2685                         // but babel is explicitly called in the user preamble
2686                         // without options. LyX will not add the default
2687                         // language to the document options if it is either
2688                         // english, or no text is set as default language.
2689                         // Therefore we need to add a language option explicitly.
2690                         // FIXME: It would be better to remove all babel calls
2691                         //        from the user preamble, but this is difficult
2692                         //        without re-introducing bug 7861.
2693                         if (h_options.empty())
2694                                 h_options = lyx2babel(default_language);
2695                         else
2696                                 h_options += ',' + lyx2babel(default_language);
2697                 }
2698         }
2699
2700         // Finally, set the quote style.
2701         // LyX knows the following quotes styles:
2702         // british, cjk, cjkangle, danish, english, french, german,
2703         // polish, russian, swedish and swiss
2704         // conversion list taken from
2705         // https://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
2706         // (quotes for kazakh are unknown)
2707         // british
2708         if (is_known(h_language, known_british_quotes_languages))
2709                 h_quotes_style = "british";
2710         // cjk
2711         else if (is_known(h_language, known_cjk_quotes_languages))
2712                 h_quotes_style = "cjk";
2713         // cjkangle
2714         else if (is_known(h_language, known_cjkangle_quotes_languages))
2715                 h_quotes_style = "cjkangle";
2716         // danish
2717         else if (is_known(h_language, known_danish_quotes_languages))
2718                 h_quotes_style = "danish";
2719         // french
2720         else if (is_known(h_language, known_french_quotes_languages))
2721                 h_quotes_style = "french";
2722         // german
2723         else if (is_known(h_language, known_german_quotes_languages))
2724                 h_quotes_style = "german";
2725         // polish
2726         else if (is_known(h_language, known_polish_quotes_languages))
2727                 h_quotes_style = "polish";
2728         // russian
2729         else if (is_known(h_language, known_russian_quotes_languages))
2730                 h_quotes_style = "russian";
2731         // swedish
2732         else if (is_known(h_language, known_swedish_quotes_languages))
2733                 h_quotes_style = "swedish";
2734         // swiss
2735         else if (is_known(h_language, known_swiss_quotes_languages))
2736                 h_quotes_style = "swiss";
2737         // english
2738         else if (is_known(h_language, known_english_quotes_languages))
2739                 h_quotes_style = "english";
2740 }
2741
2742
2743 string Preamble::parseEncoding(Parser & p, string const & forceclass)
2744 {
2745         TeX2LyXDocClass dummy;
2746         parse(p, forceclass, true, dummy);
2747         if (h_inputencoding != "auto-legacy" && h_inputencoding != "auto-legacy-plain")
2748                 return h_inputencoding;
2749         return "";
2750 }
2751
2752
2753 string babel2lyx(string const & language)
2754 {
2755         char const * const * where = is_known(language, known_languages);
2756         if (where)
2757                 return known_coded_languages[where - known_languages];
2758         return language;
2759 }
2760
2761
2762 string lyx2babel(string const & language)
2763 {
2764         char const * const * where = is_known(language, known_coded_languages);
2765         if (where)
2766                 return known_languages[where - known_coded_languages];
2767         return language;
2768 }
2769
2770
2771 string Preamble::polyglossia2lyx(string const & language)
2772 {
2773         char const * const * where = is_known(language, polyglossia_languages);
2774         if (where)
2775                 return coded_polyglossia_languages[where - polyglossia_languages];
2776         return language;
2777 }
2778
2779
2780 string rgbcolor2code(string const & name)
2781 {
2782         char const * const * where = is_known(name, known_basic_colors);
2783         if (where) {
2784                 // "red", "green" etc
2785                 return known_basic_color_codes[where - known_basic_colors];
2786         }
2787         // "255,0,0", "0,255,0" etc
2788         RGBColor c(RGBColorFromLaTeX(name));
2789         return X11hexname(c);
2790 }
2791
2792 // }])
2793
2794
2795 } // namespace lyx