]> git.lyx.org Git - features.git/blob - src/tex2lyx/Preamble.cpp
tex2lyx: update and improve noto import
[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", "cantarell", "Chivo", "cmbr", "cmss", "DejaVuSans", "DejaVuSansCondensed", "FiraSans", "helvet", "iwona",
152 "iwonac", "iwonal", "iwonalc", "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", "courier", "DejaVuSansMono",
156 "FiraMono", "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         if (contains(scale, '=')) {
482                 string const value = support::split(scale, '=');
483                 if (isStrDbl(value)) {
484                         percentage = convert<string>(
485                                 static_cast<int>(100 * convert<double>(value)));
486                         return true;
487                 }
488         }
489         return false;
490 }
491
492
493 string remove_braces(string const & value)
494 {
495         if (value.empty())
496                 return value;
497         if (value[0] == '{' && value[value.length()-1] == '}')
498                 return value.substr(1, value.length()-2);
499         return value;
500 }
501
502 } // anonymous namespace
503
504
505 Preamble::Preamble() : one_language(true), explicit_babel(false),
506         title_layout_found(false), index_number(0), h_font_cjk_set(false)
507 {
508         //h_backgroundcolor;
509         //h_boxbgcolor;
510         h_biblio_style            = "plain";
511         h_bibtex_command          = "default";
512         h_cite_engine             = "basic";
513         h_cite_engine_type        = "default";
514         h_color                   = "#008000";
515         h_defskip                 = "medskip";
516         h_dynamic_quotes          = false;
517         //h_float_placement;
518         //h_fontcolor;
519         h_fontencoding            = "default";
520         h_font_roman[0]           = "default";
521         h_font_roman[1]           = "default";
522         h_font_sans[0]            = "default";
523         h_font_sans[1]            = "default";
524         h_font_typewriter[0]      = "default";
525         h_font_typewriter[1]      = "default";
526         h_font_math[0]            = "auto";
527         h_font_math[1]            = "auto";
528         h_font_default_family     = "default";
529         h_use_non_tex_fonts       = false;
530         h_font_sc                 = "false";
531         h_font_roman_osf          = "false";
532         h_font_sans_osf           = "false";
533         h_font_typewriter_osf     = "false";
534         h_font_sf_scale[0]        = "100";
535         h_font_sf_scale[1]        = "100";
536         h_font_tt_scale[0]        = "100";
537         h_font_tt_scale[1]        = "100";
538         // h_font_roman_opts;
539         // h_font_sans_opts;
540         // h_font_typewriter_opts;
541         //h_font_cjk
542         h_is_mathindent           = "0";
543         h_math_numbering_side     = "default";
544         h_graphics                = "default";
545         h_default_output_format   = "default";
546         h_html_be_strict          = "false";
547         h_html_css_as_file        = "0";
548         h_html_math_output        = "0";
549         h_index[0]                = "Index";
550         h_index_command           = "default";
551         h_inputencoding           = "auto-legacy";
552         h_justification           = "true";
553         h_language                = "english";
554         h_language_package        = "none";
555         //h_listings_params;
556         h_maintain_unincluded_children = "false";
557         //h_margins;
558         //h_notefontcolor;
559         //h_options;
560         h_output_changes          = "false";
561         h_output_sync             = "0";
562         //h_output_sync_macro
563         h_papercolumns            = "1";
564         h_paperfontsize           = "default";
565         h_paperorientation        = "portrait";
566         h_paperpagestyle          = "default";
567         //h_papersides;
568         h_papersize               = "default";
569         h_paragraph_indentation   = "default";
570         h_paragraph_separation    = "indent";
571         //h_pdf_title;
572         //h_pdf_author;
573         //h_pdf_subject;
574         //h_pdf_keywords;
575         h_pdf_bookmarks           = "0";
576         h_pdf_bookmarksnumbered   = "0";
577         h_pdf_bookmarksopen       = "0";
578         h_pdf_bookmarksopenlevel  = "1";
579         h_pdf_breaklinks          = "0";
580         h_pdf_pdfborder           = "0";
581         h_pdf_colorlinks          = "0";
582         h_pdf_backref             = "section";
583         h_pdf_pdfusetitle         = "0";
584         //h_pdf_pagemode;
585         //h_pdf_quoted_options;
586         h_quotes_style         = "english";
587         h_secnumdepth             = "3";
588         h_shortcut[0]             = "idx";
589         h_spacing                 = "single";
590         h_save_transient_properties = "true";
591         h_suppress_date           = "false";
592         h_textclass               = "article";
593         h_tocdepth                = "3";
594         h_tracking_changes        = "false";
595         h_use_bibtopic            = "false";
596         h_use_dash_ligatures      = "true";
597         h_use_indices             = "false";
598         h_use_geometry            = "false";
599         h_use_default_options     = "false";
600         h_use_hyperref            = "false";
601         h_use_microtype           = "false";
602         h_use_lineno              = "false";
603         h_use_refstyle            = false;
604         h_use_minted              = false;
605         h_use_packages["amsmath"]    = "1";
606         h_use_packages["amssymb"]    = "0";
607         h_use_packages["cancel"]     = "0";
608         h_use_packages["esint"]      = "1";
609         h_use_packages["mhchem"]     = "0";
610         h_use_packages["mathdots"]   = "0";
611         h_use_packages["mathtools"]  = "0";
612         h_use_packages["stackrel"]   = "0";
613         h_use_packages["stmaryrd"]   = "0";
614         h_use_packages["undertilde"] = "0";
615 }
616
617
618 void Preamble::handle_hyperref(vector<string> & options)
619 {
620         // FIXME swallow inputencoding changes that might surround the
621         //       hyperref setup if it was written by LyX
622         h_use_hyperref = "true";
623         // swallow "unicode=true", since LyX does always write that
624         vector<string>::iterator it =
625                 find(options.begin(), options.end(), "unicode=true");
626         if (it != options.end())
627                 options.erase(it);
628         it = find(options.begin(), options.end(), "pdfusetitle");
629         if (it != options.end()) {
630                 h_pdf_pdfusetitle = "1";
631                 options.erase(it);
632         }
633         string bookmarks = process_keyval_opt(options, "bookmarks");
634         if (bookmarks == "true")
635                 h_pdf_bookmarks = "1";
636         else if (bookmarks == "false")
637                 h_pdf_bookmarks = "0";
638         if (h_pdf_bookmarks == "1") {
639                 string bookmarksnumbered =
640                         process_keyval_opt(options, "bookmarksnumbered");
641                 if (bookmarksnumbered == "true")
642                         h_pdf_bookmarksnumbered = "1";
643                 else if (bookmarksnumbered == "false")
644                         h_pdf_bookmarksnumbered = "0";
645                 string bookmarksopen =
646                         process_keyval_opt(options, "bookmarksopen");
647                 if (bookmarksopen == "true")
648                         h_pdf_bookmarksopen = "1";
649                 else if (bookmarksopen == "false")
650                         h_pdf_bookmarksopen = "0";
651                 if (h_pdf_bookmarksopen == "1") {
652                         string bookmarksopenlevel =
653                                 process_keyval_opt(options, "bookmarksopenlevel");
654                         if (!bookmarksopenlevel.empty())
655                                 h_pdf_bookmarksopenlevel = bookmarksopenlevel;
656                 }
657         }
658         string breaklinks = process_keyval_opt(options, "breaklinks");
659         if (breaklinks == "true")
660                 h_pdf_breaklinks = "1";
661         else if (breaklinks == "false")
662                 h_pdf_breaklinks = "0";
663         string pdfborder = process_keyval_opt(options, "pdfborder");
664         if (pdfborder == "{0 0 0}")
665                 h_pdf_pdfborder = "1";
666         else if (pdfborder == "{0 0 1}")
667                 h_pdf_pdfborder = "0";
668         string backref = process_keyval_opt(options, "backref");
669         if (!backref.empty())
670                 h_pdf_backref = backref;
671         string colorlinks = process_keyval_opt(options, "colorlinks");
672         if (colorlinks == "true")
673                 h_pdf_colorlinks = "1";
674         else if (colorlinks == "false")
675                 h_pdf_colorlinks = "0";
676         string pdfpagemode = process_keyval_opt(options, "pdfpagemode");
677         if (!pdfpagemode.empty())
678                 h_pdf_pagemode = pdfpagemode;
679         string pdftitle = process_keyval_opt(options, "pdftitle");
680         if (!pdftitle.empty()) {
681                 h_pdf_title = remove_braces(pdftitle);
682         }
683         string pdfauthor = process_keyval_opt(options, "pdfauthor");
684         if (!pdfauthor.empty()) {
685                 h_pdf_author = remove_braces(pdfauthor);
686         }
687         string pdfsubject = process_keyval_opt(options, "pdfsubject");
688         if (!pdfsubject.empty())
689                 h_pdf_subject = remove_braces(pdfsubject);
690         string pdfkeywords = process_keyval_opt(options, "pdfkeywords");
691         if (!pdfkeywords.empty())
692                 h_pdf_keywords = remove_braces(pdfkeywords);
693         if (!options.empty()) {
694                 if (!h_pdf_quoted_options.empty())
695                         h_pdf_quoted_options += ',';
696                 h_pdf_quoted_options += join(options, ",");
697                 options.clear();
698         }
699 }
700
701
702 void Preamble::handle_geometry(vector<string> & options)
703 {
704         h_use_geometry = "true";
705         vector<string>::iterator it;
706         // paper orientation
707         if ((it = find(options.begin(), options.end(), "landscape")) != options.end()) {
708                 h_paperorientation = "landscape";
709                 options.erase(it);
710         }
711         // paper size
712         // keyval version: "paper=letter"
713         string paper = process_keyval_opt(options, "paper");
714         if (!paper.empty())
715                 h_papersize = paper + "paper";
716         // alternative version: "letterpaper"
717         handle_opt(options, known_paper_sizes, h_papersize);
718         delete_opt(options, known_paper_sizes);
719         // page margins
720         char const * const * margin = known_paper_margins;
721         for (; *margin; ++margin) {
722                 string value = process_keyval_opt(options, *margin);
723                 if (!value.empty()) {
724                         int k = margin - known_paper_margins;
725                         string name = known_coded_paper_margins[k];
726                         h_margins += '\\' + name + ' ' + value + '\n';
727                 }
728         }
729 }
730
731
732 void Preamble::handle_package(Parser &p, string const & name,
733                               string const & opts, bool in_lyx_preamble,
734                               bool detectEncoding)
735 {
736         vector<string> options = split_options(opts);
737         add_package(name, options);
738
739         if (is_known(name, known_xetex_packages)) {
740                 xetex = true;
741                 h_use_non_tex_fonts = true;
742                 registerAutomaticallyLoadedPackage("fontspec");
743                 if (h_inputencoding == "auto-legacy")
744                         p.setEncoding("UTF-8");
745         }
746
747         // vector of all options for easier parsing and
748         // skipping
749         vector<string> allopts = getVectorFromString(opts);
750         // this stores the potential extra options
751         string xopts;
752
753         //
754         // roman fonts
755         //
756
757         // By default, we use the package name as LyX font name,
758         // so this only needs to be reset if these names differ
759         // Exception: noto
760         if (is_known(name, known_roman_font_packages) && name != "noto")
761                 h_font_roman[0] = name;
762
763         if (name == "ccfonts") {
764                 for (auto const & opt : allopts) {
765                         if (!xopts.empty())
766                                 xopts += ", ";
767                         xopts += opt;
768                 }
769                 if (!xopts.empty())
770                         h_font_roman_opts = xopts;
771                 options.clear();
772         }
773
774         if (name == "lmodern") {
775                 for (auto const & opt : allopts) {
776                         if (!xopts.empty())
777                                 xopts += ", ";
778                         xopts += opt;
779                 }
780                 if (!xopts.empty())
781                         h_font_roman_opts = xopts;
782                 options.clear();
783         }
784
785         if (name == "fourier") {
786                 h_font_roman[0] = "utopia";
787                 for (auto const & opt : allopts) {
788                         if (opt == "osf") {
789                                 h_font_roman_osf = "true";
790                                 continue;
791                         }
792                         if (opt == "expert") {
793                                 h_font_sc = "true";
794                                 continue;
795                         }
796                         if (!xopts.empty())
797                                 xopts += ", ";
798                         xopts += opt;
799                 }
800                 if (!xopts.empty())
801                         h_font_roman_opts = xopts;
802                 options.clear();
803         }
804
805         if (name == "garamondx") {
806                 for (auto const & opt : allopts) {
807                         if (opt == "osfI") {
808                                 h_font_roman_osf = "true";
809                                 continue;
810                         }
811                         if (!xopts.empty())
812                                 xopts += ", ";
813                         xopts += opt;
814                 }
815                 if (!xopts.empty())
816                         h_font_roman_opts = xopts;
817                 options.clear();
818         }
819
820         if (name == "libertine") {
821                 // this automatically invokes biolinum
822                 h_font_sans[0] = "biolinum";
823                 // as well as libertineMono
824                 h_font_typewriter[0] = "libertine-mono";
825                 for (auto const & opt : allopts) {
826                         if (opt == "osf") {
827                                 h_font_roman_osf = "true";
828                                 continue;
829                         }
830                         if (opt == "lining") {
831                                 h_font_roman_osf = "false";
832                                 continue;
833                         }
834                         if (!xopts.empty())
835                                 xopts += ", ";
836                         xopts += opt;
837                 }
838                 if (!xopts.empty())
839                         h_font_roman_opts = xopts;
840                 options.clear();
841         }
842
843         if (name == "libertineRoman" || name == "libertine-type1") {
844                 h_font_roman[0] = "libertine";
845                 // NOTE: contrary to libertine.sty, libertineRoman
846                 // and libertine-type1 do not automatically invoke
847                 // biolinum and libertineMono
848                 if (opts == "lining")
849                         h_font_roman_osf = "false";
850                 else if (opts == "osf")
851                         h_font_roman_osf = "true";
852         }
853
854         if (name == "MinionPro") {
855                 h_font_roman[0] = "minionpro";
856                 h_font_roman_osf = "true";
857                 h_font_math[0] = "auto";
858                 for (auto const & opt : allopts) {
859                         if (opt == "lf") {
860                                 h_font_roman_osf = "false";
861                                 continue;
862                         }
863                         if (opt == "onlytext") {
864                                 h_font_math[0] = "default";
865                                 continue;
866                         }
867                         if (!xopts.empty())
868                                 xopts += ", ";
869                         xopts += opt;
870                 }
871                 if (!xopts.empty())
872                         h_font_roman_opts = xopts;
873                 options.clear();
874         }
875
876         if (name == "mathdesign") {
877                 for (auto const & opt : allopts) {
878                         if (opt == "charter") {
879                                 h_font_roman[0] = "md-charter";
880                                 continue;
881                         }
882                         if (opt == "garamond") {
883                                 h_font_roman[0] = "md-garamond";
884                                 continue;
885                         }
886                         if (opt == "utopia") {
887                                 h_font_roman[0] = "md-utopia";
888                                 continue;
889                         }
890                         if (opt == "expert") {
891                                 h_font_sc = "true";
892                                 h_font_roman_osf = "true";
893                                 continue;
894                         }
895                 }
896         }
897
898         else if (name == "mathpazo") {
899                 h_font_roman[0] = "palatino";
900                 for (auto const & opt : allopts) {
901                         if (opt == "osf") {
902                                 h_font_roman_osf = "true";
903                                 continue;
904                         }
905                         if (opt == "sc") {
906                                 h_font_sc = "true";
907                                 continue;
908                         }
909                         if (!xopts.empty())
910                                 xopts += ", ";
911                         xopts += opt;
912                 }
913                 if (!xopts.empty())
914                         h_font_roman_opts = xopts;
915                 options.clear();
916         }
917
918         else if (name == "mathptmx") {
919                 h_font_roman[0] = "times";
920                 for (auto const & opt : allopts) {
921                         if (!xopts.empty())
922                                 xopts += ", ";
923                         xopts += opt;
924                 }
925                 if (!xopts.empty())
926                         h_font_roman_opts = xopts;
927                 options.clear();
928         }
929
930         if (name == "crimson")
931                 h_font_roman[0] = "cochineal";
932
933         if (name == "cochineal") {
934                 for (auto const & opt : allopts) {
935                         if (opt == "osf" || opt == "oldstyle") {
936                                 h_font_roman_osf = "true";
937                                 continue;
938                         }
939                         if (opt == "proportional" || opt == "p")
940                                 continue;
941                         if (!xopts.empty())
942                                 xopts += ", ";
943                         xopts += opt;
944                 }
945                 if (!xopts.empty())
946                         h_font_roman_opts = xopts;
947                 options.clear();
948         }
949
950         if (name == "eco")
951                 // font uses old-style figure
952                 h_font_roman_osf = "true";
953
954         if (name == "paratype") {
955                 // in this case all fonts are ParaType
956                 h_font_roman[0] = "PTSerif-TLF";
957                 h_font_sans[0] = "default";
958                 h_font_typewriter[0] = "default";
959         }
960
961         if (name == "PTSerif")
962                 h_font_roman[0] = "PTSerif-TLF";
963
964         if (name == "XCharter") {
965                 h_font_roman[0] = "xcharter";
966                 for (auto const & opt : allopts) {
967                         if (opt == "osf") {
968                                 h_font_roman_osf = "true";
969                                 continue;
970                         }
971                         if (!xopts.empty())
972                                 xopts += ", ";
973                         xopts += opt;
974                 }
975                 if (!xopts.empty())
976                         h_font_roman_opts = xopts;
977                 options.clear();
978         }
979
980         if (name == "plex-serif") {
981                 h_font_roman[0] = "IBMPlexSerif";
982                 for (auto const & opt : allopts) {
983                         if (opt == "thin") {
984                                 h_font_roman[0] = "IBMPlexSerifThin";
985                                 continue;
986                         }
987                         if (opt == "extralight") {
988                                 h_font_roman[0] = "IBMPlexSerifExtraLight";
989                                 continue;
990                         }
991                         if (opt == "light") {
992                                 h_font_roman[0] = "IBMPlexSerifLight";
993                                 continue;
994                         }
995                         if (!xopts.empty())
996                                 xopts += ", ";
997                         xopts += opt;
998                 }
999                 if (!xopts.empty())
1000                         h_font_roman_opts = xopts;
1001                 options.clear();
1002         }
1003
1004         if (name == "noto-serif" || name == "noto") {
1005                 bool rm = false;
1006                 bool rmx = false;
1007                 bool sf = false;
1008                 bool sfx = false;
1009                 bool tt = false;
1010                 bool thin = false;
1011                 bool extralight = false;
1012                 bool light = false;
1013                 bool medium = false;
1014                 bool osf = false;
1015                 string scl;
1016                 if (name == "noto") {
1017                         rm = true;
1018                         sf = true;
1019                         tt = true;
1020                 }
1021                 // Since the options might apply to different shapes,
1022                 // we need to parse all options first and then handle them.
1023                 for (auto const & opt : allopts) {
1024                         if (opt == "regular")
1025                                 // skip
1026                                 continue;
1027                         if (opt == "rm") {
1028                                 rm = true;
1029                                 rmx = true;
1030                                 sf = sfx;
1031                                 tt = false;
1032                                 continue;
1033                         }
1034                         if (opt == "thin") {
1035                                 thin = true;
1036                                 continue;
1037                         }
1038                         if (opt == "extralight") {
1039                                 extralight = true;
1040                                 continue;
1041                         }
1042                         if (opt == "light") {
1043                                 light = true;
1044                                 continue;
1045                         }
1046                         if (opt == "medium") {
1047                                 medium = true;
1048                                 continue;
1049                         }
1050                         if (opt == "sf") {
1051                                 sfx = true;
1052                                 sf = true;
1053                                 rm = rmx;
1054                                 tt = false;
1055                                 continue;
1056                         }
1057                         if (opt == "nott") {
1058                                 tt = false;
1059                                 continue;
1060                         }
1061                         if (opt == "osf") {
1062                                 osf = true;
1063                                 continue;
1064                         }
1065                         if (prefixIs(opt, "scaled=")) {
1066                                 scl = opt;
1067                                 continue;
1068                         }
1069                         if (!xopts.empty())
1070                                 xopts += ", ";
1071                         xopts += opt;
1072                 }
1073                 options.clear();
1074                 // handle options that might affect different shapes
1075                 if (name == "noto-serif" || rm) {
1076                         if (thin)
1077                                 h_font_roman[0] = "NotoSerifThin";
1078                         else if (extralight)
1079                                 h_font_roman[0] = "NotoSerifExtralight";
1080                         else if (light)
1081                                 h_font_roman[0] = "NotoSerifLight";
1082                         else if (medium)
1083                                 h_font_roman[0] = "NotoSerifMedium";
1084                         else
1085                                 h_font_roman[0] = "NotoSerifRegular";
1086                         if (osf)
1087                                 h_font_roman_osf = "true";
1088                         if (!xopts.empty())
1089                                 h_font_roman_opts = xopts;
1090                 }
1091                 if (name == "noto" && sf) {
1092                         if (thin)
1093                                 h_font_sans[0] = "NotoSansThin";
1094                         else if (extralight)
1095                                 h_font_sans[0] = "NotoSansExtralight";
1096                         else if (light)
1097                                 h_font_sans[0] = "NotoSansLight";
1098                         else if (medium)
1099                                 h_font_sans[0] = "NotoSansMedium";
1100                         else
1101                                 h_font_sans[0] = "NotoSansRegular";
1102                         if (osf)
1103                                 h_font_sans_osf = "true";
1104                         if (!scl.empty())
1105                                 scale_as_percentage(scl, h_font_sf_scale[0]);
1106                         if (!xopts.empty())
1107                                 h_font_sans_opts = xopts;
1108                 }
1109                 if (name == "noto" && tt) {
1110                         h_font_typewriter[0] = "NotoMonoRegular";
1111                         if (osf)
1112                                 h_font_typewriter_osf = "true";
1113                         if (!scl.empty())
1114                                 scale_as_percentage(scl, h_font_tt_scale[0]);
1115                         if (!xopts.empty())
1116                                 h_font_typewriter_opts = xopts;
1117                 }
1118         }
1119
1120         if (name == "sourceserifpro") {
1121                 h_font_roman[0] = "ADOBESourceSerifPro";
1122                 for (auto const & opt : allopts) {
1123                         if (opt == "osf") {
1124                                 h_font_roman_osf = "true";
1125                                 continue;
1126                         }
1127                         if (!xopts.empty())
1128                                 xopts += ", ";
1129                         xopts += opt;
1130                 }
1131                 if (!xopts.empty())
1132                         h_font_roman_opts = xopts;
1133                 options.clear();
1134         }
1135
1136         //
1137         // sansserif fonts
1138         //
1139
1140         // By default, we use the package name as LyX font name,
1141         // so this only needs to be reset if these names differ.
1142         // Also, we handle the scaling option here generally.
1143         // Exception: noto
1144         if (is_known(name, known_sans_font_packages) && name != "noto") {
1145                 h_font_sans[0] = name;
1146                 if (contains(opts, "scale")) {
1147                         vector<string>::const_iterator it = allopts.begin();
1148                         for (; it != allopts.end() ; ++it) {
1149                                 string const opt = *it;
1150                                 if (prefixIs(opt, "scaled=") || prefixIs(opt, "scale=")) {
1151                                         if (scale_as_percentage(opt, h_font_sf_scale[0])) {
1152                                                 allopts.erase(it);
1153                                                 break;
1154                                         }
1155                                 }
1156                         }
1157                 }
1158         }
1159
1160         if (name == "biolinum" || name == "biolinum-type1") {
1161                 h_font_sans[0] = "biolinum";
1162                 for (auto const & opt : allopts) {
1163                         if (prefixIs(opt, "osf")) {
1164                                 h_font_sans_osf = "true";
1165                                 continue;
1166                         }
1167                         if (!xopts.empty())
1168                                 xopts += ", ";
1169                         xopts += opt;
1170                 }
1171                 if (!xopts.empty())
1172                         h_font_sans_opts = xopts;
1173                 options.clear();
1174         }
1175
1176         if (name == "cantarell") {
1177                 for (auto const & opt : allopts) {
1178                         if (opt == "defaultsans")
1179                                 continue;
1180                         if (prefixIs(opt, "oldstyle")) {
1181                                 h_font_sans_osf = "true";
1182                                 continue;
1183                         }
1184                         if (!xopts.empty())
1185                                 xopts += ", ";
1186                         xopts += opt;
1187                 }
1188                 if (!xopts.empty())
1189                         h_font_sans_opts = xopts;
1190                 options.clear();
1191         }
1192
1193         if (name == "Chivo") {
1194                 for (auto const & opt : allopts) {
1195                         if (opt == "thin") {
1196                                 h_font_roman[0] = "ChivoThin";
1197                                 continue;
1198                         }
1199                         if (opt == "light") {
1200                                 h_font_roman[0] = "ChivoLight";
1201                                 continue;
1202                         }
1203                         if (opt == "regular") {
1204                                 h_font_roman[0] = "Chivo";
1205                                 continue;
1206                         }
1207                         if (opt == "medium") {
1208                                 h_font_roman[0] = "ChivoMedium";
1209                                 continue;
1210                         }
1211                         if (prefixIs(opt, "oldstyle")) {
1212                                 h_font_sans_osf = "true";
1213                                 continue;
1214                         }
1215                         if (!xopts.empty())
1216                                 xopts += ", ";
1217                         xopts += opt;
1218                 }
1219                 if (!xopts.empty())
1220                         h_font_sans_opts = xopts;
1221                 options.clear();
1222         }
1223
1224         if (name == "PTSans") {
1225                 h_font_sans[0] = "PTSans-TLF";
1226         }
1227
1228         if (name == "FiraSans") {
1229                 h_font_sans_osf = "true";
1230                 for (auto const & opt : allopts) {
1231                         if (opt == "book") {
1232                                 h_font_sans[0] = "FiraSansBook";
1233                                 continue;
1234                         }
1235                         if (opt == "thin") {
1236                                 continue;
1237                         }
1238                         if (opt == "extralight") {
1239                                 h_font_sans[0] = "FiraSansExtralight";
1240                                 continue;
1241                         }
1242                         if (opt == "light") {
1243                                 h_font_sans[0] = "FiraSansLight";
1244                                 continue;
1245                         }
1246                         if (opt == "ultralight") {
1247                                 h_font_sans[0] = "FiraSansUltralight";
1248                                 continue;
1249                         }
1250                         if (opt == "thin") {
1251                                 h_font_sans[0] = "FiraSansThin";
1252                                 continue;
1253                         }
1254                         if (opt == "lf" || opt == "lining") {
1255                                 h_font_sans_osf = "false";
1256                                 continue;
1257                         }
1258                         if (!xopts.empty())
1259                                 xopts += ", ";
1260                         xopts += opt;
1261                 }
1262                 if (!xopts.empty())
1263                         h_font_sans_opts = xopts;
1264                 options.clear();
1265         }
1266
1267         if (name == "plex-sans") {
1268                 h_font_sans[0] = "IBMPlexSans";
1269                 for (auto const & opt : allopts) {
1270                         if (opt == "condensed") {
1271                                 h_font_sans[0] = "IBMPlexSansCondensed";
1272                                 continue;
1273                         }
1274                         if (opt == "thin") {
1275                                 h_font_sans[0] = "IBMPlexSansThin";
1276                                 continue;
1277                         }
1278                         if (opt == "extralight") {
1279                                 h_font_sans[0] = "IBMPlexSansExtraLight";
1280                                 continue;
1281                         }
1282                         if (opt == "light") {
1283                                 h_font_sans[0] = "IBMPlexSansLight";
1284                                 continue;
1285                         }
1286                         if (!xopts.empty())
1287                                 xopts += ", ";
1288                         xopts += opt;
1289                 }
1290                 if (!xopts.empty())
1291                         h_font_sans_opts = xopts;
1292                 options.clear();
1293         }
1294
1295         if (name == "noto-sans") {
1296                 h_font_sans[0] = "NotoSansRegular";
1297                 for (auto const & opt : allopts) {
1298                         if (opt == "regular")
1299                                 continue;
1300                         if (opt == "medium") {
1301                                 h_font_sans[0] = "NotoSansMedium";
1302                                 continue;
1303                         }
1304                         if (opt == "thin") {
1305                                 h_font_sans[0] = "NotoSansThin";
1306                                 continue;
1307                         }
1308                         if (opt == "extralight") {
1309                                 h_font_sans[0] = "NotoSansExtralight";
1310                                 continue;
1311                         }
1312                         if (opt == "light") {
1313                                 h_font_sans[0] = "NotoSansLight";
1314                                 continue;
1315                         }
1316                         if (opt == "osf") {
1317                                 h_font_sans_osf = "true";
1318                                 continue;
1319                         }
1320                         if (!xopts.empty())
1321                                 xopts += ", ";
1322                         xopts += opt;
1323                 }
1324                 if (!xopts.empty())
1325                         h_font_sans_opts = xopts;
1326                 options.clear();
1327         }
1328
1329         if (name == "sourcesanspro") {
1330                 h_font_sans[0] = "ADOBESourceSansPro";
1331                 for (auto const & opt : allopts) {
1332                         if (opt == "osf") {
1333                                 h_font_sans_osf = "true";
1334                                 continue;
1335                         }
1336                         if (!xopts.empty())
1337                                 xopts += ", ";
1338                         xopts += opt;
1339                 }
1340                 if (!xopts.empty())
1341                         h_font_sans_opts = xopts;
1342                 options.clear();
1343         }
1344
1345         //
1346         // typewriter fonts
1347         //
1348
1349         // By default, we use the package name as LyX font name,
1350         // so this only needs to be reset if these names differ.
1351         // Also, we handle the scaling option here generally.
1352         // Exceptions: fourier, noto
1353         if (is_known(name, known_typewriter_font_packages) && name != "fourier" && name != "noto") {
1354                 h_font_typewriter[0] = name;
1355                 if (contains(opts, "scale")) {
1356                         vector<string>::const_iterator it = allopts.begin();
1357                         for (; it != allopts.end() ; ++it) {
1358                                 string const opt = *it;
1359                                 if (prefixIs(opt, "scaled=") || prefixIs(opt, "scale=")) {
1360                                         if (scale_as_percentage(opt, h_font_tt_scale[0])) {
1361                                                 allopts.erase(it);
1362                                                 break;
1363                                         }
1364                                 }
1365                         }
1366                 }
1367         }
1368
1369         if (name == "libertineMono" || name == "libertineMono-type1")
1370                 h_font_typewriter[0] = "libertine-mono";
1371
1372         if (name == "FiraMono") {
1373                 h_font_typewriter_osf = "true";
1374                 for (auto const & opt : allopts) {
1375                         if (opt == "lf" || opt == "lining") {
1376                                 h_font_typewriter_osf = "false";
1377                                 continue;
1378                         }
1379                         if (!xopts.empty())
1380                                 xopts += ", ";
1381                         xopts += opt;
1382                 }
1383                 if (!xopts.empty())
1384                         h_font_typewriter_opts = xopts;
1385                 options.clear();
1386         }
1387
1388         if (name == "PTMono")
1389                 h_font_typewriter[0] = "PTMono-TLF";
1390
1391         if (name == "plex-mono") {
1392                 h_font_typewriter[0] = "IBMPlexMono";
1393                 for (auto const & opt : allopts) {
1394                         if (opt == "thin") {
1395                                 h_font_typewriter[0] = "IBMPlexMonoThin";
1396                                 continue;
1397                         }
1398                         if (opt == "extralight") {
1399                                 h_font_typewriter[0] = "IBMPlexMonoExtraLight";
1400                                 continue;
1401                         }
1402                         if (opt == "light") {
1403                                 h_font_typewriter[0] = "IBMPlexMonoLight";
1404                                 continue;
1405                         }
1406                         if (!xopts.empty())
1407                                 xopts += ", ";
1408                         xopts += opt;
1409                 }
1410                 if (!xopts.empty())
1411                         h_font_typewriter_opts = xopts;
1412                 options.clear();
1413         }
1414
1415         if (name == "noto-mono") {
1416                 h_font_typewriter[0] = "NotoMonoRegular";
1417                 for (auto const & opt : allopts) {
1418                         if (opt == "regular")
1419                                 continue;
1420                         if (!xopts.empty())
1421                                 xopts += ", ";
1422                         xopts += opt;
1423                 }
1424                 if (!xopts.empty())
1425                         h_font_typewriter_opts = xopts;
1426                 options.clear();
1427         }
1428
1429         if (name == "sourcecodepro") {
1430                 h_font_typewriter[0] = "ADOBESourceCodePro";
1431                 for (auto const & opt : allopts) {
1432                         if (opt == "osf") {
1433                                 h_font_typewriter_osf = "true";
1434                                 continue;
1435                         }
1436                         if (!xopts.empty())
1437                                 xopts += ", ";
1438                         xopts += opt;
1439                 }
1440                 if (!xopts.empty())
1441                         h_font_typewriter_opts = xopts;
1442                 options.clear();
1443         }
1444
1445         //
1446         // math fonts
1447         //
1448
1449         // By default, we use the package name as LyX font name,
1450         // so this only needs to be reset if these names differ.
1451         if (is_known(name, known_math_font_packages))
1452                 h_font_math[0] = name;
1453
1454         if (name == "newtxmath") {
1455                 if (opts.empty())
1456                         h_font_math[0] = "newtxmath";
1457                 else if (opts == "garamondx")
1458                         h_font_math[0] = "garamondx-ntxm";
1459                 else if (opts == "libertine")
1460                         h_font_math[0] = "libertine-ntxm";
1461                 else if (opts == "minion")
1462                         h_font_math[0] = "minion-ntxm";
1463                 else if (opts == "cochineal")
1464                         h_font_math[0] = "cochineal-ntxm";
1465         }
1466
1467         if (name == "iwona")
1468                 if (opts == "math")
1469                         h_font_math[0] = "iwona-math";
1470
1471         if (name == "kurier")
1472                 if (opts == "math")
1473                         h_font_math[0] = "kurier-math";
1474
1475         // after the detection and handling of special cases, we can remove the
1476         // fonts, otherwise they would appear in the preamble, see bug #7856
1477         if (is_known(name, known_roman_font_packages) || is_known(name, known_sans_font_packages)
1478                 ||      is_known(name, known_typewriter_font_packages) || is_known(name, known_math_font_packages))
1479                 ;
1480         //"On". See the enum Package in BufferParams.h if you thought that "2" should have been "42"
1481         else if (name == "amsmath" || name == "amssymb" || name == "cancel" ||
1482                  name == "esint" || name == "mhchem" || name == "mathdots" ||
1483                  name == "mathtools" || name == "stackrel" ||
1484                  name == "stmaryrd" || name == "undertilde") {
1485                 h_use_packages[name] = "2";
1486                 registerAutomaticallyLoadedPackage(name);
1487         }
1488
1489         else if (name == "babel") {
1490                 h_language_package = "default";
1491                 // One might think we would have to do nothing if babel is loaded
1492                 // without any options to prevent pollution of the preamble with this
1493                 // babel call in every roundtrip.
1494                 // But the user could have defined babel-specific things afterwards. So
1495                 // we need to keep it in the preamble to prevent cases like bug #7861.
1496                 if (!opts.empty()) {
1497                         // check if more than one option was used - used later for inputenc
1498                         if (options.begin() != options.end() - 1)
1499                                 one_language = false;
1500                         // babel takes the last language of the option of its \usepackage
1501                         // call as document language. If there is no such language option, the
1502                         // last language in the documentclass options is used.
1503                         handle_opt(options, known_languages, h_language);
1504                         // translate the babel name to a LyX name
1505                         h_language = babel2lyx(h_language);
1506                         if (h_language == "japanese") {
1507                                 // For Japanese, the encoding isn't indicated in the source
1508                                 // file, and there's really not much we can do. We could
1509                                 // 1) offer a list of possible encodings to choose from, or
1510                                 // 2) determine the encoding of the file by inspecting it.
1511                                 // For the time being, we leave the encoding alone so that
1512                                 // we don't get iconv errors when making a wrong guess, and
1513                                 // we will output a note at the top of the document
1514                                 // explaining what to do.
1515                                 Encoding const * const enc = encodings.fromIconvName(
1516                                         p.getEncoding(), Encoding::japanese, false);
1517                                 if (enc)
1518                                         h_inputencoding = enc->name();
1519                                 is_nonCJKJapanese = true;
1520                                 // in this case babel can be removed from the preamble
1521                                 registerAutomaticallyLoadedPackage("babel");
1522                         } else {
1523                                 // If babel is called with options, LyX puts them by default into the
1524                                 // document class options. This works for most languages, except
1525                                 // for Latvian, Lithuanian, Mongolian, Turkmen and Vietnamese and
1526                                 // perhaps in future others.
1527                                 // Therefore keep the babel call as it is as the user might have
1528                                 // reasons for it.
1529                                 string const babelcall = "\\usepackage[" + opts + "]{babel}\n";
1530                                 if (!contains(h_preamble.str(), babelcall))
1531                                         h_preamble << babelcall;
1532                         }
1533                         delete_opt(options, known_languages);
1534                 } else {
1535                         if (!contains(h_preamble.str(), "\\usepackage{babel}\n"))
1536                                 h_preamble << "\\usepackage{babel}\n";
1537                         explicit_babel = true;
1538                 }
1539         }
1540
1541         else if (name == "polyglossia") {
1542                 h_language_package = "default";
1543                 h_default_output_format = "pdf4";
1544                 h_use_non_tex_fonts = true;
1545                 xetex = true;
1546                 registerAutomaticallyLoadedPackage("xunicode");
1547                 if (h_inputencoding == "auto-legacy")
1548                         p.setEncoding("UTF-8");
1549         }
1550
1551         else if (name == "CJK") {
1552                 // set the encoding to "auto-legacy" because it might be set to "auto-legacy-plain" by the babel handling
1553                 // and this would not be correct for CJK
1554                 if (h_inputencoding == "auto-legacy-plain")
1555                         h_inputencoding = "auto-legacy";
1556                 registerAutomaticallyLoadedPackage("CJK");
1557         }
1558
1559         else if (name == "CJKutf8") {
1560                 h_inputencoding = "utf8-cjk";
1561                 p.setEncoding("UTF-8");
1562                 registerAutomaticallyLoadedPackage("CJKutf8");
1563         }
1564
1565         else if (name == "fontenc") {
1566                 h_fontencoding = getStringFromVector(options, ",");
1567                 options.clear();
1568         }
1569
1570         else if (name == "inputenc" || name == "luainputenc") {
1571                 // h_inputencoding is only set when there is not more than one
1572                 // inputenc option because otherwise h_inputencoding must be
1573                 // set to "auto-legacy" (the default encodings of the document's languages)
1574                 // Therefore check that exactly one option is passed to inputenc.
1575                 // It is also only set when there is not more than one babel
1576                 // language option.
1577                 if (!options.empty()) {
1578                         string const encoding = options.back();
1579                         Encoding const * const enc = encodings.fromLaTeXName(
1580                                 encoding, Encoding::inputenc, true);
1581                         if (!enc) {
1582                                 if (!detectEncoding)
1583                                         cerr << "Unknown encoding " << encoding
1584                                              << ". Ignoring." << std::endl;
1585                         } else {
1586                                 if (!enc->unsafe() && options.size() == 1 && one_language == true)
1587                                         h_inputencoding = enc->name();
1588                                 p.setEncoding(enc->iconvName());
1589                         }
1590                         options.clear();
1591                 }
1592         }
1593
1594         else if (name == "srcltx") {
1595                 h_output_sync = "1";
1596                 if (!opts.empty()) {
1597                         h_output_sync_macro = "\\usepackage[" + opts + "]{srcltx}";
1598                         options.clear();
1599                 } else
1600                         h_output_sync_macro = "\\usepackage{srcltx}";
1601         }
1602
1603         else if (is_known(name, known_old_language_packages)) {
1604                 // known language packages from the times before babel
1605                 // if they are found and not also babel, they will be used as
1606                 // custom language package
1607                 h_language_package = "\\usepackage{" + name + "}";
1608         }
1609
1610         else if (name == "lyxskak") {
1611                 // ignore this and its options
1612                 const char * const o[] = {"ps", "mover", 0};
1613                 delete_opt(options, o);
1614         }
1615
1616         else if (is_known(name, known_lyx_packages) && options.empty()) {
1617                 if (name == "splitidx")
1618                         h_use_indices = "true";
1619                 else if (name == "minted")
1620                         h_use_minted = true;
1621                 else if (name == "refstyle")
1622                         h_use_refstyle = true;
1623                 else if (name == "prettyref")
1624                         h_use_refstyle = false;
1625                 if (!in_lyx_preamble) {
1626                         h_preamble << package_beg_sep << name
1627                                    << package_mid_sep << "\\usepackage{"
1628                                    << name << '}';
1629                         if (p.next_token().cat() == catNewline ||
1630                             (p.next_token().cat() == catSpace &&
1631                              p.next_next_token().cat() == catNewline))
1632                                 h_preamble << '\n';
1633                         h_preamble << package_end_sep;
1634                 }
1635         }
1636
1637         else if (name == "geometry")
1638                 handle_geometry(options);
1639
1640         else if (name == "subfig")
1641                 ; // ignore this FIXME: Use the package separator mechanism instead
1642
1643         else if (char const * const * where = is_known(name, known_languages))
1644                 h_language = known_coded_languages[where - known_languages];
1645
1646         else if (name == "natbib") {
1647                 h_biblio_style = "plainnat";
1648                 h_cite_engine = "natbib";
1649                 h_cite_engine_type = "authoryear";
1650                 vector<string>::iterator it =
1651                         find(options.begin(), options.end(), "authoryear");
1652                 if (it != options.end())
1653                         options.erase(it);
1654                 else {
1655                         it = find(options.begin(), options.end(), "numbers");
1656                         if (it != options.end()) {
1657                                 h_cite_engine_type = "numerical";
1658                                 options.erase(it);
1659                         }
1660                 }
1661                 if (!options.empty())
1662                         h_biblio_options = join(options, ",");
1663         }
1664
1665         else if (name == "biblatex") {
1666                 h_biblio_style = "plainnat";
1667                 h_cite_engine = "biblatex";
1668                 h_cite_engine_type = "authoryear";
1669                 string opt;
1670                 vector<string>::iterator it =
1671                         find(options.begin(), options.end(), "natbib");
1672                 if (it != options.end()) {
1673                         options.erase(it);
1674                         h_cite_engine = "biblatex-natbib";
1675                 } else {
1676                         opt = process_keyval_opt(options, "natbib");
1677                         if (opt == "true")
1678                                 h_cite_engine = "biblatex-natbib";
1679                 }
1680                 opt = process_keyval_opt(options, "style");
1681                 if (!opt.empty()) {
1682                         h_biblatex_citestyle = opt;
1683                         h_biblatex_bibstyle = opt;
1684                 } else {
1685                         opt = process_keyval_opt(options, "citestyle");
1686                         if (!opt.empty())
1687                                 h_biblatex_citestyle = opt;
1688                         opt = process_keyval_opt(options, "bibstyle");
1689                         if (!opt.empty())
1690                                 h_biblatex_bibstyle = opt;
1691                 }
1692                 opt = process_keyval_opt(options, "refsection");
1693                 if (!opt.empty()) {
1694                         if (opt == "none" || opt == "part"
1695                             || opt == "chapter" || opt == "section"
1696                             || opt == "subsection")
1697                                 h_multibib = opt;
1698                         else
1699                                 cerr << "Ignoring unkown refesection value '"
1700                                      << opt << "'.";
1701                 }
1702                 opt = process_keyval_opt(options, "bibencoding");
1703                 if (!opt.empty())
1704                         bibencoding = opt;
1705                 if (!options.empty()) {
1706                         h_biblio_options = join(options, ",");
1707                         options.clear();
1708                 }
1709         }
1710
1711         else if (name == "jurabib") {
1712                 h_biblio_style = "jurabib";
1713                 h_cite_engine = "jurabib";
1714                 h_cite_engine_type = "authoryear";
1715                 if (!options.empty())
1716                         h_biblio_options = join(options, ",");
1717         }
1718
1719         else if (name == "bibtopic")
1720                 h_use_bibtopic = "true";
1721
1722         else if (name == "chapterbib")
1723                 h_multibib = "child";
1724
1725         else if (name == "hyperref")
1726                 handle_hyperref(options);
1727
1728         else if (name == "algorithm2e") {
1729                 // Load "algorithm2e" module
1730                 addModule("algorithm2e");
1731                 // Add the package options to the global document options
1732                 if (!options.empty()) {
1733                         if (h_options.empty())
1734                                 h_options = join(options, ",");
1735                         else
1736                                 h_options += ',' + join(options, ",");
1737                 }
1738         }
1739         else if (name == "microtype") {
1740                 //we internally support only microtype without params
1741                 if (options.empty())
1742                         h_use_microtype = "true";
1743                 else
1744                         h_preamble << "\\usepackage[" << opts << "]{microtype}";
1745         }
1746
1747         else if (name == "lineno") {
1748                 h_use_lineno = "true";
1749                 if (!options.empty()) {
1750                         h_lineno_options = join(options, ",");
1751                         options.clear();
1752                 }
1753         }
1754
1755         else if (!in_lyx_preamble) {
1756                 if (options.empty())
1757                         h_preamble << "\\usepackage{" << name << '}';
1758                 else {
1759                         h_preamble << "\\usepackage[" << opts << "]{"
1760                                    << name << '}';
1761                         options.clear();
1762                 }
1763                 if (p.next_token().cat() == catNewline ||
1764                     (p.next_token().cat() == catSpace &&
1765                      p.next_next_token().cat() == catNewline))
1766                         h_preamble << '\n';
1767         }
1768
1769         // We need to do something with the options...
1770         if (!options.empty() && !detectEncoding)
1771                 cerr << "Ignoring options '" << join(options, ",")
1772                      << "' of package " << name << '.' << endl;
1773
1774         // remove the whitespace
1775         p.skip_spaces();
1776 }
1777
1778
1779 void Preamble::handle_if(Parser & p, bool in_lyx_preamble)
1780 {
1781         while (p.good()) {
1782                 Token t = p.get_token();
1783                 if (t.cat() == catEscape &&
1784                     is_known(t.cs(), known_if_commands))
1785                         handle_if(p, in_lyx_preamble);
1786                 else {
1787                         if (!in_lyx_preamble)
1788                                 h_preamble << t.asInput();
1789                         if (t.cat() == catEscape && t.cs() == "fi")
1790                                 return;
1791                 }
1792         }
1793 }
1794
1795
1796 bool Preamble::writeLyXHeader(ostream & os, bool subdoc, string const & outfiledir)
1797 {
1798         if (contains(h_float_placement, "H"))
1799                 registerAutomaticallyLoadedPackage("float");
1800         if (h_spacing != "single" && h_spacing != "default")
1801                 registerAutomaticallyLoadedPackage("setspace");
1802         if (h_use_packages["amsmath"] == "2") {
1803                 // amsbsy and amstext are already provided by amsmath
1804                 registerAutomaticallyLoadedPackage("amsbsy");
1805                 registerAutomaticallyLoadedPackage("amstext");
1806         }
1807
1808         // output the LyX file settings
1809         // Important: Keep the version formatting in sync with LyX and
1810         //            lyx2lyx (bug 7951)
1811         string const origin = roundtripMode() ? "roundtrip" : outfiledir;
1812         os << "#LyX file created by tex2lyx " << lyx_version_major << '.'
1813            << lyx_version_minor << '\n'
1814            << "\\lyxformat " << LYX_FORMAT << '\n'
1815            << "\\begin_document\n"
1816            << "\\begin_header\n"
1817            << "\\save_transient_properties " << h_save_transient_properties << "\n"
1818            << "\\origin " << origin << "\n"
1819            << "\\textclass " << h_textclass << "\n";
1820         string const raw = subdoc ? empty_string() : h_preamble.str();
1821         if (!raw.empty()) {
1822                 os << "\\begin_preamble\n";
1823                 for (string::size_type i = 0; i < raw.size(); ++i) {
1824                         if (raw[i] == package_beg_sep) {
1825                                 // Here follows some package loading code that
1826                                 // must be skipped if the package is loaded
1827                                 // automatically.
1828                                 string::size_type j = raw.find(package_mid_sep, i);
1829                                 if (j == string::npos)
1830                                         return false;
1831                                 string::size_type k = raw.find(package_end_sep, j);
1832                                 if (k == string::npos)
1833                                         return false;
1834                                 string const package = raw.substr(i + 1, j - i - 1);
1835                                 string const replacement = raw.substr(j + 1, k - j - 1);
1836                                 if (auto_packages.find(package) == auto_packages.end())
1837                                         os << replacement;
1838                                 i = k;
1839                         } else
1840                                 os.put(raw[i]);
1841                 }
1842                 os << "\n\\end_preamble\n";
1843         }
1844         if (!h_options.empty())
1845                 os << "\\options " << h_options << "\n";
1846         os << "\\use_default_options " << h_use_default_options << "\n";
1847         if (!used_modules.empty()) {
1848                 os << "\\begin_modules\n";
1849                 vector<string>::const_iterator const end = used_modules.end();
1850                 vector<string>::const_iterator it = used_modules.begin();
1851                 for (; it != end; ++it)
1852                         os << *it << '\n';
1853                 os << "\\end_modules\n";
1854         }
1855         if (!h_includeonlys.empty()) {
1856                 os << "\\begin_includeonly\n";
1857                 for (auto const & iofile : h_includeonlys)
1858                         os << iofile << '\n';
1859                 os << "\\end_includeonly\n";
1860         }
1861         os << "\\maintain_unincluded_children " << h_maintain_unincluded_children << "\n"
1862            << "\\language " << h_language << "\n"
1863            << "\\language_package " << h_language_package << "\n"
1864            << "\\inputencoding " << h_inputencoding << "\n"
1865            << "\\fontencoding " << h_fontencoding << "\n"
1866            << "\\font_roman \"" << h_font_roman[0]
1867            << "\" \"" << h_font_roman[1] << "\"\n"
1868            << "\\font_sans \"" << h_font_sans[0] << "\" \"" << h_font_sans[1] << "\"\n"
1869            << "\\font_typewriter \"" << h_font_typewriter[0]
1870            << "\" \"" << h_font_typewriter[1] << "\"\n"
1871            << "\\font_math \"" << h_font_math[0] << "\" \"" << h_font_math[1] << "\"\n"
1872            << "\\font_default_family " << h_font_default_family << "\n"
1873            << "\\use_non_tex_fonts " << (h_use_non_tex_fonts ? "true" : "false") << '\n'
1874            << "\\font_sc " << h_font_sc << "\n"
1875            << "\\font_roman_osf " << h_font_roman_osf << "\n"
1876            << "\\font_sans_osf " << h_font_sans_osf << "\n"
1877            << "\\font_typewriter_osf " << h_font_typewriter_osf << "\n";
1878         if (!h_font_roman_opts.empty())
1879                 os << "\\font_roman_opts \"" << h_font_roman_opts << "\"" << '\n';
1880         os << "\\font_sf_scale " << h_font_sf_scale[0]
1881            << ' ' << h_font_sf_scale[1] << '\n';
1882         if (!h_font_sans_opts.empty())
1883                 os << "\\font_sans_opts \"" << h_font_sans_opts << "\"" << '\n';
1884         os << "\\font_tt_scale " << h_font_tt_scale[0]
1885            << ' ' << h_font_tt_scale[1] << '\n';
1886         if (!h_font_cjk.empty())
1887                 os << "\\font_cjk " << h_font_cjk << '\n';
1888         if (!h_font_typewriter_opts.empty())
1889                 os << "\\font_typewriter_opts \"" << h_font_typewriter_opts << "\"" << '\n';
1890         os << "\\use_microtype " << h_use_microtype << '\n'
1891            << "\\use_dash_ligatures " << h_use_dash_ligatures << '\n'
1892            << "\\graphics " << h_graphics << '\n'
1893            << "\\default_output_format " << h_default_output_format << "\n"
1894            << "\\output_sync " << h_output_sync << "\n";
1895         if (h_output_sync == "1")
1896                 os << "\\output_sync_macro \"" << h_output_sync_macro << "\"\n";
1897         os << "\\bibtex_command " << h_bibtex_command << "\n"
1898            << "\\index_command " << h_index_command << "\n";
1899         if (!h_float_placement.empty())
1900                 os << "\\float_placement " << h_float_placement << "\n";
1901         os << "\\paperfontsize " << h_paperfontsize << "\n"
1902            << "\\spacing " << h_spacing << "\n"
1903            << "\\use_hyperref " << h_use_hyperref << '\n';
1904         if (h_use_hyperref == "true") {
1905                 if (!h_pdf_title.empty())
1906                         os << "\\pdf_title " << Lexer::quoteString(h_pdf_title) << '\n';
1907                 if (!h_pdf_author.empty())
1908                         os << "\\pdf_author " << Lexer::quoteString(h_pdf_author) << '\n';
1909                 if (!h_pdf_subject.empty())
1910                         os << "\\pdf_subject " << Lexer::quoteString(h_pdf_subject) << '\n';
1911                 if (!h_pdf_keywords.empty())
1912                         os << "\\pdf_keywords " << Lexer::quoteString(h_pdf_keywords) << '\n';
1913                 os << "\\pdf_bookmarks " << h_pdf_bookmarks << "\n"
1914                       "\\pdf_bookmarksnumbered " << h_pdf_bookmarksnumbered << "\n"
1915                       "\\pdf_bookmarksopen " << h_pdf_bookmarksopen << "\n"
1916                       "\\pdf_bookmarksopenlevel " << h_pdf_bookmarksopenlevel << "\n"
1917                       "\\pdf_breaklinks " << h_pdf_breaklinks << "\n"
1918                       "\\pdf_pdfborder " << h_pdf_pdfborder << "\n"
1919                       "\\pdf_colorlinks " << h_pdf_colorlinks << "\n"
1920                       "\\pdf_backref " << h_pdf_backref << "\n"
1921                       "\\pdf_pdfusetitle " << h_pdf_pdfusetitle << '\n';
1922                 if (!h_pdf_pagemode.empty())
1923                         os << "\\pdf_pagemode " << h_pdf_pagemode << '\n';
1924                 if (!h_pdf_quoted_options.empty())
1925                         os << "\\pdf_quoted_options " << Lexer::quoteString(h_pdf_quoted_options) << '\n';
1926         }
1927         os << "\\papersize " << h_papersize << "\n"
1928            << "\\use_geometry " << h_use_geometry << '\n';
1929         for (map<string, string>::const_iterator it = h_use_packages.begin();
1930              it != h_use_packages.end(); ++it)
1931                 os << "\\use_package " << it->first << ' ' << it->second << '\n';
1932         os << "\\cite_engine " << h_cite_engine << '\n'
1933            << "\\cite_engine_type " << h_cite_engine_type << '\n'
1934            << "\\biblio_style " << h_biblio_style << "\n"
1935            << "\\use_bibtopic " << h_use_bibtopic << "\n";
1936         if (!h_biblio_options.empty())
1937                 os << "\\biblio_options " << h_biblio_options << "\n";
1938         if (!h_biblatex_bibstyle.empty())
1939                 os << "\\biblatex_bibstyle " << h_biblatex_bibstyle << "\n";
1940         if (!h_biblatex_citestyle.empty())
1941                 os << "\\biblatex_citestyle " << h_biblatex_citestyle << "\n";
1942         if (!h_multibib.empty())
1943                 os << "\\multibib " << h_multibib << "\n";
1944         os << "\\use_indices " << h_use_indices << "\n"
1945            << "\\paperorientation " << h_paperorientation << '\n'
1946            << "\\suppress_date " << h_suppress_date << '\n'
1947            << "\\justification " << h_justification << '\n'
1948            << "\\use_refstyle " << h_use_refstyle << '\n'
1949            << "\\use_minted " << h_use_minted << '\n'
1950            << "\\use_lineno " << h_use_lineno << '\n';
1951         if (!h_lineno_options.empty())
1952                 os << "\\lineno_options " << h_lineno_options << '\n';
1953         if (!h_fontcolor.empty())
1954                 os << "\\fontcolor " << h_fontcolor << '\n';
1955         if (!h_notefontcolor.empty())
1956                 os << "\\notefontcolor " << h_notefontcolor << '\n';
1957         if (!h_backgroundcolor.empty())
1958                 os << "\\backgroundcolor " << h_backgroundcolor << '\n';
1959         if (!h_boxbgcolor.empty())
1960                 os << "\\boxbgcolor " << h_boxbgcolor << '\n';
1961         if (index_number != 0)
1962                 for (int i = 0; i < index_number; i++) {
1963                         os << "\\index " << h_index[i] << '\n'
1964                            << "\\shortcut " << h_shortcut[i] << '\n'
1965                            << "\\color " << h_color << '\n'
1966                            << "\\end_index\n";
1967                 }
1968         else {
1969                 os << "\\index " << h_index[0] << '\n'
1970                    << "\\shortcut " << h_shortcut[0] << '\n'
1971                    << "\\color " << h_color << '\n'
1972                    << "\\end_index\n";
1973         }
1974         os << h_margins
1975            << "\\secnumdepth " << h_secnumdepth << "\n"
1976            << "\\tocdepth " << h_tocdepth << "\n"
1977            << "\\paragraph_separation " << h_paragraph_separation << "\n";
1978         if (h_paragraph_separation == "skip")
1979                 os << "\\defskip " << h_defskip << "\n";
1980         else
1981                 os << "\\paragraph_indentation " << h_paragraph_indentation << "\n";
1982         os << "\\is_math_indent " << h_is_mathindent << "\n";
1983         if (!h_mathindentation.empty())
1984                 os << "\\math_indentation " << h_mathindentation << "\n";
1985         os << "\\math_numbering_side " << h_math_numbering_side << "\n";
1986         os << "\\quotes_style " << h_quotes_style << "\n"
1987            << "\\dynamic_quotes " << h_dynamic_quotes << "\n"
1988            << "\\papercolumns " << h_papercolumns << "\n"
1989            << "\\papersides " << h_papersides << "\n"
1990            << "\\paperpagestyle " << h_paperpagestyle << "\n";
1991         if (!h_listings_params.empty())
1992                 os << "\\listings_params " << h_listings_params << "\n";
1993         os << "\\tracking_changes " << h_tracking_changes << "\n"
1994            << "\\output_changes " << h_output_changes << "\n"
1995            << "\\html_math_output " << h_html_math_output << "\n"
1996            << "\\html_css_as_file " << h_html_css_as_file << "\n"
1997            << "\\html_be_strict " << h_html_be_strict << "\n"
1998            << authors_
1999            << "\\end_header\n\n"
2000            << "\\begin_body\n";
2001         return true;
2002 }
2003
2004
2005 void Preamble::parse(Parser & p, string const & forceclass,
2006                      TeX2LyXDocClass & tc)
2007 {
2008         // initialize fixed types
2009         special_columns_['D'] = 3;
2010         parse(p, forceclass, false, tc);
2011 }
2012
2013
2014 void Preamble::parse(Parser & p, string const & forceclass,
2015                      bool detectEncoding, TeX2LyXDocClass & tc)
2016 {
2017         bool is_full_document = false;
2018         bool is_lyx_file = false;
2019         bool in_lyx_preamble = false;
2020
2021         // determine whether this is a full document or a fragment for inclusion
2022         while (p.good()) {
2023                 Token const & t = p.get_token();
2024
2025                 if (t.cat() == catEscape && t.cs() == "documentclass") {
2026                         is_full_document = true;
2027                         break;
2028                 }
2029         }
2030         p.reset();
2031
2032         if (detectEncoding && !is_full_document)
2033                 return;
2034
2035         while (is_full_document && p.good()) {
2036                 if (detectEncoding && h_inputencoding != "auto-legacy" &&
2037                     h_inputencoding != "auto-legacy-plain")
2038                         return;
2039
2040                 Token const & t = p.get_token();
2041
2042 #ifdef FILEDEBUG
2043                 if (!detectEncoding)
2044                         cerr << "t: " << t << '\n';
2045 #endif
2046
2047                 //
2048                 // cat codes
2049                 //
2050                 if (!in_lyx_preamble &&
2051                     (t.cat() == catLetter ||
2052                      t.cat() == catSuper ||
2053                      t.cat() == catSub ||
2054                      t.cat() == catOther ||
2055                      t.cat() == catMath ||
2056                      t.cat() == catActive ||
2057                      t.cat() == catBegin ||
2058                      t.cat() == catEnd ||
2059                      t.cat() == catAlign ||
2060                      t.cat() == catParameter)) {
2061                         h_preamble << t.cs();
2062                         continue;
2063                 }
2064
2065                 if (!in_lyx_preamble &&
2066                     (t.cat() == catSpace || t.cat() == catNewline)) {
2067                         h_preamble << t.asInput();
2068                         continue;
2069                 }
2070
2071                 if (t.cat() == catComment) {
2072                         static regex const islyxfile("%% LyX .* created this file");
2073                         static regex const usercommands("User specified LaTeX commands");
2074
2075                         string const comment = t.asInput();
2076
2077                         // magically switch encoding default if it looks like XeLaTeX
2078                         static string const magicXeLaTeX =
2079                                 "% This document must be compiled with XeLaTeX ";
2080                         if (comment.size() > magicXeLaTeX.size()
2081                                   && comment.substr(0, magicXeLaTeX.size()) == magicXeLaTeX
2082                                   && h_inputencoding == "auto-legacy") {
2083                                 if (!detectEncoding)
2084                                         cerr << "XeLaTeX comment found, switching to UTF8\n";
2085                                 h_inputencoding = "utf8";
2086                         }
2087                         smatch sub;
2088                         if (regex_search(comment, sub, islyxfile)) {
2089                                 is_lyx_file = true;
2090                                 in_lyx_preamble = true;
2091                         } else if (is_lyx_file
2092                                    && regex_search(comment, sub, usercommands))
2093                                 in_lyx_preamble = false;
2094                         else if (!in_lyx_preamble)
2095                                 h_preamble << t.asInput();
2096                         continue;
2097                 }
2098
2099                 if (t.cs() == "PassOptionsToPackage") {
2100                         string const poptions = p.getArg('{', '}');
2101                         string const package = p.verbatim_item();
2102                         extra_package_options_.insert(make_pair(package, poptions));
2103                         continue;
2104                 }
2105
2106                 if (t.cs() == "pagestyle") {
2107                         h_paperpagestyle = p.verbatim_item();
2108                         continue;
2109                 }
2110
2111                 if (t.cs() == "setdefaultlanguage") {
2112                         xetex = true;
2113                         // We don't yet care about non-language variant options
2114                         // because LyX doesn't support this yet, see bug #8214
2115                         if (p.hasOpt()) {
2116                                 string langopts = p.getOpt();
2117                                 // check if the option contains a variant, if yes, extract it
2118                                 string::size_type pos_var = langopts.find("variant");
2119                                 string::size_type i = langopts.find(',', pos_var);
2120                                 string::size_type k = langopts.find('=', pos_var);
2121                                 if (pos_var != string::npos){
2122                                         string variant;
2123                                         if (i == string::npos)
2124                                                 variant = langopts.substr(k + 1, langopts.length() - k - 2);
2125                                         else
2126                                                 variant = langopts.substr(k + 1, i - k - 1);
2127                                         h_language = variant;
2128                                 }
2129                                 p.verbatim_item();
2130                         } else
2131                                 h_language = p.verbatim_item();
2132                         //finally translate the poyglossia name to a LyX name
2133                         h_language = polyglossia2lyx(h_language);
2134                         continue;
2135                 }
2136
2137                 if (t.cs() == "setotherlanguage") {
2138                         // We don't yet care about the option because LyX doesn't
2139                         // support this yet, see bug #8214
2140                         p.hasOpt() ? p.getOpt() : string();
2141                         p.verbatim_item();
2142                         continue;
2143                 }
2144
2145                 if (t.cs() == "setmainfont") {
2146                         string fontopts = p.hasOpt() ? p.getArg('[', ']') : string();
2147                         h_font_roman[1] = p.getArg('{', '}');
2148                         if (!fontopts.empty()) {
2149                                 vector<string> opts = getVectorFromString(fontopts);
2150                                 fontopts.clear();
2151                                 for (auto const & opt : opts) {
2152                                         if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2153                                                 // ignore
2154                                                 continue;
2155                                         if (!fontopts.empty())
2156                                                 fontopts += ", ";
2157                                         fontopts += opt;
2158                                 }
2159                                 h_font_roman_opts = fontopts;
2160                         }
2161                         continue;
2162                 }
2163
2164                 if (t.cs() == "setsansfont" || t.cs() == "setmonofont") {
2165                         // LyX currently only supports the scale option
2166                         string scale, fontopts;
2167                         if (p.hasOpt()) {
2168                                 fontopts = p.getArg('[', ']');
2169                                 if (!fontopts.empty()) {
2170                                         vector<string> opts = getVectorFromString(fontopts);
2171                                         fontopts.clear();
2172                                         for (auto const & opt : opts) {
2173                                                 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2174                                                         // ignore
2175                                                         continue;
2176                                                 if (prefixIs(opt, "Scale=")) {
2177                                                         scale_as_percentage(opt, scale);
2178                                                         continue;
2179                                                 }
2180                                                 if (!fontopts.empty())
2181                                                         fontopts += ", ";
2182                                                 fontopts += opt;
2183                                         }
2184                                 }
2185                         }
2186                         if (t.cs() == "setsansfont") {
2187                                 if (!scale.empty())
2188                                         h_font_sf_scale[1] = scale;
2189                                 h_font_sans[1] = p.getArg('{', '}');
2190                                 if (!fontopts.empty())
2191                                         h_font_sans_opts = fontopts;
2192                         } else {
2193                                 if (!scale.empty())
2194                                         h_font_tt_scale[1] = scale;
2195                                 h_font_typewriter[1] = p.getArg('{', '}');
2196                                 if (!fontopts.empty())
2197                                         h_font_typewriter_opts = fontopts;
2198                         }
2199                         continue;
2200                 }
2201
2202                 if (t.cs() == "babelfont") {
2203                         xetex = true;
2204                         h_use_non_tex_fonts = true;
2205                         h_language_package = "babel";
2206                         if (h_inputencoding == "auto-legacy")
2207                         p.setEncoding("UTF-8");
2208                         // we don't care about the lang option
2209                         string const lang = p.hasOpt() ? p.getArg('[', ']') : string();
2210                         string const family = p.getArg('{', '}');
2211                         string fontopts = p.hasOpt() ? p.getArg('[', ']') : string();
2212                         string const fontname = p.getArg('{', '}');
2213                         if (lang.empty() && family == "rm") {
2214                                 h_font_roman[1] = fontname;
2215                                 if (!fontopts.empty()) {
2216                                         vector<string> opts = getVectorFromString(fontopts);
2217                                         fontopts.clear();
2218                                         for (auto const & opt : opts) {
2219                                                 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2220                                                         // ignore
2221                                                         continue;
2222                                                 if (!fontopts.empty())
2223                                                         fontopts += ", ";
2224                                                 fontopts += opt;
2225                                         }
2226                                         h_font_roman_opts = fontopts;
2227                                 }
2228                                 continue;
2229                         } else if (lang.empty() && (family == "sf" || family == "tt")) {
2230                                 string scale;
2231                                 if (!fontopts.empty()) {
2232                                         vector<string> opts = getVectorFromString(fontopts);
2233                                         fontopts.clear();
2234                                         for (auto const & opt : opts) {
2235                                                 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2236                                                         // ignore
2237                                                         continue;
2238                                                 if (prefixIs(opt, "Scale=")) {
2239                                                         scale_as_percentage(opt, scale);
2240                                                         continue;
2241                                                 }
2242                                                 if (!fontopts.empty())
2243                                                         fontopts += ", ";
2244                                                 fontopts += opt;
2245                                         }
2246                                 }
2247                                 if (family == "sf") {
2248                                         if (!scale.empty())
2249                                                 h_font_sf_scale[1] = scale;
2250                                         h_font_sans[1] = fontname;
2251                                         if (!fontopts.empty())
2252                                                 h_font_sans_opts = fontopts;
2253                                 } else {
2254                                         if (!scale.empty())
2255                                                 h_font_tt_scale[1] = scale;
2256                                         h_font_typewriter[1] = fontname;
2257                                         if (!fontopts.empty())
2258                                                 h_font_typewriter_opts = fontopts;
2259                                 }
2260                                 continue;
2261                         } else {
2262                                 // not rm, sf or tt or lang specific
2263                                 h_preamble << '\\' << t.cs();
2264                                 if (!lang.empty())
2265                                         h_preamble << '[' << lang << ']';
2266                                 h_preamble << '{' << family << '}';
2267                                 if (!fontopts.empty())
2268                                         h_preamble << '[' << fontopts << ']';
2269                                 h_preamble << '{' << fontname << '}' << '\n';
2270                                 continue;
2271                         }
2272                 }
2273
2274                 if (t.cs() == "date") {
2275                         string argument = p.getArg('{', '}');
2276                         if (argument.empty())
2277                                 h_suppress_date = "true";
2278                         else
2279                                 h_preamble << t.asInput() << '{' << argument << '}';
2280                         continue;
2281                 }
2282
2283                 if (t.cs() == "color") {
2284                         string const space =
2285                                 (p.hasOpt() ? p.getOpt() : string());
2286                         string argument = p.getArg('{', '}');
2287                         // check the case that a standard color is used
2288                         if (space.empty() && is_known(argument, known_basic_colors)) {
2289                                 h_fontcolor = rgbcolor2code(argument);
2290                                 registerAutomaticallyLoadedPackage("color");
2291                         } else if (space.empty() && argument == "document_fontcolor")
2292                                 registerAutomaticallyLoadedPackage("color");
2293                         // check the case that LyX's document_fontcolor is defined
2294                         // but not used for \color
2295                         else {
2296                                 h_preamble << t.asInput();
2297                                 if (!space.empty())
2298                                         h_preamble << space;
2299                                 h_preamble << '{' << argument << '}';
2300                                 // the color might already be set because \definecolor
2301                                 // is parsed before this
2302                                 h_fontcolor = "";
2303                         }
2304                         continue;
2305                 }
2306
2307                 if (t.cs() == "pagecolor") {
2308                         string argument = p.getArg('{', '}');
2309                         // check the case that a standard color is used
2310                         if (is_known(argument, known_basic_colors)) {
2311                                 h_backgroundcolor = rgbcolor2code(argument);
2312                         } else if (argument == "page_backgroundcolor")
2313                                 registerAutomaticallyLoadedPackage("color");
2314                         // check the case that LyX's page_backgroundcolor is defined
2315                         // but not used for \pagecolor
2316                         else {
2317                                 h_preamble << t.asInput() << '{' << argument << '}';
2318                                 // the color might already be set because \definecolor
2319                                 // is parsed before this
2320                                 h_backgroundcolor = "";
2321                         }
2322                         continue;
2323                 }
2324
2325                 if (t.cs() == "makeatletter") {
2326                         // LyX takes care of this
2327                         p.setCatcode('@', catLetter);
2328                         continue;
2329                 }
2330
2331                 if (t.cs() == "makeatother") {
2332                         // LyX takes care of this
2333                         p.setCatcode('@', catOther);
2334                         continue;
2335                 }
2336
2337                 if (t.cs() == "makeindex") {
2338                         // LyX will re-add this if a print index command is found
2339                         p.skip_spaces();
2340                         continue;
2341                 }
2342
2343                 if (t.cs() == "newindex") {
2344                         string const indexname = p.getArg('[', ']');
2345                         string const shortcut = p.verbatim_item();
2346                         if (!indexname.empty())
2347                                 h_index[index_number] = indexname;
2348                         else
2349                                 h_index[index_number] = shortcut;
2350                         h_shortcut[index_number] = shortcut;
2351                         index_number += 1;
2352                         p.skip_spaces();
2353                         continue;
2354                 }
2355
2356                 if (t.cs() == "addbibresource") {
2357                         string const options =  p.getArg('[', ']');
2358                         string const arg = removeExtension(p.getArg('{', '}'));
2359                         if (!options.empty()) {
2360                                 // check if the option contains a bibencoding, if yes, extract it
2361                                 string::size_type pos = options.find("bibencoding=");
2362                                 string encoding;
2363                                 if (pos != string::npos) {
2364                                         string::size_type i = options.find(',', pos);
2365                                         if (i == string::npos)
2366                                                 encoding = options.substr(pos + 1);
2367                                         else
2368                                                 encoding = options.substr(pos, i - pos);
2369                                         pos = encoding.find('=');
2370                                         if (pos == string::npos)
2371                                                 encoding.clear();
2372                                         else
2373                                                 encoding = encoding.substr(pos + 1);
2374                                 }
2375                                 if (!encoding.empty())
2376                                         biblatex_encodings.push_back(normalize_filename(arg) + ' ' + encoding);
2377                         }
2378                         biblatex_bibliographies.push_back(arg);
2379                         continue;
2380                 }
2381
2382                 if (t.cs() == "bibliography") {
2383                         vector<string> bibs = getVectorFromString(p.getArg('{', '}'));
2384                         biblatex_bibliographies.insert(biblatex_bibliographies.end(), bibs.begin(), bibs.end());
2385                         continue;
2386                 }
2387
2388                 if (t.cs() == "RS@ifundefined") {
2389                         string const name = p.verbatim_item();
2390                         string const body1 = p.verbatim_item();
2391                         string const body2 = p.verbatim_item();
2392                         // only non-lyxspecific stuff
2393                         if (in_lyx_preamble &&
2394                             (name == "subsecref" || name == "thmref" || name == "lemref"))
2395                                 p.skip_spaces();
2396                         else {
2397                                 ostringstream ss;
2398                                 ss << '\\' << t.cs();
2399                                 ss << '{' << name << '}'
2400                                    << '{' << body1 << '}'
2401                                    << '{' << body2 << '}';
2402                                 h_preamble << ss.str();
2403                         }
2404                         continue;
2405                 }
2406
2407                 if (t.cs() == "AtBeginDocument") {
2408                         string const name = p.verbatim_item();
2409                         // only non-lyxspecific stuff
2410                         if (in_lyx_preamble &&
2411                             (name == "\\providecommand\\partref[1]{\\ref{part:#1}}"
2412                                 || name == "\\providecommand\\chapref[1]{\\ref{chap:#1}}"
2413                                 || name == "\\providecommand\\secref[1]{\\ref{sec:#1}}"
2414                                 || name == "\\providecommand\\subsecref[1]{\\ref{subsec:#1}}"
2415                                 || name == "\\providecommand\\parref[1]{\\ref{par:#1}}"
2416                                 || name == "\\providecommand\\figref[1]{\\ref{fig:#1}}"
2417                                 || name == "\\providecommand\\tabref[1]{\\ref{tab:#1}}"
2418                                 || name == "\\providecommand\\algref[1]{\\ref{alg:#1}}"
2419                                 || name == "\\providecommand\\fnref[1]{\\ref{fn:#1}}"
2420                                 || name == "\\providecommand\\enuref[1]{\\ref{enu:#1}}"
2421                                 || name == "\\providecommand\\eqref[1]{\\ref{eq:#1}}"
2422                                 || name == "\\providecommand\\lemref[1]{\\ref{lem:#1}}"
2423                                 || name == "\\providecommand\\thmref[1]{\\ref{thm:#1}}"
2424                                 || name == "\\providecommand\\corref[1]{\\ref{cor:#1}}"
2425                                 || name == "\\providecommand\\propref[1]{\\ref{prop:#1}}"))
2426                                 p.skip_spaces();
2427                         else {
2428                                 ostringstream ss;
2429                                 ss << '\\' << t.cs();
2430                                 ss << '{' << name << '}';
2431                                 h_preamble << ss.str();
2432                         }
2433                         continue;
2434                 }
2435
2436                 if (t.cs() == "newcommand" || t.cs() == "newcommandx"
2437                     || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
2438                     || t.cs() == "providecommand" || t.cs() == "providecommandx"
2439                     || t.cs() == "DeclareRobustCommand"
2440                     || t.cs() == "DeclareRobustCommandx"
2441                     || t.cs() == "ProvideTextCommandDefault"
2442                     || t.cs() == "DeclareMathAccent") {
2443                         bool star = false;
2444                         if (p.next_token().character() == '*') {
2445                                 p.get_token();
2446                                 star = true;
2447                         }
2448                         string const name = p.verbatim_item();
2449                         string const opt1 = p.getFullOpt();
2450                         string const opt2 = p.getFullOpt();
2451                         string const body = p.verbatim_item();
2452                         // store the in_lyx_preamble setting
2453                         bool const was_in_lyx_preamble = in_lyx_preamble;
2454                         // font settings
2455                         if (name == "\\rmdefault")
2456                                 if (is_known(body, known_roman_font_packages)) {
2457                                         h_font_roman[0] = body;
2458                                         p.skip_spaces();
2459                                         in_lyx_preamble = true;
2460                                 }
2461                         if (name == "\\sfdefault")
2462                                 if (is_known(body, known_sans_font_packages)) {
2463                                         h_font_sans[0] = body;
2464                                         p.skip_spaces();
2465                                         in_lyx_preamble = true;
2466                                 }
2467                         if (name == "\\ttdefault")
2468                                 if (is_known(body, known_typewriter_font_packages)) {
2469                                         h_font_typewriter[0] = body;
2470                                         p.skip_spaces();
2471                                         in_lyx_preamble = true;
2472                                 }
2473                         if (name == "\\familydefault") {
2474                                 string family = body;
2475                                 // remove leading "\"
2476                                 h_font_default_family = family.erase(0,1);
2477                                 p.skip_spaces();
2478                                 in_lyx_preamble = true;
2479                         }
2480
2481                         // remove LyX-specific definitions that are re-added by LyX
2482                         // if necessary
2483                         // \lyxline is an ancient command that is converted by tex2lyx into
2484                         // a \rule therefore remove its preamble code
2485                         if (name == "\\lyxdot" || name == "\\lyxarrow"
2486                             || name == "\\lyxline" || name == "\\LyX") {
2487                                 p.skip_spaces();
2488                                 in_lyx_preamble = true;
2489                         }
2490
2491                         // Add the command to the known commands
2492                         add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
2493
2494                         // only non-lyxspecific stuff
2495                         if (!in_lyx_preamble) {
2496                                 ostringstream ss;
2497                                 ss << '\\' << t.cs();
2498                                 if (star)
2499                                         ss << '*';
2500                                 ss << '{' << name << '}' << opt1 << opt2
2501                                    << '{' << body << "}";
2502                                 if (prefixIs(t.cs(), "renew") || !contains(h_preamble.str(), ss.str()))
2503                                         h_preamble << ss.str();
2504 /*
2505                                 ostream & out = in_preamble ? h_preamble : os;
2506                                 out << "\\" << t.cs() << "{" << name << "}"
2507                                     << opts << "{" << body << "}";
2508 */
2509                         }
2510                         // restore the in_lyx_preamble setting
2511                         in_lyx_preamble = was_in_lyx_preamble;
2512                         continue;
2513                 }
2514
2515                 if (t.cs() == "documentclass") {
2516                         vector<string>::iterator it;
2517                         vector<string> opts = split_options(p.getArg('[', ']'));
2518                         handle_opt(opts, known_fontsizes, h_paperfontsize);
2519                         delete_opt(opts, known_fontsizes);
2520                         // delete "pt" at the end
2521                         string::size_type i = h_paperfontsize.find("pt");
2522                         if (i != string::npos)
2523                                 h_paperfontsize.erase(i);
2524                         // The documentclass options are always parsed before the options
2525                         // of the babel call so that a language cannot overwrite the babel
2526                         // options.
2527                         handle_opt(opts, known_languages, h_language);
2528                         delete_opt(opts, known_languages);
2529
2530                         // math indentation
2531                         if ((it = find(opts.begin(), opts.end(), "fleqn"))
2532                                  != opts.end()) {
2533                                 h_is_mathindent = "1";
2534                                 opts.erase(it);
2535                         }
2536                         // formula numbering side
2537                         if ((it = find(opts.begin(), opts.end(), "leqno"))
2538                                  != opts.end()) {
2539                                 h_math_numbering_side = "left";
2540                                 opts.erase(it);
2541                         }
2542                         else if ((it = find(opts.begin(), opts.end(), "reqno"))
2543                                  != opts.end()) {
2544                                 h_math_numbering_side = "right";
2545                                 opts.erase(it);
2546                         }
2547
2548                         // paper orientation
2549                         if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
2550                                 h_paperorientation = "landscape";
2551                                 opts.erase(it);
2552                         }
2553                         // paper sides
2554                         if ((it = find(opts.begin(), opts.end(), "oneside"))
2555                                  != opts.end()) {
2556                                 h_papersides = "1";
2557                                 opts.erase(it);
2558                         }
2559                         if ((it = find(opts.begin(), opts.end(), "twoside"))
2560                                  != opts.end()) {
2561                                 h_papersides = "2";
2562                                 opts.erase(it);
2563                         }
2564                         // paper columns
2565                         if ((it = find(opts.begin(), opts.end(), "onecolumn"))
2566                                  != opts.end()) {
2567                                 h_papercolumns = "1";
2568                                 opts.erase(it);
2569                         }
2570                         if ((it = find(opts.begin(), opts.end(), "twocolumn"))
2571                                  != opts.end()) {
2572                                 h_papercolumns = "2";
2573                                 opts.erase(it);
2574                         }
2575                         // paper sizes
2576                         // some size options are known to any document classes, other sizes
2577                         // are handled by the \geometry command of the geometry package
2578                         handle_opt(opts, known_class_paper_sizes, h_papersize);
2579                         delete_opt(opts, known_class_paper_sizes);
2580                         // the remaining options
2581                         h_options = join(opts, ",");
2582                         // FIXME This does not work for classes that have a
2583                         //       different name in LyX than in LaTeX
2584                         h_textclass = p.getArg('{', '}');
2585                         p.skip_spaces();
2586                         continue;
2587                 }
2588
2589                 if (t.cs() == "usepackage") {
2590                         string const options = p.getArg('[', ']');
2591                         string const name = p.getArg('{', '}');
2592                         vector<string> vecnames;
2593                         split(name, vecnames, ',');
2594                         vector<string>::const_iterator it  = vecnames.begin();
2595                         vector<string>::const_iterator end = vecnames.end();
2596                         for (; it != end; ++it)
2597                                 handle_package(p, trimSpaceAndEol(*it), options,
2598                                                in_lyx_preamble, detectEncoding);
2599                         continue;
2600                 }
2601
2602                 if (t.cs() == "inputencoding") {
2603                         string const encoding = p.getArg('{','}');
2604                         Encoding const * const enc = encodings.fromLaTeXName(
2605                                 encoding, Encoding::inputenc, true);
2606                         if (!enc) {
2607                                 if (!detectEncoding)
2608                                         cerr << "Unknown encoding " << encoding
2609                                              << ". Ignoring." << std::endl;
2610                         } else {
2611                                 if (!enc->unsafe())
2612                                         h_inputencoding = enc->name();
2613                                 p.setEncoding(enc->iconvName());
2614                         }
2615                         continue;
2616                 }
2617
2618                 if (t.cs() == "newenvironment") {
2619                         string const name = p.getArg('{', '}');
2620                         string const opt1 = p.getFullOpt();
2621                         string const opt2 = p.getFullOpt();
2622                         string const beg = p.verbatim_item();
2623                         string const end = p.verbatim_item();
2624                         if (!in_lyx_preamble) {
2625                                 h_preamble << "\\newenvironment{" << name
2626                                            << '}' << opt1 << opt2 << '{'
2627                                            << beg << "}{" << end << '}';
2628                         }
2629                         add_known_environment(name, opt1, !opt2.empty(),
2630                                               from_utf8(beg), from_utf8(end));
2631                         continue;
2632                 }
2633
2634                 if (t.cs() == "newtheorem") {
2635                         bool star = false;
2636                         if (p.next_token().character() == '*') {
2637                                 p.get_token();
2638                                 star = true;
2639                         }
2640                         string const name = p.getArg('{', '}');
2641                         string const opt1 = p.getFullOpt();
2642                         string const opt2 = p.getFullOpt();
2643                         string const body = p.verbatim_item();
2644                         string const opt3 = p.getFullOpt();
2645                         string const cmd = star ? "\\newtheorem*" : "\\newtheorem";
2646
2647                         string const complete = cmd + "{" + name + '}' +
2648                                           opt1 + opt2 + '{' + body + '}' + opt3;
2649
2650                         add_known_theorem(name, opt1, !opt2.empty(), from_utf8(complete));
2651
2652                         if (!in_lyx_preamble)
2653                                 h_preamble << complete;
2654                         continue;
2655                 }
2656
2657                 if (t.cs() == "def") {
2658                         string name = p.get_token().cs();
2659                         // In fact, name may be more than the name:
2660                         // In the test case of bug 8116
2661                         // name == "csname SF@gobble@opt \endcsname".
2662                         // Therefore, we need to use asInput() instead of cs().
2663                         while (p.next_token().cat() != catBegin)
2664                                 name += p.get_token().asInput();
2665                         if (!in_lyx_preamble)
2666                                 h_preamble << "\\def\\" << name << '{'
2667                                            << p.verbatim_item() << "}";
2668                         continue;
2669                 }
2670
2671                 if (t.cs() == "newcolumntype") {
2672                         string const name = p.getArg('{', '}');
2673                         trimSpaceAndEol(name);
2674                         int nargs = 0;
2675                         string opts = p.getOpt();
2676                         if (!opts.empty()) {
2677                                 istringstream is(string(opts, 1));
2678                                 is >> nargs;
2679                         }
2680                         special_columns_[name[0]] = nargs;
2681                         h_preamble << "\\newcolumntype{" << name << "}";
2682                         if (nargs)
2683                                 h_preamble << "[" << nargs << "]";
2684                         h_preamble << "{" << p.verbatim_item() << "}";
2685                         continue;
2686                 }
2687
2688                 if (t.cs() == "setcounter") {
2689                         string const name = p.getArg('{', '}');
2690                         string const content = p.getArg('{', '}');
2691                         if (name == "secnumdepth")
2692                                 h_secnumdepth = content;
2693                         else if (name == "tocdepth")
2694                                 h_tocdepth = content;
2695                         else
2696                                 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
2697                         continue;
2698                 }
2699
2700                 if (t.cs() == "setlength") {
2701                         string const name = p.verbatim_item();
2702                         string const content = p.verbatim_item();
2703                         // the paragraphs are only not indented when \parindent is set to zero
2704                         if (name == "\\parindent" && content != "") {
2705                                 if (content[0] == '0')
2706                                         h_paragraph_separation = "skip";
2707                                 else
2708                                         h_paragraph_indentation = translate_len(content);
2709                         } else if (name == "\\parskip") {
2710                                 if (content == "\\smallskipamount")
2711                                         h_defskip = "smallskip";
2712                                 else if (content == "\\medskipamount")
2713                                         h_defskip = "medskip";
2714                                 else if (content == "\\bigskipamount")
2715                                         h_defskip = "bigskip";
2716                                 else
2717                                         h_defskip = translate_len(content);
2718                         } else if (name == "\\mathindent") {
2719                                 h_mathindentation = translate_len(content);
2720                         } else
2721                                 h_preamble << "\\setlength{" << name << "}{" << content << "}";
2722                         continue;
2723                 }
2724
2725                 if (t.cs() == "onehalfspacing") {
2726                         h_spacing = "onehalf";
2727                         continue;
2728                 }
2729
2730                 if (t.cs() == "doublespacing") {
2731                         h_spacing = "double";
2732                         continue;
2733                 }
2734
2735                 if (t.cs() == "setstretch") {
2736                         h_spacing = "other " + p.verbatim_item();
2737                         continue;
2738                 }
2739
2740                 if (t.cs() == "synctex") {
2741                         // the scheme is \synctex=value
2742                         // where value can only be "1" or "-1"
2743                         h_output_sync = "1";
2744                         // there can be any character behind the value (e.g. a linebreak or a '\'
2745                         // therefore we extract it char by char
2746                         p.get_token();
2747                         string value = p.get_token().asInput();
2748                         if (value == "-")
2749                                 value += p.get_token().asInput();
2750                         h_output_sync_macro = "\\synctex=" + value;
2751                         continue;
2752                 }
2753
2754                 if (t.cs() == "begin") {
2755                         string const name = p.getArg('{', '}');
2756                         if (name == "document")
2757                                 break;
2758                         h_preamble << "\\begin{" << name << "}";
2759                         continue;
2760                 }
2761
2762                 if (t.cs() == "geometry") {
2763                         vector<string> opts = split_options(p.getArg('{', '}'));
2764                         handle_geometry(opts);
2765                         continue;
2766                 }
2767
2768                 if (t.cs() == "definecolor") {
2769                         string const color = p.getArg('{', '}');
2770                         string const space = p.getArg('{', '}');
2771                         string const value = p.getArg('{', '}');
2772                         if (color == "document_fontcolor" && space == "rgb") {
2773                                 RGBColor c(RGBColorFromLaTeX(value));
2774                                 h_fontcolor = X11hexname(c);
2775                         } else if (color == "note_fontcolor" && space == "rgb") {
2776                                 RGBColor c(RGBColorFromLaTeX(value));
2777                                 h_notefontcolor = X11hexname(c);
2778                         } else if (color == "page_backgroundcolor" && space == "rgb") {
2779                                 RGBColor c(RGBColorFromLaTeX(value));
2780                                 h_backgroundcolor = X11hexname(c);
2781                         } else if (color == "shadecolor" && space == "rgb") {
2782                                 RGBColor c(RGBColorFromLaTeX(value));
2783                                 h_boxbgcolor = X11hexname(c);
2784                         } else {
2785                                 h_preamble << "\\definecolor{" << color
2786                                            << "}{" << space << "}{" << value
2787                                            << '}';
2788                         }
2789                         continue;
2790                 }
2791
2792                 if (t.cs() == "bibliographystyle") {
2793                         h_biblio_style = p.verbatim_item();
2794                         continue;
2795                 }
2796
2797                 if (t.cs() == "jurabibsetup") {
2798                         // FIXME p.getArg('{', '}') is most probably wrong (it
2799                         //       does not handle nested braces).
2800                         //       Use p.verbatim_item() instead.
2801                         vector<string> jurabibsetup =
2802                                 split_options(p.getArg('{', '}'));
2803                         // add jurabibsetup to the jurabib package options
2804                         add_package("jurabib", jurabibsetup);
2805                         if (!jurabibsetup.empty()) {
2806                                 h_preamble << "\\jurabibsetup{"
2807                                            << join(jurabibsetup, ",") << '}';
2808                         }
2809                         continue;
2810                 }
2811
2812                 if (t.cs() == "hypersetup") {
2813                         vector<string> hypersetup =
2814                                 split_options(p.verbatim_item());
2815                         // add hypersetup to the hyperref package options
2816                         handle_hyperref(hypersetup);
2817                         if (!hypersetup.empty()) {
2818                                 h_preamble << "\\hypersetup{"
2819                                            << join(hypersetup, ",") << '}';
2820                         }
2821                         continue;
2822                 }
2823
2824                 if (t.cs() == "includeonly") {
2825                         vector<string> includeonlys = getVectorFromString(p.getArg('{', '}'));
2826                         for (auto & iofile : includeonlys) {
2827                                 string filename(normalize_filename(iofile));
2828                                 string const path = getMasterFilePath(true);
2829                                 // We want to preserve relative/absolute filenames,
2830                                 // therefore path is only used for testing
2831                                 if (!makeAbsPath(filename, path).exists()) {
2832                                         // The file extension is probably missing.
2833                                         // Now try to find it out.
2834                                         string const tex_name =
2835                                                 find_file(filename, path,
2836                                                           known_tex_extensions);
2837                                         if (!tex_name.empty())
2838                                                 filename = tex_name;
2839                                 }
2840                                 string outname;
2841                                 if (makeAbsPath(filename, path).exists())
2842                                         fix_child_filename(filename);
2843                                 else
2844                                         cerr << "Warning: Could not find included file '"
2845                                              << filename << "'." << endl;
2846                                 outname = changeExtension(filename, "lyx");
2847                                 h_includeonlys.push_back(outname);
2848                         }
2849                         continue;
2850                 }
2851
2852                 if (is_known(t.cs(), known_if_3arg_commands)) {
2853                         // prevent misparsing of \usepackage if it is used
2854                         // as an argument (see e.g. our own output of
2855                         // \@ifundefined above)
2856                         string const arg1 = p.verbatim_item();
2857                         string const arg2 = p.verbatim_item();
2858                         string const arg3 = p.verbatim_item();
2859                         // test case \@ifundefined{date}{}{\date{}}
2860                         if (t.cs() == "@ifundefined" && arg1 == "date" &&
2861                             arg2.empty() && arg3 == "\\date{}") {
2862                                 h_suppress_date = "true";
2863                         // older tex2lyx versions did output
2864                         // \@ifundefined{definecolor}{\usepackage{color}}{}
2865                         } else if (t.cs() == "@ifundefined" &&
2866                                    arg1 == "definecolor" &&
2867                                    arg2 == "\\usepackage{color}" &&
2868                                    arg3.empty()) {
2869                                 if (!in_lyx_preamble)
2870                                         h_preamble << package_beg_sep
2871                                                    << "color"
2872                                                    << package_mid_sep
2873                                                    << "\\@ifundefined{definecolor}{color}{}"
2874                                                    << package_end_sep;
2875                         // test for case
2876                         //\@ifundefined{showcaptionsetup}{}{%
2877                         // \PassOptionsToPackage{caption=false}{subfig}}
2878                         // that LyX uses for subfloats
2879                         } else if (t.cs() == "@ifundefined" &&
2880                                    arg1 == "showcaptionsetup" && arg2.empty()
2881                                 && arg3 == "%\n \\PassOptionsToPackage{caption=false}{subfig}") {
2882                                 ; // do nothing
2883                         } else if (!in_lyx_preamble) {
2884                                 h_preamble << t.asInput()
2885                                            << '{' << arg1 << '}'
2886                                            << '{' << arg2 << '}'
2887                                            << '{' << arg3 << '}';
2888                         }
2889                         continue;
2890                 }
2891
2892                 if (is_known(t.cs(), known_if_commands)) {
2893                         // must not parse anything in conditional code, since
2894                         // LyX would output the parsed contents unconditionally
2895                         if (!in_lyx_preamble)
2896                                 h_preamble << t.asInput();
2897                         handle_if(p, in_lyx_preamble);
2898                         continue;
2899                 }
2900
2901                 if (!t.cs().empty() && !in_lyx_preamble) {
2902                         h_preamble << '\\' << t.cs();
2903                         continue;
2904                 }
2905         }
2906
2907         // remove the whitespace
2908         p.skip_spaces();
2909
2910         // Force textclass if the user wanted it
2911         if (!forceclass.empty())
2912                 h_textclass = forceclass;
2913         tc.setName(h_textclass);
2914         if (!LayoutFileList::get().haveClass(h_textclass) || !tc.load()) {
2915                 cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
2916                 exit(EXIT_FAILURE);
2917         }
2918         if (h_papersides.empty()) {
2919                 ostringstream ss;
2920                 ss << tc.sides();
2921                 h_papersides = ss.str();
2922         }
2923
2924         // If the CJK package is used we cannot set the document language from
2925         // the babel options. Instead, we guess which language is used most
2926         // and set this one.
2927         default_language = h_language;
2928         if (is_full_document &&
2929             (auto_packages.find("CJK") != auto_packages.end() ||
2930              auto_packages.find("CJKutf8") != auto_packages.end())) {
2931                 p.pushPosition();
2932                 h_language = guessLanguage(p, default_language);
2933                 p.popPosition();
2934                 if (explicit_babel && h_language != default_language) {
2935                         // We set the document language to a CJK language,
2936                         // but babel is explicitly called in the user preamble
2937                         // without options. LyX will not add the default
2938                         // language to the document options if it is either
2939                         // english, or no text is set as default language.
2940                         // Therefore we need to add a language option explicitly.
2941                         // FIXME: It would be better to remove all babel calls
2942                         //        from the user preamble, but this is difficult
2943                         //        without re-introducing bug 7861.
2944                         if (h_options.empty())
2945                                 h_options = lyx2babel(default_language);
2946                         else
2947                                 h_options += ',' + lyx2babel(default_language);
2948                 }
2949         }
2950
2951         // Finally, set the quote style.
2952         // LyX knows the following quotes styles:
2953         // british, cjk, cjkangle, danish, english, french, german,
2954         // polish, russian, swedish and swiss
2955         // conversion list taken from
2956         // https://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
2957         // (quotes for kazakh are unknown)
2958         // british
2959         if (is_known(h_language, known_british_quotes_languages))
2960                 h_quotes_style = "british";
2961         // cjk
2962         else if (is_known(h_language, known_cjk_quotes_languages))
2963                 h_quotes_style = "cjk";
2964         // cjkangle
2965         else if (is_known(h_language, known_cjkangle_quotes_languages))
2966                 h_quotes_style = "cjkangle";
2967         // danish
2968         else if (is_known(h_language, known_danish_quotes_languages))
2969                 h_quotes_style = "danish";
2970         // french
2971         else if (is_known(h_language, known_french_quotes_languages))
2972                 h_quotes_style = "french";
2973         // german
2974         else if (is_known(h_language, known_german_quotes_languages))
2975                 h_quotes_style = "german";
2976         // polish
2977         else if (is_known(h_language, known_polish_quotes_languages))
2978                 h_quotes_style = "polish";
2979         // russian
2980         else if (is_known(h_language, known_russian_quotes_languages))
2981                 h_quotes_style = "russian";
2982         // swedish
2983         else if (is_known(h_language, known_swedish_quotes_languages))
2984                 h_quotes_style = "swedish";
2985         // swiss
2986         else if (is_known(h_language, known_swiss_quotes_languages))
2987                 h_quotes_style = "swiss";
2988         // english
2989         else if (is_known(h_language, known_english_quotes_languages))
2990                 h_quotes_style = "english";
2991 }
2992
2993
2994 string Preamble::parseEncoding(Parser & p, string const & forceclass)
2995 {
2996         TeX2LyXDocClass dummy;
2997         parse(p, forceclass, true, dummy);
2998         if (h_inputencoding != "auto-legacy" && h_inputencoding != "auto-legacy-plain")
2999                 return h_inputencoding;
3000         return "";
3001 }
3002
3003
3004 string babel2lyx(string const & language)
3005 {
3006         char const * const * where = is_known(language, known_languages);
3007         if (where)
3008                 return known_coded_languages[where - known_languages];
3009         return language;
3010 }
3011
3012
3013 string lyx2babel(string const & language)
3014 {
3015         char const * const * where = is_known(language, known_coded_languages);
3016         if (where)
3017                 return known_languages[where - known_coded_languages];
3018         return language;
3019 }
3020
3021
3022 string Preamble::polyglossia2lyx(string const & language)
3023 {
3024         char const * const * where = is_known(language, polyglossia_languages);
3025         if (where)
3026                 return coded_polyglossia_languages[where - polyglossia_languages];
3027         return language;
3028 }
3029
3030
3031 string rgbcolor2code(string const & name)
3032 {
3033         char const * const * where = is_known(name, known_basic_colors);
3034         if (where) {
3035                 // "red", "green" etc
3036                 return known_basic_color_codes[where - known_basic_colors];
3037         }
3038         // "255,0,0", "0,255,0" etc
3039         RGBColor c(RGBColorFromLaTeX(name));
3040         return X11hexname(c);
3041 }
3042
3043 // }])
3044
3045
3046 } // namespace lyx