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 hungarian quotes (.lyx names)
125 const char * const known_hungarian_quotes_languages[] = {"magyar", 0};
127 /// languages with russian quotes (.lyx names)
128 const char * const known_russian_quotes_languages[] = {"azerbaijani", "oldrussian",
129 "russian", "ukrainian", 0};
131 /// languages with swedish quotes (.lyx names)
132 const char * const known_swedish_quotes_languages[] = {"finnish", "swedish", 0};
134 /// languages with swiss quotes (.lyx names)
135 const char * const known_swiss_quotes_languages[] = {"albanian",
136 "armenian", "basque", "churchslavonic", "german-ch", "german-ch-old",
137 "norsk", "nynorsk", "turkmen", "ukrainian", "vietnamese", 0};
139 /// known language packages from the times before babel
140 const char * const known_old_language_packages[] = {"french", "frenchle",
141 "frenchpro", "german", "ngerman", "pmfrench", 0};
143 char const * const known_fontsizes[] = { "10pt", "11pt", "12pt", 0 };
145 const char * const known_roman_font_packages[] = { "ae", "beraserif", "bookman",
146 "ccfonts", "chancery", "charter", "cmr", "cochineal", "crimson", "CrimsonPro", "DejaVuSerif",
147 "DejaVuSerifCondensed", "fourier", "garamondx", "libertine", "libertineRoman", "libertine-type1",
148 "lmodern", "mathdesign", "mathpazo", "mathptmx", "MinionPro", "newcent", "noto", "noto-serif",
149 "PTSerif", "tgbonum", "tgchorus", "tgpagella", "tgschola", "tgtermes", "utopia", "xcharter", 0 };
151 const char * const known_sans_font_packages[] = { "avant", "berasans", "biolinum",
152 "biolinum-type1", "cantarell", "Chivo", "cmbr", "cmss", "DejaVuSans", "DejaVuSansCondensed", "FiraSans", "helvet", "iwona",
153 "iwonac", "iwonal", "iwonalc", "kurier", "kurierc", "kurierl", "kurierlc", "LibertinusSans-LF", "lmss", "noto-sans", "PTSans",
154 "tgadventor", "tgheros", "uop", 0 };
156 const char * const known_typewriter_font_packages[] = { "beramono", "cmtl", "cmtt", "courier", "DejaVuSansMono",
157 "FiraMono", "lmtt", "luximono", "libertineMono", "libertineMono-type1", "LibertinusMono-TLF", "lmodern",
158 "mathpazo", "mathptmx", "newcent", "noto-mono", "PTMono", "tgcursor", "txtt", 0 };
160 const char * const known_math_font_packages[] = { "eulervm", "newtxmath", 0};
162 const char * const known_latex_paper_sizes[] = { "a0paper", "b0paper", "c0paper",
163 "a1paper", "b1paper", "c1paper", "a2paper", "b2paper", "c2paper", "a3paper",
164 "b3paper", "c3paper", "a4paper", "b4paper", "c4paper", "a5paper", "b5paper",
165 "c5paper", "a6paper", "b6paper", "c6paper", "executivepaper", "legalpaper",
166 "letterpaper", "b0j", "b1j", "b2j", "b3j", "b4j", "b5j", "b6j", 0};
168 const char * const known_paper_margins[] = { "lmargin", "tmargin", "rmargin",
169 "bmargin", "headheight", "headsep", "footskip", "columnsep", 0};
171 const char * const known_coded_paper_margins[] = { "leftmargin", "topmargin",
172 "rightmargin", "bottommargin", "headheight", "headsep", "footskip",
175 /// commands that can start an \if...\else...\endif sequence
176 const char * const known_if_commands[] = {"if", "ifarydshln", "ifbraket",
177 "ifcancel", "ifcolortbl", "ifeurosym", "ifmarginnote", "ifmmode", "ifpdf",
178 "ifsidecap", "ifupgreek", 0};
180 const char * const known_basic_colors[] = {"black", "blue", "brown", "cyan",
181 "darkgray", "gray", "green", "lightgray", "lime", "magenta", "orange", "olive",
182 "pink", "purple", "red", "teal", "violet", "white", "yellow", 0};
184 const char * const known_basic_color_codes[] = {"#000000", "#0000ff", "#964B00", "#00ffff",
185 "#a9a9a9", "#808080", "#00ff00", "#d3d3d3", "#bfff00", "#ff00ff", "#ff7f00", "#808000",
186 "#ffc0cb", "#800080", "#ff0000", "#008080", "#8f00ff", "#ffffff", "#ffff00", 0};
188 /// conditional commands with three arguments like \@ifundefined{}{}{}
189 const char * const known_if_3arg_commands[] = {"@ifundefined", "IfFileExists",
193 * Known file extensions for TeX files as used by \\includeonly
195 char const * const known_tex_extensions[] = {"tex", 0};
197 /// packages that work only in xetex
198 /// polyglossia is handled separately
199 const char * const known_xetex_packages[] = {"arabxetex", "fixlatvian",
200 "fontbook", "fontwrap", "mathspec", "philokalia", "unisugar",
201 "xeCJK", "xecolor", "xecyr", "xeindex", "xepersian", "xunicode", 0};
203 /// packages that are automatically skipped if loaded by LyX
204 const char * const known_lyx_packages[] = {"amsbsy", "amsmath", "amssymb",
205 "amstext", "amsthm", "array", "babel", "booktabs", "calc", "CJK", "color",
206 "float", "fontspec", "framed", "graphicx", "hhline", "ifthen", "longtable",
207 "makeidx", "minted", "multirow", "nomencl", "parskip", "pdfpages", "prettyref", "refstyle",
208 "rotating", "rotfloat", "splitidx", "setspace", "subscript", "tabularx","textcomp", "tipa",
209 "tipx", "tone", "ulem", "url", "varioref", "verbatim", "wrapfig", "xcolor", "xltabular",
212 // codes used to remove packages that are loaded automatically by LyX.
213 // Syntax: package_beg_sep<name>package_mid_sep<package loading code>package_end_sep
214 const char package_beg_sep = '\001';
215 const char package_mid_sep = '\002';
216 const char package_end_sep = '\003';
219 // returns true if at least one of the options in what has been found
220 bool handle_opt(vector<string> & opts, char const * const * what, string & target)
226 // the last language option is the document language (for babel and LyX)
227 // the last size option is the document font size
228 vector<string>::iterator it;
229 vector<string>::iterator position = opts.begin();
230 for (; *what; ++what) {
231 it = find(opts.begin(), opts.end(), *what);
232 if (it != opts.end()) {
233 if (it >= position) {
244 void delete_opt(vector<string> & opts, char const * const * what)
249 // remove found options from the list
250 // do this after handle_opt to avoid potential memory leaks
251 vector<string>::iterator it;
252 for (; *what; ++what) {
253 it = find(opts.begin(), opts.end(), *what);
254 if (it != opts.end())
261 * Split a package options string (keyval format) into a vector.
263 * authorformat=smallcaps,
265 * titleformat=colonsep,
266 * bibformat={tabular,ibidem,numbered}
268 vector<string> split_options(string const & input)
270 vector<string> options;
274 Token const & t = p.get_token();
275 if (t.asInput() == ",") {
276 options.push_back(trimSpaceAndEol(option));
278 } else if (t.asInput() == "=") {
281 if (p.next_token().asInput() == "{")
282 option += '{' + p.getArg('{', '}') + '}';
283 } else if (t.cat() != catSpace && t.cat() != catComment)
284 option += t.asInput();
288 options.push_back(trimSpaceAndEol(option));
295 * Retrieve a keyval option "name={value with=sign}" named \p name from
296 * \p options and return the value.
297 * The found option is also removed from \p options.
299 string process_keyval_opt(vector<string> & options, string const & name)
301 for (size_t i = 0; i < options.size(); ++i) {
302 vector<string> option;
303 split(options[i], option, '=');
304 if (option.size() < 2)
306 if (option[0] == name) {
307 options.erase(options.begin() + i);
308 option.erase(option.begin());
309 return join(option, "=");
315 } // anonymous namespace
319 * known polyglossia language names (including variants)
320 * FIXME: support spelling=old for german variants (german vs. ngerman LyX names etc)
322 const char * const Preamble::polyglossia_languages[] = {
323 "albanian", "american", "amharic", "ancient", "arabic", "armenian", "asturian", "australian",
324 "bahasai", "bahasam", "basque", "bengali", "brazil", "brazilian", "breton", "british", "bulgarian",
325 "catalan", "churchslavonic", "coptic", "croatian", "czech", "danish", "divehi", "dutch",
326 "english", "esperanto", "estonian", "farsi", "finnish", "french", "friulan",
327 "galician", "greek", "monotonic", "hebrew", "hindi",
328 "icelandic", "interlingua", "irish", "italian", "kannada", "khmer", "korean",
329 "lao", "latin", "latvian", "lithuanian", "lsorbian", "magyar", "malayalam", "marathi",
330 "austrian", "newzealand", "german", "norsk", "nynorsk", "occitan", "oldrussian",
331 "piedmontese", "polish", "polytonic", "portuges", "romanian", "romansh", "russian",
332 "samin", "sanskrit", "scottish", "serbian", "slovak", "slovenian", "spanish", "swedish", "syriac",
333 "tamil", "telugu", "thai", "tibetan", "turkish", "turkmen",
334 "ukrainian", "urdu", "usorbian", "vietnamese", "welsh", 0};
335 // not yet supported by LyX: "korean", "nko"
338 * the same as polyglossia_languages with .lyx names
339 * please keep this in sync with polyglossia_languages line by line!
341 const char * const Preamble::coded_polyglossia_languages[] = {
342 "albanian", "american", "amharic", "ancientgreek", "arabic_arabi", "armenian", "asturian", "australian",
343 "bahasa", "bahasam", "basque", "bengali", "brazilian", "brazilian", "breton", "british", "bulgarian",
344 "catalan", "churchslavonic", "coptic", "croatian", "czech", "danish", "divehi", "dutch",
345 "english", "esperanto", "estonian", "farsi", "finnish", "french", "friulan",
346 "galician", "greek", "greek", "hebrew", "hindi",
347 "icelandic", "interlingua", "irish", "italian", "kannada", "khmer", "korean",
348 "lao", "latin", "latvian", "lithuanian", "lowersorbian", "magyar", "malayalam", "marathi",
349 "naustrian","newzealand", "ngerman", "norsk", "nynorsk", "occitan", "oldrussian",
350 "piedmontese", "polish", "polutonikogreek", "portuges", "romanian", "romansh", "russian",
351 "samin", "sanskrit", "scottish", "serbian", "slovak", "slovene", "spanish", "swedish", "syriac",
352 "tamil", "telugu", "thai", "tibetan", "turkish", "turkmen",
353 "ukrainian", "urdu", "uppersorbian", "vietnamese", "welsh", 0};
354 // not yet supported by LyX: "korean-polyglossia", "nko"
357 bool Preamble::usePolyglossia() const
359 return h_use_non_tex_fonts && h_language_package == "default";
363 bool Preamble::indentParagraphs() const
365 return h_paragraph_separation == "indent";
369 bool Preamble::isPackageUsed(string const & package) const
371 return used_packages.find(package) != used_packages.end();
375 bool Preamble::isPackageAutoLoaded(string const & package) const
377 return auto_packages.find(package) != auto_packages.end();
381 vector<string> Preamble::getPackageOptions(string const & package) const
383 map<string, vector<string> >::const_iterator it = used_packages.find(package);
384 if (it != used_packages.end())
386 return vector<string>();
390 void Preamble::registerAutomaticallyLoadedPackage(std::string const & package)
392 auto_packages.insert(package);
396 void Preamble::addModule(string const & module)
398 for (auto const & m : used_modules) {
402 used_modules.push_back(module);
406 void Preamble::suppressDate(bool suppress)
409 h_suppress_date = "true";
411 h_suppress_date = "false";
415 void Preamble::registerAuthor(std::string const & name, string const & initials)
417 Author author(from_utf8(name), empty_docstring(), from_utf8(initials));
418 author.setUsed(true);
419 authors_.record(author);
420 h_tracking_changes = "true";
421 h_output_changes = "true";
425 Author const & Preamble::getAuthor(std::string const & name) const
427 Author author(from_utf8(name), empty_docstring(), empty_docstring());
428 for (AuthorList::Authors::const_iterator it = authors_.begin();
429 it != authors_.end(); ++it)
432 static Author const dummy;
437 int Preamble::getSpecialTableColumnArguments(char c) const
439 map<char, int>::const_iterator it = special_columns_.find(c);
440 if (it == special_columns_.end())
446 void Preamble::add_package(string const & name, vector<string> & options)
448 // every package inherits the global options
449 used_packages.insert({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());
472 void Preamble::setTextClass(string const & tclass, TeX2LyXDocClass & tc)
474 h_textclass = tclass;
475 tc.setName(h_textclass);
476 if (!LayoutFileList::get().haveClass(h_textclass) || !tc.load()) {
477 cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
485 // Given is a string like "scaled=0.9" or "scale=0.9", return 0.9 * 100
486 bool scale_as_percentage(string const & scale, string & percentage)
488 if (contains(scale, '=')) {
489 string const value = support::split(scale, '=');
490 if (isStrDbl(value)) {
491 percentage = convert<string>(
492 static_cast<int>(100 * convert<double>(value)));
500 string remove_braces(string const & value)
504 if (value[0] == '{' && value[value.length()-1] == '}')
505 return value.substr(1, value.length()-2);
509 } // anonymous namespace
512 Preamble::Preamble() : one_language(true), explicit_babel(false),
513 title_layout_found(false), index_number(0), h_font_cjk_set(false)
517 h_biblio_style = "plain";
518 h_bibtex_command = "default";
519 h_cite_engine = "basic";
520 h_cite_engine_type = "default";
522 h_defskip = "medskip";
523 h_dynamic_quotes = false;
526 h_fontencoding = "default";
527 h_font_roman[0] = "default";
528 h_font_roman[1] = "default";
529 h_font_sans[0] = "default";
530 h_font_sans[1] = "default";
531 h_font_typewriter[0] = "default";
532 h_font_typewriter[1] = "default";
533 h_font_math[0] = "auto";
534 h_font_math[1] = "auto";
535 h_font_default_family = "default";
536 h_use_non_tex_fonts = false;
538 h_font_roman_osf = "false";
539 h_font_sans_osf = "false";
540 h_font_typewriter_osf = "false";
541 h_font_sf_scale[0] = "100";
542 h_font_sf_scale[1] = "100";
543 h_font_tt_scale[0] = "100";
544 h_font_tt_scale[1] = "100";
545 // h_font_roman_opts;
547 // h_font_typewriter_opts;
549 h_is_mathindent = "0";
550 h_math_numbering_side = "default";
551 h_graphics = "default";
552 h_default_output_format = "default";
553 h_html_be_strict = "false";
554 h_html_css_as_file = "0";
555 h_html_math_output = "0";
556 h_docbook_table_output = "0";
557 h_index[0] = "Index";
558 h_index_command = "default";
559 h_inputencoding = "auto-legacy";
560 h_justification = "true";
561 h_language = "english";
562 h_language_package = "none";
564 h_maintain_unincluded_children = "no";
568 h_output_changes = "false";
569 h_change_bars = "false";
571 //h_output_sync_macro
572 h_papercolumns = "1";
573 h_paperfontsize = "default";
574 h_paperorientation = "portrait";
575 h_paperpagestyle = "default";
577 h_papersize = "default";
578 h_paragraph_indentation = "default";
579 h_paragraph_separation = "indent";
584 h_pdf_bookmarks = "0";
585 h_pdf_bookmarksnumbered = "0";
586 h_pdf_bookmarksopen = "0";
587 h_pdf_bookmarksopenlevel = "1";
588 h_pdf_breaklinks = "0";
589 h_pdf_pdfborder = "0";
590 h_pdf_colorlinks = "0";
591 h_pdf_backref = "section";
592 h_pdf_pdfusetitle = "0";
594 //h_pdf_quoted_options;
595 h_quotes_style = "english";
597 h_shortcut[0] = "idx";
598 h_spacing = "single";
599 h_save_transient_properties = "true";
600 h_suppress_date = "false";
601 h_textclass = "article";
603 h_tracking_changes = "false";
604 h_use_bibtopic = "false";
605 h_use_dash_ligatures = "true";
606 h_use_indices = "false";
607 h_use_geometry = "false";
608 h_use_default_options = "false";
609 h_use_hyperref = "false";
610 h_use_microtype = "false";
611 h_use_lineno = "false";
612 h_use_refstyle = false;
613 h_use_minted = false;
614 h_use_packages["amsmath"] = "1";
615 h_use_packages["amssymb"] = "0";
616 h_use_packages["cancel"] = "0";
617 h_use_packages["esint"] = "1";
618 h_use_packages["mhchem"] = "0";
619 h_use_packages["mathdots"] = "0";
620 h_use_packages["mathtools"] = "0";
621 h_use_packages["stackrel"] = "0";
622 h_use_packages["stmaryrd"] = "0";
623 h_use_packages["undertilde"] = "0";
627 void Preamble::handle_hyperref(vector<string> & options)
629 // FIXME swallow inputencoding changes that might surround the
630 // hyperref setup if it was written by LyX
631 h_use_hyperref = "true";
632 // swallow "unicode=true", since LyX does always write that
633 vector<string>::iterator it =
634 find(options.begin(), options.end(), "unicode=true");
635 if (it != options.end())
637 it = find(options.begin(), options.end(), "pdfusetitle");
638 if (it != options.end()) {
639 h_pdf_pdfusetitle = "1";
642 string bookmarks = process_keyval_opt(options, "bookmarks");
643 if (bookmarks == "true")
644 h_pdf_bookmarks = "1";
645 else if (bookmarks == "false")
646 h_pdf_bookmarks = "0";
647 if (h_pdf_bookmarks == "1") {
648 string bookmarksnumbered =
649 process_keyval_opt(options, "bookmarksnumbered");
650 if (bookmarksnumbered == "true")
651 h_pdf_bookmarksnumbered = "1";
652 else if (bookmarksnumbered == "false")
653 h_pdf_bookmarksnumbered = "0";
654 string bookmarksopen =
655 process_keyval_opt(options, "bookmarksopen");
656 if (bookmarksopen == "true")
657 h_pdf_bookmarksopen = "1";
658 else if (bookmarksopen == "false")
659 h_pdf_bookmarksopen = "0";
660 if (h_pdf_bookmarksopen == "1") {
661 string bookmarksopenlevel =
662 process_keyval_opt(options, "bookmarksopenlevel");
663 if (!bookmarksopenlevel.empty())
664 h_pdf_bookmarksopenlevel = bookmarksopenlevel;
667 string breaklinks = process_keyval_opt(options, "breaklinks");
668 if (breaklinks == "true")
669 h_pdf_breaklinks = "1";
670 else if (breaklinks == "false")
671 h_pdf_breaklinks = "0";
672 string pdfborder = process_keyval_opt(options, "pdfborder");
673 if (pdfborder == "{0 0 0}")
674 h_pdf_pdfborder = "1";
675 else if (pdfborder == "{0 0 1}")
676 h_pdf_pdfborder = "0";
677 string backref = process_keyval_opt(options, "backref");
678 if (!backref.empty())
679 h_pdf_backref = backref;
680 string colorlinks = process_keyval_opt(options, "colorlinks");
681 if (colorlinks == "true")
682 h_pdf_colorlinks = "1";
683 else if (colorlinks == "false")
684 h_pdf_colorlinks = "0";
685 string pdfpagemode = process_keyval_opt(options, "pdfpagemode");
686 if (!pdfpagemode.empty())
687 h_pdf_pagemode = pdfpagemode;
688 string pdftitle = process_keyval_opt(options, "pdftitle");
689 if (!pdftitle.empty()) {
690 h_pdf_title = remove_braces(pdftitle);
692 string pdfauthor = process_keyval_opt(options, "pdfauthor");
693 if (!pdfauthor.empty()) {
694 h_pdf_author = remove_braces(pdfauthor);
696 string pdfsubject = process_keyval_opt(options, "pdfsubject");
697 if (!pdfsubject.empty())
698 h_pdf_subject = remove_braces(pdfsubject);
699 string pdfkeywords = process_keyval_opt(options, "pdfkeywords");
700 if (!pdfkeywords.empty())
701 h_pdf_keywords = remove_braces(pdfkeywords);
702 if (!options.empty()) {
703 if (!h_pdf_quoted_options.empty())
704 h_pdf_quoted_options += ',';
705 h_pdf_quoted_options += join(options, ",");
711 void Preamble::handle_geometry(vector<string> & options)
713 h_use_geometry = "true";
714 vector<string>::iterator it;
716 if ((it = find(options.begin(), options.end(), "landscape")) != options.end()) {
717 h_paperorientation = "landscape";
721 // keyval version: "paper=letter" or "paper=letterpaper"
722 string paper = process_keyval_opt(options, "paper");
724 if (suffixIs(paper, "paper"))
725 paper = subst(paper, "paper", "");
726 // alternative version: "letterpaper"
727 handle_opt(options, known_latex_paper_sizes, paper);
728 if (suffixIs(paper, "paper"))
729 paper = subst(paper, "paper", "");
730 delete_opt(options, known_latex_paper_sizes);
734 char const * const * margin = known_paper_margins;
735 for (; *margin; ++margin) {
736 string value = process_keyval_opt(options, *margin);
737 if (!value.empty()) {
738 int k = margin - known_paper_margins;
739 string name = known_coded_paper_margins[k];
740 h_margins += '\\' + name + ' ' + value + '\n';
746 void Preamble::handle_package(Parser &p, string const & name,
747 string const & opts, bool in_lyx_preamble,
750 vector<string> options = split_options(opts);
751 add_package(name, options);
753 if (is_known(name, known_xetex_packages)) {
755 h_use_non_tex_fonts = true;
756 registerAutomaticallyLoadedPackage("fontspec");
757 if (h_inputencoding == "auto-legacy")
758 p.setEncoding("UTF-8");
761 // vector of all options for easier parsing and
763 vector<string> allopts = getVectorFromString(opts);
764 // this stores the potential extra options
771 // By default, we use the package name as LyX font name,
772 // so this only needs to be reset if these names differ
773 if (is_known(name, known_roman_font_packages))
774 h_font_roman[0] = name;
776 if (name == "ccfonts") {
777 for (auto const & opt : allopts) {
783 h_font_roman_opts = xopts;
787 if (name == "lmodern") {
788 for (auto const & opt : allopts) {
794 h_font_roman_opts = xopts;
798 if (name == "fourier") {
799 h_font_roman[0] = "utopia";
800 for (auto const & opt : allopts) {
802 h_font_roman_osf = "true";
805 if (opt == "expert") {
814 h_font_roman_opts = xopts;
818 if (name == "garamondx") {
819 for (auto const & opt : allopts) {
821 h_font_roman_osf = "true";
829 h_font_roman_opts = xopts;
833 if (name == "libertine") {
834 // this automatically invokes biolinum
835 h_font_sans[0] = "biolinum";
836 // as well as libertineMono
837 h_font_typewriter[0] = "libertine-mono";
838 for (auto const & opt : allopts) {
840 h_font_roman_osf = "true";
843 if (opt == "lining") {
844 h_font_roman_osf = "false";
852 h_font_roman_opts = xopts;
856 if (name == "libertineRoman" || name == "libertine-type1") {
857 h_font_roman[0] = "libertine";
858 // NOTE: contrary to libertine.sty, libertineRoman
859 // and libertine-type1 do not automatically invoke
860 // biolinum and libertineMono
861 if (opts == "lining")
862 h_font_roman_osf = "false";
863 else if (opts == "osf")
864 h_font_roman_osf = "true";
867 if (name == "libertinus" || name == "libertinus-type1") {
874 for (auto const & opt : allopts) {
875 if (opt == "rm" || opt == "serif") {
880 if (opt == "sf" || opt == "sans") {
885 if (opt == "tt=false" || opt == "mono=false") {
893 if (opt == "scaleSF") {
897 if (opt == "scaleTT") {
901 if (opt == "lining") {
902 h_font_roman_osf = "false";
910 h_font_roman[0] = "libertinus";
912 h_font_roman_osf = "true";
914 h_font_roman_osf = "false";
917 h_font_sans[0] = "LibertinusSans-LF";
919 h_font_sans_osf = "true";
921 h_font_sans_osf = "false";
922 if (!scalesf.empty())
923 scale_as_percentage(scalesf, h_font_sf_scale[0]);
926 h_font_typewriter[0] = "LibertinusMono-TLF";
927 if (!scalett.empty())
928 scale_as_percentage(scalett, h_font_tt_scale[0]);
931 h_font_roman_opts = xopts;
935 if (name == "MinionPro") {
936 h_font_roman[0] = "minionpro";
937 h_font_roman_osf = "true";
938 h_font_math[0] = "auto";
939 for (auto const & opt : allopts) {
941 h_font_roman_osf = "false";
944 if (opt == "onlytext") {
945 h_font_math[0] = "default";
953 h_font_roman_opts = xopts;
957 if (name == "mathdesign") {
958 for (auto const & opt : allopts) {
959 if (opt == "charter") {
960 h_font_roman[0] = "md-charter";
963 if (opt == "garamond") {
964 h_font_roman[0] = "md-garamond";
967 if (opt == "utopia") {
968 h_font_roman[0] = "md-utopia";
971 if (opt == "expert") {
973 h_font_roman_osf = "true";
979 else if (name == "mathpazo") {
980 h_font_roman[0] = "palatino";
981 for (auto const & opt : allopts) {
983 h_font_roman_osf = "true";
995 h_font_roman_opts = xopts;
999 else if (name == "mathptmx") {
1000 h_font_roman[0] = "times";
1001 for (auto const & opt : allopts) {
1007 h_font_roman_opts = xopts;
1011 if (name == "crimson")
1012 h_font_roman[0] = "cochineal";
1014 if (name == "cochineal") {
1015 for (auto const & opt : allopts) {
1016 if (opt == "osf" || opt == "oldstyle") {
1017 h_font_roman_osf = "true";
1020 if (opt == "proportional" || opt == "p")
1027 h_font_roman_opts = xopts;
1031 if (name == "CrimsonPro") {
1032 h_font_roman_osf = "true";
1033 for (auto const & opt : allopts) {
1034 if (opt == "lf" || opt == "lining") {
1035 h_font_roman_osf = "false";
1038 if (opt == "proportional" || opt == "p")
1040 if (opt == "medium") {
1041 h_font_roman[0] = "CrimsonProMedium";
1044 if (opt == "extralight") {
1045 h_font_roman[0] = "CrimsonProExtraLight";
1048 if (opt == "light") {
1049 h_font_roman[0] = "CrimsonProLight";
1057 h_font_roman_opts = xopts;
1063 // font uses old-style figure
1064 h_font_roman_osf = "true";
1066 if (name == "paratype") {
1067 // in this case all fonts are ParaType
1068 h_font_roman[0] = "PTSerif-TLF";
1069 h_font_sans[0] = "default";
1070 h_font_typewriter[0] = "default";
1073 if (name == "PTSerif")
1074 h_font_roman[0] = "PTSerif-TLF";
1076 if (name == "XCharter") {
1077 h_font_roman[0] = "xcharter";
1078 for (auto const & opt : allopts) {
1080 h_font_roman_osf = "true";
1088 h_font_roman_opts = xopts;
1092 if (name == "plex-serif") {
1093 h_font_roman[0] = "IBMPlexSerif";
1094 for (auto const & opt : allopts) {
1095 if (opt == "thin") {
1096 h_font_roman[0] = "IBMPlexSerifThin";
1099 if (opt == "extralight") {
1100 h_font_roman[0] = "IBMPlexSerifExtraLight";
1103 if (opt == "light") {
1104 h_font_roman[0] = "IBMPlexSerifLight";
1112 h_font_roman_opts = xopts;
1116 if (name == "noto-serif" || name == "noto") {
1123 bool extralight = false;
1125 bool medium = false;
1128 if (name == "noto") {
1133 // Since the options might apply to different shapes,
1134 // we need to parse all options first and then handle them.
1135 for (auto const & opt : allopts) {
1136 if (opt == "regular")
1146 if (opt == "thin") {
1150 if (opt == "extralight") {
1154 if (opt == "light") {
1158 if (opt == "medium") {
1169 if (opt == "nott") {
1177 if (prefixIs(opt, "scaled=")) {
1186 // handle options that might affect different shapes
1187 if (name == "noto-serif" || rm) {
1189 h_font_roman[0] = "NotoSerifThin";
1190 else if (extralight)
1191 h_font_roman[0] = "NotoSerifExtralight";
1193 h_font_roman[0] = "NotoSerifLight";
1195 h_font_roman[0] = "NotoSerifMedium";
1197 h_font_roman[0] = "NotoSerifRegular";
1199 h_font_roman_osf = "true";
1201 h_font_roman_opts = xopts;
1203 if (name == "noto" && sf) {
1205 h_font_sans[0] = "NotoSansThin";
1206 else if (extralight)
1207 h_font_sans[0] = "NotoSansExtralight";
1209 h_font_sans[0] = "NotoSansLight";
1211 h_font_sans[0] = "NotoSansMedium";
1213 h_font_sans[0] = "NotoSansRegular";
1215 h_font_sans_osf = "true";
1217 scale_as_percentage(scl, h_font_sf_scale[0]);
1219 h_font_sans_opts = xopts;
1221 if (name == "noto" && tt) {
1222 h_font_typewriter[0] = "NotoMonoRegular";
1224 h_font_typewriter_osf = "true";
1226 scale_as_percentage(scl, h_font_tt_scale[0]);
1228 h_font_typewriter_opts = xopts;
1232 if (name == "sourceserifpro") {
1233 h_font_roman[0] = "ADOBESourceSerifPro";
1234 for (auto const & opt : allopts) {
1236 h_font_roman_osf = "true";
1244 h_font_roman_opts = xopts;
1252 // By default, we use the package name as LyX font name,
1253 // so this only needs to be reset if these names differ.
1254 // Also, we handle the scaling option here generally.
1255 if (is_known(name, known_sans_font_packages)) {
1256 h_font_sans[0] = name;
1257 if (contains(opts, "scale")) {
1258 vector<string>::iterator it = allopts.begin();
1259 for (; it != allopts.end() ; ++it) {
1260 string const opt = *it;
1261 if (prefixIs(opt, "scaled=") || prefixIs(opt, "scale=")) {
1262 if (scale_as_percentage(opt, h_font_sf_scale[0])) {
1271 if (name == "biolinum" || name == "biolinum-type1") {
1272 h_font_sans[0] = "biolinum";
1273 for (auto const & opt : allopts) {
1274 if (prefixIs(opt, "osf")) {
1275 h_font_sans_osf = "true";
1283 h_font_sans_opts = xopts;
1287 if (name == "cantarell") {
1288 for (auto const & opt : allopts) {
1289 if (opt == "defaultsans")
1291 if (prefixIs(opt, "oldstyle")) {
1292 h_font_sans_osf = "true";
1300 h_font_sans_opts = xopts;
1304 if (name == "Chivo") {
1305 for (auto const & opt : allopts) {
1306 if (opt == "thin") {
1307 h_font_roman[0] = "ChivoThin";
1310 if (opt == "light") {
1311 h_font_roman[0] = "ChivoLight";
1314 if (opt == "regular") {
1315 h_font_roman[0] = "Chivo";
1318 if (opt == "medium") {
1319 h_font_roman[0] = "ChivoMedium";
1322 if (prefixIs(opt, "oldstyle")) {
1323 h_font_sans_osf = "true";
1331 h_font_sans_opts = xopts;
1335 if (name == "PTSans") {
1336 h_font_sans[0] = "PTSans-TLF";
1339 if (name == "FiraSans") {
1340 h_font_sans_osf = "true";
1341 for (auto const & opt : allopts) {
1342 if (opt == "book") {
1343 h_font_sans[0] = "FiraSansBook";
1346 if (opt == "thin") {
1349 if (opt == "extralight") {
1350 h_font_sans[0] = "FiraSansExtralight";
1353 if (opt == "light") {
1354 h_font_sans[0] = "FiraSansLight";
1357 if (opt == "ultralight") {
1358 h_font_sans[0] = "FiraSansUltralight";
1361 if (opt == "thin") {
1362 h_font_sans[0] = "FiraSansThin";
1365 if (opt == "lf" || opt == "lining") {
1366 h_font_sans_osf = "false";
1374 h_font_sans_opts = xopts;
1378 if (name == "plex-sans") {
1379 h_font_sans[0] = "IBMPlexSans";
1380 for (auto const & opt : allopts) {
1381 if (opt == "condensed") {
1382 h_font_sans[0] = "IBMPlexSansCondensed";
1385 if (opt == "thin") {
1386 h_font_sans[0] = "IBMPlexSansThin";
1389 if (opt == "extralight") {
1390 h_font_sans[0] = "IBMPlexSansExtraLight";
1393 if (opt == "light") {
1394 h_font_sans[0] = "IBMPlexSansLight";
1402 h_font_sans_opts = xopts;
1406 if (name == "noto-sans") {
1407 h_font_sans[0] = "NotoSansRegular";
1408 for (auto const & opt : allopts) {
1409 if (opt == "regular")
1411 if (opt == "medium") {
1412 h_font_sans[0] = "NotoSansMedium";
1415 if (opt == "thin") {
1416 h_font_sans[0] = "NotoSansThin";
1419 if (opt == "extralight") {
1420 h_font_sans[0] = "NotoSansExtralight";
1423 if (opt == "light") {
1424 h_font_sans[0] = "NotoSansLight";
1428 h_font_sans_osf = "true";
1436 h_font_sans_opts = xopts;
1440 if (name == "sourcesanspro") {
1441 h_font_sans[0] = "ADOBESourceSansPro";
1442 for (auto const & opt : allopts) {
1444 h_font_sans_osf = "true";
1452 h_font_sans_opts = xopts;
1460 // By default, we use the package name as LyX font name,
1461 // so this only needs to be reset if these names differ.
1462 // Also, we handle the scaling option here generally.
1463 if (is_known(name, known_typewriter_font_packages)) {
1464 h_font_typewriter[0] = name;
1465 if (contains(opts, "scale")) {
1466 vector<string>::iterator it = allopts.begin();
1467 for (; it != allopts.end() ; ++it) {
1468 string const opt = *it;
1469 if (prefixIs(opt, "scaled=") || prefixIs(opt, "scale=")) {
1470 if (scale_as_percentage(opt, h_font_tt_scale[0])) {
1479 if (name == "libertineMono" || name == "libertineMono-type1")
1480 h_font_typewriter[0] = "libertine-mono";
1482 if (name == "FiraMono") {
1483 h_font_typewriter_osf = "true";
1484 for (auto const & opt : allopts) {
1485 if (opt == "lf" || opt == "lining") {
1486 h_font_typewriter_osf = "false";
1494 h_font_typewriter_opts = xopts;
1498 if (name == "PTMono")
1499 h_font_typewriter[0] = "PTMono-TLF";
1501 if (name == "plex-mono") {
1502 h_font_typewriter[0] = "IBMPlexMono";
1503 for (auto const & opt : allopts) {
1504 if (opt == "thin") {
1505 h_font_typewriter[0] = "IBMPlexMonoThin";
1508 if (opt == "extralight") {
1509 h_font_typewriter[0] = "IBMPlexMonoExtraLight";
1512 if (opt == "light") {
1513 h_font_typewriter[0] = "IBMPlexMonoLight";
1521 h_font_typewriter_opts = xopts;
1525 if (name == "noto-mono") {
1526 h_font_typewriter[0] = "NotoMonoRegular";
1527 for (auto const & opt : allopts) {
1528 if (opt == "regular")
1535 h_font_typewriter_opts = xopts;
1539 if (name == "sourcecodepro") {
1540 h_font_typewriter[0] = "ADOBESourceCodePro";
1541 for (auto const & opt : allopts) {
1543 h_font_typewriter_osf = "true";
1551 h_font_typewriter_opts = xopts;
1559 // By default, we use the package name as LyX font name,
1560 // so this only needs to be reset if these names differ.
1561 if (is_known(name, known_math_font_packages))
1562 h_font_math[0] = name;
1564 if (name == "newtxmath") {
1566 h_font_math[0] = "newtxmath";
1567 else if (opts == "garamondx")
1568 h_font_math[0] = "garamondx-ntxm";
1569 else if (opts == "libertine")
1570 h_font_math[0] = "libertine-ntxm";
1571 else if (opts == "minion")
1572 h_font_math[0] = "minion-ntxm";
1573 else if (opts == "cochineal")
1574 h_font_math[0] = "cochineal-ntxm";
1577 if (name == "iwona")
1579 h_font_math[0] = "iwona-math";
1581 if (name == "kurier")
1583 h_font_math[0] = "kurier-math";
1585 // after the detection and handling of special cases, we can remove the
1586 // fonts, otherwise they would appear in the preamble, see bug #7856
1587 if (is_known(name, known_roman_font_packages) || is_known(name, known_sans_font_packages)
1588 || is_known(name, known_typewriter_font_packages) || is_known(name, known_math_font_packages))
1590 //"On". See the enum Package in BufferParams.h if you thought that "2" should have been "42"
1591 else if (name == "amsmath" || name == "amssymb" || name == "cancel" ||
1592 name == "esint" || name == "mhchem" || name == "mathdots" ||
1593 name == "mathtools" || name == "stackrel" ||
1594 name == "stmaryrd" || name == "undertilde") {
1595 h_use_packages[name] = "2";
1596 registerAutomaticallyLoadedPackage(name);
1599 else if (name == "babel") {
1600 h_language_package = "default";
1601 // One might think we would have to do nothing if babel is loaded
1602 // without any options to prevent pollution of the preamble with this
1603 // babel call in every roundtrip.
1604 // But the user could have defined babel-specific things afterwards. So
1605 // we need to keep it in the preamble to prevent cases like bug #7861.
1606 if (!opts.empty()) {
1607 // check if more than one option was used - used later for inputenc
1608 if (options.begin() != options.end() - 1)
1609 one_language = false;
1610 // babel takes the last language of the option of its \usepackage
1611 // call as document language. If there is no such language option, the
1612 // last language in the documentclass options is used.
1613 handle_opt(options, known_languages, h_language);
1614 // translate the babel name to a LyX name
1615 h_language = babel2lyx(h_language);
1616 if (h_language == "japanese") {
1617 // For Japanese, the encoding isn't indicated in the source
1618 // file, and there's really not much we can do. We could
1619 // 1) offer a list of possible encodings to choose from, or
1620 // 2) determine the encoding of the file by inspecting it.
1621 // For the time being, we leave the encoding alone so that
1622 // we don't get iconv errors when making a wrong guess, and
1623 // we will output a note at the top of the document
1624 // explaining what to do.
1625 Encoding const * const enc = encodings.fromIconvName(
1626 p.getEncoding(), Encoding::japanese, false);
1628 h_inputencoding = enc->name();
1629 is_nonCJKJapanese = true;
1630 // in this case babel can be removed from the preamble
1631 registerAutomaticallyLoadedPackage("babel");
1633 // If babel is called with options, LyX puts them by default into the
1634 // document class options. This works for most languages, except
1635 // for Latvian, Lithuanian, Mongolian, Turkmen and Vietnamese and
1636 // perhaps in future others.
1637 // Therefore keep the babel call as it is as the user might have
1639 string const babelcall = "\\usepackage[" + opts + "]{babel}\n";
1640 if (!contains(h_preamble.str(), babelcall))
1641 h_preamble << babelcall;
1643 delete_opt(options, known_languages);
1645 if (!contains(h_preamble.str(), "\\usepackage{babel}\n"))
1646 h_preamble << "\\usepackage{babel}\n";
1647 explicit_babel = true;
1651 else if (name == "polyglossia") {
1652 h_language_package = "default";
1653 h_default_output_format = "pdf4";
1654 h_use_non_tex_fonts = true;
1656 registerAutomaticallyLoadedPackage("xunicode");
1657 if (h_inputencoding == "auto-legacy")
1658 p.setEncoding("UTF-8");
1661 else if (name == "CJK") {
1662 // set the encoding to "auto-legacy" because it might be set to "auto-legacy-plain" by the babel handling
1663 // and this would not be correct for CJK
1664 if (h_inputencoding == "auto-legacy-plain")
1665 h_inputencoding = "auto-legacy";
1666 registerAutomaticallyLoadedPackage("CJK");
1669 else if (name == "CJKutf8") {
1670 h_inputencoding = "utf8-cjk";
1671 p.setEncoding("UTF-8");
1672 registerAutomaticallyLoadedPackage("CJKutf8");
1675 else if (name == "fontenc") {
1676 h_fontencoding = getStringFromVector(options, ",");
1680 else if (name == "inputenc" || name == "luainputenc") {
1681 // h_inputencoding is only set when there is not more than one
1682 // inputenc option because otherwise h_inputencoding must be
1683 // set to "auto-legacy" (the default encodings of the document's languages)
1684 // Therefore check that exactly one option is passed to inputenc.
1685 // It is also only set when there is not more than one babel
1687 if (!options.empty()) {
1688 string const encoding = options.back();
1689 Encoding const * const enc = encodings.fromLaTeXName(
1690 encoding, Encoding::inputenc, true);
1692 if (!detectEncoding)
1693 cerr << "Unknown encoding " << encoding
1694 << ". Ignoring." << std::endl;
1696 if (!enc->unsafe() && options.size() == 1 && one_language == true)
1697 h_inputencoding = enc->name();
1698 p.setEncoding(enc->iconvName());
1704 else if (name == "srcltx") {
1705 h_output_sync = "1";
1706 if (!opts.empty()) {
1707 h_output_sync_macro = "\\usepackage[" + opts + "]{srcltx}";
1710 h_output_sync_macro = "\\usepackage{srcltx}";
1713 else if (is_known(name, known_old_language_packages)) {
1714 // known language packages from the times before babel
1715 // if they are found and not also babel, they will be used as
1716 // custom language package
1717 h_language_package = "\\usepackage{" + name + "}";
1720 else if (name == "lyxskak") {
1721 // ignore this and its options
1722 const char * const o[] = {"ps", "mover", 0};
1723 delete_opt(options, o);
1726 else if (name == "parskip" && options.size() < 2 && (opts.empty() || prefixIs(opts, "skip="))) {
1728 h_paragraph_separation = "halfline";
1730 if (opts == "skip=\\smallskipamount")
1731 h_defskip = "smallskip";
1732 else if (opts == "skip=\\medskipamount")
1733 h_defskip = "medskip";
1734 else if (opts == "skip=\\bigskipamount")
1735 h_defskip = "bigskip";
1736 else if (opts == "skip=\\baselineskip")
1737 h_defskip = "fullline";
1740 h_paragraph_separation = "skip";
1744 else if (is_known(name, known_lyx_packages) && options.empty()) {
1745 if (name == "splitidx")
1746 h_use_indices = "true";
1747 else if (name == "minted")
1748 h_use_minted = true;
1749 else if (name == "refstyle")
1750 h_use_refstyle = true;
1751 else if (name == "prettyref")
1752 h_use_refstyle = false;
1754 if (!in_lyx_preamble) {
1755 h_preamble << package_beg_sep << name
1756 << package_mid_sep << "\\usepackage{"
1758 if (p.next_token().cat() == catNewline ||
1759 (p.next_token().cat() == catSpace &&
1760 p.next_next_token().cat() == catNewline))
1762 h_preamble << package_end_sep;
1766 else if (name == "geometry")
1767 handle_geometry(options);
1769 else if (name == "subfig")
1770 ; // ignore this FIXME: Use the package separator mechanism instead
1772 else if (char const * const * where = is_known(name, known_languages))
1773 h_language = known_coded_languages[where - known_languages];
1775 else if (name == "natbib") {
1776 h_biblio_style = "plainnat";
1777 h_cite_engine = "natbib";
1778 h_cite_engine_type = "authoryear";
1779 vector<string>::iterator it =
1780 find(options.begin(), options.end(), "authoryear");
1781 if (it != options.end())
1784 it = find(options.begin(), options.end(), "numbers");
1785 if (it != options.end()) {
1786 h_cite_engine_type = "numerical";
1790 if (!options.empty())
1791 h_biblio_options = join(options, ",");
1794 else if (name == "biblatex") {
1795 h_biblio_style = "plainnat";
1796 h_cite_engine = "biblatex";
1797 h_cite_engine_type = "authoryear";
1799 vector<string>::iterator it =
1800 find(options.begin(), options.end(), "natbib");
1801 if (it != options.end()) {
1803 h_cite_engine = "biblatex-natbib";
1805 opt = process_keyval_opt(options, "natbib");
1807 h_cite_engine = "biblatex-natbib";
1809 opt = process_keyval_opt(options, "style");
1811 h_biblatex_citestyle = opt;
1812 h_biblatex_bibstyle = opt;
1814 opt = process_keyval_opt(options, "citestyle");
1816 h_biblatex_citestyle = opt;
1817 opt = process_keyval_opt(options, "bibstyle");
1819 h_biblatex_bibstyle = opt;
1821 opt = process_keyval_opt(options, "refsection");
1823 if (opt == "none" || opt == "part"
1824 || opt == "chapter" || opt == "section"
1825 || opt == "subsection")
1828 cerr << "Ignoring unknown refsection value '"
1831 opt = process_keyval_opt(options, "bibencoding");
1834 if (!options.empty()) {
1835 h_biblio_options = join(options, ",");
1840 else if (name == "jurabib") {
1841 h_biblio_style = "jurabib";
1842 h_cite_engine = "jurabib";
1843 h_cite_engine_type = "authoryear";
1844 if (!options.empty())
1845 h_biblio_options = join(options, ",");
1848 else if (name == "bibtopic")
1849 h_use_bibtopic = "true";
1851 else if (name == "chapterbib")
1852 h_multibib = "child";
1854 else if (name == "hyperref")
1855 handle_hyperref(options);
1857 else if (name == "algorithm2e") {
1858 // Load "algorithm2e" module
1859 addModule("algorithm2e");
1860 // Add the package options to the global document options
1861 if (!options.empty()) {
1862 if (h_options.empty())
1863 h_options = join(options, ",");
1865 h_options += ',' + join(options, ",");
1868 else if (name == "microtype") {
1869 //we internally support only microtype without params
1870 if (options.empty())
1871 h_use_microtype = "true";
1873 h_preamble << "\\usepackage[" << opts << "]{microtype}";
1876 else if (name == "lineno") {
1877 h_use_lineno = "true";
1878 if (!options.empty()) {
1879 h_lineno_options = join(options, ",");
1884 else if (name == "changebar")
1885 h_output_changes = "true";
1887 else if (!in_lyx_preamble) {
1888 if (options.empty())
1889 h_preamble << "\\usepackage{" << name << '}';
1891 h_preamble << "\\usepackage[" << opts << "]{"
1895 if (p.next_token().cat() == catNewline ||
1896 (p.next_token().cat() == catSpace &&
1897 p.next_next_token().cat() == catNewline))
1901 // We need to do something with the options...
1902 if (!options.empty() && !detectEncoding)
1903 cerr << "Ignoring options '" << join(options, ",")
1904 << "' of package " << name << '.' << endl;
1906 // remove the whitespace
1911 void Preamble::handle_if(Parser & p, bool in_lyx_preamble)
1914 Token t = p.get_token();
1915 if (t.cat() == catEscape &&
1916 is_known(t.cs(), known_if_commands))
1917 handle_if(p, in_lyx_preamble);
1919 if (!in_lyx_preamble)
1920 h_preamble << t.asInput();
1921 if (t.cat() == catEscape && t.cs() == "fi")
1928 bool Preamble::writeLyXHeader(ostream & os, bool subdoc, string const & outfiledir)
1930 if (contains(h_float_placement, "H"))
1931 registerAutomaticallyLoadedPackage("float");
1932 if (h_spacing != "single" && h_spacing != "default")
1933 registerAutomaticallyLoadedPackage("setspace");
1934 if (h_use_packages["amsmath"] == "2") {
1935 // amsbsy and amstext are already provided by amsmath
1936 registerAutomaticallyLoadedPackage("amsbsy");
1937 registerAutomaticallyLoadedPackage("amstext");
1940 // output the LyX file settings
1941 // Important: Keep the version formatting in sync with LyX and
1942 // lyx2lyx (bug 7951)
1943 string const origin = roundtripMode() ? "roundtrip" : outfiledir;
1944 os << "#LyX file created by tex2lyx " << lyx_version_major << '.'
1945 << lyx_version_minor << '\n'
1946 << "\\lyxformat " << LYX_FORMAT << '\n'
1947 << "\\begin_document\n"
1948 << "\\begin_header\n"
1949 << "\\save_transient_properties " << h_save_transient_properties << "\n"
1950 << "\\origin " << origin << "\n"
1951 << "\\textclass " << h_textclass << "\n";
1952 string const raw = subdoc ? empty_string() : h_preamble.str();
1954 os << "\\begin_preamble\n";
1955 for (string::size_type i = 0; i < raw.size(); ++i) {
1956 if (raw[i] == package_beg_sep) {
1957 // Here follows some package loading code that
1958 // must be skipped if the package is loaded
1960 string::size_type j = raw.find(package_mid_sep, i);
1961 if (j == string::npos)
1963 string::size_type k = raw.find(package_end_sep, j);
1964 if (k == string::npos)
1966 string const package = raw.substr(i + 1, j - i - 1);
1967 string const replacement = raw.substr(j + 1, k - j - 1);
1968 if (auto_packages.find(package) == auto_packages.end())
1974 os << "\n\\end_preamble\n";
1976 if (!h_options.empty())
1977 os << "\\options " << h_options << "\n";
1978 os << "\\use_default_options " << h_use_default_options << "\n";
1979 if (!used_modules.empty()) {
1980 os << "\\begin_modules\n";
1981 vector<string>::const_iterator const end = used_modules.end();
1982 vector<string>::const_iterator it = used_modules.begin();
1983 for (; it != end; ++it)
1985 os << "\\end_modules\n";
1987 if (!h_includeonlys.empty()) {
1988 os << "\\begin_includeonly\n";
1989 for (auto const & iofile : h_includeonlys)
1990 os << iofile << '\n';
1991 os << "\\end_includeonly\n";
1993 os << "\\maintain_unincluded_children " << h_maintain_unincluded_children << "\n"
1994 << "\\language " << h_language << "\n"
1995 << "\\language_package " << h_language_package << "\n"
1996 << "\\inputencoding " << h_inputencoding << "\n"
1997 << "\\fontencoding " << h_fontencoding << "\n"
1998 << "\\font_roman \"" << h_font_roman[0]
1999 << "\" \"" << h_font_roman[1] << "\"\n"
2000 << "\\font_sans \"" << h_font_sans[0] << "\" \"" << h_font_sans[1] << "\"\n"
2001 << "\\font_typewriter \"" << h_font_typewriter[0]
2002 << "\" \"" << h_font_typewriter[1] << "\"\n"
2003 << "\\font_math \"" << h_font_math[0] << "\" \"" << h_font_math[1] << "\"\n"
2004 << "\\font_default_family " << h_font_default_family << "\n"
2005 << "\\use_non_tex_fonts " << (h_use_non_tex_fonts ? "true" : "false") << '\n'
2006 << "\\font_sc " << h_font_sc << "\n"
2007 << "\\font_roman_osf " << h_font_roman_osf << "\n"
2008 << "\\font_sans_osf " << h_font_sans_osf << "\n"
2009 << "\\font_typewriter_osf " << h_font_typewriter_osf << "\n";
2010 if (!h_font_roman_opts.empty())
2011 os << "\\font_roman_opts \"" << h_font_roman_opts << "\"" << '\n';
2012 os << "\\font_sf_scale " << h_font_sf_scale[0]
2013 << ' ' << h_font_sf_scale[1] << '\n';
2014 if (!h_font_sans_opts.empty())
2015 os << "\\font_sans_opts \"" << h_font_sans_opts << "\"" << '\n';
2016 os << "\\font_tt_scale " << h_font_tt_scale[0]
2017 << ' ' << h_font_tt_scale[1] << '\n';
2018 if (!h_font_cjk.empty())
2019 os << "\\font_cjk " << h_font_cjk << '\n';
2020 if (!h_font_typewriter_opts.empty())
2021 os << "\\font_typewriter_opts \"" << h_font_typewriter_opts << "\"" << '\n';
2022 os << "\\use_microtype " << h_use_microtype << '\n'
2023 << "\\use_dash_ligatures " << h_use_dash_ligatures << '\n'
2024 << "\\graphics " << h_graphics << '\n'
2025 << "\\default_output_format " << h_default_output_format << "\n"
2026 << "\\output_sync " << h_output_sync << "\n";
2027 if (h_output_sync == "1")
2028 os << "\\output_sync_macro \"" << h_output_sync_macro << "\"\n";
2029 os << "\\bibtex_command " << h_bibtex_command << "\n"
2030 << "\\index_command " << h_index_command << "\n";
2031 if (!h_float_placement.empty())
2032 os << "\\float_placement " << h_float_placement << "\n";
2033 os << "\\paperfontsize " << h_paperfontsize << "\n"
2034 << "\\spacing " << h_spacing << "\n"
2035 << "\\use_hyperref " << h_use_hyperref << '\n';
2036 if (h_use_hyperref == "true") {
2037 if (!h_pdf_title.empty())
2038 os << "\\pdf_title " << Lexer::quoteString(h_pdf_title) << '\n';
2039 if (!h_pdf_author.empty())
2040 os << "\\pdf_author " << Lexer::quoteString(h_pdf_author) << '\n';
2041 if (!h_pdf_subject.empty())
2042 os << "\\pdf_subject " << Lexer::quoteString(h_pdf_subject) << '\n';
2043 if (!h_pdf_keywords.empty())
2044 os << "\\pdf_keywords " << Lexer::quoteString(h_pdf_keywords) << '\n';
2045 os << "\\pdf_bookmarks " << h_pdf_bookmarks << "\n"
2046 "\\pdf_bookmarksnumbered " << h_pdf_bookmarksnumbered << "\n"
2047 "\\pdf_bookmarksopen " << h_pdf_bookmarksopen << "\n"
2048 "\\pdf_bookmarksopenlevel " << h_pdf_bookmarksopenlevel << "\n"
2049 "\\pdf_breaklinks " << h_pdf_breaklinks << "\n"
2050 "\\pdf_pdfborder " << h_pdf_pdfborder << "\n"
2051 "\\pdf_colorlinks " << h_pdf_colorlinks << "\n"
2052 "\\pdf_backref " << h_pdf_backref << "\n"
2053 "\\pdf_pdfusetitle " << h_pdf_pdfusetitle << '\n';
2054 if (!h_pdf_pagemode.empty())
2055 os << "\\pdf_pagemode " << h_pdf_pagemode << '\n';
2056 if (!h_pdf_quoted_options.empty())
2057 os << "\\pdf_quoted_options " << Lexer::quoteString(h_pdf_quoted_options) << '\n';
2059 os << "\\papersize " << h_papersize << "\n"
2060 << "\\use_geometry " << h_use_geometry << '\n';
2061 for (map<string, string>::const_iterator it = h_use_packages.begin();
2062 it != h_use_packages.end(); ++it)
2063 os << "\\use_package " << it->first << ' ' << it->second << '\n';
2064 os << "\\cite_engine " << h_cite_engine << '\n'
2065 << "\\cite_engine_type " << h_cite_engine_type << '\n'
2066 << "\\biblio_style " << h_biblio_style << "\n"
2067 << "\\use_bibtopic " << h_use_bibtopic << "\n";
2068 if (!h_biblio_options.empty())
2069 os << "\\biblio_options " << h_biblio_options << "\n";
2070 if (!h_biblatex_bibstyle.empty())
2071 os << "\\biblatex_bibstyle " << h_biblatex_bibstyle << "\n";
2072 if (!h_biblatex_citestyle.empty())
2073 os << "\\biblatex_citestyle " << h_biblatex_citestyle << "\n";
2074 if (!h_multibib.empty())
2075 os << "\\multibib " << h_multibib << "\n";
2076 os << "\\use_indices " << h_use_indices << "\n"
2077 << "\\paperorientation " << h_paperorientation << '\n'
2078 << "\\suppress_date " << h_suppress_date << '\n'
2079 << "\\justification " << h_justification << '\n'
2080 << "\\use_refstyle " << h_use_refstyle << '\n'
2081 << "\\use_minted " << h_use_minted << '\n'
2082 << "\\use_lineno " << h_use_lineno << '\n';
2083 if (!h_lineno_options.empty())
2084 os << "\\lineno_options " << h_lineno_options << '\n';
2085 if (!h_fontcolor.empty())
2086 os << "\\fontcolor " << h_fontcolor << '\n';
2087 if (!h_notefontcolor.empty())
2088 os << "\\notefontcolor " << h_notefontcolor << '\n';
2089 if (!h_backgroundcolor.empty())
2090 os << "\\backgroundcolor " << h_backgroundcolor << '\n';
2091 if (!h_boxbgcolor.empty())
2092 os << "\\boxbgcolor " << h_boxbgcolor << '\n';
2093 if (index_number != 0)
2094 for (int i = 0; i < index_number; i++) {
2095 os << "\\index " << h_index[i] << '\n'
2096 << "\\shortcut " << h_shortcut[i] << '\n'
2097 << "\\color " << h_color << '\n'
2101 os << "\\index " << h_index[0] << '\n'
2102 << "\\shortcut " << h_shortcut[0] << '\n'
2103 << "\\color " << h_color << '\n'
2107 << "\\secnumdepth " << h_secnumdepth << "\n"
2108 << "\\tocdepth " << h_tocdepth << "\n"
2109 << "\\paragraph_separation " << h_paragraph_separation << "\n";
2110 if (h_paragraph_separation == "skip")
2111 os << "\\defskip " << h_defskip << "\n";
2113 os << "\\paragraph_indentation " << h_paragraph_indentation << "\n";
2114 os << "\\is_math_indent " << h_is_mathindent << "\n";
2115 if (!h_mathindentation.empty())
2116 os << "\\math_indentation " << h_mathindentation << "\n";
2117 os << "\\math_numbering_side " << h_math_numbering_side << "\n";
2118 os << "\\quotes_style " << h_quotes_style << "\n"
2119 << "\\dynamic_quotes " << h_dynamic_quotes << "\n"
2120 << "\\papercolumns " << h_papercolumns << "\n"
2121 << "\\papersides " << h_papersides << "\n"
2122 << "\\paperpagestyle " << h_paperpagestyle << "\n";
2123 if (!h_listings_params.empty())
2124 os << "\\listings_params " << h_listings_params << "\n";
2125 os << "\\tracking_changes " << h_tracking_changes << "\n"
2126 << "\\output_changes " << h_output_changes << "\n"
2127 << "\\change_bars " << h_change_bars << "\n"
2128 << "\\html_math_output " << h_html_math_output << "\n"
2129 << "\\html_css_as_file " << h_html_css_as_file << "\n"
2130 << "\\html_be_strict " << h_html_be_strict << "\n"
2131 << "\\docbook_table_output " << h_docbook_table_output << "\n"
2133 << "\\end_header\n\n"
2134 << "\\begin_body\n";
2139 void Preamble::parse(Parser & p, string const & forceclass,
2140 TeX2LyXDocClass & tc)
2142 // initialize fixed types
2143 special_columns_['D'] = 3;
2144 parse(p, forceclass, false, tc);
2148 void Preamble::parse(Parser & p, string const & forceclass,
2149 bool detectEncoding, TeX2LyXDocClass & tc)
2151 bool is_full_document = false;
2152 bool is_lyx_file = false;
2153 bool in_lyx_preamble = false;
2154 bool class_set = false;
2156 // determine whether this is a full document or a fragment for inclusion
2158 Token const & t = p.get_token();
2160 if (t.cat() == catEscape && t.cs() == "documentclass") {
2161 is_full_document = true;
2167 if (detectEncoding && !is_full_document)
2170 while (is_full_document && p.good()) {
2171 if (detectEncoding && h_inputencoding != "auto-legacy" &&
2172 h_inputencoding != "auto-legacy-plain")
2175 // Force textclass if the user wanted it
2176 if (!forceclass.empty()) {
2177 setTextClass(forceclass, tc);
2181 Token const & t = p.get_token();
2184 if (!detectEncoding)
2185 cerr << "t: " << t << '\n';
2191 if (!in_lyx_preamble &&
2192 (t.cat() == catLetter ||
2193 t.cat() == catSuper ||
2194 t.cat() == catSub ||
2195 t.cat() == catOther ||
2196 t.cat() == catMath ||
2197 t.cat() == catActive ||
2198 t.cat() == catBegin ||
2199 t.cat() == catEnd ||
2200 t.cat() == catAlign ||
2201 t.cat() == catParameter)) {
2202 h_preamble << t.cs();
2206 if (!in_lyx_preamble &&
2207 (t.cat() == catSpace || t.cat() == catNewline)) {
2208 h_preamble << t.asInput();
2212 if (t.cat() == catComment) {
2213 static regex const islyxfile("%% LyX .* created this file");
2214 static regex const usercommands("User specified LaTeX commands");
2216 string const comment = t.asInput();
2218 // magically switch encoding default if it looks like XeLaTeX
2219 static string const magicXeLaTeX =
2220 "% This document must be compiled with XeLaTeX ";
2221 if (comment.size() > magicXeLaTeX.size()
2222 && comment.substr(0, magicXeLaTeX.size()) == magicXeLaTeX
2223 && h_inputencoding == "auto-legacy") {
2224 if (!detectEncoding)
2225 cerr << "XeLaTeX comment found, switching to UTF8\n";
2226 h_inputencoding = "utf8";
2229 if (regex_search(comment, sub, islyxfile)) {
2231 in_lyx_preamble = true;
2232 } else if (is_lyx_file
2233 && regex_search(comment, sub, usercommands))
2234 in_lyx_preamble = false;
2235 else if (!in_lyx_preamble)
2236 h_preamble << t.asInput();
2240 if (t.cs() == "PassOptionsToPackage") {
2241 string const poptions = p.getArg('{', '}');
2242 string const package = p.verbatim_item();
2243 extra_package_options_.insert(make_pair(package, poptions));
2247 if (t.cs() == "pagestyle") {
2248 h_paperpagestyle = p.verbatim_item();
2252 if (t.cs() == "setdefaultlanguage") {
2254 // We don't yet care about non-language variant options
2255 // because LyX doesn't support this yet, see bug #8214
2257 string langopts = p.getOpt();
2258 // check if the option contains a variant, if yes, extract it
2259 string::size_type pos_var = langopts.find("variant");
2260 string::size_type i = langopts.find(',', pos_var);
2261 string::size_type k = langopts.find('=', pos_var);
2262 if (pos_var != string::npos){
2264 if (i == string::npos)
2265 variant = langopts.substr(k + 1, langopts.length() - k - 2);
2267 variant = langopts.substr(k + 1, i - k - 1);
2268 h_language = variant;
2272 h_language = p.verbatim_item();
2273 //finally translate the poyglossia name to a LyX name
2274 h_language = polyglossia2lyx(h_language);
2278 if (t.cs() == "setotherlanguage") {
2279 // We don't yet care about the option because LyX doesn't
2280 // support this yet, see bug #8214
2281 p.hasOpt() ? p.getOpt() : string();
2286 if (t.cs() == "setmainfont") {
2287 string fontopts = p.hasOpt() ? p.getArg('[', ']') : string();
2288 h_font_roman[1] = p.getArg('{', '}');
2289 if (!fontopts.empty()) {
2290 vector<string> opts = getVectorFromString(fontopts);
2292 for (auto const & opt : opts) {
2293 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2296 if (!fontopts.empty())
2300 h_font_roman_opts = fontopts;
2305 if (t.cs() == "setsansfont" || t.cs() == "setmonofont") {
2306 // LyX currently only supports the scale option
2307 string scale, fontopts;
2309 fontopts = p.getArg('[', ']');
2310 if (!fontopts.empty()) {
2311 vector<string> opts = getVectorFromString(fontopts);
2313 for (auto const & opt : opts) {
2314 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2317 if (prefixIs(opt, "Scale=")) {
2318 scale_as_percentage(opt, scale);
2321 if (!fontopts.empty())
2327 if (t.cs() == "setsansfont") {
2329 h_font_sf_scale[1] = scale;
2330 h_font_sans[1] = p.getArg('{', '}');
2331 if (!fontopts.empty())
2332 h_font_sans_opts = fontopts;
2335 h_font_tt_scale[1] = scale;
2336 h_font_typewriter[1] = p.getArg('{', '}');
2337 if (!fontopts.empty())
2338 h_font_typewriter_opts = fontopts;
2343 if (t.cs() == "babelfont") {
2345 h_use_non_tex_fonts = true;
2346 h_language_package = "babel";
2347 if (h_inputencoding == "auto-legacy")
2348 p.setEncoding("UTF-8");
2349 // we don't care about the lang option
2350 string const lang = p.hasOpt() ? p.getArg('[', ']') : string();
2351 string const family = p.getArg('{', '}');
2352 string fontopts = p.hasOpt() ? p.getArg('[', ']') : string();
2353 string const fontname = p.getArg('{', '}');
2354 if (lang.empty() && family == "rm") {
2355 h_font_roman[1] = fontname;
2356 if (!fontopts.empty()) {
2357 vector<string> opts = getVectorFromString(fontopts);
2359 for (auto const & opt : opts) {
2360 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2363 if (!fontopts.empty())
2367 h_font_roman_opts = fontopts;
2370 } else if (lang.empty() && (family == "sf" || family == "tt")) {
2372 if (!fontopts.empty()) {
2373 vector<string> opts = getVectorFromString(fontopts);
2375 for (auto const & opt : opts) {
2376 if (opt == "Mapping=tex-text" || opt == "Ligatures=TeX")
2379 if (prefixIs(opt, "Scale=")) {
2380 scale_as_percentage(opt, scale);
2383 if (!fontopts.empty())
2388 if (family == "sf") {
2390 h_font_sf_scale[1] = scale;
2391 h_font_sans[1] = fontname;
2392 if (!fontopts.empty())
2393 h_font_sans_opts = fontopts;
2396 h_font_tt_scale[1] = scale;
2397 h_font_typewriter[1] = fontname;
2398 if (!fontopts.empty())
2399 h_font_typewriter_opts = fontopts;
2403 // not rm, sf or tt or lang specific
2404 h_preamble << '\\' << t.cs();
2406 h_preamble << '[' << lang << ']';
2407 h_preamble << '{' << family << '}';
2408 if (!fontopts.empty())
2409 h_preamble << '[' << fontopts << ']';
2410 h_preamble << '{' << fontname << '}' << '\n';
2415 if (t.cs() == "date") {
2416 string argument = p.getArg('{', '}');
2417 if (argument.empty())
2418 h_suppress_date = "true";
2420 h_preamble << t.asInput() << '{' << argument << '}';
2424 if (t.cs() == "color") {
2425 string const space =
2426 (p.hasOpt() ? p.getOpt() : string());
2427 string argument = p.getArg('{', '}');
2428 // check the case that a standard color is used
2429 if (space.empty() && is_known(argument, known_basic_colors)) {
2430 h_fontcolor = rgbcolor2code(argument);
2431 registerAutomaticallyLoadedPackage("color");
2432 } else if (space.empty() && argument == "document_fontcolor")
2433 registerAutomaticallyLoadedPackage("color");
2434 // check the case that LyX's document_fontcolor is defined
2435 // but not used for \color
2437 h_preamble << t.asInput();
2439 h_preamble << space;
2440 h_preamble << '{' << argument << '}';
2441 // the color might already be set because \definecolor
2442 // is parsed before this
2448 if (t.cs() == "pagecolor") {
2449 string argument = p.getArg('{', '}');
2450 // check the case that a standard color is used
2451 if (is_known(argument, known_basic_colors)) {
2452 h_backgroundcolor = rgbcolor2code(argument);
2453 } else if (argument == "page_backgroundcolor")
2454 registerAutomaticallyLoadedPackage("color");
2455 // check the case that LyX's page_backgroundcolor is defined
2456 // but not used for \pagecolor
2458 h_preamble << t.asInput() << '{' << argument << '}';
2459 // the color might already be set because \definecolor
2460 // is parsed before this
2461 h_backgroundcolor = "";
2466 if (t.cs() == "makeatletter") {
2467 // LyX takes care of this
2468 p.setCatcode('@', catLetter);
2472 if (t.cs() == "makeatother") {
2473 // LyX takes care of this
2474 p.setCatcode('@', catOther);
2478 if (t.cs() == "makeindex") {
2479 // LyX will re-add this if a print index command is found
2484 if (t.cs() == "newindex") {
2485 string const indexname = p.getArg('[', ']');
2486 string const shortcut = p.verbatim_item();
2487 if (!indexname.empty())
2488 h_index[index_number] = indexname;
2490 h_index[index_number] = shortcut;
2491 h_shortcut[index_number] = shortcut;
2497 if (t.cs() == "addbibresource") {
2498 string const options = p.getArg('[', ']');
2499 string const arg = removeExtension(p.getArg('{', '}'));
2500 if (!options.empty()) {
2501 // check if the option contains a bibencoding, if yes, extract it
2502 string::size_type pos = options.find("bibencoding=");
2504 if (pos != string::npos) {
2505 string::size_type i = options.find(',', pos);
2506 if (i == string::npos)
2507 encoding = options.substr(pos + 1);
2509 encoding = options.substr(pos, i - pos);
2510 pos = encoding.find('=');
2511 if (pos == string::npos)
2514 encoding = encoding.substr(pos + 1);
2516 if (!encoding.empty())
2517 biblatex_encodings.push_back(normalize_filename(arg) + ' ' + encoding);
2519 biblatex_bibliographies.push_back(arg);
2523 if (t.cs() == "bibliography") {
2524 vector<string> bibs = getVectorFromString(p.getArg('{', '}'));
2525 biblatex_bibliographies.insert(biblatex_bibliographies.end(), bibs.begin(), bibs.end());
2529 if (t.cs() == "RS@ifundefined") {
2530 string const name = p.verbatim_item();
2531 string const body1 = p.verbatim_item();
2532 string const body2 = p.verbatim_item();
2533 // only non-lyxspecific stuff
2534 if (in_lyx_preamble &&
2535 (name == "subsecref" || name == "thmref" || name == "lemref"))
2539 ss << '\\' << t.cs();
2540 ss << '{' << name << '}'
2541 << '{' << body1 << '}'
2542 << '{' << body2 << '}';
2543 h_preamble << ss.str();
2548 if (t.cs() == "AtBeginDocument") {
2549 string const name = p.verbatim_item();
2550 // only non-lyxspecific stuff
2551 if (in_lyx_preamble &&
2552 (name == "\\providecommand\\partref[1]{\\ref{part:#1}}"
2553 || name == "\\providecommand\\chapref[1]{\\ref{chap:#1}}"
2554 || name == "\\providecommand\\secref[1]{\\ref{sec:#1}}"
2555 || name == "\\providecommand\\subsecref[1]{\\ref{subsec:#1}}"
2556 || name == "\\providecommand\\parref[1]{\\ref{par:#1}}"
2557 || name == "\\providecommand\\figref[1]{\\ref{fig:#1}}"
2558 || name == "\\providecommand\\tabref[1]{\\ref{tab:#1}}"
2559 || name == "\\providecommand\\algref[1]{\\ref{alg:#1}}"
2560 || name == "\\providecommand\\fnref[1]{\\ref{fn:#1}}"
2561 || name == "\\providecommand\\enuref[1]{\\ref{enu:#1}}"
2562 || name == "\\providecommand\\eqref[1]{\\ref{eq:#1}}"
2563 || name == "\\providecommand\\lemref[1]{\\ref{lem:#1}}"
2564 || name == "\\providecommand\\thmref[1]{\\ref{thm:#1}}"
2565 || name == "\\providecommand\\corref[1]{\\ref{cor:#1}}"
2566 || name == "\\providecommand\\propref[1]{\\ref{prop:#1}}"))
2570 ss << '\\' << t.cs();
2571 ss << '{' << name << '}';
2572 h_preamble << ss.str();
2577 if (t.cs() == "newcommand" || t.cs() == "newcommandx"
2578 || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
2579 || t.cs() == "providecommand" || t.cs() == "providecommandx"
2580 || t.cs() == "DeclareRobustCommand"
2581 || t.cs() == "DeclareRobustCommandx"
2582 || t.cs() == "ProvideTextCommandDefault"
2583 || t.cs() == "DeclareMathAccent") {
2585 if (p.next_token().character() == '*') {
2589 string const name = p.verbatim_item();
2590 string const opt1 = p.getFullOpt();
2591 string const opt2 = p.getFullOpt();
2592 string const body = p.verbatim_item();
2593 // store the in_lyx_preamble setting
2594 bool const was_in_lyx_preamble = in_lyx_preamble;
2596 if (name == "\\rmdefault")
2597 if (is_known(body, known_roman_font_packages)) {
2598 h_font_roman[0] = body;
2600 in_lyx_preamble = true;
2602 if (name == "\\sfdefault") {
2603 if (is_known(body, known_sans_font_packages)) {
2604 h_font_sans[0] = body;
2606 in_lyx_preamble = true;
2608 if (body == "LibertinusSans-OsF") {
2609 h_font_sans[0] = "LibertinusSans-LF";
2610 h_font_sans_osf = "true";
2612 in_lyx_preamble = true;
2615 if (name == "\\ttdefault")
2616 if (is_known(body, known_typewriter_font_packages)) {
2617 h_font_typewriter[0] = body;
2619 in_lyx_preamble = true;
2621 if (name == "\\familydefault") {
2622 string family = body;
2623 // remove leading "\"
2624 h_font_default_family = family.erase(0,1);
2626 in_lyx_preamble = true;
2628 if (name == "\\LibertinusSans@scale") {
2629 if (isStrDbl(body)) {
2630 h_font_sf_scale[0] = convert<string>(
2631 static_cast<int>(100 * convert<double>(body)));
2634 if (name == "\\LibertinusMono@scale") {
2635 if (isStrDbl(body)) {
2636 h_font_tt_scale[0] = convert<string>(
2637 static_cast<int>(100 * convert<double>(body)));
2641 // remove LyX-specific definitions that are re-added by LyX
2643 // \lyxline is an ancient command that is converted by tex2lyx into
2644 // a \rule therefore remove its preamble code
2645 if (name == "\\lyxdot" || name == "\\lyxarrow"
2646 || name == "\\lyxline" || name == "\\LyX") {
2648 in_lyx_preamble = true;
2651 // Add the command to the known commands
2652 add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
2654 // only non-lyxspecific stuff
2655 if (!in_lyx_preamble) {
2657 ss << '\\' << t.cs();
2660 ss << '{' << name << '}' << opt1 << opt2
2661 << '{' << body << "}";
2662 if (prefixIs(t.cs(), "renew") || !contains(h_preamble.str(), ss.str()))
2663 h_preamble << ss.str();
2665 ostream & out = in_preamble ? h_preamble : os;
2666 out << "\\" << t.cs() << "{" << name << "}"
2667 << opts << "{" << body << "}";
2670 // restore the in_lyx_preamble setting
2671 in_lyx_preamble = was_in_lyx_preamble;
2675 if (t.cs() == "documentclass") {
2676 vector<string>::iterator it;
2677 vector<string> opts = split_options(p.getArg('[', ']'));
2678 // FIXME This does not work for classes that have a
2679 // different name in LyX than in LaTeX
2680 string const tclass = p.getArg('{', '}');
2682 // Only set text class if a class hasn't been forced
2683 // (this was set above)
2685 // textclass needs to be set at this place (if not already done)
2686 // as we need to know it for other parameters
2687 // (such as class-dependent paper size)
2688 setTextClass(tclass, tc);
2693 // Try those who are (most likely) known to all packages first
2694 handle_opt(opts, known_fontsizes, h_paperfontsize);
2695 delete_opt(opts, known_fontsizes);
2696 // delete "pt" at the end
2697 string::size_type i = h_paperfontsize.find("pt");
2698 if (i != string::npos)
2699 h_paperfontsize.erase(i);
2700 // Now those known specifically to the class
2701 vector<string> class_fsizes = getVectorFromString(tc.opt_fontsize(), "|");
2702 string const fsize_format = tc.fontsizeformat();
2703 for (auto const & fsize : class_fsizes) {
2704 string latexsize = subst(fsize_format, "$$s", fsize);
2705 vector<string>::iterator it = find(opts.begin(), opts.end(), latexsize);
2706 if (it != opts.end()) {
2707 h_paperfontsize = fsize;
2713 // The documentclass options are always parsed before the options
2714 // of the babel call so that a language cannot overwrite the babel
2716 handle_opt(opts, known_languages, h_language);
2717 delete_opt(opts, known_languages);
2720 if ((it = find(opts.begin(), opts.end(), "fleqn"))
2722 h_is_mathindent = "1";
2725 // formula numbering side
2726 if ((it = find(opts.begin(), opts.end(), "leqno"))
2728 h_math_numbering_side = "left";
2731 else if ((it = find(opts.begin(), opts.end(), "reqno"))
2733 h_math_numbering_side = "right";
2737 // paper orientation
2738 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
2739 h_paperorientation = "landscape";
2743 if ((it = find(opts.begin(), opts.end(), "oneside"))
2748 if ((it = find(opts.begin(), opts.end(), "twoside"))
2754 if ((it = find(opts.begin(), opts.end(), "onecolumn"))
2756 h_papercolumns = "1";
2759 if ((it = find(opts.begin(), opts.end(), "twocolumn"))
2761 h_papercolumns = "2";
2765 // some size options are known by the document class, other sizes
2766 // are handled by the \geometry command of the geometry package
2767 vector<string> class_psizes = getVectorFromString(tc.opt_pagesize(), "|");
2768 string const psize_format = tc.pagesizeformat();
2769 for (auto const & psize : class_psizes) {
2770 string latexsize = subst(psize_format, "$$s", psize);
2771 vector<string>::iterator it = find(opts.begin(), opts.end(), latexsize);
2772 if (it != opts.end()) {
2773 h_papersize = psize;
2777 if (psize_format == "$$spaper")
2779 // Also try with the default format since this is understood by
2781 latexsize = psize + "paper";
2782 it = find(opts.begin(), opts.end(), latexsize);
2783 if (it != opts.end()) {
2784 h_papersize = psize;
2789 // the remaining options
2790 h_options = join(opts, ",");
2794 if (t.cs() == "usepackage") {
2795 string const options = p.getArg('[', ']');
2796 string const name = p.getArg('{', '}');
2797 vector<string> vecnames;
2798 split(name, vecnames, ',');
2799 vector<string>::const_iterator it = vecnames.begin();
2800 vector<string>::const_iterator end = vecnames.end();
2801 for (; it != end; ++it)
2802 handle_package(p, trimSpaceAndEol(*it), options,
2803 in_lyx_preamble, detectEncoding);
2807 if (t.cs() == "inputencoding") {
2808 string const encoding = p.getArg('{','}');
2809 Encoding const * const enc = encodings.fromLaTeXName(
2810 encoding, Encoding::inputenc, true);
2812 if (!detectEncoding)
2813 cerr << "Unknown encoding " << encoding
2814 << ". Ignoring." << std::endl;
2817 h_inputencoding = enc->name();
2818 p.setEncoding(enc->iconvName());
2823 if (t.cs() == "newenvironment") {
2824 string const name = p.getArg('{', '}');
2825 string const opt1 = p.getFullOpt();
2826 string const opt2 = p.getFullOpt();
2827 string const beg = p.verbatim_item();
2828 string const end = p.verbatim_item();
2829 if (!in_lyx_preamble) {
2830 h_preamble << "\\newenvironment{" << name
2831 << '}' << opt1 << opt2 << '{'
2832 << beg << "}{" << end << '}';
2834 add_known_environment(name, opt1, !opt2.empty(),
2835 from_utf8(beg), from_utf8(end));
2839 if (t.cs() == "newtheorem") {
2841 if (p.next_token().character() == '*') {
2845 string const name = p.getArg('{', '}');
2846 string const opt1 = p.getFullOpt();
2847 string const opt2 = p.getFullOpt();
2848 string const body = p.verbatim_item();
2849 string const opt3 = p.getFullOpt();
2850 string const cmd = star ? "\\newtheorem*" : "\\newtheorem";
2852 string const complete = cmd + "{" + name + '}' +
2853 opt1 + opt2 + '{' + body + '}' + opt3;
2855 add_known_theorem(name, opt1, !opt2.empty(), from_utf8(complete));
2857 if (!in_lyx_preamble)
2858 h_preamble << complete;
2862 if (t.cs() == "def") {
2863 string name = p.get_token().cs();
2864 // In fact, name may be more than the name:
2865 // In the test case of bug 8116
2866 // name == "csname SF@gobble@opt \endcsname".
2867 // Therefore, we need to use asInput() instead of cs().
2868 while (p.next_token().cat() != catBegin)
2869 name += p.get_token().asInput();
2870 if (!in_lyx_preamble)
2871 h_preamble << "\\def\\" << name << '{'
2872 << p.verbatim_item() << "}";
2876 if (t.cs() == "newcolumntype") {
2877 string const name = p.getArg('{', '}');
2878 trimSpaceAndEol(name);
2880 string opts = p.getOpt();
2881 if (!opts.empty()) {
2882 istringstream is(string(opts, 1));
2885 special_columns_[name[0]] = nargs;
2886 h_preamble << "\\newcolumntype{" << name << "}";
2888 h_preamble << "[" << nargs << "]";
2889 h_preamble << "{" << p.verbatim_item() << "}";
2893 if (t.cs() == "setcounter") {
2894 string const name = p.getArg('{', '}');
2895 string const content = p.getArg('{', '}');
2896 if (name == "secnumdepth")
2897 h_secnumdepth = content;
2898 else if (name == "tocdepth")
2899 h_tocdepth = content;
2901 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
2905 if (t.cs() == "setlength") {
2906 string const name = p.verbatim_item();
2907 string const content = p.verbatim_item();
2908 // the paragraphs are only not indented when \parindent is set to zero
2909 if (name == "\\parindent" && content != "")
2910 h_paragraph_indentation = translate_len(content);
2911 else if (name == "\\parskip" && isPackageUsed("parskip")) {
2912 if (content == "\\smallskipamount")
2913 h_defskip = "smallskip";
2914 else if (content == "\\medskipamount")
2915 h_defskip = "medskip";
2916 else if (content == "\\bigskipamount")
2917 h_defskip = "bigskip";
2918 else if (content == "\\baselineskip")
2919 h_defskip = "fullline";
2921 h_defskip = translate_len(content);
2922 } else if (name == "\\mathindent") {
2923 h_mathindentation = translate_len(content);
2925 h_preamble << "\\setlength{" << name << "}{" << content << "}";
2929 if (t.cs() == "onehalfspacing") {
2930 h_spacing = "onehalf";
2934 if (t.cs() == "doublespacing") {
2935 h_spacing = "double";
2939 if (t.cs() == "setstretch") {
2940 h_spacing = "other " + p.verbatim_item();
2944 if (t.cs() == "synctex") {
2945 // the scheme is \synctex=value
2946 // where value can only be "1" or "-1"
2947 h_output_sync = "1";
2948 // there can be any character behind the value (e.g. a linebreak or a '\'
2949 // therefore we extract it char by char
2951 string value = p.get_token().asInput();
2953 value += p.get_token().asInput();
2954 h_output_sync_macro = "\\synctex=" + value;
2958 if (t.cs() == "begin") {
2959 string const name = p.getArg('{', '}');
2960 if (name == "document")
2962 h_preamble << "\\begin{" << name << "}";
2966 if (t.cs() == "geometry") {
2967 vector<string> opts = split_options(p.getArg('{', '}'));
2968 handle_geometry(opts);
2972 if (t.cs() == "definecolor") {
2973 string const color = p.getArg('{', '}');
2974 string const space = p.getArg('{', '}');
2975 string const value = p.getArg('{', '}');
2976 if (color == "document_fontcolor" && space == "rgb") {
2977 RGBColor c(RGBColorFromLaTeX(value));
2978 h_fontcolor = X11hexname(c);
2979 } else if (color == "note_fontcolor" && space == "rgb") {
2980 RGBColor c(RGBColorFromLaTeX(value));
2981 h_notefontcolor = X11hexname(c);
2982 } else if (color == "page_backgroundcolor" && space == "rgb") {
2983 RGBColor c(RGBColorFromLaTeX(value));
2984 h_backgroundcolor = X11hexname(c);
2985 } else if (color == "shadecolor" && space == "rgb") {
2986 RGBColor c(RGBColorFromLaTeX(value));
2987 h_boxbgcolor = X11hexname(c);
2989 h_preamble << "\\definecolor{" << color
2990 << "}{" << space << "}{" << value
2996 if (t.cs() == "bibliographystyle") {
2997 h_biblio_style = p.verbatim_item();
3001 if (t.cs() == "jurabibsetup") {
3002 // FIXME p.getArg('{', '}') is most probably wrong (it
3003 // does not handle nested braces).
3004 // Use p.verbatim_item() instead.
3005 vector<string> jurabibsetup =
3006 split_options(p.getArg('{', '}'));
3007 // add jurabibsetup to the jurabib package options
3008 add_package("jurabib", jurabibsetup);
3009 if (!jurabibsetup.empty()) {
3010 h_preamble << "\\jurabibsetup{"
3011 << join(jurabibsetup, ",") << '}';
3016 if (t.cs() == "hypersetup") {
3017 vector<string> hypersetup =
3018 split_options(p.verbatim_item());
3019 // add hypersetup to the hyperref package options
3020 handle_hyperref(hypersetup);
3021 if (!hypersetup.empty()) {
3022 h_preamble << "\\hypersetup{"
3023 << join(hypersetup, ",") << '}';
3028 if (t.cs() == "includeonly") {
3029 vector<string> includeonlys = getVectorFromString(p.getArg('{', '}'));
3030 for (auto & iofile : includeonlys) {
3031 string filename(normalize_filename(iofile));
3032 string const path = getMasterFilePath(true);
3033 // We want to preserve relative/absolute filenames,
3034 // therefore path is only used for testing
3035 if (!makeAbsPath(filename, path).exists()) {
3036 // The file extension is probably missing.
3037 // Now try to find it out.
3038 string const tex_name =
3039 find_file(filename, path,
3040 known_tex_extensions);
3041 if (!tex_name.empty())
3042 filename = tex_name;
3045 if (makeAbsPath(filename, path).exists())
3046 fix_child_filename(filename);
3048 cerr << "Warning: Could not find included file '"
3049 << filename << "'." << endl;
3050 outname = changeExtension(filename, "lyx");
3051 h_includeonlys.push_back(outname);
3056 if (is_known(t.cs(), known_if_3arg_commands)) {
3057 // prevent misparsing of \usepackage if it is used
3058 // as an argument (see e.g. our own output of
3059 // \@ifundefined above)
3060 string const arg1 = p.verbatim_item();
3061 string const arg2 = p.verbatim_item();
3062 string const arg3 = p.verbatim_item();
3063 // test case \@ifundefined{date}{}{\date{}}
3064 if (t.cs() == "@ifundefined" && arg1 == "date" &&
3065 arg2.empty() && arg3 == "\\date{}") {
3066 h_suppress_date = "true";
3067 // older tex2lyx versions did output
3068 // \@ifundefined{definecolor}{\usepackage{color}}{}
3069 } else if (t.cs() == "@ifundefined" &&
3070 arg1 == "definecolor" &&
3071 arg2 == "\\usepackage{color}" &&
3073 if (!in_lyx_preamble)
3074 h_preamble << package_beg_sep
3077 << "\\@ifundefined{definecolor}{color}{}"
3080 //\@ifundefined{showcaptionsetup}{}{%
3081 // \PassOptionsToPackage{caption=false}{subfig}}
3082 // that LyX uses for subfloats
3083 } else if (t.cs() == "@ifundefined" &&
3084 arg1 == "showcaptionsetup" && arg2.empty()
3085 && arg3 == "%\n \\PassOptionsToPackage{caption=false}{subfig}") {
3087 } else if (!in_lyx_preamble) {
3088 h_preamble << t.asInput()
3089 << '{' << arg1 << '}'
3090 << '{' << arg2 << '}'
3091 << '{' << arg3 << '}';
3096 if (is_known(t.cs(), known_if_commands)) {
3097 // must not parse anything in conditional code, since
3098 // LyX would output the parsed contents unconditionally
3099 if (!in_lyx_preamble)
3100 h_preamble << t.asInput();
3101 handle_if(p, in_lyx_preamble);
3105 if (!t.cs().empty() && !in_lyx_preamble) {
3106 h_preamble << '\\' << t.cs();
3111 // set textclass if not yet done (snippets without \documentclass and forced class)
3113 setTextClass(h_textclass, tc);
3115 // remove the whitespace
3118 if (h_papersides.empty()) {
3121 h_papersides = ss.str();
3124 // If the CJK package is used we cannot set the document language from
3125 // the babel options. Instead, we guess which language is used most
3126 // and set this one.
3127 default_language = h_language;
3128 if (is_full_document &&
3129 (auto_packages.find("CJK") != auto_packages.end() ||
3130 auto_packages.find("CJKutf8") != auto_packages.end())) {
3132 h_language = guessLanguage(p, default_language);
3134 if (explicit_babel && h_language != default_language) {
3135 // We set the document language to a CJK language,
3136 // but babel is explicitly called in the user preamble
3137 // without options. LyX will not add the default
3138 // language to the document options if it is either
3139 // english, or no text is set as default language.
3140 // Therefore we need to add a language option explicitly.
3141 // FIXME: It would be better to remove all babel calls
3142 // from the user preamble, but this is difficult
3143 // without re-introducing bug 7861.
3144 if (h_options.empty())
3145 h_options = lyx2babel(default_language);
3147 h_options += ',' + lyx2babel(default_language);
3151 // Finally, set the quote style.
3152 // LyX knows the following quotes styles:
3153 // british, cjk, cjkangle, danish, english, french, german,
3154 // polish, russian, swedish and swiss
3155 // conversion list taken from
3156 // https://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
3157 // (quotes for kazakh are unknown)
3159 if (is_known(h_language, known_british_quotes_languages))
3160 h_quotes_style = "british";
3162 else if (is_known(h_language, known_cjk_quotes_languages))
3163 h_quotes_style = "cjk";
3165 else if (is_known(h_language, known_cjkangle_quotes_languages))
3166 h_quotes_style = "cjkangle";
3168 else if (is_known(h_language, known_danish_quotes_languages))
3169 h_quotes_style = "danish";
3171 else if (is_known(h_language, known_french_quotes_languages))
3172 h_quotes_style = "french";
3174 else if (is_known(h_language, known_german_quotes_languages))
3175 h_quotes_style = "german";
3177 else if (is_known(h_language, known_polish_quotes_languages))
3178 h_quotes_style = "polish";
3180 else if (is_known(h_language, known_hungarian_quotes_languages))
3181 h_quotes_style = "hungarian";
3183 else if (is_known(h_language, known_russian_quotes_languages))
3184 h_quotes_style = "russian";
3186 else if (is_known(h_language, known_swedish_quotes_languages))
3187 h_quotes_style = "swedish";
3189 else if (is_known(h_language, known_swiss_quotes_languages))
3190 h_quotes_style = "swiss";
3192 else if (is_known(h_language, known_english_quotes_languages))
3193 h_quotes_style = "english";
3197 string Preamble::parseEncoding(Parser & p, string const & forceclass)
3199 TeX2LyXDocClass dummy;
3200 parse(p, forceclass, true, dummy);
3201 if (h_inputencoding != "auto-legacy" && h_inputencoding != "auto-legacy-plain")
3202 return h_inputencoding;
3207 string babel2lyx(string const & language)
3209 char const * const * where = is_known(language, known_languages);
3211 return known_coded_languages[where - known_languages];
3216 string lyx2babel(string const & language)
3218 char const * const * where = is_known(language, known_coded_languages);
3220 return known_languages[where - known_coded_languages];
3225 string Preamble::polyglossia2lyx(string const & language)
3227 char const * const * where = is_known(language, polyglossia_languages);
3229 return coded_polyglossia_languages[where - polyglossia_languages];
3234 string rgbcolor2code(string const & name)
3236 char const * const * where = is_known(name, known_basic_colors);
3238 // "red", "green" etc
3239 return known_basic_color_codes[where - known_basic_colors];
3241 // "255,0,0", "0,255,0" etc
3242 RGBColor c(RGBColorFromLaTeX(name));
3243 return X11hexname(c);