X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=4282defa4a8fc5fed58bd71507138b012a8a6537;hb=26ba2a65838731ce639a09539f617cb0f0be3b22;hp=8ac87d31c2d2060cfa08f56d30d417a110c02023;hpb=86bfa10abb57a0af7bbddc3af2c7cdb891c77203;p=lyx.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 8ac87d31c2..4282defa4a 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -25,6 +25,7 @@ #include "BufferEncodings.h" #include "Changes.h" #include "Counters.h" +#include "Cursor.h" #include "InsetList.h" #include "Language.h" #include "LaTeXFeatures.h" @@ -71,8 +72,8 @@ using namespace std; using namespace lyx::support; -// OSX clang, gcc < 4.8.0, and msvc < 2015 do not support C++11 thread_local -#if defined(__APPLE__) || (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ < 8) +// OSX clang and msvc < 2015 do not support C++11 thread_local +#if defined(__APPLE__) #define THREAD_LOCAL_STATIC static __thread #elif defined(_MSC_VER) && (_MSC_VER < 1900) #define THREAD_LOCAL_STATIC static __declspec(thread) @@ -759,7 +760,6 @@ void Paragraph::acceptChanges(pos_type start, pos_type end) } break; } - } } @@ -835,8 +835,9 @@ void Paragraph::Private::insertChar(pos_type pos, char_type c, speller_state_.increasePosAfterPos(pos); // Update bookmarks - theSession().bookmarks().adjustPosAfterPos(inset_owner_->buffer().fileName(), - id_, pos, 1); + if (inset_owner_ && inset_owner_->isBufferValid()) + theSession().bookmarks().adjustPosAfterPos(inset_owner_->buffer().fileName(), + id_, pos, 1); } @@ -922,8 +923,9 @@ bool Paragraph::eraseChar(pos_type pos, bool trackChanges) d->speller_state_.refreshLast(size()); // Update bookmarks - theSession().bookmarks().adjustPosAfterPos(d->inset_owner_->buffer().fileName(), - d->id_, pos, -1); + if (d->inset_owner_ && d->inset_owner_->isBufferValid()) + theSession().bookmarks().adjustPosAfterPos(d->inset_owner_->buffer().fileName(), + d->id_, pos, -1); return true; } @@ -1088,22 +1090,45 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, running_change = Change(Change::UNCHANGED); } - bool close = false; + unsigned int close_brace = 0; + bool const disp_env = (inset->isEnvironment() && inset->getLayout().isDisplay()) + || runparams.inDisplayMath; + string close_env; odocstream::pos_type const len = os.os().tellp(); 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 (runparams.use_polyglossia) { - os << "\\LRE{"; + // babel with LuaTeX does not need a switch + // babel with XeTeX needs a switch only if bidi is used + // and \L is not defined there. + && (!runparams.isFullUnicode() || bparams.useBidiPackage(runparams) || runparams.use_polyglossia) + && running_font.isRightToLeft()) { + if (bparams.useBidiPackage(runparams) || runparams.use_polyglossia) { + // (lua)bidi + // Displayed environments go in an LTR environment + if (disp_env) { + os << "\\begin{LTR}"; + close_env = "LTR"; + } else { + if (runparams.flavor == Flavor::LuaTeX) { + // luabidi's \LRE needs extra grouping + // (possibly a LuaTeX bug) + os << '{'; + close_brace = 1; + } + os << "\\LRE{"; + close_brace += 1; + } } else if (running_font.language()->lang() == "farsi" - || running_font.language()->lang() == "arabic_arabi") + || running_font.language()->lang() == "arabic_arabi") { os << "\\textLR{" << termcmd; - else + close_brace = 1; + } else { + // babel classic os << "\\L{"; - close = true; + if (disp_env) + os << safebreakln; + close_brace = 1; + } } if (open_font && fontswitch_inset) { @@ -1146,6 +1171,7 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, bool const cprotect = textinset ? textinset->hasCProtectContent(runparams.moving_arg) && !textinset->text().isMainText() + && inset->lyxCode() != BRANCH_CODE : false; unsigned int count2 = basefont.latexWriteStartChanges(os, bparams, rp, running_font, @@ -1179,8 +1205,15 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, throw; } - if (close) - os << '}'; + if (!close_env.empty()) + os << "\\end{" << close_env << "}"; + + if (close_brace > 0) { + for (unsigned i = 0; i < close_brace; ++i) + os << '}'; + if (disp_env) + os << safebreakln; + } if (os.texrow().rows() > previous_row_count) { os.texrow().start(owner_->id(), i + 1); @@ -1206,26 +1239,41 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, { char_type const c = owner_->getUChar(bparams, runparams, i); - if (style.pass_thru || runparams.pass_thru || (runparams.for_searchAdv != OutputParams::NoSearch) + if (style.pass_thru || runparams.pass_thru || runparams.find_effective() || contains(style.pass_thru_chars, c) || contains(runparams.pass_thru_chars, c)) { - if (runparams.for_searchAdv != OutputParams::NoSearch) { - if (c == '\\') + if (runparams.find_effective()) { + switch (c) { + case '\\': os << "\\\\"; - else if (c == '{') + return; + case '{': os << "\\braceleft "; - else if (c == '}') + return; + case '}': os << "\\braceright "; - else if (c != '\0') + return; + case '$': + os << "\\lyxdollar "; + return; + case '~': + os << "\\lyxtilde "; + return; + case ' ': + case '\0': + break; + default: os.put(c); + return; + } } else if (c != '\0') { Encoding const * const enc = runparams.encoding; if (enc && !enc->encodable(c)) throw EncodingException(c); os.put(c); + return; } - return; } // TIPA uses its own T3 encoding @@ -1237,7 +1285,7 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, // non-standard font encoding. If we are using such a language, // we do not output special T1 chars. if (!runparams.inIPA && !running_font.language()->internalFontEncoding() - && !runparams.isFullUnicode() && bparams.main_font_encoding() == "T1" + && !runparams.isFullUnicode() && runparams.main_fontenc == "T1" && latexSpecialT1(c, os, i, column)) return; // NOTE: "fontspec" (non-TeX fonts) sets the font encoding to "TU" (untill 2017 "EU1" or "EU2") @@ -1529,12 +1577,12 @@ void Paragraph::Private::validate(LaTeXFeatures & features) const if (c == 0x0022) { if (features.runparams().isFullUnicode() && bp.useNonTeXFonts) features.require("textquotedblp"); - else if (bp.main_font_encoding() != "T1" + else if (features.runparams().main_fontenc != "T1" || ((&owner_->getFontSettings(bp, i))->language()->internalFontEncoding())) features.require("textquotedbl"); - } else if (ci.textfeature() && contains(ci.textpreamble(), '=')) { + } else if (ci.textFeature() && contains(ci.textPreamble(), '=')) { // features that depend on the font or input encoding - string feats = ci.textpreamble(); + string feats = ci.textPreamble(); string fontenc = (&owner_->getFontSettings(bp, i))->language()->fontenc(bp); if (fontenc.empty()) fontenc = features.runparams().main_fontenc; @@ -1728,16 +1776,34 @@ void Paragraph::write(ostream & os, BufferParams const & bparams, column = 0; break; case '.': + case '!': + case '?': + case ':': + case ';': + case ',': + case 0x061F: // ؟ U+061F ARABIC QUESTION MARK + case 0x061B: // ؛ U+061B ARABIC SEMICOLON + case 0x060C: // ، U+060C ARABIC COMMA flushString(os, write_buffer); if (i + 1 < size() && d->text_[i + 1] == ' ') { - os << ".\n"; + os << to_utf8(docstring(1, c)) << '\n'; column = 0; } else - os << '.'; + os << to_utf8(docstring(1, c)); + break; + case 0x2014: // — U+2014 EM DASH + case 0x3002: // 。 U+3002 IDEOGRAPHIC FULL STOP + case 0xFF01: // ! U+FF01 FULLWIDTH EXCLAMATION MARK + case 0xFF1F: // ? U+FF1F FULLWIDTH QUESTION MARK + case 0xFF1A: // : U+FF1A FULLWIDTH COLON + case 0xFF1B: // ; U+FF1B FULLWIDTH SEMICOLON + case 0xFF0C: // , U+FF0C FULLWIDTH COMMA + flushString(os, write_buffer); + os << to_utf8(docstring(1, c)) << '\n'; + column = 0; break; default: - if ((column > 70 && c == ' ') - || column > 79) { + if (column > 500) { flushString(os, write_buffer); os << '\n'; column = 0; @@ -1909,7 +1975,8 @@ FontSpan Paragraph::fontSpan(pos_type pos) const // This should not happen, but if so, we take no chances. LYXERR0("Paragraph::fontSpan: position not found in fontinfo table!"); - LASSERT(false, return FontSpan(pos, pos)); + LASSERT(false, /**/); + return FontSpan(pos, pos); } @@ -1933,7 +2000,7 @@ Font const & Paragraph::getFirstFontSettings(BufferParams const & bparams) const // Gets the fully instantiated font at a given position in a paragraph -// This is basically the same function as Text::GetFont() in text2.cpp. +// This is basically the same function as TextMetrics::displayFont(). // The difference is that this one is used for generating the LaTeX file, // and thus cosmetic "improvements" are disallowed: This has to deliver // the true picture of the buffer. (Asger) @@ -1984,13 +2051,16 @@ char_type Paragraph::getUChar(BufferParams const & bparams, { char_type c = d->text_[pos]; - // Return unchanged character in LTR languages - // or if we use poylglossia/bidi (XeTeX). - if (rp.useBidiPackage() - || !getFontSettings(bparams, pos).isRightToLeft()) + // Return unchanged character + // 1. in all LTR languages + // 2. if we use XeTeX (both with babel and polyglossia) + // 3. if we use LuaTeX with babel + if (!getFontSettings(bparams, pos).isRightToLeft() + || rp.flavor == Flavor::XeTeX + || (rp.use_babel && rp.flavor == Flavor::LuaTeX)) return c; - // Without polyglossia/bidi, we need to account for some special cases. + // For the remaining cases, we need to account for some special cases. // FIXME This needs to be audited! // Check if: // * The input is as expected for all delimiters @@ -2001,18 +2071,27 @@ char_type Paragraph::getUChar(BufferParams const & bparams, // => checked for Hebrew! // * In arabic_arabi, brackets are transformed to Arabic // Ornate Parentheses. Is this is really wanted? + // => Yes, in file ararabeyes.enc from the arabi bundle + // the slot of the left bracket (slot 91) is encoded as + // "ornaterightparenthesis". This is also the reason + // brackets don't need to be mirrored with arabi string const & lang = getFontSettings(bparams, pos).language()->lang(); 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"; + // These are the cases where we need to mirror delimiters in RTL context + // in the remaining cases (polyglossia + LuaTeX or classic [pdf]latex): + // 1. With polyglossia and LuaTeX (luabidi) parentheses and brackets + // need to be mirrored in RTL, regardless of the language, or script. + // 2. In the languages that follow, parentheses need to be mirrored + // in classic (pdf)latex + bool const reverseparens = (rp.use_polyglossia || lang == "hebrew"); + // 3. In all RTL languages except for those that follow, brackets + // need to be mirrored in classic (pdf)latex + bool const reversebrackets = rp.use_polyglossia + || (lang != "arabic_arabtex" + && lang != "arabic_arabi" + && lang != "farsi"); // Now swap delimiters if needed. switch (c) { @@ -2054,10 +2133,8 @@ void Paragraph::setFont(pos_type pos, Font const & font) { LASSERT(pos <= size(), return); - // First, reduce font against layout/label font - // Update: The setCharFont() routine in text2.cpp already - // reduces font, so we don't need to do that here. (Asger) - + // Text::setCharFont() already reduces font against layout/label + // font, so we don't need to do that here. (Asger) d->fontlist_.set(pos, font); } @@ -2265,6 +2342,100 @@ bool Paragraph::isPassThru() const return inInset().isPassThru() || d->layout_->pass_thru; } + +bool Paragraph::parbreakIsNewline() const +{ + return inInset().getLayout().parbreakIsNewline() || d->layout_->parbreak_is_newline; +} + + +bool Paragraph::allowedInContext(Cursor const & cur, InsetLayout const & il) const +{ + set const & allowed_insets = il.allowedInInsets(); + set const & allowed_layouts = il.allowedInLayouts(); + + bool in_allowed_inset = + allowed_insets.find(inInset().getLayout().name()) != allowed_insets.end(); + + bool in_allowed_layout = + allowed_layouts.find(d->layout_->name()) != allowed_layouts.end(); + + if (!in_allowed_inset && inInset().asInsetArgument()) { + // check if the argument allows the inset in question + if (cur.depth() > 1) { + docstring parlayout = cur[cur.depth() - 2].inset().getLayout().name() + + from_ascii("@") + from_ascii(inInset().asInsetArgument()->name()); + if (allowed_insets.find(parlayout) != allowed_insets.end()) + in_allowed_inset = true; + } + } + + int have_ins = 0; + // check if we exceed the number of allowed insets in this inset + if (in_allowed_inset && inInset().asInsetText() && il.allowedOccurrences() != -1) { + ParagraphList & pars = cur.text()->paragraphs(); + for (Paragraph const & par : pars) { + for (auto const & elem : par.insetList()) + if (elem.inset->getLayout().name() == il.name()) + ++have_ins; + } + if (have_ins >= il.allowedOccurrences()) + return false; + } + + have_ins = 0; + // check if we exceed the number of allowed insets in the layout group + if (in_allowed_layout && il.allowedOccurrences() != -1) { + pit_type pit = cur.pit(); + pit_type lastpit = cur.pit(); + ParagraphList & pars = cur.text()->paragraphs(); + // If we are not on a list-type environment or AllowedOccurrencesPerItem + // is false, we check the whole paragraph group + if (d->layout_->isEnvironment() + && !(il.allowedOccurrencesPerItem() + && (d->layout_->latextype == LATEX_LIST_ENVIRONMENT + || d->layout_->latextype == LATEX_ITEM_ENVIRONMENT))) { + lastpit = cur.lastpit(); + // get the first paragraph in sequence with this layout + depth_type const current_depth = params().depth(); + while (true) { + if (pit == 0) + break; + Paragraph cpar = pars[pit - 1]; + if (&cpar.layout() == d->layout_ + && cpar.params().depth() == current_depth) + --pit; + else + break; + } + } + for (; pit <= lastpit; ++pit) { + if (&pars[pit].layout() != d->layout_) + break; + for (auto const & elem : pars[pit].insetList()) + if (elem.inset->getLayout().name() == il.name()) + ++have_ins; + } + if (have_ins >= il.allowedOccurrences()) + return false; + } + + if (in_allowed_layout || in_allowed_inset) + return true; + + return (allowed_insets.empty() && allowed_layouts.empty()); +} + + +bool Paragraph::isPartOfTextSequence() const +{ + for (pos_type i = 0; i < size(); ++i) { + if (!isInset(i) || getInset(i)->isPartOfTextSequence()) + return true; + } + return false; +} + namespace { // paragraphs inside floats need different alignment tags to avoid @@ -2336,13 +2507,31 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, (layout_->toggle_indent != ITOGGLE_NEVER) : (layout_->toggle_indent == ITOGGLE_ALWAYS); - if (canindent && params_.noindent() && !layout_->pass_thru) { - os << "\\noindent "; - column += 10; - } - LyXAlignment const curAlign = params_.align(); + // Do not output \\noindent for paragraphs + // 1. that cannot have indentation or are indented always, + // 2. that are not part of the immediate text sequence (e.g., contain only floats), + // 3. that are PassThru, + // 4. or that are centered. + if (canindent && params_.noindent() + && owner_->isPartOfTextSequence() + && !layout_->pass_thru + && curAlign != LYX_ALIGN_CENTER) { + if (!owner_->empty() + && owner_->getInset(0) + && owner_->getInset(0)->lyxCode() == VSPACE_CODE) + // If the paragraph starts with a vspace, the \\noindent + // needs to come after that (as it leaves vmode). + // If the paragraph consists only of the vspace, + // \\noindent is not needed at all. + runparams.need_noindent = owner_->size() > 1; + else { + os << "\\noindent" << termcmd; + column += 10; + } + } + if (curAlign == layout_->align) return column; @@ -2369,7 +2558,7 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, // 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(); + && !bparams.useBidiPackage(runparams); switch (curAlign) { case LYX_ALIGN_NONE: @@ -2433,7 +2622,7 @@ bool Paragraph::Private::endTeXParParams(BufferParams const & bparams, // 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(); + && !bparams.useBidiPackage(runparams); switch (curAlign) { case LYX_ALIGN_NONE: @@ -2473,7 +2662,7 @@ void Paragraph::latex(BufferParams const & bparams, OutputParams const & runparams, int start_pos, int end_pos, bool force) const { - LYXERR(Debug::LATEX, "Paragraph::latex... " << this); + LYXERR(Debug::OUTFILE, "Paragraph::latex... " << this); // FIXME This check should not be needed. Perhaps issue an // error if it triggers. @@ -2502,10 +2691,10 @@ void Paragraph::latex(BufferParams const & bparams, pos_type body_pos = beginOfBody(); unsigned int column = 0; - // If we are inside an non inheritFont() inset, the real outerfont is local_font - Font const real_outerfont = (!inInset().inheritFont() - && runparams.local_font != nullptr) - ? Font(runparams.local_font->fontInfo()) : outerfont; + // If we are inside an non inheritFont() inset, + // the outerfont is the buffer's main font + Font const real_outerfont = + inInset().inheritFont() ? outerfont : Font(bparams.getFont()); if (body_pos > 0) { // the optional argument is kept in curly brackets in @@ -2573,7 +2762,7 @@ void Paragraph::latex(BufferParams const & bparams, runparams); runningChange = Change(Change::UNCHANGED); - os << (isEnvSeparator(i) ? "}]~" : "}] "); + os << ((isEnvSeparator(i) && !runparams.find_effective()) ? "}]~" : "}] "); column +=3; } // For InTitle commands, we have already opened a group @@ -2603,10 +2792,10 @@ void Paragraph::latex(BufferParams const & bparams, // Check whether a display math inset follows bool output_changes; - if (runparams.for_searchAdv == OutputParams::NoSearch) + if (!runparams.find_effective()) output_changes = bparams.output_changes; else - output_changes = (runparams.for_searchAdv == OutputParams::SearchWithDeleted); + output_changes = runparams.find_with_deleted(); if (c == META_INSET && i >= start_pos && (end_pos == -1 || i < end_pos)) { if (isDeleted(i)) @@ -2692,7 +2881,12 @@ void Paragraph::latex(BufferParams const & bparams, && getInset(i) && getInset(i)->allowMultiPar() && getInset(i)->lyxCode() != ERT_CODE - && getInset(i)->producesOutput(); + && (getInset(i)->producesOutput() + // FIXME Something more general? + // Comments do not "produce output" but are still + // part of the TeX source and require font switches + // to be closed (otherwise LaTeX fails). + || getInset(i)->layoutName() == "Note:Comment"); bool closeLanguage = false; bool lang_switched_at_inset = false; @@ -2731,9 +2925,11 @@ void Paragraph::latex(BufferParams const & bparams, os << '}'; column += 1; } - if (closeLanguage) + if (closeLanguage) { // Force language closing current_font.setLanguage(basefont.language()); + langClosed = true; + } Font const nextfont = (i == body_pos-1) ? basefont : current_font; bool needPar = false; column += running_font.latexWriteEndChanges( @@ -2746,12 +2942,12 @@ void Paragraph::latex(BufferParams const & bparams, column += Changes::latexMarkChange(os, bparams, Change(Change::UNCHANGED), Change(Change::DELETED), rp); } - open_font = false; // Has the language been closed in the latexWriteEndChanges() call above? - langClosed = running_font.language() != basefont.language() + langClosed |= running_font.language() != basefont.language() && running_font.language() != nextfont.language() && (running_font.language()->encoding()->package() != Encoding::CJK); running_font = basefont; + open_font &= !langClosed; } // if necessary, close language environment before opening CJK @@ -2764,7 +2960,8 @@ void Paragraph::latex(BufferParams const & bparams, string end_tag = subst(lang_end_command, "$$lang", running_lang); os << from_ascii(end_tag); column += end_tag.length(); - popLanguageName(); + if (!languageStackEmpty()) + popLanguageName(); } // Switch file encoding if necessary (and allowed) @@ -2816,6 +3013,7 @@ void Paragraph::latex(BufferParams const & bparams, bool const cprotect = textinset ? textinset->hasCProtectContent(runparams.moving_arg) && !textinset->text().isMainText() + && inInset().lyxCode() != BRANCH_CODE : false; column += current_font.latexWriteStartChanges(ots, bparams, runparams, basefont, last_font, false, @@ -2849,9 +3047,14 @@ void Paragraph::latex(BufferParams const & bparams, column += Changes::latexMarkChange(os, bparams, Change(Change::UNCHANGED), change, rp); } - } else { + } else {// if fontswitch_inset + if (current_font != running_font || !langClosed) + // font is still open in fontswitch_insets if we have + // a non-lang font difference or if the language + // is the only difference but has not been forcedly + // closed meanwhile + open_font = true; running_font = current_font; - open_font = !langClosed; } } @@ -2920,7 +3123,7 @@ void Paragraph::latex(BufferParams const & bparams, d->latexInset(bparams, os, rp, running_font, basefont, real_outerfont, open_font, runningChange, style, i, column, fontswitch_inset, - closeLanguage, lang_switched_at_inset); + closeLanguage, (lang_switched_at_inset || langClosed)); if (fontswitch_inset) { if (open_font) { bool needPar = false; @@ -2947,8 +3150,9 @@ void Paragraph::latex(BufferParams const & bparams, } } } else if (i >= start_pos && (end_pos == -1 || i < end_pos)) { - if (!bparams.useNonTeXFonts) - script = Encodings::isKnownScriptChar(c); + if (!bparams.useNonTeXFonts && !runparams.pass_thru + && !contains(runparams.pass_thru_chars, c)) + script = Encodings::isKnownScriptChar(c); if (script != alien_script) { if (!alien_script.empty()) { os << "}"; @@ -3083,7 +3287,7 @@ void Paragraph::latex(BufferParams const & bparams, os << setEncoding(prev_encoding->iconvName()); } - LYXERR(Debug::LATEX, "Paragraph::latex... done " << this); + LYXERR(Debug::OUTFILE, "Paragraph::latex... done " << this); } @@ -3432,15 +3636,18 @@ std::tuple, vector> computeDocBookFontSwit std::tuple, std::vector, std::vector> Paragraph::simpleDocBookOnePar(Buffer const & buf, - OutputParams const & runparams, - Font const & outerfont, - pos_type initial, - bool is_last_par, - bool ignore_fonts) const -{ - std::vector prependedParagraphs; - std::vector generatedParagraphs; - std::vector appendedParagraphs; + OutputParams const & runparams, + Font const & outerfont, + pos_type initial, + bool is_last_par, + bool ignore_fonts) const +{ + // Return values: segregation of the content of this paragraph. + std::vector prependedParagraphs; // Anything that must be output before the main tag of this paragraph. + std::vector generatedParagraphs; // The main content of the paragraph. + std::vector appendedParagraphs; // Anything that must be output after the main tag of this paragraph. + + // Internal string stream to store the output before being added to one of the previous lists. odocstringstream os; // If there is an argument that must be output before the main tag, do it before handling the rest of the paragraph. @@ -3463,20 +3670,22 @@ std::tuple, std::vector, std::vector delayedChars; // When a font tag ends with a space, output it after the closing font tag. + xs->startDivision(false); + std::vector delayedChars; // When a font tag ends with a space, output it after the closing font tag. // This requires to store delayed characters at some point. - DocBookFontState fs; // Track whether we have opened font tags + // Track whether we have opened font tags + DocBookFontState fs; DocBookFontState old_fs = fs; Layout const & style = *d->layout_; - FontInfo font_old = style.labeltype == LABEL_MANUAL ? style.labelfont : style.font; - string const default_family = buf.masterBuffer()->params().fonts_default_family; + // Conversion of the font opening/closing into DocBook tags. vector tagsToOpen; vector tagsToClose; @@ -3490,8 +3699,8 @@ std::tuple, std::vector, std::vectorlyxCode() == NEWLINE_CODE) { if (!ignore_fonts_i) xs->closeFontTags(); @@ -3499,22 +3708,34 @@ std::tuple, std::vector, std::vectorendDivision(); + + // Create a new XMLStream for the new paragraph, completely independent of the previous one. This implies // that the string stream must be reset. os.str(from_ascii("")); delete xs; xs = new XMLStream(os); + xs->startDivision(false); // Restore the fonts for the new paragraph, so that the right tags are opened for the new entry. if (!ignore_fonts_i) { - font_old = outerfont.fontInfo(); fs = old_fs; } } - // Determine which tags should be opened or closed regarding fonts. + // Determine which tags should be opened or closed regarding fonts. Consider the last output character (i.e. not + // deleted). + int last_output_char = (i == 0) ? 0 : i - 1; + if (i > 0) { + while (last_output_char > 0 && isDeleted(last_output_char)) + --last_output_char; + } + FontInfo const font_old = (i == 0 ? + (style.labeltype == LABEL_MANUAL ? style.labelfont : style.font) : + getFont(buf.masterBuffer()->params(), last_output_char, outerfont).fontInfo()); Font const font = getFont(buf.masterBuffer()->params(), i, outerfont); - tie(tagsToOpen, tagsToClose) = computeDocBookFontSwitch(font_old, font, default_family, fs); + tie(tagsToOpen, tagsToClose) = computeDocBookFontSwitch( + font_old, font, buf.masterBuffer()->params().fonts_default_family, fs); if (!ignore_fonts_i) { vector::const_iterator cit = tagsToClose.begin(); @@ -3525,8 +3746,8 @@ std::tuple, std::vector, std::vector, std::vector, std::vector::const_iterator sen = tagsToOpen.end(); for (; sit != sen; ++sit) *xs << *sit; - - tagsToClose.clear(); - tagsToOpen.clear(); } + // The font tags are no longer useful; free their memory right now. + tagsToClose.clear(); + tagsToOpen.clear(); + // Finally, write the next character or inset. if (Inset const * inset = getInset(i)) { bool inset_is_argument_elsewhere = getInset(i)->asInsetArgument() && @@ -3557,24 +3779,42 @@ std::tuple, std::vector, std::vectorparams(), rp, i); - if (lyx::isSpace(c) && !ignore_fonts) - delayedChars.push_back(c); - else - *xs << c; + if (lyx::isSpace(c) && !ignore_fonts) { // Delay spaces *after* the font-tag closure for cleaner output. + if (c == ' ' && (style.free_spacing || rp.free_spacing)) { + delayedChars.push_back(from_ascii(" ")); + } else { + delayedChars.emplace_back(1, c); + } + } else { // No need to delay the character. + if (c == '\'' && !ignore_fonts) + *xs << XMLStream::ESCAPE_NONE << "’"; + else + *xs << c; + } } - font_old = font.fontInfo(); } + // Ensure that the tags are closed at the right place. Otherwise, there might be an open font tag with no content + // that no other code cares to close. + *xs << xml::NullTag(); + // FIXME, this code is just imported from XHTML // I'm worried about what happens if a branch, say, is itself // wrapped in some font stuff. I think that will not work. if (!ignore_fonts) xs->closeFontTags(); + // Close the potentially remaining tags, like pending font tags. + // There is no need to check for ignore_fonts, as these tags won't be + // inserted in the stack in the first place if ignore_fonts is false. + xs->endDivision(); + // Deal with the delayed characters *after* closing font tags. - if (!delayedChars.empty()) - for (char_type c: delayedChars) - *xs << c; + if (!delayedChars.empty()) { + for (const docstring &c: delayedChars) + *xs << XMLStream::ESCAPE_NONE << c; + delayedChars.clear(); + } // In listings, new lines (i.e. \n characters in the output) are very important. Avoid generating one for the // last line to get a clean output. @@ -3924,16 +4164,10 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, // FIXME XHTML // Other such tags? What about the other text ranges? - vector::const_iterator cit = tagsToClose.begin(); - vector::const_iterator cen = tagsToClose.end(); - for (; cit != cen; ++cit) - xs << *cit; - - vector::const_iterator sit = tagsToOpen.begin(); - vector::const_iterator sen = tagsToOpen.end(); - for (; sit != sen; ++sit) - xs << *sit; - + for (auto const & t : tagsToClose) + xs << t; + for (auto const & t : tagsToOpen) + xs << t; tagsToClose.clear(); tagsToOpen.clear(); @@ -3952,7 +4186,9 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, char_type c = getUChar(buf.masterBuffer()->params(), runparams, i); if (c == ' ' && (style.free_spacing || runparams.free_spacing)) - xs << XMLStream::ESCAPE_NONE << " "; + xs << XMLStream::ESCAPE_NONE << " "; + else if (c == '\'') + xs << XMLStream::ESCAPE_NONE << "’"; else xs << c; } @@ -4048,9 +4284,10 @@ 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 + // and not in tables InsetText const * textinset = inInset().asInsetText(); bool const maintext = textinset - ? textinset->text().isMainText() + ? textinset->text().isMainText() || inInset().lyxCode() == CELL_CODE : false; if (!maintext && layout().needcprotect) { @@ -4071,27 +4308,12 @@ bool Paragraph::needsCProtection(bool const fragile) const } // 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)) + for (auto const & icit : d->insetlist_) { + Inset const * ins = icit.inset; + if (!ins) continue; - Inset const * ins = getInset(i); if (ins->needsCProtection(maintext, fragile)) 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; @@ -4189,6 +4411,7 @@ docstring Paragraph::asString(pos_type beg, pos_type end, int options, const Out if (beg == 0 && options & AS_STR_LABEL + && d->layout_->labeltype != LABEL_MANUAL && !d->params_.labelString().empty()) os << d->params_.labelString() << ' '; @@ -4202,7 +4425,10 @@ docstring Paragraph::asString(pos_type beg, pos_type end, int options, const Out else if (c == META_INSET && (options & AS_STR_INSETS)) { if (c == META_INSET && (options & AS_STR_PLAINTEXT)) { LASSERT(runparams != nullptr, return docstring()); - getInset(i)->plaintext(os, *runparams); + if (runparams->find_effective() && getInset(i)->findUsesToString()) + getInset(i)->toString(os); + else + getInset(i)->plaintext(os, *runparams); } else if (c == META_INSET && (options & AS_STR_MATHED) && getInset(i)->lyxCode() == REF_CODE) { Buffer const & buf = getInset(i)->buffer(); @@ -4329,6 +4555,27 @@ bool Paragraph::allowEmpty() const } +int Paragraph::getInsetPos(InsetCode const code, int startpos, + bool ignore_deleted) const +{ + while (startpos != -1) { + int found_pos = d->insetlist_.find(code, startpos); + if (found_pos == -1) + // nothing found + return -1; + if (isDeleted(found_pos) && ignore_deleted) { + // we're not interested in deleted insets + if (found_pos + 1 == size()) + return -1; + startpos = found_pos + 1; + continue; + } else + return found_pos; + } + return -1; +} + + bool Paragraph::brokenBiblio() const { // There is a problem if there is no bibitem at position 0 in @@ -4336,10 +4583,10 @@ bool Paragraph::brokenBiblio() const // 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)) + && (getInsetPos(BIBITEM_CODE, 0, true) != 0 + || getInsetPos(BIBITEM_CODE, 1, true) > 0)) || (d->layout_->labeltype != LABEL_BIBLIO - && d->insetlist_.find(BIBITEM_CODE) != -1)); + && getInsetPos(BIBITEM_CODE, 0, true) != -1)); } @@ -4350,7 +4597,7 @@ int Paragraph::fixBiblio(Buffer const & buffer) // cursor cannot be correctly updated. bool const track_changes = buffer.params().track_changes; - int bibitem_pos = d->insetlist_.find(BIBITEM_CODE); + int bibitem_pos = getInsetPos(BIBITEM_CODE, 0, true); // The case where paragraph is not BIBLIO if (d->layout_->labeltype != LABEL_BIBLIO) { @@ -4365,7 +4612,7 @@ int Paragraph::fixBiblio(Buffer const & buffer) bool const hasbibitem0 = bibitem_pos == 0; if (hasbibitem0) { - bibitem_pos = d->insetlist_.find(BIBITEM_CODE, 1); + bibitem_pos = getInsetPos(BIBITEM_CODE, 0, true); // There was an InsetBibitem at pos 0, // and no other one => OK if (bibitem_pos == -1) @@ -4386,11 +4633,19 @@ int Paragraph::fixBiblio(Buffer const & buffer) // We need to create an inset at the beginning Inset * inset = nullptr; if (bibitem_pos > 0) { - // there was one somewhere in the paragraph, let's move it - inset = d->insetlist_.release(bibitem_pos); + // There was one somewhere in the paragraph, let's move it + // * With change tracking, we use a clone + // and leave the old inset at its position + // (marked deleted) + // * Without change tracking, we release the inset + // from its previous InsetList position + inset = track_changes + ? new InsetBibitem(const_cast(&buffer), + getInset(bibitem_pos)->asInsetCommand()->params()) + : d->insetlist_.release(bibitem_pos); eraseChar(bibitem_pos, track_changes); } else - // make a fresh one + // No inset found -- make a fresh one inset = new InsetBibitem(const_cast(&buffer), InsetCommandParams(BIBITEM_CODE)); @@ -4531,7 +4786,6 @@ void Paragraph::changeCase(BufferParams const & bparams, pos_type pos, } } - int Paragraph::find(docstring const & str, bool cs, bool mw, pos_type start_pos, bool del) const { @@ -4556,7 +4810,14 @@ int Paragraph::find(docstring const & str, bool cs, bool mw, if (!inset->isLetter() && !inset->isChar()) break; odocstringstream os; - inset->toString(os); + if (inset->lyxCode() == lyx::QUOTE_CODE || inset->lyxCode() == lyx::SPACE_CODE) { + OutputParams op(0); + op.find_set_feature(OutputParams::SearchQuick); + inset->plaintext(os, op); + } + else { + inset->toString(os); + } docstring const insetstring = os.str(); if (!insetstring.empty()) { int const insetstringsize = insetstring.length(); @@ -4572,9 +4833,10 @@ int Paragraph::find(docstring const & str, bool cs, bool mw, } if (nonmatch || i == strsize) break; - if (cs && str[i] != d->text_[pos]) + char_type dp = d->text_[pos]; + if (cs && str[i] != dp) break; - if (!cs && uppercase(str[i]) != uppercase(d->text_[pos])) + if (!cs && uppercase(str[i]) != uppercase(dp)) break; } @@ -4821,6 +5083,19 @@ Language * Paragraph::Private::getSpellLanguage(pos_type const from) const void Paragraph::requestSpellCheck(pos_type pos) { d->requestSpellCheck(pos); + if (pos == -1) { + // Also request spellcheck within (text) insets + for (auto const & insets : insetList()) { + if (!insets.inset->asInsetText()) + continue; + ParagraphList & inset_pars = + insets.inset->asInsetText()->paragraphs(); + ParagraphList::iterator pit = inset_pars.begin(); + ParagraphList::iterator pend = inset_pars.end(); + for (; pit != pend; ++pit) + pit->requestSpellCheck(); + } + } }