X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=eb2bb9548cc6f3fe1c152fb4cd0a6da86e75c923;hb=b5722962fb0393299e02df0b146522770e98aef6;hp=4a83d932678a1572c7a1aa0734d58aaf54acb1be;hpb=7e51b5f30196fa0679a7c3c32dc75bca91520f32;p=lyx.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 4a83d93267..eb2bb9548c 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -314,25 +314,21 @@ public: /// Output the surrogate pair formed by \p c and \p next to \p os. /// \return the number of characters written. - int latexSurrogatePair(otexstream & os, char_type c, char_type next, + int latexSurrogatePair(BufferParams const &, otexstream & os, + char_type c, char_type next, OutputParams const &); /// Output a space in appropriate formatting (or a surrogate pair /// if the next character is a combining character). /// \return whether a surrogate pair was output. - bool simpleTeXBlanks(OutputParams const &, + bool simpleTeXBlanks(BufferParams const &, + OutputParams const &, otexstream &, pos_type i, unsigned int & column, Font const & font, Layout const & style); - /// Output consecutive unicode chars, belonging to the same script as - /// specified by the latex macro \p ltx, to \p os starting from \p i. - /// \return the number of characters written. - int writeScriptChars(otexstream & os, docstring const & ltx, - Change const &, Encoding const &, pos_type & i); - /// This could go to ParagraphParameters if we want to. int startTeXParParams(BufferParams const &, otexstream &, OutputParams const &) const; @@ -360,7 +356,7 @@ public: BufferParams const & bparams, OutputParams const & runparams, Font const & running_font, - Change const & running_change, + string & alien_script, Layout const & style, pos_type & i, pos_type end_pos, @@ -864,9 +860,10 @@ int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges) return end - i; } - -int Paragraph::Private::latexSurrogatePair(otexstream & os, char_type c, - char_type next, OutputParams const & runparams) +// Handle combining characters +int Paragraph::Private::latexSurrogatePair(BufferParams const & bparams, + otexstream & os, char_type c, char_type next, + OutputParams const & runparams) { // Writing next here may circumvent a possible font change between // c and next. Since next is only output if it forms a surrogate pair @@ -883,23 +880,22 @@ int Paragraph::Private::latexSurrogatePair(otexstream & os, char_type c, latex1 = from_ascii(tipashortcut); } } - docstring const latex2 = encoding.latexChar(c).first; - if (docstring(1, next) == latex1) { - // the encoding supports the combination + docstring latex2 = encoding.latexChar(c).first; + + if (bparams.useNonTeXFonts || docstring(1, next) == latex1) { + // Encoding supports the combination: + // output as is (combining char after base char). os << latex2 << latex1; return latex1.length() + latex2.length(); - } else if (runparams.local_font && - runparams.local_font->language()->lang() == "polutonikogreek") { - // polutonikogreek only works without the brackets - os << latex1 << latex2; - return latex1.length() + latex2.length(); - } else - os << latex1 << '{' << latex2 << '}'; + } + + os << latex1 << "{" << latex2 << "}"; return latex1.length() + latex2.length() + 2; } -bool Paragraph::Private::simpleTeXBlanks(OutputParams const & runparams, +bool Paragraph::Private::simpleTeXBlanks(BufferParams const & bparams, + OutputParams const & runparams, otexstream & os, pos_type i, unsigned int & column, @@ -913,7 +909,7 @@ bool Paragraph::Private::simpleTeXBlanks(OutputParams const & runparams, char_type next = text_[i + 1]; if (Encodings::isCombiningChar(next)) { // This space has an accent, so we must always output it. - column += latexSurrogatePair(os, ' ', next, runparams) - 1; + column += latexSurrogatePair(bparams, os, ' ', next, runparams) - 1; return true; } } @@ -944,86 +940,6 @@ bool Paragraph::Private::simpleTeXBlanks(OutputParams const & runparams, } -int Paragraph::Private::writeScriptChars(otexstream & os, - docstring const & ltx, - Change const & runningChange, - Encoding const & encoding, - pos_type & i) -{ - // FIXME: modifying i here is not very nice... - - // We only arrive here when character text_[i] could not be translated - // into the current latex encoding (or its latex translation has been forced,) - // and it belongs to a known script. - // TODO: We need \textcyr and \textgreek wrappers also for characters - // that can be encoded in the "LaTeX encoding" but not in the - // current *font encoding*. - // (See #9681 for details and test) - // Parameter ltx contains the latex translation of text_[i] as specified - // in the unicodesymbols file and is something like "\textXXX{}". - // The latex macro name "textXXX" specifies the script to which text_[i] - // belongs and we use it in order to check whether characters from the - // same script immediately follow, such that we can collect them in a - // single "\textXXX" macro. So, we have to retain "\textXXX{" - // for the first char but only "" for all subsequent chars. - docstring::size_type const brace1 = ltx.find_first_of(from_ascii("{")); - docstring::size_type const brace2 = ltx.find_last_of(from_ascii("}")); - string script = to_ascii(ltx.substr(1, brace1 - 1)); - int pos = 0; - int length = brace2; - bool closing_brace = true; - if (script == "textgreek" && encoding.latexName() == "iso-8859-7") { - // Correct encoding is being used, so we can avoid \textgreek. - // TODO: wrong test: we need to check the *font encoding* - // (i.e. the active language and its FontEncoding tag) - // instead of the LaTeX *input encoding*! - // See #9637 for details and test-cases. - pos = brace1 + 1; - length -= pos; - closing_brace = false; - } - os << ltx.substr(pos, length); - int size = text_.size(); - while (i + 1 < size) { - char_type const next = text_[i + 1]; - // Stop here if next character belongs to another script - // or there is a change in change tracking status. - if (!Encodings::isKnownScriptChar(next, script) || - runningChange != owner_->lookupChange(i + 1)) - break; - Font prev_font; - bool found = false; - FontList::const_iterator cit = fontlist_.begin(); - FontList::const_iterator end = fontlist_.end(); - for (; cit != end; ++cit) { - if (cit->pos() >= i && !found) { - prev_font = cit->font(); - found = true; - } - if (cit->pos() >= i + 1) - break; - } - // Stop here if there is a font attribute or encoding change. - if (found && cit != end && prev_font != cit->font()) - break; - docstring const latex = encoding.latexChar(next).first; - docstring::size_type const b1 = - latex.find_first_of(from_ascii("{")); - docstring::size_type const b2 = - latex.find_last_of(from_ascii("}")); - int const len = b2 - b1 - 1; - os << latex.substr(b1 + 1, len); - length += len; - ++i; - } - if (closing_brace) { - os << '}'; - ++length; - } - return length; -} - - void Paragraph::Private::latexInset(BufferParams const & bparams, otexstream & os, OutputParams & runparams, @@ -1089,14 +1005,16 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, bool close = false; odocstream::pos_type const len = os.os().tellp(); - if (inset->forceLTR() - && !runparams.use_polyglossia + if (inset->forceLTR(runparams) && running_font.isRightToLeft() // ERT is an exception, it should be output with no // decorations at all && inset->lyxCode() != ERT_CODE) { - if (running_font.language()->lang() == "farsi") - os << "\\beginL" << termcmd; + if (runparams.use_polyglossia) { + os << "\\LRE{"; + } else if (running_font.language()->lang() == "farsi" + || running_font.language()->lang() == "arabic_arabi") + os << "\\textLR{" << termcmd; else os << "\\L{"; close = true; @@ -1152,12 +1070,8 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, throw(e); } - if (close) { - if (running_font.language()->lang() == "farsi") - os << "\\endL" << termcmd; - else - os << '}'; - } + if (close) + os << '}'; if (os.texrow().rows() > previous_row_count) { os.texrow().start(owner_->id(), i + 1); @@ -1175,16 +1089,13 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, BufferParams const & bparams, OutputParams const & runparams, Font const & running_font, - Change const & running_change, + string & alien_script, Layout const & style, pos_type & i, pos_type end_pos, unsigned int & column) { - // With polyglossia, brackets and stuff need not be reversed - // in RTL scripts (see bug #8251) - char_type const c = (runparams.use_polyglossia) ? - owner_->getUChar(bparams, i) : text_[i]; + char_type const c = owner_->getUChar(bparams, runparams, i); if (style.pass_thru || runparams.pass_thru || contains(style.pass_thru_chars, c) @@ -1210,7 +1121,7 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, && !runparams.isFullUnicode() && bparams.main_font_encoding() == "T1" && latexSpecialT1(c, os, i, column)) return; - // NOTE: XeTeX and LuaTeX use EU1/2 (pre 2017) or TU (as of 2017) encoding + // NOTE: "fontspec" (non-TeX fonts) sets the font encoding to "TU" (untill 2017 "EU1" or "EU2") else if (!runparams.inIPA && !running_font.language()->internalFontEncoding() && runparams.isFullUnicode() && latexSpecialTU(c, os, i, column)) return; @@ -1238,10 +1149,8 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, if (i + 1 < static_cast(text_.size()) && (end_pos == -1 || i + 1 < end_pos) && text_[i+1] == '-') { - // Prevent "--" becoming an endash and "---" becoming - // an emdash. - // Within \ttfamily, "--" is merged to "-" (no endash) - // so we avoid this rather irritating ligature as well + // Prevent "--" becoming an en dash and "---" an em dash. + // (Within \ttfamily, "---" is merged to en dash + hyphen.) os << "{}"; column += 2; } @@ -1289,7 +1198,13 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, case 0x2013: case 0x2014: - if (bparams.use_dash_ligatures && !bparams.useNonTeXFonts) { + // XeTeX's dash behaviour is determined via a global setting + if (bparams.use_dash_ligatures + && owner_->getFontSettings(bparams, i).fontInfo().family() != TYPEWRITER_FAMILY + && !runparams.inIPA + // TODO #10961: && not in inset Flex Code + // TODO #10961: && not in layout LyXCode + && (!bparams.useNonTeXFonts || runparams.flavor != OutputParams::XETEX)) { if (c == 0x2013) { // en-dash os << "--"; @@ -1311,17 +1226,16 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, if (i + 1 < int(text_.size())) { next = text_[i + 1]; if (Encodings::isCombiningChar(next)) { - column += latexSurrogatePair(os, c, next, runparams) - 1; + column += latexSurrogatePair(bparams, os, c, next, runparams) - 1; ++i; break; } } - string script; pair latex = encoding.latexChar(c); docstring nextlatex; bool nexttipas = false; string nexttipashortcut; - if (next != '\0' && next != META_INSET && encoding.encodable(next)) { + if (next != '\0' && next != META_INSET && !encoding.encodable(next)) { nextlatex = encoding.latexChar(next).first; if (runparams.inIPA) { nexttipashortcut = Encodings::TIPAShortcut(next); @@ -1337,19 +1251,22 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, tipas = true; } } - if (Encodings::isKnownScriptChar(c, script) - && prefixIs(latex.first, from_ascii("\\" + script))) - column += writeScriptChars(os, latex.first, - running_change, encoding, i) - 1; - else if (latex.second + // eventually close "script wrapper" command (see `Paragraph::latex`) + if (!alien_script.empty() + && alien_script != Encodings::isKnownScriptChar(next)) { + column += latex.first.length(); + alien_script.clear(); + os << latex.first << "}"; + break; + } + if (latex.second && ((!prefixIs(nextlatex, '\\') && !prefixIs(nextlatex, '{') && !prefixIs(nextlatex, '}')) || (nexttipas && !prefixIs(from_ascii(nexttipashortcut), '\\'))) && !tipas) { - // Prevent eating of a following - // space or command corruption by + // Prevent eating of a following space or command corruption by // following characters if (next == ' ' || next == '\0') { column += latex.first.length() + 1; @@ -1426,43 +1343,19 @@ bool Paragraph::Private::latexSpecialT3(char_type const c, otexstream & os, void Paragraph::Private::validate(LaTeXFeatures & features) const { if (layout_->inpreamble && inset_owner_) { - bool const is_command = layout_->latextype == LATEX_COMMAND; - Buffer const & buf = inset_owner_->buffer(); - BufferParams const & bp = features.runparams().is_child - ? buf.masterParams() : buf.params(); - Font f; - // Using a string stream here circumvents the encoding + // FIXME: Using a string stream here circumvents the encoding // switching machinery of odocstream. Therefore the // output is wrong if this paragraph contains content // that needs to switch encoding. + Buffer const & buf = inset_owner_->buffer(); otexstringstream os; os << layout_->preamble(); - if (is_command) { - os << '\\' << from_ascii(layout_->latexname()); - // we have to provide all the optional arguments here, even though - // the last one is the only one we care about. - // Separate handling of optional argument inset. - if (!layout_->latexargs().empty()) { - OutputParams rp = features.runparams(); - rp.local_font = &owner_->getFirstFontSettings(bp); - latexArgInsets(*owner_, os, rp, layout_->latexargs()); - } - os << from_ascii(layout_->latexparam()); - } size_t const length = os.length(); - // this will output "{" at the beginning, but not at the end - owner_->latex(bp, f, os, features.runparams(), 0, -1, true); - if (os.length() > length) { - if (is_command) { - os << '}'; - if (!layout_->postcommandargs().empty()) { - OutputParams rp = features.runparams(); - rp.local_font = &owner_->getFirstFontSettings(bp); - latexArgInsets(*owner_, os, rp, layout_->postcommandargs(), "post:"); - } - } + TeXOnePar(buf, *inset_owner_->getText(int(buf.getParFromID(owner_->id()).idx())), + buf.getParFromID(owner_->id()).pit(), os, + features.runparams(), string(), 0, -1, true); + if (os.length() > length) features.addPreambleSnippet(os.release(), true); - } } if (features.runparams().flavor == OutputParams::HTML @@ -1490,6 +1383,16 @@ void Paragraph::Private::validate(LaTeXFeatures & features) const for (; icit != iend; ++icit) { if (icit->inset) { features.inDeletedInset(owner_->isDeleted(icit->pos)); + if (icit->inset->lyxCode() == FOOT_CODE) { + // FIXME: an item inset would make things much easier. + if ((layout_->latextype == LATEX_LIST_ENVIRONMENT + || (layout_->latextype == LATEX_ITEM_ENVIRONMENT + && layout_->margintype == MARGIN_FIRST_DYNAMIC)) + && (icit->pos < begin_of_body_ + || (icit->pos == begin_of_body_ + && (icit->pos == 0 || text_[icit->pos - 1] != ' ')))) + features.saveNoteEnv("description"); + } icit->inset->validate(features); features.inDeletedInset(false); if (layout_->needprotect && @@ -1499,16 +1402,60 @@ void Paragraph::Private::validate(LaTeXFeatures & features) const } // then the contents - BufferParams const bp = features.buffer().masterParams(); + BufferParams const bp = features.runparams().is_child + ? features.buffer().masterParams() : features.buffer().params(); for (pos_type i = 0; i < int(text_.size()) ; ++i) { char_type c = text_[i]; + CharInfo const & ci = Encodings::unicodeCharInfo(c); if (c == 0x0022) { if (features.runparams().isFullUnicode() && bp.useNonTeXFonts) features.require("textquotedblp"); else if (bp.main_font_encoding() != "T1" || ((&owner_->getFontSettings(bp, i))->language()->internalFontEncoding())) features.require("textquotedbl"); - } + } else if (ci.textfeature() && contains(ci.textpreamble(), '=')) { + // features that depend on the font or input encoding + string feats = ci.textpreamble(); + string fontenc = (&owner_->getFontSettings(bp, i))->language()->fontenc(bp); + if (fontenc.empty()) + fontenc = features.runparams().main_fontenc; + while (!feats.empty()) { + string feat; + feats = split(feats, feat, ','); + if (contains(feat, "!=")) { + // a feature that is required except for the spcified + // font or input encodings + string realfeature; + string const contexts = ltrim(split(feat, realfeature, '!'), "="); + // multiple encodings are separated by semicolon + vector context = getVectorFromString(contexts, ";"); + // require feature if the context matches neither current font + // nor input encoding + if (std::find(context.begin(), context.end(), fontenc) == context.end() + && std::find(context.begin(), context.end(), + features.runparams().encoding->name()) == context.end()) + features.require(realfeature); + } else if (contains(feat, '=')) { + // a feature that is required only for the spcified + // font or input encodings + string realfeature; + string const contexts = split(feat, realfeature, '='); + // multiple encodings are separated by semicolon + vector context = getVectorFromString(contexts, ";"); + // require feature if the context matches either current font + // or input encoding + if (std::find(context.begin(), context.end(), fontenc) != context.end() + || std::find(context.begin(), context.end(), + features.runparams().encoding->name()) != context.end()) + features.require(realfeature); + } + } + } else if (!bp.use_dash_ligatures + && (c == 0x2013 || c == 0x2014) + && bp.useNonTeXFonts + && features.runparams().flavor == OutputParams::XETEX) + // XeTeX's dash behaviour is determined via a global setting + features.require("xetexdashbreakstate"); BufferEncodings::validate(c, features); } } @@ -1698,6 +1645,12 @@ void Paragraph::write(ostream & os, BufferParams const & bparams, void Paragraph::validate(LaTeXFeatures & features) const { d->validate(features); + bool fragile = features.runparams().moving_arg; + fragile |= layout().needprotect; + if (inInset().getLayout().isNeedProtect()) + fragile = true; + if (needsCProtection(fragile)) + features.require("cprotect"); } @@ -1900,32 +1853,59 @@ Font const Paragraph::getLayoutFont } -char_type Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const +char_type Paragraph::getUChar(BufferParams const & bparams, + OutputParams const & rp, + pos_type pos) const { char_type c = d->text_[pos]; - if (!getFontSettings(bparams, pos).isRightToLeft()) + + // Return unchanged character in LTR languages + // or if we use poylglossia/bidi (XeTeX). + if (rp.useBidiPackage() + || !getFontSettings(bparams, pos).isRightToLeft()) return c; - // FIXME: The arabic special casing is due to the difference of arabic - // round brackets input introduced in r18599. Check if this should be - // unified with Hebrew or at least if all bracket types should be - // handled the same (file format change in either case). + // Without polyglossia/bidi, we need to account for some special cases. + // FIXME This needs to be audited! + // Check if: + // * The input is as expected for all delimiters + // => checked for Hebrew! + // * The output matches the display in the LyX workarea + // => checked for Hebrew! + // * The special cases below are really necessary + // => checked for Hebrew! + // * In arabic_arabi, brackets are transformed to Arabic + // Ornate Parentheses. Is this is really wanted? + string const & lang = getFontSettings(bparams, pos).language()->lang(); - bool const arabic = lang == "arabic_arabtex" || lang == "arabic_arabi" - || lang == "farsi"; char_type uc = c; + + // 1. In the following languages, parentheses need to be reversed. + // Also with polyglodia/luabidi + bool const reverseparens = (lang == "hebrew" || rp.use_polyglossia); + + // 2. In the following languages, brackets don't need to be reversed. + bool const reversebrackets = lang != "arabic_arabtex" + && lang != "arabic_arabi" + && lang != "farsi"; + + // Now swap delimiters if needed. switch (c) { case '(': - uc = arabic ? c : ')'; + if (reverseparens) + uc = ')'; break; case ')': - uc = arabic ? c : '('; + if (reverseparens) + uc = '('; break; case '[': - uc = ']'; + if (reversebrackets) + uc = ']'; break; case ']': - uc = '['; + if (reversebrackets) + uc = '['; break; case '{': uc = '}'; @@ -2005,15 +1985,29 @@ depth_type Paragraph::getMaxDepthAfter() const } -LyXAlignment Paragraph::getAlign() const +LyXAlignment Paragraph::getAlign(BufferParams const & bparams) const { if (d->params_.align() == LYX_ALIGN_LAYOUT) - return d->layout_->align; + return getDefaultAlign(bparams); else return d->params_.align(); } +LyXAlignment Paragraph::getDefaultAlign(BufferParams const & bparams) const +{ + LyXAlignment res = layout().align; + if (isRTL(bparams)) { + // Swap sides + if (res == LYX_ALIGN_LEFT) + res = LYX_ALIGN_RIGHT; + else if (res == LYX_ALIGN_RIGHT) + res = LYX_ALIGN_LEFT; + } + return res; +} + + docstring const & Paragraph::labelString() const { return d->params_.labelString(); @@ -2252,6 +2246,10 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, string const begin_tag = "\\begin"; InsetCode code = ownerCode(); bool const lastpar = runparams.isLastPar; + // RTL in classic (PDF)LaTeX (without the Bidi package) + // Luabibdi (used by LuaTeX) behaves like classic + bool const rtl_classic = owner_->getParLanguage(bparams)->rightToLeft() + && !runparams.useBidiPackage(); switch (curAlign) { case LYX_ALIGN_NONE: @@ -2261,16 +2259,18 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, case LYX_ALIGN_DECIMAL: break; case LYX_ALIGN_LEFT: { - if (owner_->getParLanguage(bparams)->babel() != "hebrew") - corrected_env(os, begin_tag, "flushleft", code, lastpar, column); - else + if (rtl_classic) + // Classic (PDF)LaTeX switches the left/right logic in RTL mode corrected_env(os, begin_tag, "flushright", code, lastpar, column); + else + corrected_env(os, begin_tag, "flushleft", code, lastpar, column); break; } case LYX_ALIGN_RIGHT: { - if (owner_->getParLanguage(bparams)->babel() != "hebrew") - corrected_env(os, begin_tag, "flushright", code, lastpar, column); - else + if (rtl_classic) + // Classic (PDF)LaTeX switches the left/right logic in RTL mode corrected_env(os, begin_tag, "flushleft", code, lastpar, column); + else + corrected_env(os, begin_tag, "flushright", code, lastpar, column); break; } case LYX_ALIGN_CENTER: { corrected_env(os, begin_tag, "center", code, lastpar, column); @@ -2310,6 +2310,10 @@ bool Paragraph::Private::endTeXParParams(BufferParams const & bparams, string const end_tag = "\\par\\end"; InsetCode code = ownerCode(); bool const lastpar = runparams.isLastPar; + // RTL in classic (PDF)LaTeX (without the Bidi package) + // Luabibdi (used by LuaTeX) behaves like classic + bool const rtl_classic = owner_->getParLanguage(bparams)->rightToLeft() + && !runparams.useBidiPackage(); switch (curAlign) { case LYX_ALIGN_NONE: @@ -2319,16 +2323,18 @@ bool Paragraph::Private::endTeXParParams(BufferParams const & bparams, case LYX_ALIGN_DECIMAL: break; case LYX_ALIGN_LEFT: { - if (owner_->getParLanguage(bparams)->babel() != "hebrew") - output = corrected_env(os, end_tag, "flushleft", code, lastpar, col); - else + if (rtl_classic) + // Classic (PDF)LaTeX switches the left/right logic in RTL mode output = corrected_env(os, end_tag, "flushright", code, lastpar, col); + else + output = corrected_env(os, end_tag, "flushleft", code, lastpar, col); break; } case LYX_ALIGN_RIGHT: { - if (owner_->getParLanguage(bparams)->babel() != "hebrew") - output = corrected_env(os, end_tag, "flushright", code, lastpar, col); - else + if (rtl_classic) + // Classic (PDF)LaTeX switches the left/right logic in RTL mode output = corrected_env(os, end_tag, "flushleft", code, lastpar, col); + else + output = corrected_env(os, end_tag, "flushright", code, lastpar, col); break; } case LYX_ALIGN_CENTER: { corrected_env(os, end_tag, "center", code, lastpar, col); @@ -2367,6 +2373,11 @@ void Paragraph::latex(BufferParams const & bparams, // of the body. Font basefont; + // If there is an open font-encoding changing command (script wrapper), + // alien_script is set to its name + string alien_script; + string script; + // Maybe we have to create a optional argument. pos_type body_pos = beginOfBody(); unsigned int column = 0; @@ -2398,7 +2409,9 @@ void Paragraph::latex(BufferParams const & bparams, // if the paragraph is empty, the loop will not be entered at all if (empty()) { - if (style.isCommand()) { + // For InTitle commands, we have already opened a group + // in output_latex::TeXOnePar. + if (style.isCommand() && !style.intitle) { os << '{'; ++column; } @@ -2436,7 +2449,9 @@ void Paragraph::latex(BufferParams const & bparams, os << "}] "; column +=3; } - if (style.isCommand()) { + // For InTitle commands, we have already opened a group + // in output_latex::TeXOnePar. + if (style.isCommand() && !style.intitle) { os << '{'; ++column; } @@ -2493,6 +2508,11 @@ void Paragraph::latex(BufferParams const & bparams, } if (bparams.output_changes && runningChange != change) { + if (!alien_script.empty()) { + column += 1; + os << "}"; + alien_script.clear(); + } if (open_font) { bool needPar = false; column += running_font.latexWriteEndChanges( @@ -2525,6 +2545,12 @@ void Paragraph::latex(BufferParams const & bparams, (current_font != running_font || current_font.language() != running_font.language())) { + // ensure there is no open script-wrapper + if (!alien_script.empty()) { + column += 1; + os << "}"; + alien_script.clear(); + } bool needPar = false; column += running_font.latexWriteEndChanges( os, bparams, runparams, basefont, @@ -2534,23 +2560,17 @@ void Paragraph::latex(BufferParams const & bparams, open_font = false; } - string const running_lang = runparams.use_polyglossia ? - running_font.language()->polyglossia() : running_font.language()->babel(); - // close babel's font environment before opening CJK. - string const lang_end_command = runparams.use_polyglossia ? - "\\end{$$lang}" : lyxrc.language_command_end; - bool const using_begin_end = runparams.use_polyglossia || - !lang_end_command.empty(); - if (!running_lang.empty() && - (!using_begin_end || running_lang == openLanguageName()) && - current_font.language()->encoding()->package() == Encoding::CJK) { - string end_tag = subst(lang_end_command, - "$$lang", - running_lang); - os << from_ascii(end_tag); - column += end_tag.length(); - if (using_begin_end) - popLanguageName(); + // if necessary, close language environment before opening CJK + string const running_lang = running_font.language()->babel(); + string const lang_end_command = lyxrc.language_command_end; + if (!lang_end_command.empty() && !bparams.useNonTeXFonts + && !running_lang.empty() + && running_lang == openLanguageName() + && current_font.language()->encoding()->package() == Encoding::CJK) { + string end_tag = subst(lang_end_command, "$$lang", running_lang); + os << from_ascii(end_tag); + column += end_tag.length(); + popLanguageName(); } // Switch file encoding if necessary (and allowed) @@ -2624,7 +2644,7 @@ void Paragraph::latex(BufferParams const & bparams, // latexSpecialChar ignores spaces if // style.pass_thru is false. if (i != body_pos - 1) { - if (d->simpleTeXBlanks(runparams, os, + if (d->simpleTeXBlanks(bparams, runparams, os, i, column, current_font, style)) { // A surrogate pair was output. We // must not call latexSpecialChar @@ -2661,7 +2681,7 @@ void Paragraph::latex(BufferParams const & bparams, ParagraphList const & pars = textinset->text().paragraphs(); pit_type const pit = pars.size() - 1; - Font const last_font = + Font const lastfont = pit < 0 || pars[pit].empty() ? pars[pit].getLayoutFont( bparams, @@ -2669,7 +2689,7 @@ void Paragraph::latex(BufferParams const & bparams, : pars[pit].getFont(bparams, pars[pit].size() - 1, outerfont); - if (last_font.fontInfo().size() != + if (lastfont.fontInfo().size() != basefont.fontInfo().size()) { ++parInline; incremented = true; @@ -2682,10 +2702,24 @@ void Paragraph::latex(BufferParams const & bparams, --parInline; } } else if (i >= start_pos && (end_pos == -1 || i < end_pos)) { + if (!bparams.useNonTeXFonts) + script = Encodings::isKnownScriptChar(c); + if (script != alien_script) { + if (!alien_script.empty()) { + os << "}"; + alien_script.clear(); + } + string fontenc = running_font.language()->fontenc(bparams); + if (!script.empty() + && !Encodings::fontencSupportsScript(fontenc, script)) { + column += script.length() + 2; + os << "\\" << script << "{"; + alien_script = script; + } + } try { - d->latexSpecialChar(os, bparams, rp, - running_font, runningChange, - style, i, end_pos, column); + d->latexSpecialChar(os, bparams, rp, running_font, + alien_script, style, i, end_pos, column); } catch (EncodingException & e) { if (runparams.dryrun) { os << "<" << _("LyX Warning: ") @@ -2709,6 +2743,15 @@ void Paragraph::latex(BufferParams const & bparams, // such as Note that do not produce any output, so that no // command is ever executed but its opening was recorded. runparams.inulemcmd = rp.inulemcmd; + + // And finally, pass the post_macros upstream + runparams.post_macro = rp.post_macro; + } + + // Close wrapper for alien script + if (!alien_script.empty()) { + os << "}"; + alien_script.clear(); } // If we have an open font definition, we have to close it @@ -2995,7 +3038,7 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, FontShape curr_fs = INHERIT_SHAPE; FontFamily curr_fam = INHERIT_FAMILY; - FontSize curr_size = FONT_SIZE_INHERIT; + FontSize curr_size = INHERIT_SIZE; string const default_family = buf.masterBuffer()->params().fonts_default_family; @@ -3160,41 +3203,41 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, if (old_size != curr_size) { if (size_flag) { switch (old_size) { - case FONT_SIZE_TINY: + case TINY_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_TINY)); break; - case FONT_SIZE_SCRIPT: + case SCRIPT_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_SCRIPT)); break; - case FONT_SIZE_FOOTNOTE: + case FOOTNOTE_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_FOOTNOTE)); break; - case FONT_SIZE_SMALL: + case SMALL_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_SMALL)); break; - case FONT_SIZE_LARGE: + case LARGE_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_LARGE)); break; - case FONT_SIZE_LARGER: + case LARGER_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_LARGER)); break; - case FONT_SIZE_LARGEST: + case LARGEST_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_LARGEST)); break; - case FONT_SIZE_HUGE: + case HUGE_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_HUGE)); break; - case FONT_SIZE_HUGER: + case HUGER_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_HUGER)); break; - case FONT_SIZE_INCREASE: + case INCREASE_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_INCREASE)); break; - case FONT_SIZE_DECREASE: + case DECREASE_SIZE: tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_DECREASE)); break; - case FONT_SIZE_INHERIT: - case FONT_SIZE_NORMAL: + case INHERIT_SIZE: + case NORMAL_SIZE: break; default: // the other tags are for internal use @@ -3204,52 +3247,52 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, size_flag = false; } switch (curr_size) { - case FONT_SIZE_TINY: + case TINY_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_TINY)); size_flag = true; break; - case FONT_SIZE_SCRIPT: + case SCRIPT_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_SCRIPT)); size_flag = true; break; - case FONT_SIZE_FOOTNOTE: + case FOOTNOTE_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_FOOTNOTE)); size_flag = true; break; - case FONT_SIZE_SMALL: + case SMALL_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_SMALL)); size_flag = true; break; - case FONT_SIZE_LARGE: + case LARGE_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_LARGE)); size_flag = true; break; - case FONT_SIZE_LARGER: + case LARGER_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_LARGER)); size_flag = true; break; - case FONT_SIZE_LARGEST: + case LARGEST_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_LARGEST)); size_flag = true; break; - case FONT_SIZE_HUGE: + case HUGE_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_HUGE)); size_flag = true; break; - case FONT_SIZE_HUGER: + case HUGER_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_HUGER)); size_flag = true; break; - case FONT_SIZE_INCREASE: + case INCREASE_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_INCREASE)); size_flag = true; break; - case FONT_SIZE_DECREASE: + case DECREASE_SIZE: tagsToOpen.push_back(html::FontTag(html::FT_SIZE_DECREASE)); size_flag = true; break; - case FONT_SIZE_NORMAL: - case FONT_SIZE_INHERIT: + case NORMAL_SIZE: + case INHERIT_SIZE: break; default: // the other tags are for internal use @@ -3286,8 +3329,12 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, retval += inset->xhtml(xs, np); } } else { - char_type c = getUChar(buf.masterBuffer()->params(), i); - xs << c; + char_type c = getUChar(buf.masterBuffer()->params(), + runparams, i); + if (c == ' ' && (style.free_spacing || runparams.free_spacing)) + xs << XHTMLStream::ESCAPE_NONE << " "; + else + xs << c; } font_old = font.fontInfo(); } @@ -3376,6 +3423,62 @@ bool Paragraph::isHardHyphenOrApostrophe(pos_type pos) const } +bool Paragraph::needsCProtection(bool const fragile) const +{ + // first check the layout of the paragraph, but only in insets + InsetText const * textinset = inInset().asInsetText(); + bool const maintext = textinset + ? textinset->text().isMainText() + : false; + + if (!maintext && layout().needcprotect) { + // Environments need cprotection regardless the content + if (layout().latextype == LATEX_ENVIRONMENT) + return true; + + // Commands need cprotection if they contain specific chars + int const nchars_escape = 9; + static char_type const chars_escape[nchars_escape] = { + '&', '_', '$', '%', '#', '^', '{', '}', '\\'}; + + docstring const pars = asString(); + for (int k = 0; k < nchars_escape; k++) { + if (contains(pars, chars_escape[k])) + return true; + } + } + + // now check whether we have insets that need cprotection + pos_type size = pos_type(d->text_.size()); + for (pos_type i = 0; i < size; ++i) { + if (!isInset(i)) + continue; + Inset const * ins = getInset(i); + if (ins->needsCProtection(maintext, fragile)) + return true; + if (ins->getLayout().latextype() == InsetLayout::ENVIRONMENT) + // Environments need cprotection regardless the content + return true; + // Now check math environments + InsetMath const * im = getInset(i)->asInsetMath(); + if (!im || im->cell(0).empty()) + continue; + switch(im->cell(0)[0]->lyxCode()) { + case MATH_AMSARRAY_CODE: + case MATH_SUBSTACK_CODE: + case MATH_ENV_CODE: + case MATH_XYMATRIX_CODE: + // these need cprotection + return true; + default: + break; + } + } + + return false; +} + + FontSpan const & Paragraph::getSpellRange(pos_type pos) const { return d->speller_state_.getRange(pos); @@ -3435,28 +3538,22 @@ void Paragraph::changeLanguage(BufferParams const & bparams, bool Paragraph::isMultiLingual(BufferParams const & bparams) const { Language const * doc_language = bparams.language; - FontList::const_iterator cit = d->fontlist_.begin(); - FontList::const_iterator end = d->fontlist_.end(); - - for (; cit != end; ++cit) - if (cit->font().language() != ignore_language && - cit->font().language() != latex_language && - cit->font().language() != doc_language) + for (auto const & f : d->fontlist_) + if (f.font().language() != ignore_language && + f.font().language() != latex_language && + f.font().language() != doc_language) return true; return false; } -void Paragraph::getLanguages(std::set & languages) const +void Paragraph::getLanguages(std::set & langs) const { - FontList::const_iterator cit = d->fontlist_.begin(); - FontList::const_iterator end = d->fontlist_.end(); - - for (; cit != end; ++cit) { - Language const * lang = cit->font().language(); + for (auto const & f : d->fontlist_) { + Language const * lang = f.font().language(); if (lang != ignore_language && lang != latex_language) - languages.insert(lang); + langs.insert(lang); } } @@ -3503,6 +3600,8 @@ void Paragraph::forOutliner(docstring & os, size_t const maxlen, size_t tmplen = shorten ? maxlen + 1 : maxlen; if (label && !labelString().empty()) os += labelString() + ' '; + if (!layout().isTocCaption()) + return; for (pos_type i = 0; i < size() && os.length() < tmplen; ++i) { if (isDeleted(i)) continue; @@ -3605,32 +3704,43 @@ bool Paragraph::allowEmpty() const bool Paragraph::brokenBiblio() const { - // there is a problem if there is no bibitem at position 0 or - // if there is another bibitem in the paragraph. - return d->layout_->labeltype == LABEL_BIBLIO + // There is a problem if there is no bibitem at position 0 in + // paragraphs that need one, if there is another bibitem in the + // paragraph or if this paragraph is not supposed to have + // a bibitem inset at all. + return ((d->layout_->labeltype == LABEL_BIBLIO && (d->insetlist_.find(BIBITEM_CODE) != 0 - || d->insetlist_.find(BIBITEM_CODE, 1) > 0); + || d->insetlist_.find(BIBITEM_CODE, 1) > 0)) + || (d->layout_->labeltype != LABEL_BIBLIO + && d->insetlist_.find(BIBITEM_CODE) != -1)); } int Paragraph::fixBiblio(Buffer const & buffer) { - // FIXME: What about the case where paragraph is not BIBLIO - // but there is an InsetBibitem? // FIXME: when there was already an inset at 0, the return value is 1, // which does not tell whether another inset has been remove; the // cursor cannot be correctly updated. - if (d->layout_->labeltype != LABEL_BIBLIO) - return 0; - bool const track_changes = buffer.params().track_changes; int bibitem_pos = d->insetlist_.find(BIBITEM_CODE); - bool const hasbibitem0 = bibitem_pos == 0; + // The case where paragraph is not BIBLIO + if (d->layout_->labeltype != LABEL_BIBLIO) { + if (bibitem_pos == -1) + // No InsetBibitem => OK + return 0; + // There is an InsetBibitem: remove it! + d->insetlist_.release(bibitem_pos); + eraseChar(bibitem_pos, track_changes); + return (bibitem_pos == 0) ? -1 : -bibitem_pos; + } + + bool const hasbibitem0 = bibitem_pos == 0; if (hasbibitem0) { bibitem_pos = d->insetlist_.find(BIBITEM_CODE, 1); - // There was an InsetBibitem at pos 0, and no other one => OK + // There was an InsetBibitem at pos 0, + // and no other one => OK if (bibitem_pos == -1) return 0; // there is a bibitem at the 0 position, but since @@ -3645,7 +3755,7 @@ int Paragraph::fixBiblio(Buffer const & buffer) } // We need to create an inset at the beginning - Inset * inset = 0; + Inset * inset = nullptr; if (bibitem_pos > 0) { // there was one somewhere in the paragraph, let's move it inset = d->insetlist_.release(bibitem_pos); @@ -3659,6 +3769,8 @@ int Paragraph::fixBiblio(Buffer const & buffer) insertInset(0, inset, font, Change(track_changes ? Change::INSERTED : Change::UNCHANGED)); + // This is needed to get the counters right + buffer.updateBuffer(); return 1; } @@ -4099,6 +4211,9 @@ SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, docstring word = asString(from, to, AS_STR_INSETS | AS_STR_SKIPDELETE); Language * lang = d->getSpellLanguage(from); + if (getFontSettings(d->inset_owner_->buffer().params(), from).fontInfo().nospellcheck() == FONT_ON) + return result; + wl = WordLangTuple(word, lang); if (word.empty()) @@ -4148,6 +4263,15 @@ SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, } +void Paragraph::anonymize() +{ + // This is a very crude anonymization for now + for (char_type & c : d->text_) + if (isLetterChar(c) || isNumber(c)) + c = 'a'; +} + + void Paragraph::Private::markMisspelledWords( pos_type const & first, pos_type const & last, SpellChecker::Result result,