3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
9 * Full author contact details are available in file CREDITS.
20 #include "LayoutFile.h"
22 #include "TextClass.h"
25 #include "support/convert.h"
26 #include "support/FileName.h"
27 #include "support/filetools.h"
28 #include "support/lstrings.h"
35 using namespace lyx::support;
44 // CJK languages are handled in text.cpp, polyglossia languages are listed
47 * known babel language names (including synonyms)
48 * not in standard babel: arabic, arabtex, armenian, belarusian, serbian-latin, thai
49 * please keep this in sync with known_coded_languages line by line!
51 const char * const known_languages[] = {"acadian", "afrikaans", "albanian",
52 "american", "arabic", "arabtex", "australian", "austrian", "azerbaijani", "bahasa", "bahasai",
53 "bahasam", "basque", "belarusian", "bosnian", "brazil", "brazilian", "breton", "british",
54 "bulgarian", "canadian", "canadien", "catalan", "croatian", "czech", "danish",
55 "dutch", "english", "esperanto", "estonian", "farsi", "finnish", "francais",
56 "french", "frenchb", "frenchle", "frenchpro", "friulan", "galician", "german", "germanb",
57 "georgian", "greek", "hebrew", "hungarian", "icelandic", "indon", "indonesian",
58 "interlingua", "irish", "italian", "japanese", "kazakh", "kurmanji", "latin",
59 "latvian", "lithuanian", "lowersorbian", "lsorbian", "macedonian", "magyar", "malay", "meyalu",
60 "mongolian", "naustrian", "newzealand", "ngerman", "ngermanb", "norsk", "nswissgerman",
61 "nynorsk", "piedmontese", "polutonikogreek", "polish", "portuges", "portuguese",
62 "romanian", "romansh", "russian", "russianb", "samin", "scottish", "serbian", "serbian-latin",
63 "slovak", "slovene", "spanish", "swedish", "swissgerman", "thai", "turkish", "turkmen",
64 "ukraineb", "ukrainian", "uppersorbian", "UKenglish", "USenglish", "usorbian",
69 * the same as known_languages with .lyx names
70 * please keep this in sync with known_languages line by line!
72 const char * const known_coded_languages[] = {"french", "afrikaans", "albanian",
73 "american", "arabic_arabi", "arabic_arabtex", "australian", "austrian", "azerbaijani", "bahasa", "bahasa",
74 "bahasam", "basque", "belarusian", "bosnian", "brazilian", "brazilian", "breton", "british",
75 "bulgarian", "canadian", "canadien", "catalan", "croatian", "czech", "danish",
76 "dutch", "english", "esperanto", "estonian", "farsi", "finnish", "french",
77 "french", "french", "french", "french", "friulan", "galician", "german", "german",
78 "georgian", "greek", "hebrew", "magyar", "icelandic", "bahasa", "bahasa",
79 "interlingua", "irish", "italian", "japanese", "kazakh", "kurmanji", "latin",
80 "latvian", "lithuanian", "lowersorbian", "lowersorbian", "macedonian", "magyar", "bahasam", "bahasam",
81 "mongolian", "naustrian", "newzealand", "ngerman", "ngerman", "norsk", "german-ch",
82 "nynorsk", "piedmontese", "polutonikogreek", "polish", "portuguese", "portuguese",
83 "romanian", "romansh", "russian", "russian", "samin", "scottish", "serbian", "serbian-latin",
84 "slovak", "slovene", "spanish", "swedish", "german-ch-old", "thai", "turkish", "turkmen",
85 "ukrainian", "ukrainian", "uppersorbian", "english", "english", "uppersorbian",
86 "vietnamese", "welsh",
89 /// languages with british quotes (.lyx names)
90 const char * const known_british_quotes_languages[] = {"british", "welsh", 0};
92 /// languages with cjk quotes (.lyx names)
93 const char * const known_cjk_quotes_languages[] = {"chinese-traditional",
94 "japanese", "japanese-cjk", 0};
96 /// languages with cjk-angle quotes (.lyx names)
97 const char * const known_cjkangle_quotes_languages[] = {"korean", 0};
99 /// languages with danish quotes (.lyx names)
100 const char * const known_danish_quotes_languages[] = {"danish", 0};
102 /// languages with english quotes (.lyx names)
103 const char * const known_english_quotes_languages[] = {"american", "australian",
104 "bahasa", "bahasam", "bengali", "brazilian", "canadian", "chinese-simplified", "english",
105 "esperanto", "farsi", "interlingua", "irish", "newzealand", "scottish",
106 "thai", "turkish", "vietnamese", 0};
108 /// languages with french quotes (.lyx names)
109 const char * const known_french_quotes_languages[] = {"ancientgreek",
110 "arabic_arabi", "arabic_arabtex", "asturian", "belarusian", "breton",
111 "canadien", "catalan", "french", "friulan", "galician", "italian", "occitan",
112 "piedmontese", "portuguese", "spanish", "spanish-mexico", 0};
114 /// languages with german quotes (.lyx names)
115 const char * const known_german_quotes_languages[] = {"austrian", "bulgarian",
116 "czech", "estonian", "georgian", "german", "icelandic", "latvian", "lithuanian",
117 "lowersorbian", "macedonian", "naustrian", "ngerman", "romansh", "slovak", "slovene",
120 /// languages with polish quotes (.lyx names)
121 const char * const known_polish_quotes_languages[] = {"afrikaans", "bosnian", "croatian",
122 "dutch", "magyar", "polish", "romanian", "serbian", "serbian-latin", 0};
124 /// languages with russian quotes (.lyx names)
125 const char * const known_russian_quotes_languages[] = {"azerbaijani", "oldrussian",
126 "russian", "ukrainian", 0};
128 /// languages with swedish quotes (.lyx names)
129 const char * const known_swedish_quotes_languages[] = {"finnish", "swedish", 0};
131 /// languages with swiss quotes (.lyx names)
132 const char * const known_swiss_quotes_languages[] = {"albanian",
133 "armenian", "basque", "churchslavonic", "german-ch", "german-ch-old",
134 "norsk", "nynorsk", "turkmen", "ukrainian", "vietnamese", 0};
136 /// known language packages from the times before babel
137 const char * const known_old_language_packages[] = {"french", "frenchle",
138 "frenchpro", "german", "ngerman", "pmfrench", 0};
140 char const * const known_fontsizes[] = { "10pt", "11pt", "12pt", 0 };
142 const char * const known_roman_font_packages[] = { "ae", "beraserif", "bookman",
143 "ccfonts", "chancery", "charter", "cmr", "cochineal", "crimson", "CrimsonPro", "DejaVuSerif",
144 "DejaVuSerifCondensed", "fourier", "garamondx", "libertine", "libertineRoman", "libertine-type1",
145 "lmodern", "mathdesign", "mathpazo", "mathptmx", "MinionPro", "newcent", "noto", "noto-serif",
146 "PTSerif", "tgbonum", "tgchorus", "tgpagella", "tgschola", "tgtermes", "utopia", "xcharter", 0 };
148 const char * const known_sans_font_packages[] = { "avant", "berasans", "biolinum",
149 "biolinum-type1", "cantarell", "Chivo", "cmbr", "cmss", "DejaVuSans", "DejaVuSansCondensed", "FiraSans", "helvet", "iwona",
150 "iwonac", "iwonal", "iwonalc", "kurier", "kurierc", "kurierl", "kurierlc", "LibertinusSans-LF", "lmss", "noto-sans", "PTSans",
151 "tgadventor", "tgheros", "uop", 0 };
153 const char * const known_typewriter_font_packages[] = { "beramono", "cmtl", "cmtt", "courier", "DejaVuSansMono",
154 "FiraMono", "lmtt", "luximono", "libertineMono", "libertineMono-type1", "LibertinusMono-TLF", "lmodern",
155 "mathpazo", "mathptmx", "newcent", "noto-mono", "PTMono", "tgcursor", "txtt", 0 };
157 const char * const known_math_font_packages[] = { "eulervm", "newtxmath", 0};
159 const char * const known_latex_paper_sizes[] = { "a0paper", "b0paper", "c0paper",
160 "a1paper", "b1paper", "c1paper", "a2paper", "b2paper", "c2paper", "a3paper",
161 "b3paper", "c3paper", "a4paper", "b4paper", "c4paper", "a5paper", "b5paper",
162 "c5paper", "a6paper", "b6paper", "c6paper", "executivepaper", "legalpaper",
163 "letterpaper", "b0j", "b1j", "b2j", "b3j", "b4j", "b5j", "b6j", 0};
165 const char * const known_paper_margins[] = { "lmargin", "tmargin", "rmargin",
166 "bmargin", "headheight", "headsep", "footskip", "columnsep", 0};
168 const char * const known_coded_paper_margins[] = { "leftmargin", "topmargin",
169 "rightmargin", "bottommargin", "headheight", "headsep", "footskip",
172 /// commands that can start an \if...\else...\endif sequence
173 const char * const known_if_commands[] = {"if", "ifarydshln", "ifbraket",
174 "ifcancel", "ifcolortbl", "ifeurosym", "ifmarginnote", "ifmmode", "ifpdf",
175 "ifsidecap", "ifupgreek", 0};
177 const char * const known_basic_colors[] = {"black", "blue", "brown", "cyan",
178 "darkgray", "gray", "green", "lightgray", "lime", "magenta", "orange", "olive",
179 "pink", "purple", "red", "teal", "violet", "white", "yellow", 0};
181 const char * const known_basic_color_codes[] = {"#000000", "#0000ff", "#964B00", "#00ffff",
182 "#a9a9a9", "#808080", "#00ff00", "#d3d3d3", "#bfff00", "#ff00ff", "#ff7f00", "#808000",
183 "#ffc0cb", "#800080", "#ff0000", "#008080", "#8f00ff", "#ffffff", "#ffff00", 0};
185 /// conditional commands with three arguments like \@ifundefined{}{}{}
186 const char * const known_if_3arg_commands[] = {"@ifundefined", "IfFileExists",
190 * Known file extensions for TeX files as used by \\includeonly
192 char const * const known_tex_extensions[] = {"tex", 0};
194 /// packages that work only in xetex
195 /// polyglossia is handled separately
196 const char * const known_xetex_packages[] = {"arabxetex", "fixlatvian",
197 "fontbook", "fontwrap", "mathspec", "philokalia", "unisugar",
198 "xeCJK", "xecolor", "xecyr", "xeindex", "xepersian", "xunicode", 0};
200 /// packages that are automatically skipped if loaded by LyX
201 const char * const known_lyx_packages[] = {"amsbsy", "amsmath", "amssymb",
202 "amstext", "amsthm", "array", "babel", "booktabs", "calc", "CJK", "color",
203 "float", "fontspec", "framed", "graphicx", "hhline", "ifthen", "longtable",
204 "makeidx", "minted", "multirow", "nomencl", "parskip", "pdfpages", "prettyref", "refstyle",
205 "rotating", "rotfloat", "splitidx", "setspace", "subscript", "tabularx","textcomp", "tipa",
206 "tipx", "tone", "ulem", "url", "varioref", "verbatim", "wrapfig", "xcolor", "xltabular",
209 // codes used to remove packages that are loaded automatically by LyX.
210 // Syntax: package_beg_sep<name>package_mid_sep<package loading code>package_end_sep
211 const char package_beg_sep = '\001';
212 const char package_mid_sep = '\002';
213 const char package_end_sep = '\003';
216 // returns true if at least one of the options in what has been found
217 bool handle_opt(vector<string> & opts, char const * const * what, string & target)
223 // the last language option is the document language (for babel and LyX)
224 // the last size option is the document font size
225 vector<string>::iterator it;
226 vector<string>::iterator position = opts.begin();
227 for (; *what; ++what) {
228 it = find(opts.begin(), opts.end(), *what);
229 if (it != opts.end()) {
230 if (it >= position) {
241 void delete_opt(vector<string> & opts, char const * const * what)
246 // remove found options from the list
247 // do this after handle_opt to avoid potential memory leaks
248 vector<string>::iterator it;
249 for (; *what; ++what) {
250 it = find(opts.begin(), opts.end(), *what);
251 if (it != opts.end())
258 * Split a package options string (keyval format) into a vector.
260 * authorformat=smallcaps,
262 * titleformat=colonsep,
263 * bibformat={tabular,ibidem,numbered}
265 vector<string> split_options(string const & input)
267 vector<string> options;
271 Token const & t = p.get_token();
272 if (t.asInput() == ",") {
273 options.push_back(trimSpaceAndEol(option));
275 } else if (t.asInput() == "=") {
278 if (p.next_token().asInput() == "{")
279 option += '{' + p.getArg('{', '}') + '}';
280 } else if (t.cat() != catSpace && t.cat() != catComment)
281 option += t.asInput();
285 options.push_back(trimSpaceAndEol(option));
292 * Retrieve a keyval option "name={value with=sign}" named \p name from
293 * \p options and return the value.
294 * The found option is also removed from \p options.
296 string process_keyval_opt(vector<string> & options, string const & name)
298 for (size_t i = 0; i < options.size(); ++i) {
299 vector<string> option;
300 split(options[i], option, '=');
301 if (option.size() < 2)
303 if (option[0] == name) {
304 options.erase(options.begin() + i);
305 option.erase(option.begin());
306 return join(option, "=");
312 } // anonymous namespace
316 * known polyglossia language names (including variants)
317 * FIXME: support spelling=old for german variants (german vs. ngerman LyX names etc)
319 const char * const Preamble::polyglossia_languages[] = {
320 "albanian", "american", "amharic", "ancient", "arabic", "armenian", "asturian", "australian",
321 "bahasai", "bahasam", "basque", "bengali", "brazil", "brazilian", "breton", "british", "bulgarian",
322 "catalan", "churchslavonic", "coptic", "croatian", "czech", "danish", "divehi", "dutch",
323 "english", "esperanto", "estonian", "farsi", "finnish", "french", "friulan",
324 "galician", "greek", "monotonic", "hebrew", "hindi",
325 "icelandic", "interlingua", "irish", "italian", "kannada", "khmer", "korean",
326 "lao", "latin", "latvian", "lithuanian", "lsorbian", "magyar", "malayalam", "marathi",
327 "austrian", "newzealand", "german", "norsk", "nynorsk", "occitan", "oldrussian",
328 "piedmontese", "polish", "polytonic", "portuges", "romanian", "romansh", "russian",
329 "samin", "sanskrit", "scottish", "serbian", "slovak", "slovenian", "spanish", "swedish", "syriac",
330 "tamil", "telugu", "thai", "tibetan", "turkish", "turkmen",
331 "ukrainian", "urdu", "usorbian", "vietnamese", "welsh", 0};
332 // not yet supported by LyX: "korean", "nko"
335 * the same as polyglossia_languages with .lyx names
336 * please keep this in sync with polyglossia_languages line by line!
338 const char * const Preamble::coded_polyglossia_languages[] = {
339 "albanian", "american", "amharic", "ancientgreek", "arabic_arabi", "armenian", "asturian", "australian",
340 "bahasa", "bahasam", "basque", "bengali", "brazilian", "brazilian", "breton", "british", "bulgarian",
341 "catalan", "churchslavonic", "coptic", "croatian", "czech", "danish", "divehi", "dutch",
342 "english", "esperanto", "estonian", "farsi", "finnish", "french", "friulan",
343 "galician", "greek", "greek", "hebrew", "hindi",
344 "icelandic", "interlingua", "irish", "italian", "kannada", "khmer", "korean",
345 "lao", "latin", "latvian", "lithuanian", "lowersorbian", "magyar", "malayalam", "marathi",
346 "naustrian","newzealand", "ngerman", "norsk", "nynorsk", "occitan", "oldrussian",
347 "piedmontese", "polish", "polutonikogreek", "portuges", "romanian", "romansh", "russian",
348 "samin", "sanskrit", "scottish", "serbian", "slovak", "slovene", "spanish", "swedish", "syriac",
349 "tamil", "telugu", "thai", "tibetan", "turkish", "turkmen",
350 "ukrainian", "urdu", "uppersorbian", "vietnamese", "welsh", 0};
351 // not yet supported by LyX: "korean-polyglossia", "nko"
354 bool Preamble::usePolyglossia() const
356 return h_use_non_tex_fonts && h_language_package == "default";
360 bool Preamble::indentParagraphs() const
362 return h_paragraph_separation == "indent";
366 bool Preamble::isPackageUsed(string const & package) const
368 return used_packages.find(package) != used_packages.end();
372 bool Preamble::isPackageAutoLoaded(string const & package) const
374 return auto_packages.find(package) != auto_packages.end();
378 vector<string> Preamble::getPackageOptions(string const & package) const
380 map<string, vector<string> >::const_iterator it = used_packages.find(package);
381 if (it != used_packages.end())
383 return vector<string>();
387 void Preamble::registerAutomaticallyLoadedPackage(std::string const & package)
389 auto_packages.insert(package);
393 void Preamble::addModule(string const & module)
395 for (auto const & m : used_modules) {
399 used_modules.push_back(module);
403 void Preamble::suppressDate(bool suppress)
406 h_suppress_date = "true";
408 h_suppress_date = "false";
412 void Preamble::registerAuthor(std::string const & name, string const & initials)
414 Author author(from_utf8(name), empty_docstring(), from_utf8(initials));
415 author.setUsed(true);
416 authors_.record(author);
417 h_tracking_changes = "true";
418 h_output_changes = "true";
422 Author const & Preamble::getAuthor(std::string const & name) const
424 Author author(from_utf8(name), empty_docstring(), empty_docstring());
425 for (AuthorList::Authors::const_iterator it = authors_.begin();
426 it != authors_.end(); ++it)
429 static Author const dummy;
434 int Preamble::getSpecialTableColumnArguments(char c) const
436 map<char, int>::const_iterator it = special_columns_.find(c);
437 if (it == special_columns_.end())
443 void Preamble::add_package(string const & name, vector<string> & options)
445 // every package inherits the global options
446 used_packages.insert({name, split_options(h_options)});
448 // Insert options passed via PassOptionsToPackage
449 for (auto const & p : extra_package_options_) {
450 if (p.first == name) {
451 vector<string> eo = getVectorFromString(p.second);
452 for (auto const & eoi : eo)
453 options.push_back(eoi);
457 vector<string> & v = used_packages[name];
458 v.insert(v.end(), options.begin(), options.end());
459 if (name == "jurabib") {
460 // Don't output the order argument (see the cite command
461 // handling code in text.cpp).
462 vector<string>::iterator end =
463 remove(options.begin(), options.end(), "natbiborder");
464 end = remove(options.begin(), end, "jurabiborder");
465 options.erase(end, options.end());
469 void Preamble::setTextClass(string const & tclass, TeX2LyXDocClass & tc)
471 h_textclass = tclass;
472 tc.setName(h_textclass);
473 if (!LayoutFileList::get().haveClass(h_textclass) || !tc.load()) {
474 cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
482 // Given is a string like "scaled=0.9" or "scale=0.9", return 0.9 * 100
483 bool scale_as_percentage(string const & scale, string & percentage)
485 if (contains(scale, '=')) {
486 string const value = support::split(scale, '=');
487 if (isStrDbl(value)) {
488 percentage = convert<string>(
489 static_cast<int>(100 * convert<double>(value)));
497 string remove_braces(string const & value)
501 if (value[0] == '{' && value[value.length()-1] == '}')
502 return value.substr(1, value.length()-2);
506 } // anonymous namespace
509 Preamble::Preamble() : one_language(true), explicit_babel(false),
510 title_layout_found(false), index_number(0), h_font_cjk_set(false)
514 h_biblio_style = "plain";
515 h_bibtex_command = "default";
516 h_cite_engine = "basic";
517 h_cite_engine_type = "default";
519 h_defskip = "medskip";
520 h_dynamic_quotes = false;
523 h_fontencoding = "default";
524 h_font_roman[0] = "default";
525 h_font_roman[1] = "default";
526 h_font_sans[0] = "default";
527 h_font_sans[1] = "default";
528 h_font_typewriter[0] = "default";
529 h_font_typewriter[1] = "default";
530 h_font_math[0] = "auto";
531 h_font_math[1] = "auto";
532 h_font_default_family = "default";
533 h_use_non_tex_fonts = false;
535 h_font_roman_osf = "false";
536 h_font_sans_osf = "false";
537 h_font_typewriter_osf = "false";
538 h_font_sf_scale[0] = "100";
539 h_font_sf_scale[1] = "100";
540 h_font_tt_scale[0] = "100";
541 h_font_tt_scale[1] = "100";
542 // h_font_roman_opts;
544 // h_font_typewriter_opts;
546 h_is_mathindent = "0";
547 h_math_numbering_side = "default";
548 h_graphics = "default";
549 h_default_output_format = "default";
550 h_html_be_strict = "false";
551 h_html_css_as_file = "0";
552 h_html_math_output = "0";
553 h_docbook_table_output = "0";
554 h_index[0] = "Index";
555 h_index_command = "default";
556 h_inputencoding = "auto-legacy";
557 h_justification = "true";
558 h_language = "english";
559 h_language_package = "none";
561 h_maintain_unincluded_children = "no";
565 h_output_changes = "false";
566 h_change_bars = "false";
568 //h_output_sync_macro
569 h_papercolumns = "1";
570 h_paperfontsize = "default";
571 h_paperorientation = "portrait";
572 h_paperpagestyle = "default";
574 h_papersize = "default";
575 h_paragraph_indentation = "default";
576 h_paragraph_separation = "indent";
581 h_pdf_bookmarks = "0";
582 h_pdf_bookmarksnumbered = "0";
583 h_pdf_bookmarksopen = "0";
584 h_pdf_bookmarksopenlevel = "1";
585 h_pdf_breaklinks = "0";
586 h_pdf_pdfborder = "0";
587 h_pdf_colorlinks = "0";
588 h_pdf_backref = "section";
589 h_pdf_pdfusetitle = "0";
591 //h_pdf_quoted_options;
592 h_quotes_style = "english";
594 h_shortcut[0] = "idx";
595 h_spacing = "single";
596 h_save_transient_properties = "true";
597 h_suppress_date = "false";
598 h_textclass = "article";
600 h_tracking_changes = "false";
601 h_use_bibtopic = "false";
602 h_use_dash_ligatures = "true";
603 h_use_indices = "false";
604 h_use_geometry = "false";
605 h_use_default_options = "false";
606 h_use_hyperref = "false";
607 h_use_microtype = "false";
608 h_use_lineno = "false";
609 h_use_refstyle = false;
610 h_use_minted = false;
611 h_use_packages["amsmath"] = "1";
612 h_use_packages["amssymb"] = "0";
613 h_use_packages["cancel"] = "0";
614 h_use_packages["esint"] = "1";
615 h_use_packages["mhchem"] = "0";
616 h_use_packages["mathdots"] = "0";
617 h_use_packages["mathtools"] = "0";
618 h_use_packages["stackrel"] = "0";
619 h_use_packages["stmaryrd"] = "0";
620 h_use_packages["undertilde"] = "0";
624 void Preamble::handle_hyperref(vector<string> & options)
626 // FIXME swallow inputencoding changes that might surround the
627 // hyperref setup if it was written by LyX
628 h_use_hyperref = "true";
629 // swallow "unicode=true", since LyX does always write that
630 vector<string>::iterator it =
631 find(options.begin(), options.end(), "unicode=true");
632 if (it != options.end())
634 it = find(options.begin(), options.end(), "pdfusetitle");
635 if (it != options.end()) {
636 h_pdf_pdfusetitle = "1";
639 string bookmarks = process_keyval_opt(options, "bookmarks");
640 if (bookmarks == "true")
641 h_pdf_bookmarks = "1";
642 else if (bookmarks == "false")
643 h_pdf_bookmarks = "0";
644 if (h_pdf_bookmarks == "1") {
645 string bookmarksnumbered =
646 process_keyval_opt(options, "bookmarksnumbered");
647 if (bookmarksnumbered == "true")
648 h_pdf_bookmarksnumbered = "1";
649 else if (bookmarksnumbered == "false")
650 h_pdf_bookmarksnumbered = "0";
651 string bookmarksopen =
652 process_keyval_opt(options, "bookmarksopen");
653 if (bookmarksopen == "true")
654 h_pdf_bookmarksopen = "1";
655 else if (bookmarksopen == "false")
656 h_pdf_bookmarksopen = "0";
657 if (h_pdf_bookmarksopen == "1") {
658 string bookmarksopenlevel =
659 process_keyval_opt(options, "bookmarksopenlevel");
660 if (!bookmarksopenlevel.empty())
661 h_pdf_bookmarksopenlevel = bookmarksopenlevel;
664 string breaklinks = process_keyval_opt(options, "breaklinks");
665 if (breaklinks == "true")
666 h_pdf_breaklinks = "1";
667 else if (breaklinks == "false")
668 h_pdf_breaklinks = "0";
669 string pdfborder = process_keyval_opt(options, "pdfborder");
670 if (pdfborder == "{0 0 0}")
671 h_pdf_pdfborder = "1";
672 else if (pdfborder == "{0 0 1}")
673 h_pdf_pdfborder = "0";
674 string backref = process_keyval_opt(options, "backref");
675 if (!backref.empty())
676 h_pdf_backref = backref;
677 string colorlinks = process_keyval_opt(options, "colorlinks");
678 if (colorlinks == "true")
679 h_pdf_colorlinks = "1";
680 else if (colorlinks == "false")
681 h_pdf_colorlinks = "0";
682 string pdfpagemode = process_keyval_opt(options, "pdfpagemode");
683 if (!pdfpagemode.empty())
684 h_pdf_pagemode = pdfpagemode;
685 string pdftitle = process_keyval_opt(options, "pdftitle");
686 if (!pdftitle.empty()) {
687 h_pdf_title = remove_braces(pdftitle);
689 string pdfauthor = process_keyval_opt(options, "pdfauthor");
690 if (!pdfauthor.empty()) {
691 h_pdf_author = remove_braces(pdfauthor);
693 string pdfsubject = process_keyval_opt(options, "pdfsubject");
694 if (!pdfsubject.empty())
695 h_pdf_subject = remove_braces(pdfsubject);
696 string pdfkeywords = process_keyval_opt(options, "pdfkeywords");
697 if (!pdfkeywords.empty())
698 h_pdf_keywords = remove_braces(pdfkeywords);
699 if (!options.empty()) {
700 if (!h_pdf_quoted_options.empty())
701 h_pdf_quoted_options += ',';
702 h_pdf_quoted_options += join(options, ",");
708 void Preamble::handle_geometry(vector<string> & options)
710 h_use_geometry = "true";
711 vector<string>::iterator it;
713 if ((it = find(options.begin(), options.end(), "landscape")) != options.end()) {
714 h_paperorientation = "landscape";
718 // keyval version: "paper=letter" or "paper=letterpaper"
719 string paper = process_keyval_opt(options, "paper");
721 if (suffixIs(paper, "paper"))
722 paper = subst(paper, "paper", "");
723 // alternative version: "letterpaper"
724 handle_opt(options, known_latex_paper_sizes, paper);
725 if (suffixIs(paper, "paper"))
726 paper = subst(paper, "paper", "");
727 delete_opt(options, known_latex_paper_sizes);
731 char const * const * margin = known_paper_margins;
732 for (; *margin; ++margin) {
733 string value = process_keyval_opt(options, *margin);
734 if (!value.empty()) {
735 int k = margin - known_paper_margins;
736 string name = known_coded_paper_margins[k];
737 h_margins += '\\' + name + ' ' + value + '\n';
743 void Preamble::handle_package(Parser &p, string const & name,
744 string const & opts, bool in_lyx_preamble,
747 vector<string> options = split_options(opts);
748 add_package(name, options);
750 if (is_known(name, known_xetex_packages)) {
752 h_use_non_tex_fonts = true;
753 registerAutomaticallyLoadedPackage("fontspec");
754 if (h_inputencoding == "auto-legacy")
755 p.setEncoding("UTF-8");
758 // vector of all options for easier parsing and
760 vector<string> allopts = getVectorFromString(opts);
761 // this stores the potential extra options
768 // By default, we use the package name as LyX font name,
769 // so this only needs to be reset if these names differ
770 if (is_known(name, known_roman_font_packages))
771 h_font_roman[0] = name;
773 if (name == "ccfonts") {
774 for (auto const & opt : allopts) {
780 h_font_roman_opts = xopts;
784 if (name == "lmodern") {
785 for (auto const & opt : allopts) {
791 h_font_roman_opts = xopts;
795 if (name == "fourier") {
796 h_font_roman[0] = "utopia";
797 for (auto const & opt : allopts) {
799 h_font_roman_osf = "true";
802 if (opt == "expert") {
811 h_font_roman_opts = xopts;
815 if (name == "garamondx") {
816 for (auto const & opt : allopts) {
818 h_font_roman_osf = "true";
826 h_font_roman_opts = xopts;
830 if (name == "libertine") {
831 // this automatically invokes biolinum
832 h_font_sans[0] = "biolinum";
833 // as well as libertineMono
834 h_font_typewriter[0] = "libertine-mono";
835 for (auto const & opt : allopts) {
837 h_font_roman_osf = "true";
840 if (opt == "lining") {
841 h_font_roman_osf = "false";
849 h_font_roman_opts = xopts;
853 if (name == "libertineRoman" || name == "libertine-type1") {
854 h_font_roman[0] = "libertine";
855 // NOTE: contrary to libertine.sty, libertineRoman
856 // and libertine-type1 do not automatically invoke
857 // biolinum and libertineMono
858 if (opts == "lining")
859 h_font_roman_osf = "false";
860 else if (opts == "osf")
861 h_font_roman_osf = "true";
864 if (name == "libertinus" || name == "libertinus-type1") {
871 for (auto const & opt : allopts) {
872 if (opt == "rm" || opt == "serif") {
877 if (opt == "sf" || opt == "sans") {
882 if (opt == "tt=false" || opt == "mono=false") {
890 if (opt == "scaleSF") {
894 if (opt == "scaleTT") {
898 if (opt == "lining") {
899 h_font_roman_osf = "false";
907 h_font_roman[0] = "libertinus";
909 h_font_roman_osf = "true";
911 h_font_roman_osf = "false";
914 h_font_sans[0] = "LibertinusSans-LF";
916 h_font_sans_osf = "true";
918 h_font_sans_osf = "false";
919 if (!scalesf.empty())
920 scale_as_percentage(scalesf, h_font_sf_scale[0]);
923 h_font_typewriter[0] = "LibertinusMono-TLF";
924 if (!scalett.empty())
925 scale_as_percentage(scalett, h_font_tt_scale[0]);
928 h_font_roman_opts = xopts;
932 if (name == "MinionPro") {
933 h_font_roman[0] = "minionpro";
934 h_font_roman_osf = "true";
935 h_font_math[0] = "auto";
936 for (auto const & opt : allopts) {
938 h_font_roman_osf = "false";
941 if (opt == "onlytext") {
942 h_font_math[0] = "default";
950 h_font_roman_opts = xopts;
954 if (name == "mathdesign") {
955 for (auto const & opt : allopts) {
956 if (opt == "charter") {
957 h_font_roman[0] = "md-charter";
960 if (opt == "garamond") {
961 h_font_roman[0] = "md-garamond";
964 if (opt == "utopia") {
965 h_font_roman[0] = "md-utopia";
968 if (opt == "expert") {
970 h_font_roman_osf = "true";
976 else if (name == "mathpazo") {
977 h_font_roman[0] = "palatino";
978 for (auto const & opt : allopts) {
980 h_font_roman_osf = "true";
992 h_font_roman_opts = xopts;
996 else if (name == "mathptmx") {
997 h_font_roman[0] = "times";
998 for (auto const & opt : allopts) {
1004 h_font_roman_opts = xopts;
1008 if (name == "crimson")
1009 h_font_roman[0] = "cochineal";
1011 if (name == "cochineal") {
1012 for (auto const & opt : allopts) {
1013 if (opt == "osf" || opt == "oldstyle") {
1014 h_font_roman_osf = "true";
1017 if (opt == "proportional" || opt == "p")
1024 h_font_roman_opts = xopts;
1028 if (name == "CrimsonPro") {
1029 h_font_roman_osf = "true";
1030 for (auto const & opt : allopts) {
1031 if (opt == "lf" || opt == "lining") {
1032 h_font_roman_osf = "false";
1035 if (opt == "proportional" || opt == "p")
1037 if (opt == "medium") {
1038 h_font_roman[0] = "CrimsonProMedium";
1041 if (opt == "extralight") {
1042 h_font_roman[0] = "CrimsonProExtraLight";
1045 if (opt == "light") {
1046 h_font_roman[0] = "CrimsonProLight";
1054 h_font_roman_opts = xopts;
1060 // font uses old-style figure
1061 h_font_roman_osf = "true";
1063 if (name == "paratype") {
1064 // in this case all fonts are ParaType
1065 h_font_roman[0] = "PTSerif-TLF";
1066 h_font_sans[0] = "default";
1067 h_font_typewriter[0] = "default";
1070 if (name == "PTSerif")
1071 h_font_roman[0] = "PTSerif-TLF";
1073 if (name == "XCharter") {
1074 h_font_roman[0] = "xcharter";
1075 for (auto const & opt : allopts) {
1077 h_font_roman_osf = "true";
1085 h_font_roman_opts = xopts;
1089 if (name == "plex-serif") {
1090 h_font_roman[0] = "IBMPlexSerif";
1091 for (auto const & opt : allopts) {
1092 if (opt == "thin") {
1093 h_font_roman[0] = "IBMPlexSerifThin";
1096 if (opt == "extralight") {
1097 h_font_roman[0] = "IBMPlexSerifExtraLight";
1100 if (opt == "light") {
1101 h_font_roman[0] = "IBMPlexSerifLight";
1109 h_font_roman_opts = xopts;
1113 if (name == "noto-serif" || name == "noto") {
1120 bool extralight = false;
1122 bool medium = false;
1125 if (name == "noto") {
1130 // Since the options might apply to different shapes,
1131 // we need to parse all options first and then handle them.
1132 for (auto const & opt : allopts) {
1133 if (opt == "regular")
1143 if (opt == "thin") {
1147 if (opt == "extralight") {
1151 if (opt == "light") {
1155 if (opt == "medium") {
1166 if (opt == "nott") {
1174 if (prefixIs(opt, "scaled=")) {
1183 // handle options that might affect different shapes
1184 if (name == "noto-serif" || rm) {
1186 h_font_roman[0] = "NotoSerifThin";
1187 else if (extralight)
1188 h_font_roman[0] = "NotoSerifExtralight";
1190 h_font_roman[0] = "NotoSerifLight";
1192 h_font_roman[0] = "NotoSerifMedium";
1194 h_font_roman[0] = "NotoSerifRegular";
1196 h_font_roman_osf = "true";
1198 h_font_roman_opts = xopts;
1200 if (name == "noto" && sf) {
1202 h_font_sans[0] = "NotoSansThin";
1203 else if (extralight)
1204 h_font_sans[0] = "NotoSansExtralight";
1206 h_font_sans[0] = "NotoSansLight";
1208 h_font_sans[0] = "NotoSansMedium";
1210 h_font_sans[0] = "NotoSansRegular";
1212 h_font_sans_osf = "true";
1214 scale_as_percentage(scl, h_font_sf_scale[0]);
1216 h_font_sans_opts = xopts;
1218 if (name == "noto" && tt) {
1219 h_font_typewriter[0] = "NotoMonoRegular";
1221 h_font_typewriter_osf = "true";
1223 scale_as_percentage(scl, h_font_tt_scale[0]);
1225 h_font_typewriter_opts = xopts;
1229 if (name == "sourceserifpro") {
1230 h_font_roman[0] = "ADOBESourceSerifPro";
1231 for (auto const & opt : allopts) {
1233 h_font_roman_osf = "true";
1241 h_font_roman_opts = xopts;
1249 // By default, we use the package name as LyX font name,
1250 // so this only needs to be reset if these names differ.
1251 // Also, we handle the scaling option here generally.
1252 if (is_known(name, known_sans_font_packages)) {
1253 h_font_sans[0] = name;
1254 if (contains(opts, "scale")) {
1255 vector<string>::iterator it = allopts.begin();
1256 for (; it != allopts.end() ; ++it) {
1257 string const opt = *it;
1258 if (prefixIs(opt, "scaled=") || prefixIs(opt, "scale=")) {
1259 if (scale_as_percentage(opt, h_font_sf_scale[0])) {
1268 if (name == "biolinum" || name == "biolinum-type1") {
1269 h_font_sans[0] = "biolinum";
1270 for (auto const & opt : allopts) {
1271 if (prefixIs(opt, "osf")) {
1272 h_font_sans_osf = "true";
1280 h_font_sans_opts = xopts;
1284 if (name == "cantarell") {
1285 for (auto const & opt : allopts) {
1286 if (opt == "defaultsans")
1288 if (prefixIs(opt, "oldstyle")) {
1289 h_font_sans_osf = "true";
1297 h_font_sans_opts = xopts;
1301 if (name == "Chivo") {
1302 for (auto const & opt : allopts) {
1303 if (opt == "thin") {
1304 h_font_roman[0] = "ChivoThin";
1307 if (opt == "light") {
1308 h_font_roman[0] = "ChivoLight";
1311 if (opt == "regular") {
1312 h_font_roman[0] = "Chivo";
1315 if (opt == "medium") {
1316 h_font_roman[0] = "ChivoMedium";
1319 if (prefixIs(opt, "oldstyle")) {
1320 h_font_sans_osf = "true";
1328 h_font_sans_opts = xopts;
1332 if (name == "PTSans") {
1333 h_font_sans[0] = "PTSans-TLF";
1336 if (name == "FiraSans") {
1337 h_font_sans_osf = "true";
1338 for (auto const & opt : allopts) {
1339 if (opt == "book") {
1340 h_font_sans[0] = "FiraSansBook";
1343 if (opt == "thin") {
1346 if (opt == "extralight") {
1347 h_font_sans[0] = "FiraSansExtralight";
1350 if (opt == "light") {
1351 h_font_sans[0] = "FiraSansLight";
1354 if (opt == "ultralight") {
1355 h_font_sans[0] = "FiraSansUltralight";
1358 if (opt == "thin") {
1359 h_font_sans[0] = "FiraSansThin";
1362 if (opt == "lf" || opt == "lining") {
1363 h_font_sans_osf = "false";
1371 h_font_sans_opts = xopts;
1375 if (name == "plex-sans") {
1376 h_font_sans[0] = "IBMPlexSans";
1377 for (auto const & opt : allopts) {
1378 if (opt == "condensed") {
1379 h_font_sans[0] = "IBMPlexSansCondensed";
1382 if (opt == "thin") {
1383 h_font_sans[0] = "IBMPlexSansThin";
1386 if (opt == "extralight") {
1387 h_font_sans[0] = "IBMPlexSansExtraLight";
1390 if (opt == "light") {
1391 h_font_sans[0] = "IBMPlexSansLight";
1399 h_font_sans_opts = xopts;
1403 if (name == "noto-sans") {
1404 h_font_sans[0] = "NotoSansRegular";
1405 for (auto const & opt : allopts) {
1406 if (opt == "regular")
1408 if (opt == "medium") {
1409 h_font_sans[0] = "NotoSansMedium";
1412 if (opt == "thin") {
1413 h_font_sans[0] = "NotoSansThin";
1416 if (opt == "extralight") {
1417 h_font_sans[0] = "NotoSansExtralight";
1420 if (opt == "light") {
1421 h_font_sans[0] = "NotoSansLight";
1425 h_font_sans_osf = "true";
1433 h_font_sans_opts = xopts;
1437 if (name == "sourcesanspro") {
1438 h_font_sans[0] = "ADOBESourceSansPro";
1439 for (auto const & opt : allopts) {
1441 h_font_sans_osf = "true";
1449 h_font_sans_opts = xopts;
1457 // By default, we use the package name as LyX font name,
1458 // so this only needs to be reset if these names differ.
1459 // Also, we handle the scaling option here generally.
1460 if (is_known(name, known_typewriter_font_packages)) {
1461 h_font_typewriter[0] = name;
1462 if (contains(opts, "scale")) {
1463 vector<string>::iterator it = allopts.begin();
1464 for (; it != allopts.end() ; ++it) {
1465 string const opt = *it;
1466 if (prefixIs(opt, "scaled=") || prefixIs(opt, "scale=")) {
1467 if (scale_as_percentage(opt, h_font_tt_scale[0])) {
1476 if (name == "libertineMono" || name == "libertineMono-type1")
1477 h_font_typewriter[0] = "libertine-mono";
1479 if (name == "FiraMono") {
1480 h_font_typewriter_osf = "true";
1481 for (auto const & opt : allopts) {
1482 if (opt == "lf" || opt == "lining") {
1483 h_font_typewriter_osf = "false";
1491 h_font_typewriter_opts = xopts;
1495 if (name == "PTMono")
1496 h_font_typewriter[0] = "PTMono-TLF";
1498 if (name == "plex-mono") {
1499 h_font_typewriter[0] = "IBMPlexMono";
1500 for (auto const & opt : allopts) {
1501 if (opt == "thin") {
1502 h_font_typewriter[0] = "IBMPlexMonoThin";
1505 if (opt == "extralight") {
1506 h_font_typewriter[0] = "IBMPlexMonoExtraLight";
1509 if (opt == "light") {
1510 h_font_typewriter[0] = "IBMPlexMonoLight";
1518 h_font_typewriter_opts = xopts;
1522 if (name == "noto-mono") {
1523 h_font_typewriter[0] = "NotoMonoRegular";
1524 for (auto const & opt : allopts) {
1525 if (opt == "regular")
1532 h_font_typewriter_opts = xopts;
1536 if (name == "sourcecodepro") {
1537 h_font_typewriter[0] = "ADOBESourceCodePro";
1538 for (auto const & opt : allopts) {
1540 h_font_typewriter_osf = "true";
1548 h_font_typewriter_opts = xopts;
1556 // By default, we use the package name as LyX font name,
1557 // so this only needs to be reset if these names differ.
1558 if (is_known(name, known_math_font_packages))
1559 h_font_math[0] = name;
1561 if (name == "newtxmath") {
1563 h_font_math[0] = "newtxmath";
1564 else if (opts == "garamondx")
1565 h_font_math[0] = "garamondx-ntxm";
1566 else if (opts == "libertine")
1567 h_font_math[0] = "libertine-ntxm";
1568 else if (opts == "minion")
1569 h_font_math[0] = "minion-ntxm";
1570 else if (opts == "cochineal")
1571 h_font_math[0] = "cochineal-ntxm";
1574 if (name == "iwona")
1576 h_font_math[0] = "iwona-math";
1578 if (name == "kurier")
1580 h_font_math[0] = "kurier-math";
1582 // after the detection and handling of special cases, we can remove the
1583 // fonts, otherwise they would appear in the preamble, see bug #7856
1584 if (is_known(name, known_roman_font_packages) || is_known(name, known_sans_font_packages)
1585 || is_known(name, known_typewriter_font_packages) || is_known(name, known_math_font_packages))
1587 //"On". See the enum Package in BufferParams.h if you thought that "2" should have been "42"
1588 else if (name == "amsmath" || name == "amssymb" || name == "cancel" ||
1589 name == "esint" || name == "mhchem" || name == "mathdots" ||
1590 name == "mathtools" || name == "stackrel" ||
1591 name == "stmaryrd" || name == "undertilde") {
1592 h_use_packages[name] = "2";
1593 registerAutomaticallyLoadedPackage(name);
1596 else if (name == "babel") {
1597 h_language_package = "default";
1598 // One might think we would have to do nothing if babel is loaded
1599 // without any options to prevent pollution of the preamble with this
1600 // babel call in every roundtrip.
1601 // But the user could have defined babel-specific things afterwards. So
1602 // we need to keep it in the preamble to prevent cases like bug #7861.
1603 if (!opts.empty()) {
1604 // check if more than one option was used - used later for inputenc
1605 if (options.begin() != options.end() - 1)
1606 one_language = false;
1607 // babel takes the last language of the option of its \usepackage
1608 // call as document language. If there is no such language option, the
1609 // last language in the documentclass options is used.
1610 handle_opt(options, known_languages, h_language);
1611 // translate the babel name to a LyX name
1612 h_language = babel2lyx(h_language);
1613 if (h_language == "japanese") {
1614 // For Japanese, the encoding isn't indicated in the source
1615 // file, and there's really not much we can do. We could
1616 // 1) offer a list of possible encodings to choose from, or
1617 // 2) determine the encoding of the file by inspecting it.
1618 // For the time being, we leave the encoding alone so that
1619 // we don't get iconv errors when making a wrong guess, and
1620 // we will output a note at the top of the document
1621 // explaining what to do.
1622 Encoding const * const enc = encodings.fromIconvName(
1623 p.getEncoding(), Encoding::japanese, false);
1625 h_inputencoding = enc->name();
1626 is_nonCJKJapanese = true;
1627 // in this case babel can be removed from the preamble
1628 registerAutomaticallyLoadedPackage("babel");
1630 // If babel is called with options, LyX puts them by default into the
1631 // document class options. This works for most languages, except
1632 // for Latvian, Lithuanian, Mongolian, Turkmen and Vietnamese and
1633 // perhaps in future others.
1634 // Therefore keep the babel call as it is as the user might have
1636 string const babelcall = "\\usepackage[" + opts + "]{babel}\n";
1637 if (!contains(h_preamble.str(), babelcall))
1638 h_preamble << babelcall;
1640 delete_opt(options, known_languages);
1642 if (!contains(h_preamble.str(), "\\usepackage{babel}\n"))
1643 h_preamble << "\\usepackage{babel}\n";
1644 explicit_babel = true;
1648 else if (name == "polyglossia") {
1649 h_language_package = "default";
1650 h_default_output_format = "pdf4";
1651 h_use_non_tex_fonts = true;
1653 registerAutomaticallyLoadedPackage("xunicode");
1654 if (h_inputencoding == "auto-legacy")
1655 p.setEncoding("UTF-8");
1658 else if (name == "CJK") {
1659 // set the encoding to "auto-legacy" because it might be set to "auto-legacy-plain" by the babel handling
1660 // and this would not be correct for CJK
1661 if (h_inputencoding == "auto-legacy-plain")
1662 h_inputencoding = "auto-legacy";
1663 registerAutomaticallyLoadedPackage("CJK");
1666 else if (name == "CJKutf8") {
1667 h_inputencoding = "utf8-cjk";
1668 p.setEncoding("UTF-8");
1669 registerAutomaticallyLoadedPackage("CJKutf8");
1672 else if (name == "fontenc") {
1673 h_fontencoding = getStringFromVector(options, ",");
1677 else if (name == "inputenc" || name == "luainputenc") {
1678 // h_inputencoding is only set when there is not more than one
1679 // inputenc option because otherwise h_inputencoding must be
1680 // set to "auto-legacy" (the default encodings of the document's languages)
1681 // Therefore check that exactly one option is passed to inputenc.
1682 // It is also only set when there is not more than one babel
1684 if (!options.empty()) {
1685 string const encoding = options.back();
1686 Encoding const * const enc = encodings.fromLaTeXName(
1687 encoding, Encoding::inputenc, true);
1689 if (!detectEncoding)
1690 cerr << "Unknown encoding " << encoding
1691 << ". Ignoring." << std::endl;
1693 if (!enc->unsafe() && options.size() == 1 && one_language == true)
1694 h_inputencoding = enc->name();
1695 p.setEncoding(enc->iconvName());
1701 else if (name == "srcltx") {
1702 h_output_sync = "1";
1703 if (!opts.empty()) {
1704 h_output_sync_macro = "\\usepackage[" + opts + "]{srcltx}";
1707 h_output_sync_macro = "\\usepackage{srcltx}";
1710 else if (is_known(name, known_old_language_packages)) {
1711 // known language packages from the times before babel
1712 // if they are found and not also babel, they will be used as
1713 // custom language package
1714 h_language_package = "\\usepackage{" + name + "}";
1717 else if (name == "lyxskak") {
1718 // ignore this and its options
1719 const char * const o[] = {"ps", "mover", 0};
1720 delete_opt(options, o);
1723 else if (name == "parskip" && options.size() < 2 && (opts.empty() || prefixIs(opts, "skip="))) {
1725 h_paragraph_separation = "halfline";
1727 if (opts == "skip=\\smallskipamount")
1728 h_defskip = "smallskip";
1729 else if (opts == "skip=\\medskipamount")
1730 h_defskip = "medskip";
1731 else if (opts == "skip=\\bigskipamount")
1732 h_defskip = "bigskip";
1733 else if (opts == "skip=\\baselineskip")
1734 h_defskip = "fullline";
1737 h_paragraph_separation = "skip";
1741 else if (is_known(name, known_lyx_packages) && options.empty()) {
1742 if (name == "splitidx")
1743 h_use_indices = "true";
1744 else if (name == "minted")
1745 h_use_minted = true;
1746 else if (name == "refstyle")
1747 h_use_refstyle = true;
1748 else if (name == "prettyref")
1749 h_use_refstyle = false;
1751 if (!in_lyx_preamble) {
1752 h_preamble << package_beg_sep << name
1753 << package_mid_sep << "\\usepackage{"
1755 if (p.next_token().cat() == catNewline ||
1756 (p.next_token().cat() == catSpace &&
1757 p.next_next_token().cat() == catNewline))
1759 h_preamble << package_end_sep;
1763 else if (name == "geometry")
1764 handle_geometry(options);
1766 else if (name == "subfig")
1767 ; // ignore this FIXME: Use the package separator mechanism instead
1769 else if (char const * const * where = is_known(name, known_languages))
1770 h_language = known_coded_languages[where - known_languages];
1772 else if (name == "natbib") {
1773 h_biblio_style = "plainnat";
1774 h_cite_engine = "natbib";
1775 h_cite_engine_type = "authoryear";
1776 vector<string>::iterator it =
1777 find(options.begin(), options.end(), "authoryear");
1778 if (it != options.end())
1781 it = find(options.begin(), options.end(), "numbers");
1782 if (it != options.end()) {
1783 h_cite_engine_type = "numerical";
1787 if (!options.empty())
1788 h_biblio_options = join(options, ",");
1791 else if (name == "biblatex") {
1792 h_biblio_style = "plainnat";
1793 h_cite_engine = "biblatex";
1794 h_cite_engine_type = "authoryear";
1796 vector<string>::iterator it =
1797 find(options.begin(), options.end(), "natbib");
1798 if (it != options.end()) {
1800 h_cite_engine = "biblatex-natbib";
1802 opt = process_keyval_opt(options, "natbib");
1804 h_cite_engine = "biblatex-natbib";
1806 opt = process_keyval_opt(options, "style");
1808 h_biblatex_citestyle = opt;
1809 h_biblatex_bibstyle = opt;
1811 opt = process_keyval_opt(options, "citestyle");
1813 h_biblatex_citestyle = opt;
1814 opt = process_keyval_opt(options, "bibstyle");
1816 h_biblatex_bibstyle = opt;
1818 opt = process_keyval_opt(options, "refsection");
1820 if (opt == "none" || opt == "part"
1821 || opt == "chapter" || opt == "section"
1822 || opt == "subsection")
1825 cerr << "Ignoring unknown refsection value '"
1828 opt = process_keyval_opt(options, "bibencoding");
1831 if (!options.empty()) {
1832 h_biblio_options = join(options, ",");
1837 else if (name == "jurabib") {
1838 h_biblio_style = "jurabib";
1839 h_cite_engine = "jurabib";
1840 h_cite_engine_type = "authoryear";
1841 if (!options.empty())
1842 h_biblio_options = join(options, ",");
1845 else if (name == "bibtopic")
1846 h_use_bibtopic = "true";
1848 else if (name == "chapterbib")
1849 h_multibib = "child";
1851 else if (name == "hyperref")
1852 handle_hyperref(options);
1854 else if (name == "algorithm2e") {
1855 // Load "algorithm2e" module
1856 addModule("algorithm2e");
1857 // Add the package options to the global document options
1858 if (!options.empty()) {
1859 if (h_options.empty())
1860 h_options = join(options, ",");
1862 h_options += ',' + join(options, ",");
1865 else if (name == "microtype") {
1866 //we internally support only microtype without params
1867 if (options.empty())
1868 h_use_microtype = "true";
1870 h_preamble << "\\usepackage[" << opts << "]{microtype}";
1873 else if (name == "lineno") {
1874 h_use_lineno = "true";
1875 if (!options.empty()) {
1876 h_lineno_options = join(options, ",");
1881 else if (name == "changebar")
1882 h_output_changes = "true";
1884 else if (!in_lyx_preamble) {
1885 if (options.empty())
1886 h_preamble << "\\usepackage{" << name << '}';
1888 h_preamble << "\\usepackage[" << opts << "]{"
1892 if (p.next_token().cat() == catNewline ||
1893 (p.next_token().cat() == catSpace &&
1894 p.next_next_token().cat() == catNewline))
1898 // We need to do something with the options...
1899 if (!options.empty() && !detectEncoding)
1900 cerr << "Ignoring options '" << join(options, ",")
1901 << "' of package " << name << '.' << endl;
1903 // remove the whitespace
1908 void Preamble::handle_if(Parser & p, bool in_lyx_preamble)
1911 Token t = p.get_token();
1912 if (t.cat() == catEscape &&
1913 is_known(t.cs(), known_if_commands))
1914 handle_if(p, in_lyx_preamble);
1916 if (!in_lyx_preamble)
1917 h_preamble << t.asInput();
1918 if (t.cat() == catEscape && t.cs() == "fi")
1925 bool Preamble::writeLyXHeader(ostream & os, bool subdoc, string const & outfiledir)
1927 if (contains(h_float_placement, "H"))
1928 registerAutomaticallyLoadedPackage("float");
1929 if (h_spacing != "single" && h_spacing != "default")
1930 registerAutomaticallyLoadedPackage("setspace");
1931 if (h_use_packages["amsmath"] == "2") {
1932 // amsbsy and amstext are already provided by amsmath
1933 registerAutomaticallyLoadedPackage("amsbsy");
1934 registerAutomaticallyLoadedPackage("amstext");
1937 // output the LyX file settings
1938 // Important: Keep the version formatting in sync with LyX and
1939 // lyx2lyx (bug 7951)
1940 string const origin = roundtripMode() ? "roundtrip" : outfiledir;
1941 os << "#LyX file created by tex2lyx " << lyx_version_major << '.'
1942 << lyx_version_minor << '\n'
1943 << "\\lyxformat " << LYX_FORMAT << '\n'
1944 << "\\begin_document\n"
1945 << "\\begin_header\n"
1946 << "\\save_transient_properties " << h_save_transient_properties << "\n"
1947 << "\\origin " << origin << "\n"
1948 << "\\textclass " << h_textclass << "\n";
1949 string const raw = subdoc ? empty_string() : h_preamble.str();
1951 os << "\\begin_preamble\n";
1952 for (string::size_type i = 0; i < raw.size(); ++i) {
1953 if (raw[i] == package_beg_sep) {
1954 // Here follows some package loading code that
1955 // must be skipped if the package is loaded
1957 string::size_type j = raw.find(package_mid_sep, i);
1958 if (j == string::npos)
1960 string::size_type k = raw.find(package_end_sep, j);
1961 if (k == string::npos)
1963 string const package = raw.substr(i + 1, j - i - 1);
1964 string const replacement = raw.substr(j + 1, k - j - 1);
1965 if (auto_packages.find(package) == auto_packages.end())
1971 os << "\n\\end_preamble\n";
1973 if (!h_options.empty())
1974 os << "\\options " << h_options << "\n";
1975 os << "\\use_default_options " << h_use_default_options << "\n";
1976 if (!used_modules.empty()) {
1977 os << "\\begin_modules\n";
1978 vector<string>::const_iterator const end = used_modules.end();
1979 vector<string>::const_iterator it = used_modules.begin();
1980 for (; it != end; ++it)
1982 os << "\\end_modules\n";
1984 if (!h_includeonlys.empty()) {
1985 os << "\\begin_includeonly\n";
1986 for (auto const & iofile : h_includeonlys)
1987 os << iofile << '\n';
1988 os << "\\end_includeonly\n";
1990 os << "\\maintain_unincluded_children " << h_maintain_unincluded_children << "\n"
1991 << "\\language " << h_language << "\n"
1992 << "\\language_package " << h_language_package << "\n"
1993 << "\\inputencoding " << h_inputencoding << "\n"
1994 << "\\fontencoding " << h_fontencoding << "\n"
1995 << "\\font_roman \"" << h_font_roman[0]
1996 << "\" \"" << h_font_roman[1] << "\"\n"
1997 << "\\font_sans \"" << h_font_sans[0] << "\" \"" << h_font_sans[1] << "\"\n"
1998 << "\\font_typewriter \"" << h_font_typewriter[0]
1999 << "\" \"" << h_font_typewriter[1] << "\"\n"
2000 << "\\font_math \"" << h_font_math[0] << "\" \"" << h_font_math[1] << "\"\n"
2001 << "\\font_default_family " << h_font_default_family << "\n"
2002 << "\\use_non_tex_fonts " << (h_use_non_tex_fonts ? "true" : "false") << '\n'
2003 << "\\font_sc " << h_font_sc << "\n"
2004 << "\\font_roman_osf " << h_font_roman_osf << "\n"
2005 << "\\font_sans_osf " << h_font_sans_osf << "\n"
2006 << "\\font_typewriter_osf " << h_font_typewriter_osf << "\n";
2007 if (!h_font_roman_opts.empty())
2008 os << "\\font_roman_opts \"" << h_font_roman_opts << "\"" << '\n';
2009 os << "\\font_sf_scale " << h_font_sf_scale[0]
2010 << ' ' << h_font_sf_scale[1] << '\n';
2011 if (!h_font_sans_opts.empty())
2012 os << "\\font_sans_opts \"" << h_font_sans_opts << "\"" << '\n';
2013 os << "\\font_tt_scale " << h_font_tt_scale[0]
2014 << ' ' << h_font_tt_scale[1] << '\n';
2015 if (!h_font_cjk.empty())
2016 os << "\\font_cjk " << h_font_cjk << '\n';
2017 if (!h_font_typewriter_opts.empty())
2018 os << "\\font_typewriter_opts \"" << h_font_typewriter_opts << "\"" << '\n';
2019 os << "\\use_microtype " << h_use_microtype << '\n'
2020 << "\\use_dash_ligatures " << h_use_dash_ligatures << '\n'
2021 << "\\graphics " << h_graphics << '\n'
2022 << "\\default_output_format " << h_default_output_format << "\n"
2023 << "\\output_sync " << h_output_sync << "\n";
2024 if (h_output_sync == "1")
2025 os << "\\output_sync_macro \"" << h_output_sync_macro << "\"\n";
2026 os << "\\bibtex_command " << h_bibtex_command << "\n"
2027 << "\\index_command " << h_index_command << "\n";
2028 if (!h_float_placement.empty())
2029 os << "\\float_placement " << h_float_placement << "\n";
2030 os << "\\paperfontsize " << h_paperfontsize << "\n"
2031 << "\\spacing " << h_spacing << "\n"
2032 << "\\use_hyperref " << h_use_hyperref << '\n';
2033 if (h_use_hyperref == "true") {
2034 if (!h_pdf_title.empty())
2035 os << "\\pdf_title " << Lexer::quoteString(h_pdf_title) << '\n';
2036 if (!h_pdf_author.empty())
2037 os << "\\pdf_author " << Lexer::quoteString(h_pdf_author) << '\n';
2038 if (!h_pdf_subject.empty())
2039 os << "\\pdf_subject " << Lexer::quoteString(h_pdf_subject) << '\n';
2040 if (!h_pdf_keywords.empty())
2041 os << "\\pdf_keywords " << Lexer::quoteString(h_pdf_keywords) << '\n';
2042 os << "\\pdf_bookmarks " << h_pdf_bookmarks << "\n"
2043 "\\pdf_bookmarksnumbered " << h_pdf_bookmarksnumbered << "\n"
2044 "\\pdf_bookmarksopen " << h_pdf_bookmarksopen << "\n"
2045 "\\pdf_bookmarksopenlevel " << h_pdf_bookmarksopenlevel << "\n"
2046 "\\pdf_breaklinks " << h_pdf_breaklinks << "\n"
2047 "\\pdf_pdfborder " << h_pdf_pdfborder << "\n"
2048 "\\pdf_colorlinks " << h_pdf_colorlinks << "\n"
2049 "\\pdf_backref " << h_pdf_backref << "\n"
2050 "\\pdf_pdfusetitle " << h_pdf_pdfusetitle << '\n';
2051 if (!h_pdf_pagemode.empty())
2052 os << "\\pdf_pagemode " << h_pdf_pagemode << '\n';
2053 if (!h_pdf_quoted_options.empty())
2054 os << "\\pdf_quoted_options " << Lexer::quoteString(h_pdf_quoted_options) << '\n';
2056 os << "\\papersize " << h_papersize << "\n"
2057 << "\\use_geometry " << h_use_geometry << '\n';
2058 for (map<string, string>::const_iterator it = h_use_packages.begin();
2059 it != h_use_packages.end(); ++it)
2060 os << "\\use_package " << it->first << ' ' << it->second << '\n';
2061 os << "\\cite_engine " << h_cite_engine << '\n'
2062 << "\\cite_engine_type " << h_cite_engine_type << '\n'
2063 << "\\biblio_style " << h_biblio_style << "\n"
2064 << "\\use_bibtopic " << h_use_bibtopic << "\n";
2065 if (!h_biblio_options.empty())
2066 os << "\\biblio_options " << h_biblio_options << "\n";
2067 if (!h_biblatex_bibstyle.empty())
2068 os << "\\biblatex_bibstyle " << h_biblatex_bibstyle << "\n";
2069 if (!h_biblatex_citestyle.empty())
2070 os << "\\biblatex_citestyle " << h_biblatex_citestyle << "\n";
2071 if (!h_multibib.empty())
2072 os << "\\multibib " << h_multibib << "\n";
2073 os << "\\use_indices " << h_use_indices << "\n"
2074 << "\\paperorientation " << h_paperorientation << '\n'
2075 << "\\suppress_date " << h_suppress_date << '\n'
2076 << "\\justification " << h_justification << '\n'
2077 << "\\use_refstyle " << h_use_refstyle << '\n'
2078 << "\\use_minted " << h_use_minted << '\n'
2079 << "\\use_lineno " << h_use_lineno << '\n';
2080 if (!h_lineno_options.empty())
2081 os << "\\lineno_options " << h_lineno_options << '\n';
2082 if (!h_fontcolor.empty())
2083 os << "\\fontcolor " << h_fontcolor << '\n';
2084 if (!h_notefontcolor.empty())
2085 os << "\\notefontcolor " << h_notefontcolor << '\n';
2086 if (!h_backgroundcolor.empty())
2087 os << "\\backgroundcolor " << h_backgroundcolor << '\n';
2088 if (!h_boxbgcolor.empty())
2089 os << "\\boxbgcolor " << h_boxbgcolor << '\n';
2090 if (index_number != 0)
2091 for (int i = 0; i < index_number; i++) {
2092 os << "\\index " << h_index[i] << '\n'
2093 << "\\shortcut " << h_shortcut[i] << '\n'
2094 << "\\color " << h_color << '\n'
2098 os << "\\index " << h_index[0] << '\n'
2099 << "\\shortcut " << h_shortcut[0] << '\n'
2100 << "\\color " << h_color << '\n'
2104 << "\\secnumdepth " << h_secnumdepth << "\n"
2105 << "\\tocdepth " << h_tocdepth << "\n"
2106 << "\\paragraph_separation " << h_paragraph_separation << "\n";
2107 if (h_paragraph_separation == "skip")
2108 os << "\\defskip " << h_defskip << "\n";
2110 os << "\\paragraph_indentation " << h_paragraph_indentation << "\n";
2111 os << "\\is_math_indent " << h_is_mathindent << "\n";
2112 if (!h_mathindentation.empty())
2113 os << "\\math_indentation " << h_mathindentation << "\n";
2114 os << "\\math_numbering_side " << h_math_numbering_side << "\n";
2115 os << "\\quotes_style " << h_quotes_style << "\n"
2116 << "\\dynamic_quotes " << h_dynamic_quotes << "\n"
2117 << "\\papercolumns " << h_papercolumns << "\n"
2118 << "\\papersides " << h_papersides << "\n"
2119 << "\\paperpagestyle " << h_paperpagestyle << "\n";
2120 if (!h_listings_params.empty())
2121 os << "\\listings_params " << h_listings_params << "\n";
2122 os << "\\tracking_changes " << h_tracking_changes << "\n"
2123 << "\\output_changes " << h_output_changes << "\n"
2124 << "\\change_bars " << h_change_bars << "\n"
2125 << "\\html_math_output " << h_html_math_output << "\n"
2126 << "\\html_css_as_file " << h_html_css_as_file << "\n"
2127 << "\\html_be_strict " << h_html_be_strict << "\n"
2128 << "\\docbook_table_output " << h_docbook_table_output << "\n"
2130 << "\\end_header\n\n"
2131 << "\\begin_body\n";
2136 void Preamble::parse(Parser & p, string const & forceclass,
2137 TeX2LyXDocClass & tc)
2139 // initialize fixed types
2140 special_columns_['D'] = 3;
2141 parse(p, forceclass, false, tc);
2145 void Preamble::parse(Parser & p, string const & forceclass,
2146 bool detectEncoding, TeX2LyXDocClass & tc)
2148 bool is_full_document = false;
2149 bool is_lyx_file = false;
2150 bool in_lyx_preamble = false;
2151 bool class_set = false;
2153 // determine whether this is a full document or a fragment for inclusion
2155 Token const & t = p.get_token();
2157 if (t.cat() == catEscape && t.cs() == "documentclass") {
2158 is_full_document = true;
2164 if (detectEncoding && !is_full_document)
2167 while (is_full_document && p.good()) {
2168 if (detectEncoding && h_inputencoding != "auto-legacy" &&
2169 h_inputencoding != "auto-legacy-plain")
2172 // Force textclass if the user wanted it
2173 if (!forceclass.empty()) {
2174 setTextClass(forceclass, tc);
2178 Token const & t = p.get_token();
2181 if (!detectEncoding)
2182 cerr << "t: " << t << '\n';
2188 if (!in_lyx_preamble &&
2189 (t.cat() == catLetter ||
2190 t.cat() == catSuper ||
2191 t.cat() == catSub ||
2192 t.cat() == catOther ||
2193 t.cat() == catMath ||
2194 t.cat() == catActive ||
2195 t.cat() == catBegin ||
2196 t.cat() == catEnd ||
2197 t.cat() == catAlign ||
2198 t.cat() == catParameter)) {
2199 h_preamble << t.cs();
2203 if (!in_lyx_preamble &&
2204 (t.cat() == catSpace || t.cat() == catNewline)) {
2205 h_preamble << t.asInput();
2209 if (t.cat() == catComment) {
2210 static regex const islyxfile("%% LyX .* created this file");
2211 static regex const usercommands("User specified LaTeX commands");
2213 string const comment = t.asInput();
2215 // magically switch encoding default if it looks like XeLaTeX
2216 static string const magicXeLaTeX =
2217 "% This document must be compiled with XeLaTeX ";
2218 if (comment.size() > magicXeLaTeX.size()
2219 && comment.substr(0, magicXeLaTeX.size()) == magicXeLaTeX
2220 && h_inputencoding == "auto-legacy") {
2221 if (!detectEncoding)
2222 cerr << "XeLaTeX comment found, switching to UTF8\n";
2223 h_inputencoding = "utf8";
2226 if (regex_search(comment, sub, islyxfile)) {
2228 in_lyx_preamble = true;
2229 } else if (is_lyx_file
2230 && regex_search(comment, sub, usercommands))
2231 in_lyx_preamble = false;
2232 else if (!in_lyx_preamble)
2233 h_preamble << t.asInput();
2237 if (t.cs() == "PassOptionsToPackage") {
2238 string const poptions = p.getArg('{', '}');
2239 string const package = p.verbatim_item();
2240 extra_package_options_.insert(make_pair(package, poptions));
2244 if (t.cs() == "pagestyle") {
2245 h_paperpagestyle = p.verbatim_item();
2249 if (t.cs() == "setdefaultlanguage") {
2251 // We don't yet care about non-language variant options
2252 // because LyX doesn't support this yet, see bug #8214
2254 string langopts = p.getOpt();
2255 // check if the option contains a variant, if yes, extract it
2256 string::size_type pos_var = langopts.find("variant");
2257 string::size_type i = langopts.find(',', pos_var);
2258 string::size_type k = langopts.find('=', pos_var);
2259 if (pos_var != string::npos){
2261 if (i == string::npos)
2262 variant = langopts.substr(k + 1, langopts.length() - k - 2);
2264 variant = langopts.substr(k + 1, i - k - 1);
2265 h_language = variant;
2269 h_language = p.verbatim_item();
2270 //finally translate the poyglossia name to a LyX name
2271 h_language = polyglossia2lyx(h_language);
2275 if (t.cs() == "setotherlanguage") {
2276 // We don't yet care about the option because LyX doesn't
2277 // support this yet, see bug #8214
2278 p.hasOpt() ? p.getOpt() : string();
2283 if (t.cs() == "setmainfont") {
2284 string fontopts = p.hasOpt() ? p.getArg('[', ']') : string();
2285 h_font_roman[1] = p.getArg('{', '}');
2286 if (!fontopts.empty()) {
2287 vector<string> opts = getVectorFromString(fontopts);
2289 for (auto const & opt : opts) {
2290 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2293 if (!fontopts.empty())
2297 h_font_roman_opts = fontopts;
2302 if (t.cs() == "setsansfont" || t.cs() == "setmonofont") {
2303 // LyX currently only supports the scale option
2304 string scale, fontopts;
2306 fontopts = p.getArg('[', ']');
2307 if (!fontopts.empty()) {
2308 vector<string> opts = getVectorFromString(fontopts);
2310 for (auto const & opt : opts) {
2311 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2314 if (prefixIs(opt, "Scale=")) {
2315 scale_as_percentage(opt, scale);
2318 if (!fontopts.empty())
2324 if (t.cs() == "setsansfont") {
2326 h_font_sf_scale[1] = scale;
2327 h_font_sans[1] = p.getArg('{', '}');
2328 if (!fontopts.empty())
2329 h_font_sans_opts = fontopts;
2332 h_font_tt_scale[1] = scale;
2333 h_font_typewriter[1] = p.getArg('{', '}');
2334 if (!fontopts.empty())
2335 h_font_typewriter_opts = fontopts;
2340 if (t.cs() == "babelfont") {
2342 h_use_non_tex_fonts = true;
2343 h_language_package = "babel";
2344 if (h_inputencoding == "auto-legacy")
2345 p.setEncoding("UTF-8");
2346 // we don't care about the lang option
2347 string const lang = p.hasOpt() ? p.getArg('[', ']') : string();
2348 string const family = p.getArg('{', '}');
2349 string fontopts = p.hasOpt() ? p.getArg('[', ']') : string();
2350 string const fontname = p.getArg('{', '}');
2351 if (lang.empty() && family == "rm") {
2352 h_font_roman[1] = fontname;
2353 if (!fontopts.empty()) {
2354 vector<string> opts = getVectorFromString(fontopts);
2356 for (auto const & opt : opts) {
2357 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2360 if (!fontopts.empty())
2364 h_font_roman_opts = fontopts;
2367 } else if (lang.empty() && (family == "sf" || family == "tt")) {
2369 if (!fontopts.empty()) {
2370 vector<string> opts = getVectorFromString(fontopts);
2372 for (auto const & opt : opts) {
2373 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2376 if (prefixIs(opt, "Scale=")) {
2377 scale_as_percentage(opt, scale);
2380 if (!fontopts.empty())
2385 if (family == "sf") {
2387 h_font_sf_scale[1] = scale;
2388 h_font_sans[1] = fontname;
2389 if (!fontopts.empty())
2390 h_font_sans_opts = fontopts;
2393 h_font_tt_scale[1] = scale;
2394 h_font_typewriter[1] = fontname;
2395 if (!fontopts.empty())
2396 h_font_typewriter_opts = fontopts;
2400 // not rm, sf or tt or lang specific
2401 h_preamble << '\\' << t.cs();
2403 h_preamble << '[' << lang << ']';
2404 h_preamble << '{' << family << '}';
2405 if (!fontopts.empty())
2406 h_preamble << '[' << fontopts << ']';
2407 h_preamble << '{' << fontname << '}' << '\n';
2412 if (t.cs() == "date") {
2413 string argument = p.getArg('{', '}');
2414 if (argument.empty())
2415 h_suppress_date = "true";
2417 h_preamble << t.asInput() << '{' << argument << '}';
2421 if (t.cs() == "color") {
2422 string const space =
2423 (p.hasOpt() ? p.getOpt() : string());
2424 string argument = p.getArg('{', '}');
2425 // check the case that a standard color is used
2426 if (space.empty() && is_known(argument, known_basic_colors)) {
2427 h_fontcolor = rgbcolor2code(argument);
2428 registerAutomaticallyLoadedPackage("color");
2429 } else if (space.empty() && argument == "document_fontcolor")
2430 registerAutomaticallyLoadedPackage("color");
2431 // check the case that LyX's document_fontcolor is defined
2432 // but not used for \color
2434 h_preamble << t.asInput();
2436 h_preamble << space;
2437 h_preamble << '{' << argument << '}';
2438 // the color might already be set because \definecolor
2439 // is parsed before this
2445 if (t.cs() == "pagecolor") {
2446 string argument = p.getArg('{', '}');
2447 // check the case that a standard color is used
2448 if (is_known(argument, known_basic_colors)) {
2449 h_backgroundcolor = rgbcolor2code(argument);
2450 } else if (argument == "page_backgroundcolor")
2451 registerAutomaticallyLoadedPackage("color");
2452 // check the case that LyX's page_backgroundcolor is defined
2453 // but not used for \pagecolor
2455 h_preamble << t.asInput() << '{' << argument << '}';
2456 // the color might already be set because \definecolor
2457 // is parsed before this
2458 h_backgroundcolor = "";
2463 if (t.cs() == "makeatletter") {
2464 // LyX takes care of this
2465 p.setCatcode('@', catLetter);
2469 if (t.cs() == "makeatother") {
2470 // LyX takes care of this
2471 p.setCatcode('@', catOther);
2475 if (t.cs() == "makeindex") {
2476 // LyX will re-add this if a print index command is found
2481 if (t.cs() == "newindex") {
2482 string const indexname = p.getArg('[', ']');
2483 string const shortcut = p.verbatim_item();
2484 if (!indexname.empty())
2485 h_index[index_number] = indexname;
2487 h_index[index_number] = shortcut;
2488 h_shortcut[index_number] = shortcut;
2494 if (t.cs() == "addbibresource") {
2495 string const options = p.getArg('[', ']');
2496 string const arg = removeExtension(p.getArg('{', '}'));
2497 if (!options.empty()) {
2498 // check if the option contains a bibencoding, if yes, extract it
2499 string::size_type pos = options.find("bibencoding=");
2501 if (pos != string::npos) {
2502 string::size_type i = options.find(',', pos);
2503 if (i == string::npos)
2504 encoding = options.substr(pos + 1);
2506 encoding = options.substr(pos, i - pos);
2507 pos = encoding.find('=');
2508 if (pos == string::npos)
2511 encoding = encoding.substr(pos + 1);
2513 if (!encoding.empty())
2514 biblatex_encodings.push_back(normalize_filename(arg) + ' ' + encoding);
2516 biblatex_bibliographies.push_back(arg);
2520 if (t.cs() == "bibliography") {
2521 vector<string> bibs = getVectorFromString(p.getArg('{', '}'));
2522 biblatex_bibliographies.insert(biblatex_bibliographies.end(), bibs.begin(), bibs.end());
2526 if (t.cs() == "RS@ifundefined") {
2527 string const name = p.verbatim_item();
2528 string const body1 = p.verbatim_item();
2529 string const body2 = p.verbatim_item();
2530 // only non-lyxspecific stuff
2531 if (in_lyx_preamble &&
2532 (name == "subsecref" || name == "thmref" || name == "lemref"))
2536 ss << '\\' << t.cs();
2537 ss << '{' << name << '}'
2538 << '{' << body1 << '}'
2539 << '{' << body2 << '}';
2540 h_preamble << ss.str();
2545 if (t.cs() == "AtBeginDocument") {
2546 string const name = p.verbatim_item();
2547 // only non-lyxspecific stuff
2548 if (in_lyx_preamble &&
2549 (name == "\\providecommand\\partref[1]{\\ref{part:#1}}"
2550 || name == "\\providecommand\\chapref[1]{\\ref{chap:#1}}"
2551 || name == "\\providecommand\\secref[1]{\\ref{sec:#1}}"
2552 || name == "\\providecommand\\subsecref[1]{\\ref{subsec:#1}}"
2553 || name == "\\providecommand\\parref[1]{\\ref{par:#1}}"
2554 || name == "\\providecommand\\figref[1]{\\ref{fig:#1}}"
2555 || name == "\\providecommand\\tabref[1]{\\ref{tab:#1}}"
2556 || name == "\\providecommand\\algref[1]{\\ref{alg:#1}}"
2557 || name == "\\providecommand\\fnref[1]{\\ref{fn:#1}}"
2558 || name == "\\providecommand\\enuref[1]{\\ref{enu:#1}}"
2559 || name == "\\providecommand\\eqref[1]{\\ref{eq:#1}}"
2560 || name == "\\providecommand\\lemref[1]{\\ref{lem:#1}}"
2561 || name == "\\providecommand\\thmref[1]{\\ref{thm:#1}}"
2562 || name == "\\providecommand\\corref[1]{\\ref{cor:#1}}"
2563 || name == "\\providecommand\\propref[1]{\\ref{prop:#1}}"))
2567 ss << '\\' << t.cs();
2568 ss << '{' << name << '}';
2569 h_preamble << ss.str();
2574 if (t.cs() == "newcommand" || t.cs() == "newcommandx"
2575 || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
2576 || t.cs() == "providecommand" || t.cs() == "providecommandx"
2577 || t.cs() == "DeclareRobustCommand"
2578 || t.cs() == "DeclareRobustCommandx"
2579 || t.cs() == "ProvideTextCommandDefault"
2580 || t.cs() == "DeclareMathAccent") {
2582 if (p.next_token().character() == '*') {
2586 string const name = p.verbatim_item();
2587 string const opt1 = p.getFullOpt();
2588 string const opt2 = p.getFullOpt();
2589 string const body = p.verbatim_item();
2590 // store the in_lyx_preamble setting
2591 bool const was_in_lyx_preamble = in_lyx_preamble;
2593 if (name == "\\rmdefault")
2594 if (is_known(body, known_roman_font_packages)) {
2595 h_font_roman[0] = body;
2597 in_lyx_preamble = true;
2599 if (name == "\\sfdefault") {
2600 if (is_known(body, known_sans_font_packages)) {
2601 h_font_sans[0] = body;
2603 in_lyx_preamble = true;
2605 if (body == "LibertinusSans-OsF") {
2606 h_font_sans[0] = "LibertinusSans-LF";
2607 h_font_sans_osf = "true";
2609 in_lyx_preamble = true;
2612 if (name == "\\ttdefault")
2613 if (is_known(body, known_typewriter_font_packages)) {
2614 h_font_typewriter[0] = body;
2616 in_lyx_preamble = true;
2618 if (name == "\\familydefault") {
2619 string family = body;
2620 // remove leading "\"
2621 h_font_default_family = family.erase(0,1);
2623 in_lyx_preamble = true;
2625 if (name == "\\LibertinusSans@scale") {
2626 if (isStrDbl(body)) {
2627 h_font_sf_scale[0] = convert<string>(
2628 static_cast<int>(100 * convert<double>(body)));
2631 if (name == "\\LibertinusMono@scale") {
2632 if (isStrDbl(body)) {
2633 h_font_tt_scale[0] = convert<string>(
2634 static_cast<int>(100 * convert<double>(body)));
2638 // remove LyX-specific definitions that are re-added by LyX
2640 // \lyxline is an ancient command that is converted by tex2lyx into
2641 // a \rule therefore remove its preamble code
2642 if (name == "\\lyxdot" || name == "\\lyxarrow"
2643 || name == "\\lyxline" || name == "\\LyX") {
2645 in_lyx_preamble = true;
2648 // Add the command to the known commands
2649 add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
2651 // only non-lyxspecific stuff
2652 if (!in_lyx_preamble) {
2654 ss << '\\' << t.cs();
2657 ss << '{' << name << '}' << opt1 << opt2
2658 << '{' << body << "}";
2659 if (prefixIs(t.cs(), "renew") || !contains(h_preamble.str(), ss.str()))
2660 h_preamble << ss.str();
2662 ostream & out = in_preamble ? h_preamble : os;
2663 out << "\\" << t.cs() << "{" << name << "}"
2664 << opts << "{" << body << "}";
2667 // restore the in_lyx_preamble setting
2668 in_lyx_preamble = was_in_lyx_preamble;
2672 if (t.cs() == "documentclass") {
2673 vector<string>::iterator it;
2674 vector<string> opts = split_options(p.getArg('[', ']'));
2675 // FIXME This does not work for classes that have a
2676 // different name in LyX than in LaTeX
2677 string const tclass = p.getArg('{', '}');
2679 // Only set text class if a class hasn't been forced
2680 // (this was set above)
2682 // textclass needs to be set at this place (if not already done)
2683 // as we need to know it for other parameters
2684 // (such as class-dependent paper size)
2685 setTextClass(tclass, tc);
2690 // Try those who are (most likely) known to all packages first
2691 handle_opt(opts, known_fontsizes, h_paperfontsize);
2692 delete_opt(opts, known_fontsizes);
2693 // delete "pt" at the end
2694 string::size_type i = h_paperfontsize.find("pt");
2695 if (i != string::npos)
2696 h_paperfontsize.erase(i);
2697 // Now those known specifically to the class
2698 vector<string> class_fsizes = getVectorFromString(tc.opt_fontsize(), "|");
2699 string const fsize_format = tc.fontsizeformat();
2700 for (auto const & fsize : class_fsizes) {
2701 string latexsize = subst(fsize_format, "$$s", fsize);
2702 vector<string>::iterator it = find(opts.begin(), opts.end(), latexsize);
2703 if (it != opts.end()) {
2704 h_paperfontsize = fsize;
2710 // The documentclass options are always parsed before the options
2711 // of the babel call so that a language cannot overwrite the babel
2713 handle_opt(opts, known_languages, h_language);
2714 delete_opt(opts, known_languages);
2717 if ((it = find(opts.begin(), opts.end(), "fleqn"))
2719 h_is_mathindent = "1";
2722 // formula numbering side
2723 if ((it = find(opts.begin(), opts.end(), "leqno"))
2725 h_math_numbering_side = "left";
2728 else if ((it = find(opts.begin(), opts.end(), "reqno"))
2730 h_math_numbering_side = "right";
2734 // paper orientation
2735 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
2736 h_paperorientation = "landscape";
2740 if ((it = find(opts.begin(), opts.end(), "oneside"))
2745 if ((it = find(opts.begin(), opts.end(), "twoside"))
2751 if ((it = find(opts.begin(), opts.end(), "onecolumn"))
2753 h_papercolumns = "1";
2756 if ((it = find(opts.begin(), opts.end(), "twocolumn"))
2758 h_papercolumns = "2";
2762 // some size options are known by the document class, other sizes
2763 // are handled by the \geometry command of the geometry package
2764 vector<string> class_psizes = getVectorFromString(tc.opt_pagesize(), "|");
2765 string const psize_format = tc.pagesizeformat();
2766 for (auto const & psize : class_psizes) {
2767 string latexsize = subst(psize_format, "$$s", psize);
2768 vector<string>::iterator it = find(opts.begin(), opts.end(), latexsize);
2769 if (it != opts.end()) {
2770 h_papersize = psize;
2774 if (psize_format == "$$spaper")
2776 // Also try with the default format since this is understood by
2778 latexsize = psize + "paper";
2779 it = find(opts.begin(), opts.end(), latexsize);
2780 if (it != opts.end()) {
2781 h_papersize = psize;
2786 // the remaining options
2787 h_options = join(opts, ",");
2791 if (t.cs() == "usepackage") {
2792 string const options = p.getArg('[', ']');
2793 string const name = p.getArg('{', '}');
2794 vector<string> vecnames;
2795 split(name, vecnames, ',');
2796 vector<string>::const_iterator it = vecnames.begin();
2797 vector<string>::const_iterator end = vecnames.end();
2798 for (; it != end; ++it)
2799 handle_package(p, trimSpaceAndEol(*it), options,
2800 in_lyx_preamble, detectEncoding);
2804 if (t.cs() == "inputencoding") {
2805 string const encoding = p.getArg('{','}');
2806 Encoding const * const enc = encodings.fromLaTeXName(
2807 encoding, Encoding::inputenc, true);
2809 if (!detectEncoding)
2810 cerr << "Unknown encoding " << encoding
2811 << ". Ignoring." << std::endl;
2814 h_inputencoding = enc->name();
2815 p.setEncoding(enc->iconvName());
2820 if (t.cs() == "newenvironment") {
2821 string const name = p.getArg('{', '}');
2822 string const opt1 = p.getFullOpt();
2823 string const opt2 = p.getFullOpt();
2824 string const beg = p.verbatim_item();
2825 string const end = p.verbatim_item();
2826 if (!in_lyx_preamble) {
2827 h_preamble << "\\newenvironment{" << name
2828 << '}' << opt1 << opt2 << '{'
2829 << beg << "}{" << end << '}';
2831 add_known_environment(name, opt1, !opt2.empty(),
2832 from_utf8(beg), from_utf8(end));
2836 if (t.cs() == "newtheorem") {
2838 if (p.next_token().character() == '*') {
2842 string const name = p.getArg('{', '}');
2843 string const opt1 = p.getFullOpt();
2844 string const opt2 = p.getFullOpt();
2845 string const body = p.verbatim_item();
2846 string const opt3 = p.getFullOpt();
2847 string const cmd = star ? "\\newtheorem*" : "\\newtheorem";
2849 string const complete = cmd + "{" + name + '}' +
2850 opt1 + opt2 + '{' + body + '}' + opt3;
2852 add_known_theorem(name, opt1, !opt2.empty(), from_utf8(complete));
2854 if (!in_lyx_preamble)
2855 h_preamble << complete;
2859 if (t.cs() == "def") {
2860 string name = p.get_token().cs();
2861 // In fact, name may be more than the name:
2862 // In the test case of bug 8116
2863 // name == "csname SF@gobble@opt \endcsname".
2864 // Therefore, we need to use asInput() instead of cs().
2865 while (p.next_token().cat() != catBegin)
2866 name += p.get_token().asInput();
2867 if (!in_lyx_preamble)
2868 h_preamble << "\\def\\" << name << '{'
2869 << p.verbatim_item() << "}";
2873 if (t.cs() == "newcolumntype") {
2874 string const name = p.getArg('{', '}');
2875 trimSpaceAndEol(name);
2877 string opts = p.getOpt();
2878 if (!opts.empty()) {
2879 istringstream is(string(opts, 1));
2882 special_columns_[name[0]] = nargs;
2883 h_preamble << "\\newcolumntype{" << name << "}";
2885 h_preamble << "[" << nargs << "]";
2886 h_preamble << "{" << p.verbatim_item() << "}";
2890 if (t.cs() == "setcounter") {
2891 string const name = p.getArg('{', '}');
2892 string const content = p.getArg('{', '}');
2893 if (name == "secnumdepth")
2894 h_secnumdepth = content;
2895 else if (name == "tocdepth")
2896 h_tocdepth = content;
2898 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
2902 if (t.cs() == "setlength") {
2903 string const name = p.verbatim_item();
2904 string const content = p.verbatim_item();
2905 // the paragraphs are only not indented when \parindent is set to zero
2906 if (name == "\\parindent" && content != "")
2907 h_paragraph_indentation = translate_len(content);
2908 else if (name == "\\parskip" && isPackageUsed("parskip")) {
2909 if (content == "\\smallskipamount")
2910 h_defskip = "smallskip";
2911 else if (content == "\\medskipamount")
2912 h_defskip = "medskip";
2913 else if (content == "\\bigskipamount")
2914 h_defskip = "bigskip";
2915 else if (content == "\\baselineskip")
2916 h_defskip = "fullline";
2918 h_defskip = translate_len(content);
2919 } else if (name == "\\mathindent") {
2920 h_mathindentation = translate_len(content);
2922 h_preamble << "\\setlength{" << name << "}{" << content << "}";
2926 if (t.cs() == "onehalfspacing") {
2927 h_spacing = "onehalf";
2931 if (t.cs() == "doublespacing") {
2932 h_spacing = "double";
2936 if (t.cs() == "setstretch") {
2937 h_spacing = "other " + p.verbatim_item();
2941 if (t.cs() == "synctex") {
2942 // the scheme is \synctex=value
2943 // where value can only be "1" or "-1"
2944 h_output_sync = "1";
2945 // there can be any character behind the value (e.g. a linebreak or a '\'
2946 // therefore we extract it char by char
2948 string value = p.get_token().asInput();
2950 value += p.get_token().asInput();
2951 h_output_sync_macro = "\\synctex=" + value;
2955 if (t.cs() == "begin") {
2956 string const name = p.getArg('{', '}');
2957 if (name == "document")
2959 h_preamble << "\\begin{" << name << "}";
2963 if (t.cs() == "geometry") {
2964 vector<string> opts = split_options(p.getArg('{', '}'));
2965 handle_geometry(opts);
2969 if (t.cs() == "definecolor") {
2970 string const color = p.getArg('{', '}');
2971 string const space = p.getArg('{', '}');
2972 string const value = p.getArg('{', '}');
2973 if (color == "document_fontcolor" && space == "rgb") {
2974 RGBColor c(RGBColorFromLaTeX(value));
2975 h_fontcolor = X11hexname(c);
2976 } else if (color == "note_fontcolor" && space == "rgb") {
2977 RGBColor c(RGBColorFromLaTeX(value));
2978 h_notefontcolor = X11hexname(c);
2979 } else if (color == "page_backgroundcolor" && space == "rgb") {
2980 RGBColor c(RGBColorFromLaTeX(value));
2981 h_backgroundcolor = X11hexname(c);
2982 } else if (color == "shadecolor" && space == "rgb") {
2983 RGBColor c(RGBColorFromLaTeX(value));
2984 h_boxbgcolor = X11hexname(c);
2986 h_preamble << "\\definecolor{" << color
2987 << "}{" << space << "}{" << value
2993 if (t.cs() == "bibliographystyle") {
2994 h_biblio_style = p.verbatim_item();
2998 if (t.cs() == "jurabibsetup") {
2999 // FIXME p.getArg('{', '}') is most probably wrong (it
3000 // does not handle nested braces).
3001 // Use p.verbatim_item() instead.
3002 vector<string> jurabibsetup =
3003 split_options(p.getArg('{', '}'));
3004 // add jurabibsetup to the jurabib package options
3005 add_package("jurabib", jurabibsetup);
3006 if (!jurabibsetup.empty()) {
3007 h_preamble << "\\jurabibsetup{"
3008 << join(jurabibsetup, ",") << '}';
3013 if (t.cs() == "hypersetup") {
3014 vector<string> hypersetup =
3015 split_options(p.verbatim_item());
3016 // add hypersetup to the hyperref package options
3017 handle_hyperref(hypersetup);
3018 if (!hypersetup.empty()) {
3019 h_preamble << "\\hypersetup{"
3020 << join(hypersetup, ",") << '}';
3025 if (t.cs() == "includeonly") {
3026 vector<string> includeonlys = getVectorFromString(p.getArg('{', '}'));
3027 for (auto & iofile : includeonlys) {
3028 string filename(normalize_filename(iofile));
3029 string const path = getMasterFilePath(true);
3030 // We want to preserve relative/absolute filenames,
3031 // therefore path is only used for testing
3032 if (!makeAbsPath(filename, path).exists()) {
3033 // The file extension is probably missing.
3034 // Now try to find it out.
3035 string const tex_name =
3036 find_file(filename, path,
3037 known_tex_extensions);
3038 if (!tex_name.empty())
3039 filename = tex_name;
3042 if (makeAbsPath(filename, path).exists())
3043 fix_child_filename(filename);
3045 cerr << "Warning: Could not find included file '"
3046 << filename << "'." << endl;
3047 outname = changeExtension(filename, "lyx");
3048 h_includeonlys.push_back(outname);
3053 if (is_known(t.cs(), known_if_3arg_commands)) {
3054 // prevent misparsing of \usepackage if it is used
3055 // as an argument (see e.g. our own output of
3056 // \@ifundefined above)
3057 string const arg1 = p.verbatim_item();
3058 string const arg2 = p.verbatim_item();
3059 string const arg3 = p.verbatim_item();
3060 // test case \@ifundefined{date}{}{\date{}}
3061 if (t.cs() == "@ifundefined" && arg1 == "date" &&
3062 arg2.empty() && arg3 == "\\date{}") {
3063 h_suppress_date = "true";
3064 // older tex2lyx versions did output
3065 // \@ifundefined{definecolor}{\usepackage{color}}{}
3066 } else if (t.cs() == "@ifundefined" &&
3067 arg1 == "definecolor" &&
3068 arg2 == "\\usepackage{color}" &&
3070 if (!in_lyx_preamble)
3071 h_preamble << package_beg_sep
3074 << "\\@ifundefined{definecolor}{color}{}"
3077 //\@ifundefined{showcaptionsetup}{}{%
3078 // \PassOptionsToPackage{caption=false}{subfig}}
3079 // that LyX uses for subfloats
3080 } else if (t.cs() == "@ifundefined" &&
3081 arg1 == "showcaptionsetup" && arg2.empty()
3082 && arg3 == "%\n \\PassOptionsToPackage{caption=false}{subfig}") {
3084 } else if (!in_lyx_preamble) {
3085 h_preamble << t.asInput()
3086 << '{' << arg1 << '}'
3087 << '{' << arg2 << '}'
3088 << '{' << arg3 << '}';
3093 if (is_known(t.cs(), known_if_commands)) {
3094 // must not parse anything in conditional code, since
3095 // LyX would output the parsed contents unconditionally
3096 if (!in_lyx_preamble)
3097 h_preamble << t.asInput();
3098 handle_if(p, in_lyx_preamble);
3102 if (!t.cs().empty() && !in_lyx_preamble) {
3103 h_preamble << '\\' << t.cs();
3108 // set textclass if not yet done (snippets without \documentclass and forced class)
3110 setTextClass(h_textclass, tc);
3112 // remove the whitespace
3115 if (h_papersides.empty()) {
3118 h_papersides = ss.str();
3121 // If the CJK package is used we cannot set the document language from
3122 // the babel options. Instead, we guess which language is used most
3123 // and set this one.
3124 default_language = h_language;
3125 if (is_full_document &&
3126 (auto_packages.find("CJK") != auto_packages.end() ||
3127 auto_packages.find("CJKutf8") != auto_packages.end())) {
3129 h_language = guessLanguage(p, default_language);
3131 if (explicit_babel && h_language != default_language) {
3132 // We set the document language to a CJK language,
3133 // but babel is explicitly called in the user preamble
3134 // without options. LyX will not add the default
3135 // language to the document options if it is either
3136 // english, or no text is set as default language.
3137 // Therefore we need to add a language option explicitly.
3138 // FIXME: It would be better to remove all babel calls
3139 // from the user preamble, but this is difficult
3140 // without re-introducing bug 7861.
3141 if (h_options.empty())
3142 h_options = lyx2babel(default_language);
3144 h_options += ',' + lyx2babel(default_language);
3148 // Finally, set the quote style.
3149 // LyX knows the following quotes styles:
3150 // british, cjk, cjkangle, danish, english, french, german,
3151 // polish, russian, swedish and swiss
3152 // conversion list taken from
3153 // https://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
3154 // (quotes for kazakh are unknown)
3156 if (is_known(h_language, known_british_quotes_languages))
3157 h_quotes_style = "british";
3159 else if (is_known(h_language, known_cjk_quotes_languages))
3160 h_quotes_style = "cjk";
3162 else if (is_known(h_language, known_cjkangle_quotes_languages))
3163 h_quotes_style = "cjkangle";
3165 else if (is_known(h_language, known_danish_quotes_languages))
3166 h_quotes_style = "danish";
3168 else if (is_known(h_language, known_french_quotes_languages))
3169 h_quotes_style = "french";
3171 else if (is_known(h_language, known_german_quotes_languages))
3172 h_quotes_style = "german";
3174 else if (is_known(h_language, known_polish_quotes_languages))
3175 h_quotes_style = "polish";
3177 else if (is_known(h_language, known_russian_quotes_languages))
3178 h_quotes_style = "russian";
3180 else if (is_known(h_language, known_swedish_quotes_languages))
3181 h_quotes_style = "swedish";
3183 else if (is_known(h_language, known_swiss_quotes_languages))
3184 h_quotes_style = "swiss";
3186 else if (is_known(h_language, known_english_quotes_languages))
3187 h_quotes_style = "english";
3191 string Preamble::parseEncoding(Parser & p, string const & forceclass)
3193 TeX2LyXDocClass dummy;
3194 parse(p, forceclass, true, dummy);
3195 if (h_inputencoding != "auto-legacy" && h_inputencoding != "auto-legacy-plain")
3196 return h_inputencoding;
3201 string babel2lyx(string const & language)
3203 char const * const * where = is_known(language, known_languages);
3205 return known_coded_languages[where - known_languages];
3210 string lyx2babel(string const & language)
3212 char const * const * where = is_known(language, known_coded_languages);
3214 return known_languages[where - known_coded_languages];
3219 string Preamble::polyglossia2lyx(string const & language)
3221 char const * const * where = is_known(language, polyglossia_languages);
3223 return coded_polyglossia_languages[where - polyglossia_languages];
3228 string rgbcolor2code(string const & name)
3230 char const * const * where = is_known(name, known_basic_colors);
3232 // "red", "green" etc
3233 return known_basic_color_codes[where - known_basic_colors];
3235 // "255,0,0", "0,255,0" etc
3236 RGBColor c(RGBColorFromLaTeX(name));
3237 return X11hexname(c);