X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=4282defa4a8fc5fed58bd71507138b012a8a6537;hb=26ba2a65838731ce639a09539f617cb0f0be3b22;hp=86e5b0ec854ef32a61152010db0939c51b2f6da9;hpb=fee10d2d0dda5269084cbb1484d6ab9aeaaed96c;p=lyx.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 86e5b0ec85..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" @@ -759,7 +760,6 @@ void Paragraph::acceptChanges(pos_type start, pos_type end) } break; } - } } @@ -1090,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) { @@ -1182,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); @@ -1746,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; @@ -1927,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); } @@ -1951,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) @@ -2002,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 @@ -2019,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) { @@ -2072,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); } @@ -2290,6 +2349,84 @@ bool Paragraph::parbreakIsNewline() const } +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) { @@ -2421,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: @@ -2485,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: @@ -3013,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 << "}"; @@ -3498,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. @@ -3529,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; @@ -3556,8 +3699,8 @@ std::tuple, std::vector, std::vectorlyxCode() == NEWLINE_CODE) { if (!ignore_fonts_i) xs->closeFontTags(); @@ -3565,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(); @@ -3591,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() && @@ -3623,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. @@ -3990,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(); @@ -4018,7 +4186,7 @@ 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 @@ -4116,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) { @@ -4242,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() << ' '; @@ -4255,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(); @@ -4382,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 @@ -4389,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)); } @@ -4403,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) { @@ -4418,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) @@ -4439,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)); @@ -4608,7 +4810,7 @@ int Paragraph::find(docstring const & str, bool cs, bool mw, if (!inset->isLetter() && !inset->isChar()) break; odocstringstream os; - if (inset->lyxCode() == lyx::QUOTE_CODE) { + if (inset->lyxCode() == lyx::QUOTE_CODE || inset->lyxCode() == lyx::SPACE_CODE) { OutputParams op(0); op.find_set_feature(OutputParams::SearchQuick); inset->plaintext(os, op);