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"
23 #include "TextClass.h"
26 #include "support/convert.h"
27 #include "support/FileName.h"
28 #include "support/filetools.h"
29 #include "support/lstrings.h"
31 #include "support/regex.h"
37 using namespace lyx::support;
46 // CJK languages are handled in text.cpp, polyglossia languages are listed
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!
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",
71 * the same as known_languages with .lyx names
72 * please keep this in sync with known_languages line by line!
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",
91 /// languages with british quotes (.lyx names)
92 const char * const known_british_quotes_languages[] = {"british", "welsh", 0};
94 /// languages with cjk quotes (.lyx names)
95 const char * const known_cjk_quotes_languages[] = {"chinese-traditional",
96 "japanese", "japanese-cjk", 0};
98 /// languages with cjk-angle quotes (.lyx names)
99 const char * const known_cjkangle_quotes_languages[] = {"korean", 0};
101 /// languages with danish quotes (.lyx names)
102 const char * const known_danish_quotes_languages[] = {"danish", 0};
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};
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};
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",
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};
126 /// languages with russian quotes (.lyx names)
127 const char * const known_russian_quotes_languages[] = {"azerbaijani", "oldrussian",
128 "russian", "ukrainian", 0};
130 /// languages with swedish quotes (.lyx names)
131 const char * const known_swedish_quotes_languages[] = {"finnish", "swedish", 0};
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};
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};
142 char const * const known_fontsizes[] = { "10pt", "11pt", "12pt", 0 };
144 const char * const known_roman_font_packages[] = { "ae", "beraserif", "bookman",
145 "ccfonts", "chancery", "charter", "cmr", "cochineal", "crimson", "CrimsonPro", "DejaVuSerif",
146 "DejaVuSerifCondensed", "fourier", "garamondx", "libertine", "libertineRoman", "libertine-type1",
147 "lmodern", "mathdesign", "mathpazo", "mathptmx", "MinionPro", "newcent", "noto", "noto-serif",
148 "PTSerif", "tgbonum", "tgchorus", "tgpagella", "tgschola", "tgtermes", "utopia", "xcharter", 0 };
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", "LibertinusSans-LF", "lmss", "noto-sans", "PTSans",
153 "tgadventor", "tgheros", "uop", 0 };
155 const char * const known_typewriter_font_packages[] = { "beramono", "cmtl", "cmtt", "courier", "DejaVuSansMono",
156 "FiraMono", "lmtt", "luximono", "libertineMono", "libertineMono-type1", "LibertinusMono-TLF", "lmodern",
157 "mathpazo", "mathptmx", "newcent", "noto-mono", "PTMono", "tgcursor", "txtt", 0 };
159 const char * const known_math_font_packages[] = { "eulervm", "newtxmath", 0};
161 const char * const known_latex_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};
167 const char * const known_paper_margins[] = { "lmargin", "tmargin", "rmargin",
168 "bmargin", "headheight", "headsep", "footskip", "columnsep", 0};
170 const char * const known_coded_paper_margins[] = { "leftmargin", "topmargin",
171 "rightmargin", "bottommargin", "headheight", "headsep", "footskip",
174 /// commands that can start an \if...\else...\endif sequence
175 const char * const known_if_commands[] = {"if", "ifarydshln", "ifbraket",
176 "ifcancel", "ifcolortbl", "ifeurosym", "ifmarginnote", "ifmmode", "ifpdf",
177 "ifsidecap", "ifupgreek", 0};
179 const char * const known_basic_colors[] = {"black", "blue", "brown", "cyan",
180 "darkgray", "gray", "green", "lightgray", "lime", "magenta", "orange", "olive",
181 "pink", "purple", "red", "teal", "violet", "white", "yellow", 0};
183 const char * const known_basic_color_codes[] = {"#000000", "#0000ff", "#964B00", "#00ffff",
184 "#a9a9a9", "#808080", "#00ff00", "#d3d3d3", "#bfff00", "#ff00ff", "#ff7f00", "#808000",
185 "#ffc0cb", "#800080", "#ff0000", "#008080", "#8f00ff", "#ffffff", "#ffff00", 0};
187 /// conditional commands with three arguments like \@ifundefined{}{}{}
188 const char * const known_if_3arg_commands[] = {"@ifundefined", "IfFileExists",
192 * Known file extensions for TeX files as used by \\includeonly
194 char const * const known_tex_extensions[] = {"tex", 0};
196 /// packages that work only in xetex
197 /// polyglossia is handled separately
198 const char * const known_xetex_packages[] = {"arabxetex", "fixlatvian",
199 "fontbook", "fontwrap", "mathspec", "philokalia", "unisugar",
200 "xeCJK", "xecolor", "xecyr", "xeindex", "xepersian", "xunicode", 0};
202 /// packages that are automatically skipped if loaded by LyX
203 const char * const known_lyx_packages[] = {"amsbsy", "amsmath", "amssymb",
204 "amstext", "amsthm", "array", "babel", "booktabs", "calc", "CJK", "color",
205 "float", "fontspec", "framed", "graphicx", "hhline", "ifthen", "longtable",
206 "makeidx", "minted", "multirow", "nomencl", "parskip", "pdfpages", "prettyref", "refstyle",
207 "rotating", "rotfloat", "splitidx", "setspace", "subscript", "tabularx","textcomp", "tipa",
208 "tipx", "tone", "ulem", "url", "varioref", "verbatim", "wrapfig", "xcolor", "xltabular",
211 // codes used to remove packages that are loaded automatically by LyX.
212 // Syntax: package_beg_sep<name>package_mid_sep<package loading code>package_end_sep
213 const char package_beg_sep = '\001';
214 const char package_mid_sep = '\002';
215 const char package_end_sep = '\003';
218 // returns true if at least one of the options in what has been found
219 bool handle_opt(vector<string> & opts, char const * const * what, string & target)
225 // the last language option is the document language (for babel and LyX)
226 // the last size option is the document font size
227 vector<string>::iterator it;
228 vector<string>::iterator position = opts.begin();
229 for (; *what; ++what) {
230 it = find(opts.begin(), opts.end(), *what);
231 if (it != opts.end()) {
232 if (it >= position) {
243 void delete_opt(vector<string> & opts, char const * const * what)
248 // remove found options from the list
249 // do this after handle_opt to avoid potential memory leaks
250 vector<string>::iterator it;
251 for (; *what; ++what) {
252 it = find(opts.begin(), opts.end(), *what);
253 if (it != opts.end())
260 * Split a package options string (keyval format) into a vector.
262 * authorformat=smallcaps,
264 * titleformat=colonsep,
265 * bibformat={tabular,ibidem,numbered}
267 vector<string> split_options(string const & input)
269 vector<string> options;
273 Token const & t = p.get_token();
274 if (t.asInput() == ",") {
275 options.push_back(trimSpaceAndEol(option));
277 } else if (t.asInput() == "=") {
280 if (p.next_token().asInput() == "{")
281 option += '{' + p.getArg('{', '}') + '}';
282 } else if (t.cat() != catSpace && t.cat() != catComment)
283 option += t.asInput();
287 options.push_back(trimSpaceAndEol(option));
294 * Retrieve a keyval option "name={value with=sign}" named \p name from
295 * \p options and return the value.
296 * The found option is also removed from \p options.
298 string process_keyval_opt(vector<string> & options, string name)
300 for (size_t i = 0; i < options.size(); ++i) {
301 vector<string> option;
302 split(options[i], option, '=');
303 if (option.size() < 2)
305 if (option[0] == name) {
306 options.erase(options.begin() + i);
307 option.erase(option.begin());
308 return join(option, "=");
314 } // anonymous namespace
318 * known polyglossia language names (including variants)
319 * FIXME: support spelling=old for german variants (german vs. ngerman LyX names etc)
321 const char * const Preamble::polyglossia_languages[] = {
322 "albanian", "american", "amharic", "ancient", "arabic", "armenian", "asturian", "australian",
323 "bahasai", "bahasam", "basque", "bengali", "brazil", "brazilian", "breton", "british", "bulgarian",
324 "catalan", "churchslavonic", "coptic", "croatian", "czech", "danish", "divehi", "dutch",
325 "english", "esperanto", "estonian", "farsi", "finnish", "french", "friulan",
326 "galician", "greek", "monotonic", "hebrew", "hindi",
327 "icelandic", "interlingua", "irish", "italian", "kannada", "khmer", "korean",
328 "lao", "latin", "latvian", "lithuanian", "lsorbian", "magyar", "malayalam", "marathi",
329 "austrian", "newzealand", "german", "norsk", "nynorsk", "occitan", "oldrussian",
330 "piedmontese", "polish", "polytonic", "portuges", "romanian", "romansh", "russian",
331 "samin", "sanskrit", "scottish", "serbian", "slovak", "slovenian", "spanish", "swedish", "syriac",
332 "tamil", "telugu", "thai", "tibetan", "turkish", "turkmen",
333 "ukrainian", "urdu", "usorbian", "vietnamese", "welsh", 0};
334 // not yet supported by LyX: "korean", "nko"
337 * the same as polyglossia_languages with .lyx names
338 * please keep this in sync with polyglossia_languages line by line!
340 const char * const Preamble::coded_polyglossia_languages[] = {
341 "albanian", "american", "amharic", "ancientgreek", "arabic_arabi", "armenian", "asturian", "australian",
342 "bahasa", "bahasam", "basque", "bengali", "brazilian", "brazilian", "breton", "british", "bulgarian",
343 "catalan", "churchslavonic", "coptic", "croatian", "czech", "danish", "divehi", "dutch",
344 "english", "esperanto", "estonian", "farsi", "finnish", "french", "friulan",
345 "galician", "greek", "greek", "hebrew", "hindi",
346 "icelandic", "interlingua", "irish", "italian", "kannada", "khmer", "korean",
347 "lao", "latin", "latvian", "lithuanian", "lowersorbian", "magyar", "malayalam", "marathi",
348 "naustrian","newzealand", "ngerman", "norsk", "nynorsk", "occitan", "oldrussian",
349 "piedmontese", "polish", "polutonikogreek", "portuges", "romanian", "romansh", "russian",
350 "samin", "sanskrit", "scottish", "serbian", "slovak", "slovene", "spanish", "swedish", "syriac",
351 "tamil", "telugu", "thai", "tibetan", "turkish", "turkmen",
352 "ukrainian", "urdu", "uppersorbian", "vietnamese", "welsh", 0};
353 // not yet supported by LyX: "korean-polyglossia", "nko"
356 bool Preamble::usePolyglossia() const
358 return h_use_non_tex_fonts && h_language_package == "default";
362 bool Preamble::indentParagraphs() const
364 return h_paragraph_separation == "indent";
368 bool Preamble::isPackageUsed(string const & package) const
370 return used_packages.find(package) != used_packages.end();
374 bool Preamble::isPackageAutoLoaded(string const & package) const
376 return auto_packages.find(package) != auto_packages.end();
380 vector<string> Preamble::getPackageOptions(string const & package) const
382 map<string, vector<string> >::const_iterator it = used_packages.find(package);
383 if (it != used_packages.end())
385 return vector<string>();
389 void Preamble::registerAutomaticallyLoadedPackage(std::string const & package)
391 auto_packages.insert(package);
395 void Preamble::addModule(string const & module)
397 for (auto const & m : used_modules) {
401 used_modules.push_back(module);
405 void Preamble::suppressDate(bool suppress)
408 h_suppress_date = "true";
410 h_suppress_date = "false";
414 void Preamble::registerAuthor(std::string const & name, string const & initials)
416 Author author(from_utf8(name), empty_docstring(), from_utf8(initials));
417 author.setUsed(true);
418 authors_.record(author);
419 h_tracking_changes = "true";
420 h_output_changes = "true";
424 Author const & Preamble::getAuthor(std::string const & name) const
426 Author author(from_utf8(name), empty_docstring(), empty_docstring());
427 for (AuthorList::Authors::const_iterator it = authors_.begin();
428 it != authors_.end(); ++it)
431 static Author const dummy;
436 int Preamble::getSpecialTableColumnArguments(char c) const
438 map<char, int>::const_iterator it = special_columns_.find(c);
439 if (it == special_columns_.end())
445 void Preamble::add_package(string const & name, vector<string> & options)
447 // every package inherits the global options
448 if (used_packages.find(name) == used_packages.end())
449 used_packages[name] = split_options(h_options);
451 // Insert options passed via PassOptionsToPackage
452 for (auto const & p : extra_package_options_) {
453 if (p.first == name) {
454 vector<string> eo = getVectorFromString(p.second);
455 for (auto const & eoi : eo)
456 options.push_back(eoi);
460 vector<string> & v = used_packages[name];
461 v.insert(v.end(), options.begin(), options.end());
462 if (name == "jurabib") {
463 // Don't output the order argument (see the cite command
464 // handling code in text.cpp).
465 vector<string>::iterator end =
466 remove(options.begin(), options.end(), "natbiborder");
467 end = remove(options.begin(), end, "jurabiborder");
468 options.erase(end, options.end());
475 // Given is a string like "scaled=0.9" or "scale=0.9", return 0.9 * 100
476 bool scale_as_percentage(string const & scale, string & percentage)
478 if (contains(scale, '=')) {
479 string const value = support::split(scale, '=');
480 if (isStrDbl(value)) {
481 percentage = convert<string>(
482 static_cast<int>(100 * convert<double>(value)));
490 string remove_braces(string const & value)
494 if (value[0] == '{' && value[value.length()-1] == '}')
495 return value.substr(1, value.length()-2);
499 } // anonymous namespace
502 Preamble::Preamble() : one_language(true), explicit_babel(false),
503 title_layout_found(false), index_number(0), h_font_cjk_set(false)
507 h_biblio_style = "plain";
508 h_bibtex_command = "default";
509 h_cite_engine = "basic";
510 h_cite_engine_type = "default";
512 h_defskip = "medskip";
513 h_dynamic_quotes = false;
516 h_fontencoding = "default";
517 h_font_roman[0] = "default";
518 h_font_roman[1] = "default";
519 h_font_sans[0] = "default";
520 h_font_sans[1] = "default";
521 h_font_typewriter[0] = "default";
522 h_font_typewriter[1] = "default";
523 h_font_math[0] = "auto";
524 h_font_math[1] = "auto";
525 h_font_default_family = "default";
526 h_use_non_tex_fonts = false;
528 h_font_roman_osf = "false";
529 h_font_sans_osf = "false";
530 h_font_typewriter_osf = "false";
531 h_font_sf_scale[0] = "100";
532 h_font_sf_scale[1] = "100";
533 h_font_tt_scale[0] = "100";
534 h_font_tt_scale[1] = "100";
535 // h_font_roman_opts;
537 // h_font_typewriter_opts;
539 h_is_mathindent = "0";
540 h_math_numbering_side = "default";
541 h_graphics = "default";
542 h_default_output_format = "default";
543 h_html_be_strict = "false";
544 h_html_css_as_file = "0";
545 h_html_math_output = "0";
546 h_index[0] = "Index";
547 h_index_command = "default";
548 h_inputencoding = "auto-legacy";
549 h_justification = "true";
550 h_language = "english";
551 h_language_package = "none";
553 h_maintain_unincluded_children = "no";
557 h_output_changes = "false";
558 h_change_bars = "false";
560 //h_output_sync_macro
561 h_papercolumns = "1";
562 h_paperfontsize = "default";
563 h_paperorientation = "portrait";
564 h_paperpagestyle = "default";
566 h_papersize = "default";
567 h_paragraph_indentation = "default";
568 h_paragraph_separation = "indent";
573 h_pdf_bookmarks = "0";
574 h_pdf_bookmarksnumbered = "0";
575 h_pdf_bookmarksopen = "0";
576 h_pdf_bookmarksopenlevel = "1";
577 h_pdf_breaklinks = "0";
578 h_pdf_pdfborder = "0";
579 h_pdf_colorlinks = "0";
580 h_pdf_backref = "section";
581 h_pdf_pdfusetitle = "0";
583 //h_pdf_quoted_options;
584 h_quotes_style = "english";
586 h_shortcut[0] = "idx";
587 h_spacing = "single";
588 h_save_transient_properties = "true";
589 h_suppress_date = "false";
590 h_textclass = "article";
592 h_tracking_changes = "false";
593 h_use_bibtopic = "false";
594 h_use_dash_ligatures = "true";
595 h_use_indices = "false";
596 h_use_geometry = "false";
597 h_use_default_options = "false";
598 h_use_hyperref = "false";
599 h_use_microtype = "false";
600 h_use_lineno = "false";
601 h_use_refstyle = false;
602 h_use_minted = false;
603 h_use_packages["amsmath"] = "1";
604 h_use_packages["amssymb"] = "0";
605 h_use_packages["cancel"] = "0";
606 h_use_packages["esint"] = "1";
607 h_use_packages["mhchem"] = "0";
608 h_use_packages["mathdots"] = "0";
609 h_use_packages["mathtools"] = "0";
610 h_use_packages["stackrel"] = "0";
611 h_use_packages["stmaryrd"] = "0";
612 h_use_packages["undertilde"] = "0";
616 void Preamble::handle_hyperref(vector<string> & options)
618 // FIXME swallow inputencoding changes that might surround the
619 // hyperref setup if it was written by LyX
620 h_use_hyperref = "true";
621 // swallow "unicode=true", since LyX does always write that
622 vector<string>::iterator it =
623 find(options.begin(), options.end(), "unicode=true");
624 if (it != options.end())
626 it = find(options.begin(), options.end(), "pdfusetitle");
627 if (it != options.end()) {
628 h_pdf_pdfusetitle = "1";
631 string bookmarks = process_keyval_opt(options, "bookmarks");
632 if (bookmarks == "true")
633 h_pdf_bookmarks = "1";
634 else if (bookmarks == "false")
635 h_pdf_bookmarks = "0";
636 if (h_pdf_bookmarks == "1") {
637 string bookmarksnumbered =
638 process_keyval_opt(options, "bookmarksnumbered");
639 if (bookmarksnumbered == "true")
640 h_pdf_bookmarksnumbered = "1";
641 else if (bookmarksnumbered == "false")
642 h_pdf_bookmarksnumbered = "0";
643 string bookmarksopen =
644 process_keyval_opt(options, "bookmarksopen");
645 if (bookmarksopen == "true")
646 h_pdf_bookmarksopen = "1";
647 else if (bookmarksopen == "false")
648 h_pdf_bookmarksopen = "0";
649 if (h_pdf_bookmarksopen == "1") {
650 string bookmarksopenlevel =
651 process_keyval_opt(options, "bookmarksopenlevel");
652 if (!bookmarksopenlevel.empty())
653 h_pdf_bookmarksopenlevel = bookmarksopenlevel;
656 string breaklinks = process_keyval_opt(options, "breaklinks");
657 if (breaklinks == "true")
658 h_pdf_breaklinks = "1";
659 else if (breaklinks == "false")
660 h_pdf_breaklinks = "0";
661 string pdfborder = process_keyval_opt(options, "pdfborder");
662 if (pdfborder == "{0 0 0}")
663 h_pdf_pdfborder = "1";
664 else if (pdfborder == "{0 0 1}")
665 h_pdf_pdfborder = "0";
666 string backref = process_keyval_opt(options, "backref");
667 if (!backref.empty())
668 h_pdf_backref = backref;
669 string colorlinks = process_keyval_opt(options, "colorlinks");
670 if (colorlinks == "true")
671 h_pdf_colorlinks = "1";
672 else if (colorlinks == "false")
673 h_pdf_colorlinks = "0";
674 string pdfpagemode = process_keyval_opt(options, "pdfpagemode");
675 if (!pdfpagemode.empty())
676 h_pdf_pagemode = pdfpagemode;
677 string pdftitle = process_keyval_opt(options, "pdftitle");
678 if (!pdftitle.empty()) {
679 h_pdf_title = remove_braces(pdftitle);
681 string pdfauthor = process_keyval_opt(options, "pdfauthor");
682 if (!pdfauthor.empty()) {
683 h_pdf_author = remove_braces(pdfauthor);
685 string pdfsubject = process_keyval_opt(options, "pdfsubject");
686 if (!pdfsubject.empty())
687 h_pdf_subject = remove_braces(pdfsubject);
688 string pdfkeywords = process_keyval_opt(options, "pdfkeywords");
689 if (!pdfkeywords.empty())
690 h_pdf_keywords = remove_braces(pdfkeywords);
691 if (!options.empty()) {
692 if (!h_pdf_quoted_options.empty())
693 h_pdf_quoted_options += ',';
694 h_pdf_quoted_options += join(options, ",");
700 void Preamble::handle_geometry(vector<string> & options)
702 h_use_geometry = "true";
703 vector<string>::iterator it;
705 if ((it = find(options.begin(), options.end(), "landscape")) != options.end()) {
706 h_paperorientation = "landscape";
710 // keyval version: "paper=letter" or "paper=letterpaper"
711 string paper = process_keyval_opt(options, "paper");
713 if (suffixIs(paper, "paper"))
714 paper = subst(paper, "paper", "");
715 // alternative version: "letterpaper"
716 handle_opt(options, known_latex_paper_sizes, paper);
717 if (suffixIs(paper, "paper"))
718 paper = subst(paper, "paper", "");
719 delete_opt(options, known_latex_paper_sizes);
723 char const * const * margin = known_paper_margins;
724 for (; *margin; ++margin) {
725 string value = process_keyval_opt(options, *margin);
726 if (!value.empty()) {
727 int k = margin - known_paper_margins;
728 string name = known_coded_paper_margins[k];
729 h_margins += '\\' + name + ' ' + value + '\n';
735 void Preamble::handle_package(Parser &p, string const & name,
736 string const & opts, bool in_lyx_preamble,
739 vector<string> options = split_options(opts);
740 add_package(name, options);
742 if (is_known(name, known_xetex_packages)) {
744 h_use_non_tex_fonts = true;
745 registerAutomaticallyLoadedPackage("fontspec");
746 if (h_inputencoding == "auto-legacy")
747 p.setEncoding("UTF-8");
750 // vector of all options for easier parsing and
752 vector<string> allopts = getVectorFromString(opts);
753 // this stores the potential extra options
760 // By default, we use the package name as LyX font name,
761 // so this only needs to be reset if these names differ
762 if (is_known(name, known_roman_font_packages))
763 h_font_roman[0] = name;
765 if (name == "ccfonts") {
766 for (auto const & opt : allopts) {
772 h_font_roman_opts = xopts;
776 if (name == "lmodern") {
777 for (auto const & opt : allopts) {
783 h_font_roman_opts = xopts;
787 if (name == "fourier") {
788 h_font_roman[0] = "utopia";
789 for (auto const & opt : allopts) {
791 h_font_roman_osf = "true";
794 if (opt == "expert") {
803 h_font_roman_opts = xopts;
807 if (name == "garamondx") {
808 for (auto const & opt : allopts) {
810 h_font_roman_osf = "true";
818 h_font_roman_opts = xopts;
822 if (name == "libertine") {
823 // this automatically invokes biolinum
824 h_font_sans[0] = "biolinum";
825 // as well as libertineMono
826 h_font_typewriter[0] = "libertine-mono";
827 for (auto const & opt : allopts) {
829 h_font_roman_osf = "true";
832 if (opt == "lining") {
833 h_font_roman_osf = "false";
841 h_font_roman_opts = xopts;
845 if (name == "libertineRoman" || name == "libertine-type1") {
846 h_font_roman[0] = "libertine";
847 // NOTE: contrary to libertine.sty, libertineRoman
848 // and libertine-type1 do not automatically invoke
849 // biolinum and libertineMono
850 if (opts == "lining")
851 h_font_roman_osf = "false";
852 else if (opts == "osf")
853 h_font_roman_osf = "true";
856 if (name == "libertinus" || name == "libertinus-type1") {
863 for (auto const & opt : allopts) {
864 if (opt == "rm" || opt == "serif") {
869 if (opt == "sf" || opt == "sans") {
874 if (opt == "tt=false" || opt == "mono=false") {
882 if (opt == "scaleSF") {
886 if (opt == "scaleTT") {
890 if (opt == "lining") {
891 h_font_roman_osf = "false";
899 h_font_roman[0] = "libertinus";
901 h_font_roman_osf = "true";
903 h_font_roman_osf = "false";
906 h_font_sans[0] = "LibertinusSans-LF";
908 h_font_sans_osf = "true";
910 h_font_sans_osf = "false";
911 if (!scalesf.empty())
912 scale_as_percentage(scalesf, h_font_sf_scale[0]);
915 h_font_typewriter[0] = "LibertinusMono-TLF";
916 if (!scalett.empty())
917 scale_as_percentage(scalett, h_font_tt_scale[0]);
920 h_font_roman_opts = xopts;
924 if (name == "MinionPro") {
925 h_font_roman[0] = "minionpro";
926 h_font_roman_osf = "true";
927 h_font_math[0] = "auto";
928 for (auto const & opt : allopts) {
930 h_font_roman_osf = "false";
933 if (opt == "onlytext") {
934 h_font_math[0] = "default";
942 h_font_roman_opts = xopts;
946 if (name == "mathdesign") {
947 for (auto const & opt : allopts) {
948 if (opt == "charter") {
949 h_font_roman[0] = "md-charter";
952 if (opt == "garamond") {
953 h_font_roman[0] = "md-garamond";
956 if (opt == "utopia") {
957 h_font_roman[0] = "md-utopia";
960 if (opt == "expert") {
962 h_font_roman_osf = "true";
968 else if (name == "mathpazo") {
969 h_font_roman[0] = "palatino";
970 for (auto const & opt : allopts) {
972 h_font_roman_osf = "true";
984 h_font_roman_opts = xopts;
988 else if (name == "mathptmx") {
989 h_font_roman[0] = "times";
990 for (auto const & opt : allopts) {
996 h_font_roman_opts = xopts;
1000 if (name == "crimson")
1001 h_font_roman[0] = "cochineal";
1003 if (name == "cochineal") {
1004 for (auto const & opt : allopts) {
1005 if (opt == "osf" || opt == "oldstyle") {
1006 h_font_roman_osf = "true";
1009 if (opt == "proportional" || opt == "p")
1016 h_font_roman_opts = xopts;
1020 if (name == "CrimsonPro") {
1021 h_font_roman_osf = "true";
1022 for (auto const & opt : allopts) {
1023 if (opt == "lf" || opt == "lining") {
1024 h_font_roman_osf = "false";
1027 if (opt == "proportional" || opt == "p")
1029 if (opt == "medium") {
1030 h_font_roman[0] = "CrimsonProMedium";
1033 if (opt == "extralight") {
1034 h_font_roman[0] = "CrimsonProExtraLight";
1037 if (opt == "light") {
1038 h_font_roman[0] = "CrimsonProLight";
1046 h_font_roman_opts = xopts;
1052 // font uses old-style figure
1053 h_font_roman_osf = "true";
1055 if (name == "paratype") {
1056 // in this case all fonts are ParaType
1057 h_font_roman[0] = "PTSerif-TLF";
1058 h_font_sans[0] = "default";
1059 h_font_typewriter[0] = "default";
1062 if (name == "PTSerif")
1063 h_font_roman[0] = "PTSerif-TLF";
1065 if (name == "XCharter") {
1066 h_font_roman[0] = "xcharter";
1067 for (auto const & opt : allopts) {
1069 h_font_roman_osf = "true";
1077 h_font_roman_opts = xopts;
1081 if (name == "plex-serif") {
1082 h_font_roman[0] = "IBMPlexSerif";
1083 for (auto const & opt : allopts) {
1084 if (opt == "thin") {
1085 h_font_roman[0] = "IBMPlexSerifThin";
1088 if (opt == "extralight") {
1089 h_font_roman[0] = "IBMPlexSerifExtraLight";
1092 if (opt == "light") {
1093 h_font_roman[0] = "IBMPlexSerifLight";
1101 h_font_roman_opts = xopts;
1105 if (name == "noto-serif" || name == "noto") {
1112 bool extralight = false;
1114 bool medium = false;
1117 if (name == "noto") {
1122 // Since the options might apply to different shapes,
1123 // we need to parse all options first and then handle them.
1124 for (auto const & opt : allopts) {
1125 if (opt == "regular")
1135 if (opt == "thin") {
1139 if (opt == "extralight") {
1143 if (opt == "light") {
1147 if (opt == "medium") {
1158 if (opt == "nott") {
1166 if (prefixIs(opt, "scaled=")) {
1175 // handle options that might affect different shapes
1176 if (name == "noto-serif" || rm) {
1178 h_font_roman[0] = "NotoSerifThin";
1179 else if (extralight)
1180 h_font_roman[0] = "NotoSerifExtralight";
1182 h_font_roman[0] = "NotoSerifLight";
1184 h_font_roman[0] = "NotoSerifMedium";
1186 h_font_roman[0] = "NotoSerifRegular";
1188 h_font_roman_osf = "true";
1190 h_font_roman_opts = xopts;
1192 if (name == "noto" && sf) {
1194 h_font_sans[0] = "NotoSansThin";
1195 else if (extralight)
1196 h_font_sans[0] = "NotoSansExtralight";
1198 h_font_sans[0] = "NotoSansLight";
1200 h_font_sans[0] = "NotoSansMedium";
1202 h_font_sans[0] = "NotoSansRegular";
1204 h_font_sans_osf = "true";
1206 scale_as_percentage(scl, h_font_sf_scale[0]);
1208 h_font_sans_opts = xopts;
1210 if (name == "noto" && tt) {
1211 h_font_typewriter[0] = "NotoMonoRegular";
1213 h_font_typewriter_osf = "true";
1215 scale_as_percentage(scl, h_font_tt_scale[0]);
1217 h_font_typewriter_opts = xopts;
1221 if (name == "sourceserifpro") {
1222 h_font_roman[0] = "ADOBESourceSerifPro";
1223 for (auto const & opt : allopts) {
1225 h_font_roman_osf = "true";
1233 h_font_roman_opts = xopts;
1241 // By default, we use the package name as LyX font name,
1242 // so this only needs to be reset if these names differ.
1243 // Also, we handle the scaling option here generally.
1244 if (is_known(name, known_sans_font_packages)) {
1245 h_font_sans[0] = name;
1246 if (contains(opts, "scale")) {
1247 vector<string>::iterator it = allopts.begin();
1248 for (; it != allopts.end() ; ++it) {
1249 string const opt = *it;
1250 if (prefixIs(opt, "scaled=") || prefixIs(opt, "scale=")) {
1251 if (scale_as_percentage(opt, h_font_sf_scale[0])) {
1260 if (name == "biolinum" || name == "biolinum-type1") {
1261 h_font_sans[0] = "biolinum";
1262 for (auto const & opt : allopts) {
1263 if (prefixIs(opt, "osf")) {
1264 h_font_sans_osf = "true";
1272 h_font_sans_opts = xopts;
1276 if (name == "cantarell") {
1277 for (auto const & opt : allopts) {
1278 if (opt == "defaultsans")
1280 if (prefixIs(opt, "oldstyle")) {
1281 h_font_sans_osf = "true";
1289 h_font_sans_opts = xopts;
1293 if (name == "Chivo") {
1294 for (auto const & opt : allopts) {
1295 if (opt == "thin") {
1296 h_font_roman[0] = "ChivoThin";
1299 if (opt == "light") {
1300 h_font_roman[0] = "ChivoLight";
1303 if (opt == "regular") {
1304 h_font_roman[0] = "Chivo";
1307 if (opt == "medium") {
1308 h_font_roman[0] = "ChivoMedium";
1311 if (prefixIs(opt, "oldstyle")) {
1312 h_font_sans_osf = "true";
1320 h_font_sans_opts = xopts;
1324 if (name == "PTSans") {
1325 h_font_sans[0] = "PTSans-TLF";
1328 if (name == "FiraSans") {
1329 h_font_sans_osf = "true";
1330 for (auto const & opt : allopts) {
1331 if (opt == "book") {
1332 h_font_sans[0] = "FiraSansBook";
1335 if (opt == "thin") {
1338 if (opt == "extralight") {
1339 h_font_sans[0] = "FiraSansExtralight";
1342 if (opt == "light") {
1343 h_font_sans[0] = "FiraSansLight";
1346 if (opt == "ultralight") {
1347 h_font_sans[0] = "FiraSansUltralight";
1350 if (opt == "thin") {
1351 h_font_sans[0] = "FiraSansThin";
1354 if (opt == "lf" || opt == "lining") {
1355 h_font_sans_osf = "false";
1363 h_font_sans_opts = xopts;
1367 if (name == "plex-sans") {
1368 h_font_sans[0] = "IBMPlexSans";
1369 for (auto const & opt : allopts) {
1370 if (opt == "condensed") {
1371 h_font_sans[0] = "IBMPlexSansCondensed";
1374 if (opt == "thin") {
1375 h_font_sans[0] = "IBMPlexSansThin";
1378 if (opt == "extralight") {
1379 h_font_sans[0] = "IBMPlexSansExtraLight";
1382 if (opt == "light") {
1383 h_font_sans[0] = "IBMPlexSansLight";
1391 h_font_sans_opts = xopts;
1395 if (name == "noto-sans") {
1396 h_font_sans[0] = "NotoSansRegular";
1397 for (auto const & opt : allopts) {
1398 if (opt == "regular")
1400 if (opt == "medium") {
1401 h_font_sans[0] = "NotoSansMedium";
1404 if (opt == "thin") {
1405 h_font_sans[0] = "NotoSansThin";
1408 if (opt == "extralight") {
1409 h_font_sans[0] = "NotoSansExtralight";
1412 if (opt == "light") {
1413 h_font_sans[0] = "NotoSansLight";
1417 h_font_sans_osf = "true";
1425 h_font_sans_opts = xopts;
1429 if (name == "sourcesanspro") {
1430 h_font_sans[0] = "ADOBESourceSansPro";
1431 for (auto const & opt : allopts) {
1433 h_font_sans_osf = "true";
1441 h_font_sans_opts = xopts;
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 // Also, we handle the scaling option here generally.
1452 if (is_known(name, known_typewriter_font_packages)) {
1453 h_font_typewriter[0] = name;
1454 if (contains(opts, "scale")) {
1455 vector<string>::iterator it = allopts.begin();
1456 for (; it != allopts.end() ; ++it) {
1457 string const opt = *it;
1458 if (prefixIs(opt, "scaled=") || prefixIs(opt, "scale=")) {
1459 if (scale_as_percentage(opt, h_font_tt_scale[0])) {
1468 if (name == "libertineMono" || name == "libertineMono-type1")
1469 h_font_typewriter[0] = "libertine-mono";
1471 if (name == "FiraMono") {
1472 h_font_typewriter_osf = "true";
1473 for (auto const & opt : allopts) {
1474 if (opt == "lf" || opt == "lining") {
1475 h_font_typewriter_osf = "false";
1483 h_font_typewriter_opts = xopts;
1487 if (name == "PTMono")
1488 h_font_typewriter[0] = "PTMono-TLF";
1490 if (name == "plex-mono") {
1491 h_font_typewriter[0] = "IBMPlexMono";
1492 for (auto const & opt : allopts) {
1493 if (opt == "thin") {
1494 h_font_typewriter[0] = "IBMPlexMonoThin";
1497 if (opt == "extralight") {
1498 h_font_typewriter[0] = "IBMPlexMonoExtraLight";
1501 if (opt == "light") {
1502 h_font_typewriter[0] = "IBMPlexMonoLight";
1510 h_font_typewriter_opts = xopts;
1514 if (name == "noto-mono") {
1515 h_font_typewriter[0] = "NotoMonoRegular";
1516 for (auto const & opt : allopts) {
1517 if (opt == "regular")
1524 h_font_typewriter_opts = xopts;
1528 if (name == "sourcecodepro") {
1529 h_font_typewriter[0] = "ADOBESourceCodePro";
1530 for (auto const & opt : allopts) {
1532 h_font_typewriter_osf = "true";
1540 h_font_typewriter_opts = xopts;
1548 // By default, we use the package name as LyX font name,
1549 // so this only needs to be reset if these names differ.
1550 if (is_known(name, known_math_font_packages))
1551 h_font_math[0] = name;
1553 if (name == "newtxmath") {
1555 h_font_math[0] = "newtxmath";
1556 else if (opts == "garamondx")
1557 h_font_math[0] = "garamondx-ntxm";
1558 else if (opts == "libertine")
1559 h_font_math[0] = "libertine-ntxm";
1560 else if (opts == "minion")
1561 h_font_math[0] = "minion-ntxm";
1562 else if (opts == "cochineal")
1563 h_font_math[0] = "cochineal-ntxm";
1566 if (name == "iwona")
1568 h_font_math[0] = "iwona-math";
1570 if (name == "kurier")
1572 h_font_math[0] = "kurier-math";
1574 // after the detection and handling of special cases, we can remove the
1575 // fonts, otherwise they would appear in the preamble, see bug #7856
1576 if (is_known(name, known_roman_font_packages) || is_known(name, known_sans_font_packages)
1577 || is_known(name, known_typewriter_font_packages) || is_known(name, known_math_font_packages))
1579 //"On". See the enum Package in BufferParams.h if you thought that "2" should have been "42"
1580 else if (name == "amsmath" || name == "amssymb" || name == "cancel" ||
1581 name == "esint" || name == "mhchem" || name == "mathdots" ||
1582 name == "mathtools" || name == "stackrel" ||
1583 name == "stmaryrd" || name == "undertilde") {
1584 h_use_packages[name] = "2";
1585 registerAutomaticallyLoadedPackage(name);
1588 else if (name == "babel") {
1589 h_language_package = "default";
1590 // One might think we would have to do nothing if babel is loaded
1591 // without any options to prevent pollution of the preamble with this
1592 // babel call in every roundtrip.
1593 // But the user could have defined babel-specific things afterwards. So
1594 // we need to keep it in the preamble to prevent cases like bug #7861.
1595 if (!opts.empty()) {
1596 // check if more than one option was used - used later for inputenc
1597 if (options.begin() != options.end() - 1)
1598 one_language = false;
1599 // babel takes the last language of the option of its \usepackage
1600 // call as document language. If there is no such language option, the
1601 // last language in the documentclass options is used.
1602 handle_opt(options, known_languages, h_language);
1603 // translate the babel name to a LyX name
1604 h_language = babel2lyx(h_language);
1605 if (h_language == "japanese") {
1606 // For Japanese, the encoding isn't indicated in the source
1607 // file, and there's really not much we can do. We could
1608 // 1) offer a list of possible encodings to choose from, or
1609 // 2) determine the encoding of the file by inspecting it.
1610 // For the time being, we leave the encoding alone so that
1611 // we don't get iconv errors when making a wrong guess, and
1612 // we will output a note at the top of the document
1613 // explaining what to do.
1614 Encoding const * const enc = encodings.fromIconvName(
1615 p.getEncoding(), Encoding::japanese, false);
1617 h_inputencoding = enc->name();
1618 is_nonCJKJapanese = true;
1619 // in this case babel can be removed from the preamble
1620 registerAutomaticallyLoadedPackage("babel");
1622 // If babel is called with options, LyX puts them by default into the
1623 // document class options. This works for most languages, except
1624 // for Latvian, Lithuanian, Mongolian, Turkmen and Vietnamese and
1625 // perhaps in future others.
1626 // Therefore keep the babel call as it is as the user might have
1628 string const babelcall = "\\usepackage[" + opts + "]{babel}\n";
1629 if (!contains(h_preamble.str(), babelcall))
1630 h_preamble << babelcall;
1632 delete_opt(options, known_languages);
1634 if (!contains(h_preamble.str(), "\\usepackage{babel}\n"))
1635 h_preamble << "\\usepackage{babel}\n";
1636 explicit_babel = true;
1640 else if (name == "polyglossia") {
1641 h_language_package = "default";
1642 h_default_output_format = "pdf4";
1643 h_use_non_tex_fonts = true;
1645 registerAutomaticallyLoadedPackage("xunicode");
1646 if (h_inputencoding == "auto-legacy")
1647 p.setEncoding("UTF-8");
1650 else if (name == "CJK") {
1651 // set the encoding to "auto-legacy" because it might be set to "auto-legacy-plain" by the babel handling
1652 // and this would not be correct for CJK
1653 if (h_inputencoding == "auto-legacy-plain")
1654 h_inputencoding = "auto-legacy";
1655 registerAutomaticallyLoadedPackage("CJK");
1658 else if (name == "CJKutf8") {
1659 h_inputencoding = "utf8-cjk";
1660 p.setEncoding("UTF-8");
1661 registerAutomaticallyLoadedPackage("CJKutf8");
1664 else if (name == "fontenc") {
1665 h_fontencoding = getStringFromVector(options, ",");
1669 else if (name == "inputenc" || name == "luainputenc") {
1670 // h_inputencoding is only set when there is not more than one
1671 // inputenc option because otherwise h_inputencoding must be
1672 // set to "auto-legacy" (the default encodings of the document's languages)
1673 // Therefore check that exactly one option is passed to inputenc.
1674 // It is also only set when there is not more than one babel
1676 if (!options.empty()) {
1677 string const encoding = options.back();
1678 Encoding const * const enc = encodings.fromLaTeXName(
1679 encoding, Encoding::inputenc, true);
1681 if (!detectEncoding)
1682 cerr << "Unknown encoding " << encoding
1683 << ". Ignoring." << std::endl;
1685 if (!enc->unsafe() && options.size() == 1 && one_language == true)
1686 h_inputencoding = enc->name();
1687 p.setEncoding(enc->iconvName());
1693 else if (name == "srcltx") {
1694 h_output_sync = "1";
1695 if (!opts.empty()) {
1696 h_output_sync_macro = "\\usepackage[" + opts + "]{srcltx}";
1699 h_output_sync_macro = "\\usepackage{srcltx}";
1702 else if (is_known(name, known_old_language_packages)) {
1703 // known language packages from the times before babel
1704 // if they are found and not also babel, they will be used as
1705 // custom language package
1706 h_language_package = "\\usepackage{" + name + "}";
1709 else if (name == "lyxskak") {
1710 // ignore this and its options
1711 const char * const o[] = {"ps", "mover", 0};
1712 delete_opt(options, o);
1715 else if (name == "parskip" && options.size() < 2 && (opts.empty() || prefixIs(opts, "skip="))) {
1717 h_paragraph_separation = "halfline";
1719 if (opts == "skip=\\smallskipamount")
1720 h_defskip = "smallskip";
1721 else if (opts == "skip=\\medskipamount")
1722 h_defskip = "medskip";
1723 else if (opts == "skip=\\bigskipamount")
1724 h_defskip = "bigskip";
1725 else if (opts == "skip=\\baselineskip")
1726 h_defskip = "fullline";
1729 h_paragraph_separation = "skip";
1733 else if (is_known(name, known_lyx_packages) && options.empty()) {
1734 if (name == "splitidx")
1735 h_use_indices = "true";
1736 else if (name == "minted")
1737 h_use_minted = true;
1738 else if (name == "refstyle")
1739 h_use_refstyle = true;
1740 else if (name == "prettyref")
1741 h_use_refstyle = false;
1743 if (!in_lyx_preamble) {
1744 h_preamble << package_beg_sep << name
1745 << package_mid_sep << "\\usepackage{"
1747 if (p.next_token().cat() == catNewline ||
1748 (p.next_token().cat() == catSpace &&
1749 p.next_next_token().cat() == catNewline))
1751 h_preamble << package_end_sep;
1755 else if (name == "geometry")
1756 handle_geometry(options);
1758 else if (name == "subfig")
1759 ; // ignore this FIXME: Use the package separator mechanism instead
1761 else if (char const * const * where = is_known(name, known_languages))
1762 h_language = known_coded_languages[where - known_languages];
1764 else if (name == "natbib") {
1765 h_biblio_style = "plainnat";
1766 h_cite_engine = "natbib";
1767 h_cite_engine_type = "authoryear";
1768 vector<string>::iterator it =
1769 find(options.begin(), options.end(), "authoryear");
1770 if (it != options.end())
1773 it = find(options.begin(), options.end(), "numbers");
1774 if (it != options.end()) {
1775 h_cite_engine_type = "numerical";
1779 if (!options.empty())
1780 h_biblio_options = join(options, ",");
1783 else if (name == "biblatex") {
1784 h_biblio_style = "plainnat";
1785 h_cite_engine = "biblatex";
1786 h_cite_engine_type = "authoryear";
1788 vector<string>::iterator it =
1789 find(options.begin(), options.end(), "natbib");
1790 if (it != options.end()) {
1792 h_cite_engine = "biblatex-natbib";
1794 opt = process_keyval_opt(options, "natbib");
1796 h_cite_engine = "biblatex-natbib";
1798 opt = process_keyval_opt(options, "style");
1800 h_biblatex_citestyle = opt;
1801 h_biblatex_bibstyle = opt;
1803 opt = process_keyval_opt(options, "citestyle");
1805 h_biblatex_citestyle = opt;
1806 opt = process_keyval_opt(options, "bibstyle");
1808 h_biblatex_bibstyle = opt;
1810 opt = process_keyval_opt(options, "refsection");
1812 if (opt == "none" || opt == "part"
1813 || opt == "chapter" || opt == "section"
1814 || opt == "subsection")
1817 cerr << "Ignoring unknown refsection value '"
1820 opt = process_keyval_opt(options, "bibencoding");
1823 if (!options.empty()) {
1824 h_biblio_options = join(options, ",");
1829 else if (name == "jurabib") {
1830 h_biblio_style = "jurabib";
1831 h_cite_engine = "jurabib";
1832 h_cite_engine_type = "authoryear";
1833 if (!options.empty())
1834 h_biblio_options = join(options, ",");
1837 else if (name == "bibtopic")
1838 h_use_bibtopic = "true";
1840 else if (name == "chapterbib")
1841 h_multibib = "child";
1843 else if (name == "hyperref")
1844 handle_hyperref(options);
1846 else if (name == "algorithm2e") {
1847 // Load "algorithm2e" module
1848 addModule("algorithm2e");
1849 // Add the package options to the global document options
1850 if (!options.empty()) {
1851 if (h_options.empty())
1852 h_options = join(options, ",");
1854 h_options += ',' + join(options, ",");
1857 else if (name == "microtype") {
1858 //we internally support only microtype without params
1859 if (options.empty())
1860 h_use_microtype = "true";
1862 h_preamble << "\\usepackage[" << opts << "]{microtype}";
1865 else if (name == "lineno") {
1866 h_use_lineno = "true";
1867 if (!options.empty()) {
1868 h_lineno_options = join(options, ",");
1873 else if (name == "changebar")
1874 h_output_changes = "true";
1876 else if (!in_lyx_preamble) {
1877 if (options.empty())
1878 h_preamble << "\\usepackage{" << name << '}';
1880 h_preamble << "\\usepackage[" << opts << "]{"
1884 if (p.next_token().cat() == catNewline ||
1885 (p.next_token().cat() == catSpace &&
1886 p.next_next_token().cat() == catNewline))
1890 // We need to do something with the options...
1891 if (!options.empty() && !detectEncoding)
1892 cerr << "Ignoring options '" << join(options, ",")
1893 << "' of package " << name << '.' << endl;
1895 // remove the whitespace
1900 void Preamble::handle_if(Parser & p, bool in_lyx_preamble)
1903 Token t = p.get_token();
1904 if (t.cat() == catEscape &&
1905 is_known(t.cs(), known_if_commands))
1906 handle_if(p, in_lyx_preamble);
1908 if (!in_lyx_preamble)
1909 h_preamble << t.asInput();
1910 if (t.cat() == catEscape && t.cs() == "fi")
1917 bool Preamble::writeLyXHeader(ostream & os, bool subdoc, string const & outfiledir)
1919 if (contains(h_float_placement, "H"))
1920 registerAutomaticallyLoadedPackage("float");
1921 if (h_spacing != "single" && h_spacing != "default")
1922 registerAutomaticallyLoadedPackage("setspace");
1923 if (h_use_packages["amsmath"] == "2") {
1924 // amsbsy and amstext are already provided by amsmath
1925 registerAutomaticallyLoadedPackage("amsbsy");
1926 registerAutomaticallyLoadedPackage("amstext");
1929 // output the LyX file settings
1930 // Important: Keep the version formatting in sync with LyX and
1931 // lyx2lyx (bug 7951)
1932 string const origin = roundtripMode() ? "roundtrip" : outfiledir;
1933 os << "#LyX file created by tex2lyx " << lyx_version_major << '.'
1934 << lyx_version_minor << '\n'
1935 << "\\lyxformat " << LYX_FORMAT << '\n'
1936 << "\\begin_document\n"
1937 << "\\begin_header\n"
1938 << "\\save_transient_properties " << h_save_transient_properties << "\n"
1939 << "\\origin " << origin << "\n"
1940 << "\\textclass " << h_textclass << "\n";
1941 string const raw = subdoc ? empty_string() : h_preamble.str();
1943 os << "\\begin_preamble\n";
1944 for (string::size_type i = 0; i < raw.size(); ++i) {
1945 if (raw[i] == package_beg_sep) {
1946 // Here follows some package loading code that
1947 // must be skipped if the package is loaded
1949 string::size_type j = raw.find(package_mid_sep, i);
1950 if (j == string::npos)
1952 string::size_type k = raw.find(package_end_sep, j);
1953 if (k == string::npos)
1955 string const package = raw.substr(i + 1, j - i - 1);
1956 string const replacement = raw.substr(j + 1, k - j - 1);
1957 if (auto_packages.find(package) == auto_packages.end())
1963 os << "\n\\end_preamble\n";
1965 if (!h_options.empty())
1966 os << "\\options " << h_options << "\n";
1967 os << "\\use_default_options " << h_use_default_options << "\n";
1968 if (!used_modules.empty()) {
1969 os << "\\begin_modules\n";
1970 vector<string>::const_iterator const end = used_modules.end();
1971 vector<string>::const_iterator it = used_modules.begin();
1972 for (; it != end; ++it)
1974 os << "\\end_modules\n";
1976 if (!h_includeonlys.empty()) {
1977 os << "\\begin_includeonly\n";
1978 for (auto const & iofile : h_includeonlys)
1979 os << iofile << '\n';
1980 os << "\\end_includeonly\n";
1982 os << "\\maintain_unincluded_children " << h_maintain_unincluded_children << "\n"
1983 << "\\language " << h_language << "\n"
1984 << "\\language_package " << h_language_package << "\n"
1985 << "\\inputencoding " << h_inputencoding << "\n"
1986 << "\\fontencoding " << h_fontencoding << "\n"
1987 << "\\font_roman \"" << h_font_roman[0]
1988 << "\" \"" << h_font_roman[1] << "\"\n"
1989 << "\\font_sans \"" << h_font_sans[0] << "\" \"" << h_font_sans[1] << "\"\n"
1990 << "\\font_typewriter \"" << h_font_typewriter[0]
1991 << "\" \"" << h_font_typewriter[1] << "\"\n"
1992 << "\\font_math \"" << h_font_math[0] << "\" \"" << h_font_math[1] << "\"\n"
1993 << "\\font_default_family " << h_font_default_family << "\n"
1994 << "\\use_non_tex_fonts " << (h_use_non_tex_fonts ? "true" : "false") << '\n'
1995 << "\\font_sc " << h_font_sc << "\n"
1996 << "\\font_roman_osf " << h_font_roman_osf << "\n"
1997 << "\\font_sans_osf " << h_font_sans_osf << "\n"
1998 << "\\font_typewriter_osf " << h_font_typewriter_osf << "\n";
1999 if (!h_font_roman_opts.empty())
2000 os << "\\font_roman_opts \"" << h_font_roman_opts << "\"" << '\n';
2001 os << "\\font_sf_scale " << h_font_sf_scale[0]
2002 << ' ' << h_font_sf_scale[1] << '\n';
2003 if (!h_font_sans_opts.empty())
2004 os << "\\font_sans_opts \"" << h_font_sans_opts << "\"" << '\n';
2005 os << "\\font_tt_scale " << h_font_tt_scale[0]
2006 << ' ' << h_font_tt_scale[1] << '\n';
2007 if (!h_font_cjk.empty())
2008 os << "\\font_cjk " << h_font_cjk << '\n';
2009 if (!h_font_typewriter_opts.empty())
2010 os << "\\font_typewriter_opts \"" << h_font_typewriter_opts << "\"" << '\n';
2011 os << "\\use_microtype " << h_use_microtype << '\n'
2012 << "\\use_dash_ligatures " << h_use_dash_ligatures << '\n'
2013 << "\\graphics " << h_graphics << '\n'
2014 << "\\default_output_format " << h_default_output_format << "\n"
2015 << "\\output_sync " << h_output_sync << "\n";
2016 if (h_output_sync == "1")
2017 os << "\\output_sync_macro \"" << h_output_sync_macro << "\"\n";
2018 os << "\\bibtex_command " << h_bibtex_command << "\n"
2019 << "\\index_command " << h_index_command << "\n";
2020 if (!h_float_placement.empty())
2021 os << "\\float_placement " << h_float_placement << "\n";
2022 os << "\\paperfontsize " << h_paperfontsize << "\n"
2023 << "\\spacing " << h_spacing << "\n"
2024 << "\\use_hyperref " << h_use_hyperref << '\n';
2025 if (h_use_hyperref == "true") {
2026 if (!h_pdf_title.empty())
2027 os << "\\pdf_title " << Lexer::quoteString(h_pdf_title) << '\n';
2028 if (!h_pdf_author.empty())
2029 os << "\\pdf_author " << Lexer::quoteString(h_pdf_author) << '\n';
2030 if (!h_pdf_subject.empty())
2031 os << "\\pdf_subject " << Lexer::quoteString(h_pdf_subject) << '\n';
2032 if (!h_pdf_keywords.empty())
2033 os << "\\pdf_keywords " << Lexer::quoteString(h_pdf_keywords) << '\n';
2034 os << "\\pdf_bookmarks " << h_pdf_bookmarks << "\n"
2035 "\\pdf_bookmarksnumbered " << h_pdf_bookmarksnumbered << "\n"
2036 "\\pdf_bookmarksopen " << h_pdf_bookmarksopen << "\n"
2037 "\\pdf_bookmarksopenlevel " << h_pdf_bookmarksopenlevel << "\n"
2038 "\\pdf_breaklinks " << h_pdf_breaklinks << "\n"
2039 "\\pdf_pdfborder " << h_pdf_pdfborder << "\n"
2040 "\\pdf_colorlinks " << h_pdf_colorlinks << "\n"
2041 "\\pdf_backref " << h_pdf_backref << "\n"
2042 "\\pdf_pdfusetitle " << h_pdf_pdfusetitle << '\n';
2043 if (!h_pdf_pagemode.empty())
2044 os << "\\pdf_pagemode " << h_pdf_pagemode << '\n';
2045 if (!h_pdf_quoted_options.empty())
2046 os << "\\pdf_quoted_options " << Lexer::quoteString(h_pdf_quoted_options) << '\n';
2048 os << "\\papersize " << h_papersize << "\n"
2049 << "\\use_geometry " << h_use_geometry << '\n';
2050 for (map<string, string>::const_iterator it = h_use_packages.begin();
2051 it != h_use_packages.end(); ++it)
2052 os << "\\use_package " << it->first << ' ' << it->second << '\n';
2053 os << "\\cite_engine " << h_cite_engine << '\n'
2054 << "\\cite_engine_type " << h_cite_engine_type << '\n'
2055 << "\\biblio_style " << h_biblio_style << "\n"
2056 << "\\use_bibtopic " << h_use_bibtopic << "\n";
2057 if (!h_biblio_options.empty())
2058 os << "\\biblio_options " << h_biblio_options << "\n";
2059 if (!h_biblatex_bibstyle.empty())
2060 os << "\\biblatex_bibstyle " << h_biblatex_bibstyle << "\n";
2061 if (!h_biblatex_citestyle.empty())
2062 os << "\\biblatex_citestyle " << h_biblatex_citestyle << "\n";
2063 if (!h_multibib.empty())
2064 os << "\\multibib " << h_multibib << "\n";
2065 os << "\\use_indices " << h_use_indices << "\n"
2066 << "\\paperorientation " << h_paperorientation << '\n'
2067 << "\\suppress_date " << h_suppress_date << '\n'
2068 << "\\justification " << h_justification << '\n'
2069 << "\\use_refstyle " << h_use_refstyle << '\n'
2070 << "\\use_minted " << h_use_minted << '\n'
2071 << "\\use_lineno " << h_use_lineno << '\n';
2072 if (!h_lineno_options.empty())
2073 os << "\\lineno_options " << h_lineno_options << '\n';
2074 if (!h_fontcolor.empty())
2075 os << "\\fontcolor " << h_fontcolor << '\n';
2076 if (!h_notefontcolor.empty())
2077 os << "\\notefontcolor " << h_notefontcolor << '\n';
2078 if (!h_backgroundcolor.empty())
2079 os << "\\backgroundcolor " << h_backgroundcolor << '\n';
2080 if (!h_boxbgcolor.empty())
2081 os << "\\boxbgcolor " << h_boxbgcolor << '\n';
2082 if (index_number != 0)
2083 for (int i = 0; i < index_number; i++) {
2084 os << "\\index " << h_index[i] << '\n'
2085 << "\\shortcut " << h_shortcut[i] << '\n'
2086 << "\\color " << h_color << '\n'
2090 os << "\\index " << h_index[0] << '\n'
2091 << "\\shortcut " << h_shortcut[0] << '\n'
2092 << "\\color " << h_color << '\n'
2096 << "\\secnumdepth " << h_secnumdepth << "\n"
2097 << "\\tocdepth " << h_tocdepth << "\n"
2098 << "\\paragraph_separation " << h_paragraph_separation << "\n";
2099 if (h_paragraph_separation == "skip")
2100 os << "\\defskip " << h_defskip << "\n";
2102 os << "\\paragraph_indentation " << h_paragraph_indentation << "\n";
2103 os << "\\is_math_indent " << h_is_mathindent << "\n";
2104 if (!h_mathindentation.empty())
2105 os << "\\math_indentation " << h_mathindentation << "\n";
2106 os << "\\math_numbering_side " << h_math_numbering_side << "\n";
2107 os << "\\quotes_style " << h_quotes_style << "\n"
2108 << "\\dynamic_quotes " << h_dynamic_quotes << "\n"
2109 << "\\papercolumns " << h_papercolumns << "\n"
2110 << "\\papersides " << h_papersides << "\n"
2111 << "\\paperpagestyle " << h_paperpagestyle << "\n";
2112 if (!h_listings_params.empty())
2113 os << "\\listings_params " << h_listings_params << "\n";
2114 os << "\\tracking_changes " << h_tracking_changes << "\n"
2115 << "\\output_changes " << h_output_changes << "\n"
2116 << "\\change_bars " << h_change_bars << "\n"
2117 << "\\html_math_output " << h_html_math_output << "\n"
2118 << "\\html_css_as_file " << h_html_css_as_file << "\n"
2119 << "\\html_be_strict " << h_html_be_strict << "\n"
2121 << "\\end_header\n\n"
2122 << "\\begin_body\n";
2127 void Preamble::parse(Parser & p, string const & forceclass,
2128 TeX2LyXDocClass & tc)
2130 // initialize fixed types
2131 special_columns_['D'] = 3;
2132 parse(p, forceclass, false, tc);
2136 void Preamble::parse(Parser & p, string const & forceclass,
2137 bool detectEncoding, TeX2LyXDocClass & tc)
2139 bool is_full_document = false;
2140 bool is_lyx_file = false;
2141 bool in_lyx_preamble = false;
2143 // determine whether this is a full document or a fragment for inclusion
2145 Token const & t = p.get_token();
2147 if (t.cat() == catEscape && t.cs() == "documentclass") {
2148 is_full_document = true;
2154 if (detectEncoding && !is_full_document)
2157 while (is_full_document && p.good()) {
2158 if (detectEncoding && h_inputencoding != "auto-legacy" &&
2159 h_inputencoding != "auto-legacy-plain")
2162 Token const & t = p.get_token();
2165 if (!detectEncoding)
2166 cerr << "t: " << t << '\n';
2172 if (!in_lyx_preamble &&
2173 (t.cat() == catLetter ||
2174 t.cat() == catSuper ||
2175 t.cat() == catSub ||
2176 t.cat() == catOther ||
2177 t.cat() == catMath ||
2178 t.cat() == catActive ||
2179 t.cat() == catBegin ||
2180 t.cat() == catEnd ||
2181 t.cat() == catAlign ||
2182 t.cat() == catParameter)) {
2183 h_preamble << t.cs();
2187 if (!in_lyx_preamble &&
2188 (t.cat() == catSpace || t.cat() == catNewline)) {
2189 h_preamble << t.asInput();
2193 if (t.cat() == catComment) {
2194 static regex const islyxfile("%% LyX .* created this file");
2195 static regex const usercommands("User specified LaTeX commands");
2197 string const comment = t.asInput();
2199 // magically switch encoding default if it looks like XeLaTeX
2200 static string const magicXeLaTeX =
2201 "% This document must be compiled with XeLaTeX ";
2202 if (comment.size() > magicXeLaTeX.size()
2203 && comment.substr(0, magicXeLaTeX.size()) == magicXeLaTeX
2204 && h_inputencoding == "auto-legacy") {
2205 if (!detectEncoding)
2206 cerr << "XeLaTeX comment found, switching to UTF8\n";
2207 h_inputencoding = "utf8";
2210 if (regex_search(comment, sub, islyxfile)) {
2212 in_lyx_preamble = true;
2213 } else if (is_lyx_file
2214 && regex_search(comment, sub, usercommands))
2215 in_lyx_preamble = false;
2216 else if (!in_lyx_preamble)
2217 h_preamble << t.asInput();
2221 if (t.cs() == "PassOptionsToPackage") {
2222 string const poptions = p.getArg('{', '}');
2223 string const package = p.verbatim_item();
2224 extra_package_options_.insert(make_pair(package, poptions));
2228 if (t.cs() == "pagestyle") {
2229 h_paperpagestyle = p.verbatim_item();
2233 if (t.cs() == "setdefaultlanguage") {
2235 // We don't yet care about non-language variant options
2236 // because LyX doesn't support this yet, see bug #8214
2238 string langopts = p.getOpt();
2239 // check if the option contains a variant, if yes, extract it
2240 string::size_type pos_var = langopts.find("variant");
2241 string::size_type i = langopts.find(',', pos_var);
2242 string::size_type k = langopts.find('=', pos_var);
2243 if (pos_var != string::npos){
2245 if (i == string::npos)
2246 variant = langopts.substr(k + 1, langopts.length() - k - 2);
2248 variant = langopts.substr(k + 1, i - k - 1);
2249 h_language = variant;
2253 h_language = p.verbatim_item();
2254 //finally translate the poyglossia name to a LyX name
2255 h_language = polyglossia2lyx(h_language);
2259 if (t.cs() == "setotherlanguage") {
2260 // We don't yet care about the option because LyX doesn't
2261 // support this yet, see bug #8214
2262 p.hasOpt() ? p.getOpt() : string();
2267 if (t.cs() == "setmainfont") {
2268 string fontopts = p.hasOpt() ? p.getArg('[', ']') : string();
2269 h_font_roman[1] = p.getArg('{', '}');
2270 if (!fontopts.empty()) {
2271 vector<string> opts = getVectorFromString(fontopts);
2273 for (auto const & opt : opts) {
2274 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2277 if (!fontopts.empty())
2281 h_font_roman_opts = fontopts;
2286 if (t.cs() == "setsansfont" || t.cs() == "setmonofont") {
2287 // LyX currently only supports the scale option
2288 string scale, fontopts;
2290 fontopts = p.getArg('[', ']');
2291 if (!fontopts.empty()) {
2292 vector<string> opts = getVectorFromString(fontopts);
2294 for (auto const & opt : opts) {
2295 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2298 if (prefixIs(opt, "Scale=")) {
2299 scale_as_percentage(opt, scale);
2302 if (!fontopts.empty())
2308 if (t.cs() == "setsansfont") {
2310 h_font_sf_scale[1] = scale;
2311 h_font_sans[1] = p.getArg('{', '}');
2312 if (!fontopts.empty())
2313 h_font_sans_opts = fontopts;
2316 h_font_tt_scale[1] = scale;
2317 h_font_typewriter[1] = p.getArg('{', '}');
2318 if (!fontopts.empty())
2319 h_font_typewriter_opts = fontopts;
2324 if (t.cs() == "babelfont") {
2326 h_use_non_tex_fonts = true;
2327 h_language_package = "babel";
2328 if (h_inputencoding == "auto-legacy")
2329 p.setEncoding("UTF-8");
2330 // we don't care about the lang option
2331 string const lang = p.hasOpt() ? p.getArg('[', ']') : string();
2332 string const family = p.getArg('{', '}');
2333 string fontopts = p.hasOpt() ? p.getArg('[', ']') : string();
2334 string const fontname = p.getArg('{', '}');
2335 if (lang.empty() && family == "rm") {
2336 h_font_roman[1] = fontname;
2337 if (!fontopts.empty()) {
2338 vector<string> opts = getVectorFromString(fontopts);
2340 for (auto const & opt : opts) {
2341 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2344 if (!fontopts.empty())
2348 h_font_roman_opts = fontopts;
2351 } else if (lang.empty() && (family == "sf" || family == "tt")) {
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 (prefixIs(opt, "Scale=")) {
2361 scale_as_percentage(opt, scale);
2364 if (!fontopts.empty())
2369 if (family == "sf") {
2371 h_font_sf_scale[1] = scale;
2372 h_font_sans[1] = fontname;
2373 if (!fontopts.empty())
2374 h_font_sans_opts = fontopts;
2377 h_font_tt_scale[1] = scale;
2378 h_font_typewriter[1] = fontname;
2379 if (!fontopts.empty())
2380 h_font_typewriter_opts = fontopts;
2384 // not rm, sf or tt or lang specific
2385 h_preamble << '\\' << t.cs();
2387 h_preamble << '[' << lang << ']';
2388 h_preamble << '{' << family << '}';
2389 if (!fontopts.empty())
2390 h_preamble << '[' << fontopts << ']';
2391 h_preamble << '{' << fontname << '}' << '\n';
2396 if (t.cs() == "date") {
2397 string argument = p.getArg('{', '}');
2398 if (argument.empty())
2399 h_suppress_date = "true";
2401 h_preamble << t.asInput() << '{' << argument << '}';
2405 if (t.cs() == "color") {
2406 string const space =
2407 (p.hasOpt() ? p.getOpt() : string());
2408 string argument = p.getArg('{', '}');
2409 // check the case that a standard color is used
2410 if (space.empty() && is_known(argument, known_basic_colors)) {
2411 h_fontcolor = rgbcolor2code(argument);
2412 registerAutomaticallyLoadedPackage("color");
2413 } else if (space.empty() && argument == "document_fontcolor")
2414 registerAutomaticallyLoadedPackage("color");
2415 // check the case that LyX's document_fontcolor is defined
2416 // but not used for \color
2418 h_preamble << t.asInput();
2420 h_preamble << space;
2421 h_preamble << '{' << argument << '}';
2422 // the color might already be set because \definecolor
2423 // is parsed before this
2429 if (t.cs() == "pagecolor") {
2430 string argument = p.getArg('{', '}');
2431 // check the case that a standard color is used
2432 if (is_known(argument, known_basic_colors)) {
2433 h_backgroundcolor = rgbcolor2code(argument);
2434 } else if (argument == "page_backgroundcolor")
2435 registerAutomaticallyLoadedPackage("color");
2436 // check the case that LyX's page_backgroundcolor is defined
2437 // but not used for \pagecolor
2439 h_preamble << t.asInput() << '{' << argument << '}';
2440 // the color might already be set because \definecolor
2441 // is parsed before this
2442 h_backgroundcolor = "";
2447 if (t.cs() == "makeatletter") {
2448 // LyX takes care of this
2449 p.setCatcode('@', catLetter);
2453 if (t.cs() == "makeatother") {
2454 // LyX takes care of this
2455 p.setCatcode('@', catOther);
2459 if (t.cs() == "makeindex") {
2460 // LyX will re-add this if a print index command is found
2465 if (t.cs() == "newindex") {
2466 string const indexname = p.getArg('[', ']');
2467 string const shortcut = p.verbatim_item();
2468 if (!indexname.empty())
2469 h_index[index_number] = indexname;
2471 h_index[index_number] = shortcut;
2472 h_shortcut[index_number] = shortcut;
2478 if (t.cs() == "addbibresource") {
2479 string const options = p.getArg('[', ']');
2480 string const arg = removeExtension(p.getArg('{', '}'));
2481 if (!options.empty()) {
2482 // check if the option contains a bibencoding, if yes, extract it
2483 string::size_type pos = options.find("bibencoding=");
2485 if (pos != string::npos) {
2486 string::size_type i = options.find(',', pos);
2487 if (i == string::npos)
2488 encoding = options.substr(pos + 1);
2490 encoding = options.substr(pos, i - pos);
2491 pos = encoding.find('=');
2492 if (pos == string::npos)
2495 encoding = encoding.substr(pos + 1);
2497 if (!encoding.empty())
2498 biblatex_encodings.push_back(normalize_filename(arg) + ' ' + encoding);
2500 biblatex_bibliographies.push_back(arg);
2504 if (t.cs() == "bibliography") {
2505 vector<string> bibs = getVectorFromString(p.getArg('{', '}'));
2506 biblatex_bibliographies.insert(biblatex_bibliographies.end(), bibs.begin(), bibs.end());
2510 if (t.cs() == "RS@ifundefined") {
2511 string const name = p.verbatim_item();
2512 string const body1 = p.verbatim_item();
2513 string const body2 = p.verbatim_item();
2514 // only non-lyxspecific stuff
2515 if (in_lyx_preamble &&
2516 (name == "subsecref" || name == "thmref" || name == "lemref"))
2520 ss << '\\' << t.cs();
2521 ss << '{' << name << '}'
2522 << '{' << body1 << '}'
2523 << '{' << body2 << '}';
2524 h_preamble << ss.str();
2529 if (t.cs() == "AtBeginDocument") {
2530 string const name = p.verbatim_item();
2531 // only non-lyxspecific stuff
2532 if (in_lyx_preamble &&
2533 (name == "\\providecommand\\partref[1]{\\ref{part:#1}}"
2534 || name == "\\providecommand\\chapref[1]{\\ref{chap:#1}}"
2535 || name == "\\providecommand\\secref[1]{\\ref{sec:#1}}"
2536 || name == "\\providecommand\\subsecref[1]{\\ref{subsec:#1}}"
2537 || name == "\\providecommand\\parref[1]{\\ref{par:#1}}"
2538 || name == "\\providecommand\\figref[1]{\\ref{fig:#1}}"
2539 || name == "\\providecommand\\tabref[1]{\\ref{tab:#1}}"
2540 || name == "\\providecommand\\algref[1]{\\ref{alg:#1}}"
2541 || name == "\\providecommand\\fnref[1]{\\ref{fn:#1}}"
2542 || name == "\\providecommand\\enuref[1]{\\ref{enu:#1}}"
2543 || name == "\\providecommand\\eqref[1]{\\ref{eq:#1}}"
2544 || name == "\\providecommand\\lemref[1]{\\ref{lem:#1}}"
2545 || name == "\\providecommand\\thmref[1]{\\ref{thm:#1}}"
2546 || name == "\\providecommand\\corref[1]{\\ref{cor:#1}}"
2547 || name == "\\providecommand\\propref[1]{\\ref{prop:#1}}"))
2551 ss << '\\' << t.cs();
2552 ss << '{' << name << '}';
2553 h_preamble << ss.str();
2558 if (t.cs() == "newcommand" || t.cs() == "newcommandx"
2559 || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
2560 || t.cs() == "providecommand" || t.cs() == "providecommandx"
2561 || t.cs() == "DeclareRobustCommand"
2562 || t.cs() == "DeclareRobustCommandx"
2563 || t.cs() == "ProvideTextCommandDefault"
2564 || t.cs() == "DeclareMathAccent") {
2566 if (p.next_token().character() == '*') {
2570 string const name = p.verbatim_item();
2571 string const opt1 = p.getFullOpt();
2572 string const opt2 = p.getFullOpt();
2573 string const body = p.verbatim_item();
2574 // store the in_lyx_preamble setting
2575 bool const was_in_lyx_preamble = in_lyx_preamble;
2577 if (name == "\\rmdefault")
2578 if (is_known(body, known_roman_font_packages)) {
2579 h_font_roman[0] = body;
2581 in_lyx_preamble = true;
2583 if (name == "\\sfdefault") {
2584 if (is_known(body, known_sans_font_packages)) {
2585 h_font_sans[0] = body;
2587 in_lyx_preamble = true;
2589 if (body == "LibertinusSans-OsF") {
2590 h_font_sans[0] = "LibertinusSans-LF";
2591 h_font_sans_osf = "true";
2593 in_lyx_preamble = true;
2596 if (name == "\\ttdefault")
2597 if (is_known(body, known_typewriter_font_packages)) {
2598 h_font_typewriter[0] = body;
2600 in_lyx_preamble = true;
2602 if (name == "\\familydefault") {
2603 string family = body;
2604 // remove leading "\"
2605 h_font_default_family = family.erase(0,1);
2607 in_lyx_preamble = true;
2609 if (name == "\\LibertinusSans@scale") {
2610 if (isStrDbl(body)) {
2611 h_font_sf_scale[0] = convert<string>(
2612 static_cast<int>(100 * convert<double>(body)));
2615 if (name == "\\LibertinusMono@scale") {
2616 if (isStrDbl(body)) {
2617 h_font_tt_scale[0] = convert<string>(
2618 static_cast<int>(100 * convert<double>(body)));
2622 // remove LyX-specific definitions that are re-added by LyX
2624 // \lyxline is an ancient command that is converted by tex2lyx into
2625 // a \rule therefore remove its preamble code
2626 if (name == "\\lyxdot" || name == "\\lyxarrow"
2627 || name == "\\lyxline" || name == "\\LyX") {
2629 in_lyx_preamble = true;
2632 // Add the command to the known commands
2633 add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
2635 // only non-lyxspecific stuff
2636 if (!in_lyx_preamble) {
2638 ss << '\\' << t.cs();
2641 ss << '{' << name << '}' << opt1 << opt2
2642 << '{' << body << "}";
2643 if (prefixIs(t.cs(), "renew") || !contains(h_preamble.str(), ss.str()))
2644 h_preamble << ss.str();
2646 ostream & out = in_preamble ? h_preamble : os;
2647 out << "\\" << t.cs() << "{" << name << "}"
2648 << opts << "{" << body << "}";
2651 // restore the in_lyx_preamble setting
2652 in_lyx_preamble = was_in_lyx_preamble;
2656 if (t.cs() == "documentclass") {
2657 vector<string>::iterator it;
2658 vector<string> opts = split_options(p.getArg('[', ']'));
2659 // FIXME This does not work for classes that have a
2660 // different name in LyX than in LaTeX
2661 h_textclass = p.getArg('{', '}');
2663 // Force textclass if the user wanted it
2664 if (!forceclass.empty())
2665 h_textclass = forceclass;
2666 tc.setName(h_textclass);
2667 if (!LayoutFileList::get().haveClass(h_textclass) || !tc.load()) {
2668 cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
2673 // Try those who are (most likely) known to all packages first
2674 handle_opt(opts, known_fontsizes, h_paperfontsize);
2675 delete_opt(opts, known_fontsizes);
2676 // delete "pt" at the end
2677 string::size_type i = h_paperfontsize.find("pt");
2678 if (i != string::npos)
2679 h_paperfontsize.erase(i);
2680 // Now those known specifically to the class
2681 vector<string> class_fsizes = getVectorFromString(tc.opt_fontsize(), "|");
2682 string const fsize_format = tc.fontsizeformat();
2683 for (auto const & fsize : class_fsizes) {
2684 string latexsize = subst(fsize_format, "$$s", fsize);
2685 vector<string>::iterator it = find(opts.begin(), opts.end(), latexsize);
2686 if (it != opts.end()) {
2687 h_paperfontsize = fsize;
2693 // The documentclass options are always parsed before the options
2694 // of the babel call so that a language cannot overwrite the babel
2696 handle_opt(opts, known_languages, h_language);
2697 delete_opt(opts, known_languages);
2700 if ((it = find(opts.begin(), opts.end(), "fleqn"))
2702 h_is_mathindent = "1";
2705 // formula numbering side
2706 if ((it = find(opts.begin(), opts.end(), "leqno"))
2708 h_math_numbering_side = "left";
2711 else if ((it = find(opts.begin(), opts.end(), "reqno"))
2713 h_math_numbering_side = "right";
2717 // paper orientation
2718 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
2719 h_paperorientation = "landscape";
2723 if ((it = find(opts.begin(), opts.end(), "oneside"))
2728 if ((it = find(opts.begin(), opts.end(), "twoside"))
2734 if ((it = find(opts.begin(), opts.end(), "onecolumn"))
2736 h_papercolumns = "1";
2739 if ((it = find(opts.begin(), opts.end(), "twocolumn"))
2741 h_papercolumns = "2";
2745 // some size options are known by the document class, other sizes
2746 // are handled by the \geometry command of the geometry package
2748 vector<string> class_psizes = getVectorFromString(tc.opt_pagesize(), "|");
2749 string const psize_format = tc.pagesizeformat();
2750 for (auto const & psize : class_psizes) {
2751 string latexsize = subst(psize_format, "$$s", psize);
2752 vector<string>::iterator it = find(opts.begin(), opts.end(), latexsize);
2753 if (it != opts.end()) {
2754 h_papersize = psize;
2758 if (psize_format == "$$spaper")
2760 // Also try with the default format since this is understood by
2762 latexsize = psize + "paper";
2763 it = find(opts.begin(), opts.end(), latexsize);
2764 if (it != opts.end()) {
2765 h_papersize = psize;
2770 // the remaining options
2771 h_options = join(opts, ",");
2775 if (t.cs() == "usepackage") {
2776 string const options = p.getArg('[', ']');
2777 string const name = p.getArg('{', '}');
2778 vector<string> vecnames;
2779 split(name, vecnames, ',');
2780 vector<string>::const_iterator it = vecnames.begin();
2781 vector<string>::const_iterator end = vecnames.end();
2782 for (; it != end; ++it)
2783 handle_package(p, trimSpaceAndEol(*it), options,
2784 in_lyx_preamble, detectEncoding);
2788 if (t.cs() == "inputencoding") {
2789 string const encoding = p.getArg('{','}');
2790 Encoding const * const enc = encodings.fromLaTeXName(
2791 encoding, Encoding::inputenc, true);
2793 if (!detectEncoding)
2794 cerr << "Unknown encoding " << encoding
2795 << ". Ignoring." << std::endl;
2798 h_inputencoding = enc->name();
2799 p.setEncoding(enc->iconvName());
2804 if (t.cs() == "newenvironment") {
2805 string const name = p.getArg('{', '}');
2806 string const opt1 = p.getFullOpt();
2807 string const opt2 = p.getFullOpt();
2808 string const beg = p.verbatim_item();
2809 string const end = p.verbatim_item();
2810 if (!in_lyx_preamble) {
2811 h_preamble << "\\newenvironment{" << name
2812 << '}' << opt1 << opt2 << '{'
2813 << beg << "}{" << end << '}';
2815 add_known_environment(name, opt1, !opt2.empty(),
2816 from_utf8(beg), from_utf8(end));
2820 if (t.cs() == "newtheorem") {
2822 if (p.next_token().character() == '*') {
2826 string const name = p.getArg('{', '}');
2827 string const opt1 = p.getFullOpt();
2828 string const opt2 = p.getFullOpt();
2829 string const body = p.verbatim_item();
2830 string const opt3 = p.getFullOpt();
2831 string const cmd = star ? "\\newtheorem*" : "\\newtheorem";
2833 string const complete = cmd + "{" + name + '}' +
2834 opt1 + opt2 + '{' + body + '}' + opt3;
2836 add_known_theorem(name, opt1, !opt2.empty(), from_utf8(complete));
2838 if (!in_lyx_preamble)
2839 h_preamble << complete;
2843 if (t.cs() == "def") {
2844 string name = p.get_token().cs();
2845 // In fact, name may be more than the name:
2846 // In the test case of bug 8116
2847 // name == "csname SF@gobble@opt \endcsname".
2848 // Therefore, we need to use asInput() instead of cs().
2849 while (p.next_token().cat() != catBegin)
2850 name += p.get_token().asInput();
2851 if (!in_lyx_preamble)
2852 h_preamble << "\\def\\" << name << '{'
2853 << p.verbatim_item() << "}";
2857 if (t.cs() == "newcolumntype") {
2858 string const name = p.getArg('{', '}');
2859 trimSpaceAndEol(name);
2861 string opts = p.getOpt();
2862 if (!opts.empty()) {
2863 istringstream is(string(opts, 1));
2866 special_columns_[name[0]] = nargs;
2867 h_preamble << "\\newcolumntype{" << name << "}";
2869 h_preamble << "[" << nargs << "]";
2870 h_preamble << "{" << p.verbatim_item() << "}";
2874 if (t.cs() == "setcounter") {
2875 string const name = p.getArg('{', '}');
2876 string const content = p.getArg('{', '}');
2877 if (name == "secnumdepth")
2878 h_secnumdepth = content;
2879 else if (name == "tocdepth")
2880 h_tocdepth = content;
2882 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
2886 if (t.cs() == "setlength") {
2887 string const name = p.verbatim_item();
2888 string const content = p.verbatim_item();
2889 // the paragraphs are only not indented when \parindent is set to zero
2890 if (name == "\\parindent" && content != "")
2891 h_paragraph_indentation = translate_len(content);
2892 else if (name == "\\parskip" && isPackageUsed("parskip")) {
2893 if (content == "\\smallskipamount")
2894 h_defskip = "smallskip";
2895 else if (content == "\\medskipamount")
2896 h_defskip = "medskip";
2897 else if (content == "\\bigskipamount")
2898 h_defskip = "bigskip";
2899 else if (content == "\\baselineskip")
2900 h_defskip = "fullline";
2902 h_defskip = translate_len(content);
2903 } else if (name == "\\mathindent") {
2904 h_mathindentation = translate_len(content);
2906 h_preamble << "\\setlength{" << name << "}{" << content << "}";
2910 if (t.cs() == "onehalfspacing") {
2911 h_spacing = "onehalf";
2915 if (t.cs() == "doublespacing") {
2916 h_spacing = "double";
2920 if (t.cs() == "setstretch") {
2921 h_spacing = "other " + p.verbatim_item();
2925 if (t.cs() == "synctex") {
2926 // the scheme is \synctex=value
2927 // where value can only be "1" or "-1"
2928 h_output_sync = "1";
2929 // there can be any character behind the value (e.g. a linebreak or a '\'
2930 // therefore we extract it char by char
2932 string value = p.get_token().asInput();
2934 value += p.get_token().asInput();
2935 h_output_sync_macro = "\\synctex=" + value;
2939 if (t.cs() == "begin") {
2940 string const name = p.getArg('{', '}');
2941 if (name == "document")
2943 h_preamble << "\\begin{" << name << "}";
2947 if (t.cs() == "geometry") {
2948 vector<string> opts = split_options(p.getArg('{', '}'));
2949 handle_geometry(opts);
2953 if (t.cs() == "definecolor") {
2954 string const color = p.getArg('{', '}');
2955 string const space = p.getArg('{', '}');
2956 string const value = p.getArg('{', '}');
2957 if (color == "document_fontcolor" && space == "rgb") {
2958 RGBColor c(RGBColorFromLaTeX(value));
2959 h_fontcolor = X11hexname(c);
2960 } else if (color == "note_fontcolor" && space == "rgb") {
2961 RGBColor c(RGBColorFromLaTeX(value));
2962 h_notefontcolor = X11hexname(c);
2963 } else if (color == "page_backgroundcolor" && space == "rgb") {
2964 RGBColor c(RGBColorFromLaTeX(value));
2965 h_backgroundcolor = X11hexname(c);
2966 } else if (color == "shadecolor" && space == "rgb") {
2967 RGBColor c(RGBColorFromLaTeX(value));
2968 h_boxbgcolor = X11hexname(c);
2970 h_preamble << "\\definecolor{" << color
2971 << "}{" << space << "}{" << value
2977 if (t.cs() == "bibliographystyle") {
2978 h_biblio_style = p.verbatim_item();
2982 if (t.cs() == "jurabibsetup") {
2983 // FIXME p.getArg('{', '}') is most probably wrong (it
2984 // does not handle nested braces).
2985 // Use p.verbatim_item() instead.
2986 vector<string> jurabibsetup =
2987 split_options(p.getArg('{', '}'));
2988 // add jurabibsetup to the jurabib package options
2989 add_package("jurabib", jurabibsetup);
2990 if (!jurabibsetup.empty()) {
2991 h_preamble << "\\jurabibsetup{"
2992 << join(jurabibsetup, ",") << '}';
2997 if (t.cs() == "hypersetup") {
2998 vector<string> hypersetup =
2999 split_options(p.verbatim_item());
3000 // add hypersetup to the hyperref package options
3001 handle_hyperref(hypersetup);
3002 if (!hypersetup.empty()) {
3003 h_preamble << "\\hypersetup{"
3004 << join(hypersetup, ",") << '}';
3009 if (t.cs() == "includeonly") {
3010 vector<string> includeonlys = getVectorFromString(p.getArg('{', '}'));
3011 for (auto & iofile : includeonlys) {
3012 string filename(normalize_filename(iofile));
3013 string const path = getMasterFilePath(true);
3014 // We want to preserve relative/absolute filenames,
3015 // therefore path is only used for testing
3016 if (!makeAbsPath(filename, path).exists()) {
3017 // The file extension is probably missing.
3018 // Now try to find it out.
3019 string const tex_name =
3020 find_file(filename, path,
3021 known_tex_extensions);
3022 if (!tex_name.empty())
3023 filename = tex_name;
3026 if (makeAbsPath(filename, path).exists())
3027 fix_child_filename(filename);
3029 cerr << "Warning: Could not find included file '"
3030 << filename << "'." << endl;
3031 outname = changeExtension(filename, "lyx");
3032 h_includeonlys.push_back(outname);
3037 if (is_known(t.cs(), known_if_3arg_commands)) {
3038 // prevent misparsing of \usepackage if it is used
3039 // as an argument (see e.g. our own output of
3040 // \@ifundefined above)
3041 string const arg1 = p.verbatim_item();
3042 string const arg2 = p.verbatim_item();
3043 string const arg3 = p.verbatim_item();
3044 // test case \@ifundefined{date}{}{\date{}}
3045 if (t.cs() == "@ifundefined" && arg1 == "date" &&
3046 arg2.empty() && arg3 == "\\date{}") {
3047 h_suppress_date = "true";
3048 // older tex2lyx versions did output
3049 // \@ifundefined{definecolor}{\usepackage{color}}{}
3050 } else if (t.cs() == "@ifundefined" &&
3051 arg1 == "definecolor" &&
3052 arg2 == "\\usepackage{color}" &&
3054 if (!in_lyx_preamble)
3055 h_preamble << package_beg_sep
3058 << "\\@ifundefined{definecolor}{color}{}"
3061 //\@ifundefined{showcaptionsetup}{}{%
3062 // \PassOptionsToPackage{caption=false}{subfig}}
3063 // that LyX uses for subfloats
3064 } else if (t.cs() == "@ifundefined" &&
3065 arg1 == "showcaptionsetup" && arg2.empty()
3066 && arg3 == "%\n \\PassOptionsToPackage{caption=false}{subfig}") {
3068 } else if (!in_lyx_preamble) {
3069 h_preamble << t.asInput()
3070 << '{' << arg1 << '}'
3071 << '{' << arg2 << '}'
3072 << '{' << arg3 << '}';
3077 if (is_known(t.cs(), known_if_commands)) {
3078 // must not parse anything in conditional code, since
3079 // LyX would output the parsed contents unconditionally
3080 if (!in_lyx_preamble)
3081 h_preamble << t.asInput();
3082 handle_if(p, in_lyx_preamble);
3086 if (!t.cs().empty() && !in_lyx_preamble) {
3087 h_preamble << '\\' << t.cs();
3092 // remove the whitespace
3095 if (h_papersides.empty()) {
3098 h_papersides = ss.str();
3101 // If the CJK package is used we cannot set the document language from
3102 // the babel options. Instead, we guess which language is used most
3103 // and set this one.
3104 default_language = h_language;
3105 if (is_full_document &&
3106 (auto_packages.find("CJK") != auto_packages.end() ||
3107 auto_packages.find("CJKutf8") != auto_packages.end())) {
3109 h_language = guessLanguage(p, default_language);
3111 if (explicit_babel && h_language != default_language) {
3112 // We set the document language to a CJK language,
3113 // but babel is explicitly called in the user preamble
3114 // without options. LyX will not add the default
3115 // language to the document options if it is either
3116 // english, or no text is set as default language.
3117 // Therefore we need to add a language option explicitly.
3118 // FIXME: It would be better to remove all babel calls
3119 // from the user preamble, but this is difficult
3120 // without re-introducing bug 7861.
3121 if (h_options.empty())
3122 h_options = lyx2babel(default_language);
3124 h_options += ',' + lyx2babel(default_language);
3128 // Finally, set the quote style.
3129 // LyX knows the following quotes styles:
3130 // british, cjk, cjkangle, danish, english, french, german,
3131 // polish, russian, swedish and swiss
3132 // conversion list taken from
3133 // https://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
3134 // (quotes for kazakh are unknown)
3136 if (is_known(h_language, known_british_quotes_languages))
3137 h_quotes_style = "british";
3139 else if (is_known(h_language, known_cjk_quotes_languages))
3140 h_quotes_style = "cjk";
3142 else if (is_known(h_language, known_cjkangle_quotes_languages))
3143 h_quotes_style = "cjkangle";
3145 else if (is_known(h_language, known_danish_quotes_languages))
3146 h_quotes_style = "danish";
3148 else if (is_known(h_language, known_french_quotes_languages))
3149 h_quotes_style = "french";
3151 else if (is_known(h_language, known_german_quotes_languages))
3152 h_quotes_style = "german";
3154 else if (is_known(h_language, known_polish_quotes_languages))
3155 h_quotes_style = "polish";
3157 else if (is_known(h_language, known_russian_quotes_languages))
3158 h_quotes_style = "russian";
3160 else if (is_known(h_language, known_swedish_quotes_languages))
3161 h_quotes_style = "swedish";
3163 else if (is_known(h_language, known_swiss_quotes_languages))
3164 h_quotes_style = "swiss";
3166 else if (is_known(h_language, known_english_quotes_languages))
3167 h_quotes_style = "english";
3171 string Preamble::parseEncoding(Parser & p, string const & forceclass)
3173 TeX2LyXDocClass dummy;
3174 parse(p, forceclass, true, dummy);
3175 if (h_inputencoding != "auto-legacy" && h_inputencoding != "auto-legacy-plain")
3176 return h_inputencoding;
3181 string babel2lyx(string const & language)
3183 char const * const * where = is_known(language, known_languages);
3185 return known_coded_languages[where - known_languages];
3190 string lyx2babel(string const & language)
3192 char const * const * where = is_known(language, known_coded_languages);
3194 return known_languages[where - known_coded_languages];
3199 string Preamble::polyglossia2lyx(string const & language)
3201 char const * const * where = is_known(language, polyglossia_languages);
3203 return coded_polyglossia_languages[where - polyglossia_languages];
3208 string rgbcolor2code(string const & name)
3210 char const * const * where = is_known(name, known_basic_colors);
3212 // "red", "green" etc
3213 return known_basic_color_codes[where - known_basic_colors];
3215 // "255,0,0", "0,255,0" etc
3216 RGBColor c(RGBColorFromLaTeX(name));
3217 return X11hexname(c);