From 0dd7505ec66a235af2e2f22602a60947cd310848 Mon Sep 17 00:00:00 2001 From: Enrico Forestieri Date: Sun, 30 Oct 2016 00:16:50 +0200 Subject: [PATCH] Sort the language nesting mess with polyglossia When using polyglossia, lyx was making a real mess when changing language inside nested insets. The \begin{language} and \end{language} commands were not well paired such that they could easily occur just before and after the start or end of an environment. Of course this was causing latex errors such that "\begin{otherlanguage} ended by \end{environment}". Fixes #9633 --- src/Font.cpp | 3 + src/Paragraph.cpp | 2 + src/insets/InsetIPA.h | 2 + src/output_latex.cpp | 189 ++++++++++++++++++++++++++++++++++++------ src/output_latex.h | 9 ++ status.22x | 2 + 6 files changed, 182 insertions(+), 25 deletions(-) diff --git a/src/Font.cpp b/src/Font.cpp index f0814a3a3e..82d58a723d 100644 --- a/src/Font.cpp +++ b/src/Font.cpp @@ -286,6 +286,7 @@ int Font::latexWriteStartChanges(odocstream & os, BufferParams const & bparams, tmp += "{"; os << from_ascii(tmp); count += tmp.length(); + pushPolyglossiaLang(language()->polyglossia(), true); } else if (language()->encoding()->package() != Encoding::CJK) { os << '{'; count += 1; @@ -544,6 +545,8 @@ int Font::latexWriteEndChanges(otexstream & os, BufferParams const & bparams, && language()->encoding()->package() != Encoding::CJK) { os << '}'; ++count; + if (runparams.use_polyglossia) + popPolyglossiaLang(); } return count; diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 23315204be..1f8b18d7e9 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -2445,6 +2445,8 @@ void Paragraph::latex(BufferParams const & bparams, running_lang); os << from_ascii(end_tag); column += end_tag.length(); + if (runparams.use_polyglossia) + popPolyglossiaLang(); } // Switch file encoding if necessary (and allowed) diff --git a/src/insets/InsetIPA.h b/src/insets/InsetIPA.h index 783871d8a4..f123237b07 100644 --- a/src/insets/InsetIPA.h +++ b/src/insets/InsetIPA.h @@ -44,6 +44,8 @@ public: Inset * clone() const { return new InsetIPA(*this); } bool neverIndent() const { return true; } + + bool forceLocalFontSwitch() const { return true; } InsetCode lyxCode() const { return IPA_CODE; } diff --git a/src/output_latex.cpp b/src/output_latex.cpp index 6de0e811a5..cdce62aec7 100644 --- a/src/output_latex.cpp +++ b/src/output_latex.cpp @@ -41,6 +41,7 @@ #include #include +#include using namespace std; using namespace lyx::support; @@ -60,13 +61,15 @@ enum OpenEncoding { struct OutputState { OutputState() : open_encoding_(none), cjk_inherited_(0), - prev_env_language_(0), open_polyglossia_lang_("") + prev_env_language_(0), nest_level_(0) { } OpenEncoding open_encoding_; int cjk_inherited_; Language const * prev_env_language_; - string open_polyglossia_lang_; + int nest_level_; + stack lang_switch_depth_; // Both are always empty when + stack open_polyglossia_lang_; // not using polyglossia }; @@ -81,6 +84,50 @@ OutputState * getOutputState() } +string const & openPolyglossiaLang(OutputState const * state) +{ + // Return a reference to the last active language opened with + // polyglossia. If none or when using babel, return a reference + // to an empty string. + + static string const empty; + + return state->open_polyglossia_lang_.empty() + ? empty + : state->open_polyglossia_lang_.top(); +} + + +bool atSameLastLangSwitchDepth(OutputState const * state) +{ + // Return true if the actual nest level is the same at which the + // language was switched when using polyglossia. Instead, return + // always true when using babel. + + return state->lang_switch_depth_.size() == 0 + ? true + : abs(state->lang_switch_depth_.top()) == state->nest_level_; +} + + +bool isLocalSwitch(OutputState const * state) +{ + // Return true if the language was opened by the \text command. + + return state->lang_switch_depth_.size() + && state->lang_switch_depth_.top() < 0; +} + + +bool langOpenedAtThisLevel(OutputState const * state) +{ + // Return true if the language was opened at the current nesting level. + + return state->lang_switch_depth_.size() + && abs(state->lang_switch_depth_.top()) == state->nest_level_; +} + + string const getPolyglossiaEnvName(Language const * lang) { string result = lang->polyglossia(); @@ -162,9 +209,11 @@ static TeXEnvironmentData prepareEnvironment(Buffer const & buf, string const lang_end_command = use_polyglossia ? "\\end{$$lang}" : lyxrc.language_command_end; + // For polyglossia, switch language outside of environment, if possible. if (par_lang != prev_par_lang) { if (!lang_end_command.empty() && prev_par_lang != doc_lang && + atSameLastLangSwitchDepth(state) && !prev_par_lang.empty()) { os << from_ascii(subst( lang_end_command, @@ -172,10 +221,21 @@ static TeXEnvironmentData prepareEnvironment(Buffer const & buf, prev_par_lang)) // the '%' is necessary to prevent unwanted whitespace << "%\n"; + if (use_polyglossia) + popPolyglossiaLang(); } + // If no language was explicitly opened and we are using + // polyglossia, then the current polyglossia language is + // the document language. + string const & pol_lang = use_polyglossia + && state->lang_switch_depth_.size() + ? openPolyglossiaLang(state) + : doc_lang; + if ((lang_end_command.empty() || - par_lang != doc_lang) && + par_lang != doc_lang || + par_lang != pol_lang) && !par_lang.empty()) { string bc = use_polyglossia ? getPolyglossiaBegin(lang_begin_command, par_lang, @@ -184,6 +244,8 @@ static TeXEnvironmentData prepareEnvironment(Buffer const & buf, os << bc; // the '%' is necessary to prevent unwanted whitespace os << "%\n"; + if (use_polyglossia) + pushPolyglossiaLang(par_lang); } } @@ -196,8 +258,7 @@ static TeXEnvironmentData prepareEnvironment(Buffer const & buf, } if (style.isEnvironment()) { - if (par_lang != doc_lang) - state->open_polyglossia_lang_ = par_lang; + state->nest_level_ += 1; os << "\\begin{" << from_ascii(style.latexname()) << '}'; if (!style.latexargs().empty()) { OutputParams rp = runparams; @@ -247,8 +308,22 @@ static void finishEnvironment(otexstream & os, OutputParams const & runparams, } if (data.style->isEnvironment()) { - os << breakln - << "\\end{" << from_ascii(data.style->latexname()) << "}\n"; + os << breakln; + // Close any polyglossia language opened at this nest level + if (runparams.use_polyglossia) { + while (langOpenedAtThisLevel(state)) { + if (isLocalSwitch(state)) { + os << "}"; + } else { + os << "\\end{" + << openPolyglossiaLang(state) + << "}%\n"; + } + popPolyglossiaLang(); + } + } + state->nest_level_ -= 1; + os << "\\end{" << from_ascii(data.style->latexname()) << "}\n"; state->prev_env_language_ = data.par_language; if (runparams.encoding != data.prev_encoding) { runparams.encoding = data.prev_encoding; @@ -426,6 +501,25 @@ void getArgInsets(otexstream & os, OutputParams const & runparams, Layout::LaTeX } // namespace anon +void pushPolyglossiaLang(string const & lang_name, bool localswitch) +{ + OutputState * state = getOutputState(); + + int nest_level = localswitch ? -state->nest_level_ : state->nest_level_; + state->lang_switch_depth_.push(nest_level); + state->open_polyglossia_lang_.push(lang_name); +} + + +void popPolyglossiaLang() +{ + OutputState * state = getOutputState(); + + state->lang_switch_depth_.pop(); + state->open_polyglossia_lang_.pop(); +} + + void latexArgInsets(Paragraph const & par, otexstream & os, OutputParams const & runparams, Layout::LaTeXArgMap const & latexargs, string const & prefix) { @@ -687,7 +781,8 @@ void TeXOnePar(Buffer const & buf, string lang_command_termination = "%\n"; // In some insets (such as Arguments), we cannot use \selectlanguage - bool const localswitch = text.inset().forceLocalFontSwitch(); + bool const localswitch = text.inset().forceLocalFontSwitch() + || (use_polyglossia && text.inset().forcePlainLayout()); if (localswitch) { lang_begin_command = use_polyglossia ? "\\text$$lang$$opts{" : lyxrc.language_command_local; @@ -704,14 +799,15 @@ void TeXOnePar(Buffer const & buf, { if (!lang_end_command.empty() && prev_lang != outer_lang && - !prev_lang.empty()) + !prev_lang.empty() && + (!use_polyglossia || !style.isEnvironment())) { os << from_ascii(subst(lang_end_command, "$$lang", prev_lang)) << lang_command_termination; - if (prev_lang == state->open_polyglossia_lang_) - state->open_polyglossia_lang_ = ""; + if (use_polyglossia) + popPolyglossiaLang(); } // We need to open a new language if we couldn't close the previous @@ -719,7 +815,9 @@ void TeXOnePar(Buffer const & buf, // the previous one, if the current language is different than the // outer_language (which is currently in effect once the previous one // is closed). - if ((lang_end_command.empty() || par_lang != outer_lang) + if ((lang_end_command.empty() || par_lang != outer_lang + || (!use_polyglossia + || (style.isEnvironment() && par_lang != prev_lang))) && !par_lang.empty()) { // If we're inside an inset, and that inset is within an \L or \R // (or equivalents), then within the inset, too, any opposite @@ -759,12 +857,15 @@ void TeXOnePar(Buffer const & buf, } // With CJK, the CJK tag has to be closed first (see below) if (runparams.encoding->package() != Encoding::CJK + && par_lang != openPolyglossiaLang(state) && !par_lang.empty()) { string bc = use_polyglossia ? getPolyglossiaBegin(lang_begin_command, par_lang, par_language->polyglossiaOpts()) : subst(lang_begin_command, "$$lang", par_lang); os << bc; os << lang_command_termination; + if (use_polyglossia) + pushPolyglossiaLang(par_lang, localswitch); } } } @@ -816,12 +917,15 @@ void TeXOnePar(Buffer const & buf, } // With CJK, the CJK tag had to be closed first (see above) if (runparams.encoding->package() == Encoding::CJK + && par_lang != openPolyglossiaLang(state) && !par_lang.empty()) { os << from_ascii(subst( lang_begin_command, "$$lang", par_lang)) << lang_command_termination; + if (use_polyglossia) + pushPolyglossiaLang(par_lang, localswitch); } runparams.encoding = encoding; } @@ -892,9 +996,15 @@ void TeXOnePar(Buffer const & buf, bool pending_newline = false; bool unskip_newline = false; + bool close_lang_switch = false; switch (style.latextype) { case LATEX_ITEM_ENVIRONMENT: case LATEX_LIST_ENVIRONMENT: + if ((nextpar && par_lang != nextpar_lang + && nextpar->getDepth() == par.getDepth()) + || (atSameLastLangSwitchDepth(state) && nextpar + && nextpar->getDepth() < par.getDepth())) + close_lang_switch = use_polyglossia; if (nextpar && par.params().depth() < nextpar->params().depth()) pending_newline = true; break; @@ -902,9 +1012,13 @@ void TeXOnePar(Buffer const & buf, // if its the last paragraph of the current environment // skip it otherwise fall through if (nextpar - && (nextpar->layout() != par.layout() - || nextpar->params().depth() != par.params().depth())) + && ((nextpar->layout() != par.layout() + || nextpar->params().depth() != par.params().depth()) + || (!use_polyglossia || par_lang != nextpar_lang))) + { + close_lang_switch = use_polyglossia; break; + } } // fall through possible @@ -945,13 +1059,15 @@ void TeXOnePar(Buffer const & buf, || (runparams.isLastPar && par_lang != outer_lang)); if (closing_rtl_ltr_environment - || (runparams.isLastPar - && par_lang != outer_lang)) { + || ((runparams.isLastPar || close_lang_switch) + && (par_lang != outer_lang || (use_polyglossia + && style.isEnvironment() + && par_lang != nextpar_lang)))) { // Since \selectlanguage write the language to the aux file, // we need to reset the language at the end of footnote or // float. - if (pending_newline) + if (pending_newline || close_lang_switch) os << '\n'; // when the paragraph uses CJK, the language has to be closed earlier @@ -966,7 +1082,8 @@ void TeXOnePar(Buffer const & buf, string const current_lang = use_polyglossia ? getPolyglossiaEnvName(current_language) : current_language->babel(); - if (!current_lang.empty()) { + if (!current_lang.empty() + && current_lang != openPolyglossiaLang(state)) { string bc = use_polyglossia ? getPolyglossiaBegin(lang_begin_command, current_lang, current_language->polyglossiaOpts()) @@ -974,16 +1091,34 @@ void TeXOnePar(Buffer const & buf, os << bc; pending_newline = !localswitch; unskip_newline = !localswitch; + if (use_polyglossia) + pushPolyglossiaLang(current_lang, localswitch); } } else if (!par_lang.empty()) { // If we are in an environment, we have to close the "outer" language afterwards - if (!style.isEnvironment() || state->open_polyglossia_lang_ != par_lang) { + string const & pol_lang = openPolyglossiaLang(state); + if (!style.isEnvironment() + || (close_lang_switch + && atSameLastLangSwitchDepth(state) + && par_lang != outer_lang + && (par_lang != pol_lang + || (pol_lang != outer_lang + && nextpar + && style != nextpar->layout()))) + || (atSameLastLangSwitchDepth(state) + && state->lang_switch_depth_.size() + && pol_lang != par_lang)) + { + if (use_polyglossia && !localswitch) + os << breakln; os << from_ascii(subst( lang_end_command, "$$lang", par_lang)); pending_newline = !localswitch; unskip_newline = !localswitch; + if (use_polyglossia) + popPolyglossiaLang(); } } } @@ -999,7 +1134,7 @@ void TeXOnePar(Buffer const & buf, // prevent unwanted whitespace os << '%'; if (!os.afterParbreak() && !last_was_separator) - os << '\n'; + os << breakln; } // if this is a CJK-paragraph and the next isn't, close CJK @@ -1161,6 +1296,8 @@ void latexParagraphs(Buffer const & buf, : subst(lang_begin_command, "$$lang", mainlang); os << bc; os << '\n'; + if (runparams.use_polyglossia) + pushPolyglossiaLang(mainlang); } ParagraphList const & paragraphs = text.paragraphs(); @@ -1265,8 +1402,8 @@ void latexParagraphs(Buffer const & buf, "$$lang", mainlang)) << '\n'; - if (state->open_polyglossia_lang_ == mainlang) - state->open_polyglossia_lang_ = ""; + if (runparams.use_polyglossia) + popPolyglossiaLang(); } // If the last paragraph is an environment, we'll have to close @@ -1276,12 +1413,14 @@ void latexParagraphs(Buffer const & buf, state->open_encoding_ = none; } // Likewise for polyglossia - if (maintext && !is_child && state->open_polyglossia_lang_ != "") { + string const & pol_lang = openPolyglossiaLang(state); + if (maintext && !is_child && !pol_lang.empty()) { os << from_utf8(subst(lang_end_command, "$$lang", - state->open_polyglossia_lang_)) + pol_lang)) << '\n'; - state->open_polyglossia_lang_ = ""; + if (runparams.use_polyglossia) + popPolyglossiaLang(); } // reset inherited encoding diff --git a/src/output_latex.h b/src/output_latex.h index 8bfeb6aafc..fdbc38febb 100644 --- a/src/output_latex.h +++ b/src/output_latex.h @@ -29,6 +29,15 @@ class Paragraph; class OutputParams; class Text; +/** Register a language switch when using polyglossia. + Set \p localswitch to true if the \text command is used. + */ +void pushPolyglossiaLang(std::string const & lang, bool localswitch = false); + +/** Unregister the last language switch when using polyglossia. + */ +void popPolyglossiaLang(); + /** Export optional and required arguments of the paragraph \p par. Non-existing required arguments are output empty: {}. */ diff --git a/status.22x b/status.22x index 7ec6a4e0de..90983dfa3c 100644 --- a/status.22x +++ b/status.22x @@ -63,6 +63,8 @@ What's new * DOCUMENT INPUT/OUTPUT +- Fix nested language handling with polyglossia (bug 9633). + - Fix LaTeX output of fixed-width cells with decimal separator (bug 9568). - Do not hardcode required packages for Note inset. -- 2.39.5