X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=1b6a766fb54f6a7d289bc7eeb443260ff0f25c7f;hb=d9082639080b9de993742bd352f92e5183058cf5;hp=390de02b030b60c71375b8bac176dd87545fe53d;hpb=06675b853b4f2d896775f612115c7b9b79c2414c;p=lyx.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 390de02b03..1b6a766fb5 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -5,7 +5,7 @@ * * \author Asger Alstrup * \author Lars Gullik Bjønnes - * \author Richard Heck (XHTML output) + * \author Richard Kimberly Heck (XHTML output) * \author Jean-Marc Lasgouttes * \author Angus Leeming * \author John Levon @@ -20,17 +20,15 @@ #include "Paragraph.h" -#include "LayoutFile.h" #include "Buffer.h" #include "BufferParams.h" +#include "BufferEncodings.h" #include "Changes.h" #include "Counters.h" -#include "BufferEncodings.h" #include "InsetList.h" #include "Language.h" #include "LaTeXFeatures.h" #include "Layout.h" -#include "Length.h" #include "Font.h" #include "FontList.h" #include "LyXRC.h" @@ -40,11 +38,10 @@ #include "output_docbook.h" #include "ParagraphParameters.h" #include "SpellChecker.h" -#include "xml.h" #include "texstream.h" -#include "TextClass.h" #include "TexRow.h" #include "Text.h" +#include "TextClass.h" #include "WordLangTuple.h" #include "WordList.h" @@ -59,12 +56,10 @@ #include "support/debug.h" #include "support/docstring_list.h" -#include "support/ExceptionMessage.h" #include "support/gettext.h" #include "support/lassert.h" #include "support/lstrings.h" #include "support/textutils.h" -#include "output_docbook.h" #include #include @@ -84,14 +79,6 @@ using namespace lyx::support; namespace lyx { -namespace { - -/// Inset identifier (above 0x10ffff, for ucs-4) -char_type const META_INSET = 0x200001; - -} // namespace - - ///////////////////////////////////////////////////////////////////// // // SpellResultRange @@ -274,9 +261,9 @@ private: Ranges ranges_; /// the area of the paragraph with pending spell check FontSpan refresh_; - bool needs_refresh_; /// spell state cache version number SpellChecker::ChangeNumber current_change_number_; + bool needs_refresh_; void correctRangesAfterPos(pos_type pos, int offset) @@ -350,7 +337,10 @@ public: Change & running_change, Layout const & style, pos_type & i, - unsigned int & column); + unsigned int & column, + bool const fontswitch_inset, + bool const closeLanguage, + bool const lang_switched_at_inset); /// void latexSpecialChar( @@ -369,19 +359,19 @@ public: char_type const c, otexstream & os, pos_type i, - unsigned int & column); + unsigned int & column) const; /// bool latexSpecialTU( char_type const c, otexstream & os, pos_type i, - unsigned int & column); + unsigned int & column) const; /// bool latexSpecialT3( char_type const c, otexstream & os, pos_type i, - unsigned int & column); + unsigned int & column) const; /// void validate(LaTeXFeatures & features) const; @@ -460,7 +450,7 @@ public: { int numskips = 0; while (it != et && it->first < start) { - int skip = it->last - it->first + 1; + long skip = it->last - it->first + 1; start += skip; numskips += skip; ++it; @@ -487,9 +477,6 @@ public: /// FontList fontlist_; - /// - int id_; - /// ParagraphParameters params_; @@ -514,11 +501,13 @@ public: Layout const * layout_; /// SpellCheckerState speller_state_; + /// + int id_; }; Paragraph::Private::Private(Paragraph * owner, Layout const & layout) - : owner_(owner), inset_owner_(nullptr), id_(-1), begin_of_body_(0), layout_(&layout) + : owner_(owner), inset_owner_(nullptr), begin_of_body_(0), layout_(&layout), id_(-1) { text_.reserve(100); } @@ -531,17 +520,16 @@ int Paragraph::Private::make_id() // LFUN_PARAGRAPH_GOTO to switch to a different buffer, for instance in the // outliner. // (thread-safe) - static atomic_uint next_id(0); + static int next_id(0); return next_id++; } Paragraph::Private::Private(Private const & p, Paragraph * owner) : owner_(owner), inset_owner_(p.inset_owner_), fontlist_(p.fontlist_), - id_(make_id()), params_(p.params_), changes_(p.changes_), insetlist_(p.insetlist_), begin_of_body_(p.begin_of_body_), text_(p.text_), words_(p.words_), - layout_(p.layout_) + layout_(p.layout_), id_(make_id()) { requestSpellCheck(p.text_.size()); } @@ -549,11 +537,11 @@ Paragraph::Private::Private(Private const & p, Paragraph * owner) Paragraph::Private::Private(Private const & p, Paragraph * owner, pos_type beg, pos_type end) - : owner_(owner), inset_owner_(p.inset_owner_), id_(make_id()), + : owner_(owner), inset_owner_(p.inset_owner_), params_(p.params_), changes_(p.changes_), insetlist_(p.insetlist_, beg, end), begin_of_body_(p.begin_of_body_), words_(p.words_), - layout_(p.layout_) + layout_(p.layout_), id_(make_id()) { if (beg >= pos_type(p.text_.size())) return; @@ -877,6 +865,7 @@ int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges) return end - i; } + // Handle combining characters int Paragraph::Private::latexSurrogatePair(BufferParams const & bparams, otexstream & os, char_type c, char_type next, @@ -967,7 +956,10 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, Change & running_change, Layout const & style, pos_type & i, - unsigned int & column) + unsigned int & column, + bool const fontswitch_inset, + bool const closeLanguage, + bool const lang_switched_at_inset) { Inset * inset = owner_->getInset(i); LBUFERR(inset); @@ -1037,50 +1029,64 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, close = true; } - // FIXME: Bug: we can have an empty font change here! - // if there has just been a font change, we are going to close it - // right now, which means stupid latex code like \textsf{}. AFAIK, - // this does not harm dvi output. A minor bug, thus (JMarc) - - // Some insets cannot be inside a font change command. - // However, even such insets *can* be placed in \L or \R - // or their equivalents (for RTL language switches), so we don't - // close the language in those cases. - // ArabTeX, though, cannot handle this special behavior, it seems. - bool arabtex = basefont.language()->lang() == "arabic_arabtex" - || running_font.language()->lang() == "arabic_arabtex"; - if (open_font && !inset->inheritFont()) { - bool needPar = false; - bool closeLanguage = arabtex - || basefont.isRightToLeft() == running_font.isRightToLeft(); - // We pass non_inherit_inset = true here since size switches - // ought not to be terminated here (#8384). - unsigned int count = running_font.latexWriteEndChanges(os, - bparams, runparams, basefont, basefont, - needPar, closeLanguage, true); - column += count; - // if any font properties were closed, update the running_font, - // making sure, however, to leave the language as it was - if (count > 0) { - // FIXME: probably a better way to keep track of the old - // language, than copying the entire font? - Font const copy_font(running_font); - basefont = owner_->getLayoutFont(bparams, outerfont); - running_font = basefont; - if (!closeLanguage) - running_font.setLanguage(copy_font.language()); - // For these, we use switches, so no need to close - basefont.fontInfo().setSize(copy_font.fontInfo().size()); - basefont.fontInfo().setFamily(copy_font.fontInfo().family()); - basefont.fontInfo().setSeries(copy_font.fontInfo().series()); - // leave font open if language or any of the switches is still open - open_font = (running_font.language() == basefont.language() - || running_font.fontInfo().size() == basefont.fontInfo().size() - || running_font.fontInfo().family() == basefont.fontInfo().family() - || running_font.fontInfo().series() == basefont.fontInfo().series()); - if (closeLanguage) - runparams.local_font = &basefont; + if (open_font && fontswitch_inset) { + bool lang_closed = false; + // Close language if needed + if (closeLanguage && !lang_switched_at_inset) { + // We need prev_font here as language changes directly at inset + // will only be started inside the inset. + Font const prev_font = (i > 0) ? + owner_->getFont(bparams, i - 1, outerfont) + : running_font; + Font tmpfont(basefont); + tmpfont.setLanguage(prev_font.language()); + bool needPar = false; + unsigned int count = tmpfont.latexWriteEndChanges(os, bparams, runparams, + basefont, basefont, + needPar, closeLanguage); + column += count; + lang_closed = count > 0; } + // Update the running_font, making sure, however, + // to leave the language as it was. + // FIXME: probably a better way to keep track of the old + // language, than copying the entire font? + Font const copy_font(running_font); + basefont = owner_->getLayoutFont(bparams, outerfont); + running_font = basefont; + if (!closeLanguage) + running_font.setLanguage(copy_font.language()); + OutputParams rp = runparams; + rp.encoding = basefont.language()->encoding(); + // For these, we use switches, so they should be taken as + // base inside the inset. + basefont.fontInfo().setSize(copy_font.fontInfo().size()); + basefont.fontInfo().setFamily(copy_font.fontInfo().family()); + basefont.fontInfo().setSeries(copy_font.fontInfo().series()); + // Now re-do font changes in a way needed here + // (using switches with multi-par insets) + InsetText const * textinset = inset->asInsetText(); + bool const cprotect = textinset + ? textinset->hasCProtectContent(runparams.moving_arg) + && !textinset->text().isMainText() + : false; + unsigned int count2 = basefont.latexWriteStartChanges(os, bparams, + rp, running_font, + basefont, true, + cprotect); + open_font = true; + column += count2; + if (count2 == 0 && (lang_closed || lang_switched_at_inset)) + // All fonts closed + open_font = false; + if (closeLanguage) + runparams.local_font = &basefont; + } + + if (fontswitch_inset && !closeLanguage) { + // The directionality has been switched at inset. + // Force markup inside. + runparams.local_font = &basefont; } size_t const previous_row_count = os.texrow().rows(); @@ -1093,7 +1099,7 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, // add location information and throw again. e.par_id = id_; e.pos = i; - throw(e); + throw; } if (close) @@ -1123,10 +1129,12 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, { char_type const c = owner_->getUChar(bparams, runparams, i); - if (style.pass_thru || runparams.pass_thru + if (style.pass_thru || runparams.pass_thru || runparams.for_search || contains(style.pass_thru_chars, c) || contains(runparams.pass_thru_chars, c)) { - if (c != '\0') { + if ((c == '\\') && runparams.for_search) + os << "\\\\"; + else if (c != '\0') { Encoding const * const enc = runparams.encoding; if (enc && !enc->encodable(c)) throw EncodingException(c); @@ -1230,7 +1238,7 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, && !runparams.inIPA // TODO #10961: && not in inset Flex Code // TODO #10961: && not in layout LyXCode - && (!bparams.useNonTeXFonts || runparams.flavor != OutputParams::XETEX)) { + && (!bparams.useNonTeXFonts || runparams.flavor != Flavor::XeTeX)) { if (c == 0x2013) { // en-dash os << "--"; @@ -1311,7 +1319,7 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, bool Paragraph::Private::latexSpecialT1(char_type const c, otexstream & os, - pos_type i, unsigned int & column) + pos_type i, unsigned int & column) const { switch (c) { case '>': @@ -1339,7 +1347,7 @@ bool Paragraph::Private::latexSpecialT1(char_type const c, otexstream & os, bool Paragraph::Private::latexSpecialTU(char_type const c, otexstream & os, - pos_type i, unsigned int & column) + pos_type i, unsigned int & column) const { // TU encoding is currently on par with T1. return latexSpecialT1(c, os, i, column); @@ -1347,7 +1355,7 @@ bool Paragraph::Private::latexSpecialTU(char_type const c, otexstream & os, bool Paragraph::Private::latexSpecialT3(char_type const c, otexstream & os, - pos_type /*i*/, unsigned int & column) + pos_type /*i*/, unsigned int & column) const { switch (c) { case '*': @@ -1384,7 +1392,7 @@ void Paragraph::Private::validate(LaTeXFeatures & features) const features.addPreambleSnippet(os.release(), true); } - if (features.runparams().flavor == OutputParams::HTML + if (features.runparams().flavor == Flavor::Html && layout_->htmltitle()) { features.setHTMLTitle(owner_->asString(AS_STR_INSETS | AS_STR_SKIPDELETE)); } @@ -1479,7 +1487,7 @@ void Paragraph::Private::validate(LaTeXFeatures & features) const } else if (!bp.use_dash_ligatures && (c == 0x2013 || c == 0x2014) && bp.useNonTeXFonts - && features.runparams().flavor == OutputParams::XETEX) + && features.runparams().flavor == Flavor::XeTeX) // XeTeX's dash behaviour is determined via a global setting features.require("xetexdashbreakstate"); BufferEncodings::validate(c, features); @@ -1555,19 +1563,19 @@ void flushString(ostream & os, docstring & s) void Paragraph::write(ostream & os, BufferParams const & bparams, - depth_type & dth) const + depth_type & depth) const { // The beginning or end of a deeper (i.e. nested) area? - if (dth != d->params_.depth()) { - if (d->params_.depth() > dth) { - while (d->params_.depth() > dth) { + if (depth != d->params_.depth()) { + if (d->params_.depth() > depth) { + while (d->params_.depth() > depth) { os << "\n\\begin_deeper"; - ++dth; + ++depth; } } else { - while (d->params_.depth() < dth) { + while (d->params_.depth() < depth) { os << "\n\\end_deeper"; - --dth; + --depth; } } } @@ -1680,11 +1688,11 @@ void Paragraph::validate(LaTeXFeatures & features) const } -void Paragraph::insert(pos_type start, docstring const & str, +void Paragraph::insert(pos_type pos, docstring const & str, Font const & font, Change const & change) { for (size_t i = 0, n = str.size(); i != n ; ++i) - insertChar(start + i, str[i], font, change); + insertChar(pos + i, str[i], font, change); } @@ -2403,8 +2411,9 @@ void Paragraph::latex(BufferParams const & bparams, pos_type body_pos = beginOfBody(); unsigned int column = 0; - // If we are inside an inset, the real outerfont is local_font - Font const real_outerfont = (runparams.local_font != nullptr) + // 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 (body_pos > 0) { @@ -2503,7 +2512,7 @@ void Paragraph::latex(BufferParams const & bparams, if (c == META_INSET && i >= start_pos && (end_pos == -1 || i < end_pos)) { if (isDeleted(i)) - runparams.ctObject = getInset(i)->CtObject(runparams); + runparams.ctObject = getInset(i)->getCtObject(runparams); InsetMath const * im = getInset(i)->asInsetMath(); if (im && im->asHullInset() @@ -2568,18 +2577,49 @@ void Paragraph::latex(BufferParams const & bparams, ++column; // Fully instantiated font - Font const current_font = getFont(bparams, i, real_outerfont); + Font current_font = getFont(bparams, i, outerfont); + // Previous font + Font const prev_font = (i > 0) ? + getFont(bparams, i - 1, outerfont) + : current_font; Font const last_font = running_font; bool const in_ct_deletion = (bparams.output_changes && runningChange == change && change.type == Change::DELETED && !os.afterParbreak()); + // Insets where font switches are used (rather than font commands) + bool const fontswitch_inset = + c == META_INSET + && getInset(i) + && getInset(i)->allowMultiPar() + && getInset(i)->lyxCode() != ERT_CODE + && getInset(i)->producesOutput(); + + bool closeLanguage = false; + bool lang_switched_at_inset = false; + if (fontswitch_inset) { + // Some insets cannot be inside a font change command. + // However, even such insets *can* be placed in \L or \R + // or their equivalents (for RTL language switches), + // so we don't close the language in those cases + // (= differing isRightToLeft()). + // ArabTeX, though, doesn't seem to handle this special behavior. + closeLanguage = basefont.isRightToLeft() == current_font.isRightToLeft() + || basefont.language()->lang() == "arabic_arabtex" + || current_font.language()->lang() == "arabic_arabtex"; + // We need to check prev_font as language changes directly at inset + // will only be started inside the inset. + lang_switched_at_inset = prev_font.language() != current_font.language(); + } // Do we need to close the previous font? + bool langClosed = false; if (open_font && - (current_font != running_font || - current_font.language() != running_font.language())) + ((current_font != running_font + || current_font.language() != running_font.language()) + || (fontswitch_inset + && (current_font == prev_font)))) { // ensure there is no open script-wrapper if (!alien_script.empty()) { @@ -2587,17 +2627,20 @@ void Paragraph::latex(BufferParams const & bparams, os << "}"; alien_script.clear(); } - bool needPar = false; if (in_ct_deletion) { // We have to close and then reopen \lyxdeleted, // as strikeout needs to be on lowest level. os << '}'; column += 1; } + if (closeLanguage) + // Force language closing + current_font.setLanguage(basefont.language()); + Font const nextfont = (i == body_pos-1) ? basefont : current_font; + bool needPar = false; column += running_font.latexWriteEndChanges( os, bparams, runparams, basefont, - (i == body_pos-1) ? basefont : current_font, - needPar); + nextfont, needPar); if (in_ct_deletion) { // We have to close and then reopen \lyxdeleted, // as strikeout needs to be on lowest level. @@ -2605,8 +2648,12 @@ void Paragraph::latex(BufferParams const & bparams, column += Changes::latexMarkChange(os, bparams, Change(Change::UNCHANGED), Change(Change::DELETED), rp); } - running_font = basefont; open_font = false; + // Has the language been closed in the latexWriteEndChanges() call above? + langClosed = running_font.language() != basefont.language() + && running_font.language() != nextfont.language() + && (running_font.language()->encoding()->package() != Encoding::CJK); + running_font = basefont; } // if necessary, close language environment before opening CJK @@ -2623,7 +2670,8 @@ void Paragraph::latex(BufferParams const & bparams, } // Switch file encoding if necessary (and allowed) - if (!runparams.pass_thru && !style.pass_thru && + if ((!fontswitch_inset || closeLanguage) + && !runparams.pass_thru && !style.pass_thru && runparams.encoding->package() != Encoding::none && current_font.language()->encoding()->package() != Encoding::none) { pair const enc_switch = @@ -2653,50 +2701,59 @@ void Paragraph::latex(BufferParams const & bparams, current_font.language() != running_font.language()) && i != body_pos - 1) { - if (in_ct_deletion) { - // We have to close and then reopen \lyxdeleted, - // as strikeout needs to be on lowest level. - bool needPar = false; - OutputParams rp = runparams; - column += running_font.latexWriteEndChanges( - os, bparams, rp, basefont, - basefont, needPar); - os << '}'; - column += 1; - } - otexstringstream ots; - bool const non_inherit_inset = (c == META_INSET && getInset(i) && !getInset(i)->inheritFont()); - column += current_font.latexWriteStartChanges(ots, bparams, - runparams, basefont, - last_font, non_inherit_inset); - // Check again for display math in ulem commands as a - // font change may also occur just before a math inset. - if (runparams.inDisplayMath && !deleted_display_math - && runparams.inulemcmd) { - if (os.afterParbreak()) - os << "\\noindent"; - else - os << "\\\\\n"; - } - running_font = current_font; - open_font = true; - docstring fontchange = ots.str(); - os << fontchange; - // check whether the fontchange ends with a \\textcolor - // modifier and the text starts with a space. If so we - // need to add } in order to prevent \\textcolor from gobbling - // the space (bug 4473). - docstring const last_modifier = rsplit(fontchange, '\\'); - if (prefixIs(last_modifier, from_ascii("textcolor")) && c == ' ') - os << from_ascii("{}"); - else if (ots.terminateCommand()) - os << termcmd; - if (in_ct_deletion) { - // We have to close and then reopen \lyxdeleted, - // as strikeout needs to be on lowest level. - OutputParams rp = runparams; - column += Changes::latexMarkChange(os, bparams, - Change(Change::UNCHANGED), change, rp); + if (!fontswitch_inset) { + if (in_ct_deletion) { + // We have to close and then reopen \lyxdeleted, + // as strikeout needs to be on lowest level. + OutputParams rp = runparams; + bool needPar = false; + column += running_font.latexWriteEndChanges( + os, bparams, rp, basefont, + basefont, needPar); + os << '}'; + column += 1; + } + otexstringstream ots; + InsetText const * textinset = inInset().asInsetText(); + bool const cprotect = textinset + ? textinset->hasCProtectContent(runparams.moving_arg) + && !textinset->text().isMainText() + : false; + column += current_font.latexWriteStartChanges(ots, bparams, + runparams, basefont, last_font, false, + cprotect); + // Check again for display math in ulem commands as a + // font change may also occur just before a math inset. + if (runparams.inDisplayMath && !deleted_display_math + && runparams.inulemcmd) { + if (os.afterParbreak()) + os << "\\noindent"; + else + os << "\\\\\n"; + } + running_font = current_font; + open_font = true; + docstring fontchange = ots.str(); + os << fontchange; + // check whether the fontchange ends with a \\textcolor + // modifier and the text starts with a space. If so we + // need to add } in order to prevent \\textcolor from gobbling + // the space (bug 4473). + docstring const last_modifier = rsplit(fontchange, '\\'); + if (prefixIs(last_modifier, from_ascii("textcolor")) && c == ' ') + os << from_ascii("{}"); + else if (ots.terminateCommand()) + os << termcmd; + if (in_ct_deletion) { + // We have to close and then reopen \lyxdeleted, + // as strikeout needs to be on lowest level. + OutputParams rp = runparams; + column += Changes::latexMarkChange(os, bparams, + Change(Change::UNCHANGED), change, rp); + } + } else { + running_font = current_font; + open_font = !langClosed; } } @@ -2759,20 +2816,36 @@ void Paragraph::latex(BufferParams const & bparams, incremented = true; } } + // We need to restore parts of this after insets with + // allowMultiPar() true + Font const save_basefont = basefont; d->latexInset(bparams, os, rp, running_font, basefont, real_outerfont, open_font, - runningChange, style, i, column); + runningChange, style, i, column, fontswitch_inset, + closeLanguage, lang_switched_at_inset); + if (fontswitch_inset) { + if (open_font) { + bool needPar = false; + column += running_font.latexWriteEndChanges( + os, bparams, runparams, + basefont, basefont, needPar); + open_font = false; + } + basefont.fontInfo().setSize(save_basefont.fontInfo().size()); + basefont.fontInfo().setFamily(save_basefont.fontInfo().family()); + basefont.fontInfo().setSeries(save_basefont.fontInfo().series()); + } if (incremented) --parInline; - if (runparams.ctObject == OutputParams::CT_DISPLAYOBJECT - || runparams.ctObject == OutputParams::CT_UDISPLAYOBJECT) { + if (runparams.ctObject == CtObject::DisplayObject + || runparams.ctObject == CtObject::UDisplayObject) { // Close \lyx*deleted and force its // reopening (if needed) os << '}'; column++; runningChange = Change(Change::UNCHANGED); - runparams.ctObject = OutputParams::CT_NORMAL; + runparams.ctObject = CtObject::Normal; } } } else if (i >= start_pos && (end_pos == -1 || i < end_pos)) { @@ -2804,7 +2877,7 @@ void Paragraph::latex(BufferParams const & bparams, // add location information and throw again. e.par_id = id(); e.pos = i; - throw(e); + throw; } } } @@ -2832,6 +2905,31 @@ void Paragraph::latex(BufferParams const & bparams, alien_script.clear(); } + Font const font = empty() + ? getLayoutFont(bparams, real_outerfont) + : getFont(bparams, size() - 1, real_outerfont); + + InsetText const * textinset = inInset().asInsetText(); + + bool const maintext = textinset + ? textinset->text().isMainText() + : false; + + size_t const numpars = textinset + ? textinset->text().paragraphs().size() + : 0; + + bool needPar = false; + + if (style.resfont.size() != font.fontInfo().size() + && (!runparams.isLastPar || maintext + || (numpars > 1 && d->ownerCode() != CELL_CODE + && (inInset().getLayout().isDisplay() + || parInline))) + && !style.isCommand()) { + needPar = true; + } + // If we have an open font definition, we have to close it if (open_font) { // Make sure that \\par is done with the font of the last @@ -2843,31 +2941,6 @@ void Paragraph::latex(BufferParams const & bparams, // We must not change the font for the last paragraph // of non-multipar insets, tabular cells or commands, // since this produces unwanted whitespace. - - Font const font = empty() - ? getLayoutFont(bparams, real_outerfont) - : getFont(bparams, size() - 1, real_outerfont); - - InsetText const * textinset = inInset().asInsetText(); - - bool const maintext = textinset - ? textinset->text().isMainText() - : false; - - size_t const numpars = textinset - ? textinset->text().paragraphs().size() - : 0; - - bool needPar = false; - - if (style.resfont.size() != font.fontInfo().size() - && (!runparams.isLastPar || maintext - || (numpars > 1 && d->ownerCode() != CELL_CODE - && (inInset().getLayout().isDisplay() - || parInline))) - && !style.isCommand()) { - needPar = true; - } #ifdef FIXED_LANGUAGE_END_DETECTION if (next_) { running_font.latexWriteEndChanges(os, bparams, @@ -2885,11 +2958,11 @@ void Paragraph::latex(BufferParams const & bparams, running_font.latexWriteEndChanges(os, bparams, runparams, basefont, basefont, needPar); #endif - if (needPar) { - // The \par could not be inserted at the same nesting - // level of the font size change, so do it now. - os << "{\\" << font.latexSize() << "\\par}"; - } + } + if (needPar) { + // The \par could not be inserted at the same nesting + // level of the font size change, so do it now. + os << "{\\" << font.latexSize() << "\\par}"; } column += Changes::latexMarkChange(os, bparams, runningChange, @@ -3027,11 +3100,11 @@ void doFontSwitchDocBook(vector & tagsToOpen, class OptionalFontType { public: - bool has_value; xml::FontTypes ft; + bool has_value; - OptionalFontType(): has_value(false), ft(xml::FT_EMPH) {} // A possible value at random for ft. - OptionalFontType(xml::FontTypes ft): has_value(true), ft(ft) {} + OptionalFontType(): ft(xml::FT_EMPH), has_value(false) {} // A possible value at random for ft. + OptionalFontType(xml::FontTypes ft): ft(ft), has_value(true) {} }; OptionalFontType fontShapeToXml(FontShape fs) @@ -3058,13 +3131,10 @@ OptionalFontType fontFamilyToXml(FontFamily fm) switch (fm) { case ROMAN_FAMILY: return {xml::FT_ROMAN}; - break; case SANS_FAMILY: return {xml::FT_SANS}; - break; case TYPEWRITER_FAMILY: return {xml::FT_TYPE}; - break; case INHERIT_FAMILY: return {}; default: @@ -3111,6 +3181,10 @@ OptionalFontType fontSizeToXml(FontSize fs) struct DocBookFontState { + FontShape curr_fs = INHERIT_SHAPE; + FontFamily curr_fam = INHERIT_FAMILY; + FontSize curr_size = INHERIT_SIZE; + // track whether we have opened these tags bool emph_flag = false; bool bold_flag = false; @@ -3126,16 +3200,12 @@ struct DocBookFontState bool faml_flag = false; // size tags bool size_flag = false; - - FontShape curr_fs = INHERIT_SHAPE; - FontFamily curr_fam = INHERIT_FAMILY; - FontSize curr_size = INHERIT_SIZE; }; std::tuple, vector> computeDocBookFontSwitch(FontInfo const & font_old, Font const & font, std::string const & default_family, - DocBookFontState fs) + DocBookFontState & fs) { vector tagsToOpen; vector tagsToClose; @@ -3160,6 +3230,11 @@ std::tuple, vector> computeDocBookFontSwit if (font_old.strikeout() != curstate) doFontSwitchDocBook(tagsToOpen, tagsToClose, fs.sout_flag, curstate, xml::FT_SOUT); + // xout + curstate = font.fontInfo().xout(); + if (font_old.xout() != curstate) + doFontSwitchDocBook(tagsToOpen, tagsToClose, fs.xout_flag, curstate, xml::FT_XOUT); + // double underbar curstate = font.fontInfo().uuline(); if (font_old.uuline() != curstate) @@ -3182,16 +3257,14 @@ std::tuple, vector> computeDocBookFontSwit if (old_fs != fs.curr_fs) { if (fs.shap_flag) { OptionalFontType tag = fontShapeToXml(old_fs); - if (tag.has_value) { + if (tag.has_value) tagsToClose.push_back(docbookEndFontTag(tag.ft)); - } fs.shap_flag = false; } OptionalFontType tag = fontShapeToXml(fs.curr_fs); - if (tag.has_value) { + if (tag.has_value) tagsToOpen.push_back(docbookStartFontTag(tag.ft)); - } } // Font family @@ -3200,9 +3273,8 @@ std::tuple, vector> computeDocBookFontSwit if (old_fam != fs.curr_fam) { if (fs.faml_flag) { OptionalFontType tag = fontFamilyToXml(old_fam); - if (tag.has_value) { + if (tag.has_value) tagsToClose.push_back(docbookEndFontTag(tag.ft)); - } fs.faml_flag = false; } switch (fs.curr_fam) { @@ -3241,9 +3313,8 @@ std::tuple, vector> computeDocBookFontSwit if (old_size != fs.curr_size) { if (fs.size_flag) { OptionalFontType tag = fontSizeToXml(old_size); - if (tag.has_value) { + if (tag.has_value) tagsToClose.push_back(docbookEndFontTag(tag.ft)); - } fs.size_flag = false; } @@ -3257,22 +3328,19 @@ std::tuple, vector> computeDocBookFontSwit return std::tuple, vector>(tagsToOpen, tagsToClose); } -}// anonymous namespace +} // anonymous namespace -void Paragraph::simpleDocBookOnePar(Buffer const & buf, - XMLStream & xs, - OutputParams const & runparams, - Font const & outerfont, - bool start_paragraph, bool close_paragraph, - pos_type initial) const +std::vector Paragraph::simpleDocBookOnePar(Buffer const & buf, + OutputParams const & runparams, + Font const & outerfont, + pos_type initial, + bool is_last_par, + bool ignore_fonts) const { - // track whether we have opened these tags + // Track whether we have opened these tags DocBookFontState fs; - if (start_paragraph) - xs.startDivision(allowEmpty()); - Layout const & style = *d->layout_; FontInfo font_old = style.labeltype == LABEL_MANUAL ? style.labelfont : style.font; @@ -3283,48 +3351,88 @@ void Paragraph::simpleDocBookOnePar(Buffer const & buf, vector tagsToOpen; vector tagsToClose; - // parsing main loop + std::vector generatedParagraphs; + DocBookFontState old_fs = fs; + odocstringstream os; + auto * xs = new XMLStream(os); // XMLStream has no copy constructor: to create a new object, the only solution + // is to hold a pointer to the XMLStream (xs = XMLStream(os) is not allowed once the first object is built). + + // When a font tag ends with a space, output it after the closing font tag. This requires to store delayed + // characters at some point. + std::vector delayedChars; + + // Parsing main loop. for (pos_type i = initial; i < size(); ++i) { - // let's not show deleted material in the output + // Don't show deleted material in the output. if (isDeleted(i)) continue; - Font const font = getFont(buf.masterBuffer()->params(), i, outerfont); - - // Determine which tags should be opened or closed. - tie(tagsToOpen, tagsToClose) = computeDocBookFontSwitch(font_old, font, default_family, fs); - - // FIXME XHTML - // Other such tags? What about the other text ranges? + // If this is an InsetNewline, generate a new paragraph. Also reset the fonts, so that tags are closed in + // this paragraph. + if (getInset(i) && getInset(i)->lyxCode() == NEWLINE_CODE) { + if (!ignore_fonts) + xs->closeFontTags(); + + // Output one paragraph (i.e. one string entry in generatedParagraphs). + generatedParagraphs.push_back(os.str()); + + // Create a new XMLStream for the new paragraph, completely independent from the previous one. This implies + // that the string stream must be reset. + os.str(from_ascii("")); + delete xs; + xs = new XMLStream(os); + + // Restore the fonts for the new paragraph, so that the right tags are opened for the new entry. + if (!ignore_fonts) { + font_old = outerfont.fontInfo(); + fs = old_fs; + } + } - vector::const_iterator cit = tagsToClose.begin(); - vector::const_iterator cen = tagsToClose.end(); - for (; cit != cen; ++cit) - xs << *cit; + // Determine which tags should be opened or closed regarding fonts. + Font const font = getFont(buf.masterBuffer()->params(), i, outerfont); + if (!ignore_fonts) { + tie(tagsToOpen, tagsToClose) = computeDocBookFontSwitch(font_old, font, default_family, fs); + + // 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; + + // Deal with the delayed characters *after* closing font tags. + if (!delayedChars.empty()) { + for (char_type c: delayedChars) + *xs << c; + delayedChars.clear(); + } - vector::const_iterator sit = tagsToOpen.begin(); - vector::const_iterator sen = tagsToOpen.end(); - for (; sit != sen; ++sit) - xs << *sit; + vector::const_iterator sit = tagsToOpen.begin(); + vector::const_iterator sen = tagsToOpen.end(); + for (; sit != sen; ++sit) + *xs << *sit; - tagsToClose.clear(); - tagsToOpen.clear(); + tagsToClose.clear(); + tagsToOpen.clear(); + } if (Inset const * inset = getInset(i)) { if (!runparams.for_toc || inset->isInToc()) { OutputParams np = runparams; np.local_font = &font; - // If the paragraph has size 1, then we are in the "special - // case" where we do not output the containing paragraph info. - // This "special case" is defined in more details in output_docbook.cpp, makeParagraphs. The results - // of that brittle logic is passed to this function through open_par. - if (!inset->getLayout().htmlisblock() && size() != 1) // TODO: htmlisblock here too! - np.docbook_in_par = true; - inset->docbook(xs, np); + + // TODO: special case will bite here. + np.docbook_in_par = true; + inset->docbook(*xs, np); } } else { char_type c = getUChar(buf.masterBuffer()->params(), runparams, i); - xs << c; + if (lyx::isSpace(c) && !ignore_fonts) + delayedChars.push_back(c); + else + *xs << c; } font_old = font.fontInfo(); } @@ -3332,11 +3440,24 @@ void Paragraph::simpleDocBookOnePar(Buffer const & buf, // 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. - xs.closeFontTags(); - if (runparams.docbook_in_listing) - xs << xml::CR(); - if (close_paragraph) - xs.endDivision(); + if (!ignore_fonts) + xs->closeFontTags(); + + // Deal with the delayed characters *after* closing font tags. + if (!delayedChars.empty()) + for (char_type c: delayedChars) + *xs << c; + + // 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. + if (runparams.docbook_in_listing && !is_last_par) + *xs << xml::CR(); + + // Finalise the last (and most likely only) paragraph. + generatedParagraphs.push_back(os.str()); + delete xs; + + return generatedParagraphs; } @@ -3771,8 +3892,8 @@ bool Paragraph::isHardHyphenOrApostrophe(pos_type pos) const char_type const c = d->text_[pos]; if (c != '-' && c != '\'') return false; - int nextpos = pos + 1; - int prevpos = pos > 0 ? pos - 1 : 0; + pos_type nextpos = pos + 1; + pos_type prevpos = pos > 0 ? pos - 1 : 0; if ((nextpos == psize || isSpace(nextpos)) && (pos == 0 || isSpace(prevpos))) return false; @@ -3813,9 +3934,6 @@ bool Paragraph::needsCProtection(bool const fragile) const 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()) @@ -4015,12 +4133,12 @@ void Paragraph::setPlainLayout(DocumentClass const & tc) } -void Paragraph::setPlainOrDefaultLayout(DocumentClass const & tclass) +void Paragraph::setPlainOrDefaultLayout(DocumentClass const & tc) { if (usePlainLayout()) - setPlainLayout(tclass); + setPlainLayout(tc); else - setDefaultLayout(tclass); + setDefaultLayout(tc); } @@ -4076,7 +4194,7 @@ bool Paragraph::brokenBiblio() const int Paragraph::fixBiblio(Buffer const & buffer) { // 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 + // which does not tell whether another inset has been removed; the // cursor cannot be correctly updated. bool const track_changes = buffer.params().track_changes; @@ -4108,6 +4226,8 @@ int Paragraph::fixBiblio(Buffer const & buffer) // than keep the first? (JMarc) Inset * inset = releaseInset(bibitem_pos); d->insetlist_.begin()->inset = inset; + // This needs to be done to update the counter (#8499) + buffer.updateBuffer(); return -bibitem_pos; } @@ -4247,10 +4367,8 @@ void Paragraph::changeCase(BufferParams const & bparams, pos_type pos, } int erasePos = pos - changes.size(); - for (size_t i = 0; i < changes.size(); i++) { - insertChar(pos, changes[i].first, - changes[i].second, - trackChanges); + for (auto const & change : changes) { + insertChar(pos, change.first, change.second, trackChanges); if (!eraseChar(erasePos, trackChanges)) { ++erasePos; ++pos; // advance @@ -4273,9 +4391,12 @@ int Paragraph::find(docstring const & str, bool cs, bool mw, // Ignore "invisible" letters such as ligature breaks // and hyphenation chars while searching while (pos < parsize - 1 && isInset(pos)) { + Inset const * inset = getInset(pos); + if (!inset->isLetter()) + break; odocstringstream os; - getInset(pos)->toString(os); - if (!getInset(pos)->isLetter() || !os.str().empty()) + inset->toString(os); + if (!os.str().empty()) break; pos++; } @@ -4469,7 +4590,18 @@ Language * Paragraph::Private::locateSpellRange( while (last < to && samelang && sameinset) { // hop to end of word while (last < to && !owner_->isWordSeparator(last)) { - if (owner_->getInset(last)) { + Inset const * inset = owner_->getInset(last); + if (inset && dynamic_cast(inset)) { + // check for "invisible" letters such as ligature breaks + odocstringstream os; + inset->toString(os); + if (os.str().length() != 0) { + // avoid spell check of visible special char insets + // stop the loop in front of the special char inset + sameinset = false; + break; + } + } else if (inset) { appendSkipPosition(skips, last); } else if (owner_->isDeleted(last)) { appendSkipPosition(skips, last); @@ -4695,7 +4827,7 @@ void Paragraph::spellCheck() const // start the spell checker on the unit of meaning docstring word = asString(first, last, AS_STR_INSETS + AS_STR_SKIPDELETE); WordLangTuple wl = WordLangTuple(word, lang); - SpellChecker::Result result = word.size() ? + SpellChecker::Result result = !word.empty() ? speller->check(wl) : SpellChecker::WORD_OK; d->markMisspelledWords(first, last, result, word, skips); first = ++last;