X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=b5f31281f4da9ee0dff6d11b8825bdb8a68a63f1;hb=3256dbc78a76f947080dc8e3414abc60e4b5c5af;hp=a324d8098932a1180304b3506d455018fe0b4807;hpb=65b3790153aa39d9dbcdf7ce6c24125607a580a7;p=lyx.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index a324d80989..b5f31281f4 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -24,23 +24,20 @@ #include "Changes.h" #include "Counters.h" #include "Encoding.h" -#include "debug.h" -#include "gettext.h" #include "InsetList.h" #include "Language.h" #include "LaTeXFeatures.h" -#include "Color.h" #include "Layout.h" #include "Length.h" #include "Font.h" #include "FontList.h" #include "LyXRC.h" -#include "Messages.h" #include "OutputParams.h" #include "output_latex.h" #include "paragraph_funcs.h" #include "ParagraphParameters.h" #include "sgml.h" +#include "TextClass.h" #include "TexRow.h" #include "VSpace.h" @@ -48,27 +45,27 @@ #include "frontends/FontMetrics.h" #include "insets/InsetBibitem.h" -#include "insets/InsetOptArg.h" +#include "insets/InsetLabel.h" +#include "support/convert.h" +#include "support/debug.h" +#include "support/gettext.h" #include "support/lstrings.h" +#include "support/Messages.h" #include "support/textutils.h" -#include "support/convert.h" -#include "support/unicode.h" #include +#include -using std::endl; -using std::string; -using std::ostream; +using namespace std; +using namespace lyx::support; namespace lyx { -using support::contains; -using support::prefixIs; -using support::suffixIs; -using support::rsplit; -using support::rtrim; - +namespace { +/// Inset identifier (above 0x10ffff, for ucs-4) +char_type const META_INSET = 0x200001; +}; ///////////////////////////////////////////////////////////////////// // @@ -85,32 +82,39 @@ public: Private(Private const &, Paragraph * owner); /// - value_type getChar(pos_type pos) const; - /// - void insertChar(pos_type pos, value_type c, Change const & change); + void insertChar(pos_type pos, char_type c, Change const & change); /// Output the surrogate pair formed by \p c and \p next to \p os. /// \return the number of characters written. - int latexSurrogatePair(odocstream & os, value_type c, value_type next, + int latexSurrogatePair(odocstream & os, char_type c, char_type next, Encoding const &); /// Output a space in appropriate formatting (or a surrogate pair /// if the next character is a combining character). /// \return whether a surrogate pair was output. - bool simpleTeXBlanks(Encoding const &, + bool simpleTeXBlanks(OutputParams const &, odocstream &, TexRow & texrow, - pos_type & i, + pos_type i, unsigned int & column, Font const & font, Layout const & style); - /// Output consecutive known unicode chars, belonging to the same - /// language as specified by \p preamble, to \p os starting from \p c. + /// Output consecutive unicode chars, belonging to the same script as + /// specified by the latex macro \p ltx, to \p os starting from \p i. /// \return the number of characters written. - int knownLangChars(odocstream & os, value_type c, string & preamble, - Change &, Encoding const &, pos_type &); + int writeScriptChars(odocstream & os, docstring const & ltx, + Change &, Encoding const &, pos_type & i); + + /// This could go to ParagraphParameters if we want to. + int startTeXParParams(BufferParams const &, odocstream &, TexRow &, + bool) const; + + /// This could go to ParagraphParameters if we want to. + int endTeXParParams(BufferParams const &, odocstream &, TexRow &, + bool) const; + /// - void simpleTeXSpecialChars(Buffer const &, BufferParams const &, + void latexInset(Buffer const &, BufferParams const &, odocstream &, TexRow & texrow, OutputParams &, Font & running_font, @@ -120,17 +124,47 @@ public: Change & running_change, Layout const & style, pos_type & i, - unsigned int & column, value_type const c); + unsigned int & column); + + /// + void latexSpecialChar( + odocstream & os, + OutputParams & runparams, + Font & running_font, + Change & running_change, + Layout const & style, + pos_type & i, + unsigned int & column); + + /// + bool latexSpecialT1( + char_type const c, + odocstream & os, + pos_type & i, + unsigned int & column); + /// + bool latexSpecialTypewriter( + char_type const c, + odocstream & os, + pos_type & i, + unsigned int & column); + /// + bool latexSpecialPhrase( + odocstream & os, + pos_type & i, + unsigned int & column, + OutputParams & runparams); /// void validate(LaTeXFeatures & features, Layout const & layout) const; - /// - pos_type size() const { return owner_->size(); } + /// Checks if the paragraph contains only text and no inset or font change. + bool onlyText(Buffer const & buf, Font const & outerfont, + pos_type initial) const; /// match a string against a particular point in the paragraph - bool isTextAt(std::string const & str, pos_type pos) const; + bool isTextAt(string const & str, pos_type pos) const; /// Which Paragraph owns us? Paragraph * owner_; @@ -153,15 +187,17 @@ public: /// InsetList insetlist_; -}; - + /// + LayoutPtr layout_; + /// end of label + pos_type begin_of_body_; -using std::endl; -using std::upper_bound; -using std::lower_bound; -using std::string; + typedef docstring TextContainer; + /// + TextContainer text_; +}; // Initialization of the counter for the paragraph id's, @@ -188,16 +224,17 @@ size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase); Paragraph::Private::Private(Paragraph * owner) - : owner_(owner) + : owner_(owner), inset_owner_(0), begin_of_body_(0) { - inset_owner_ = 0; id_ = paragraph_id++; + text_.reserve(100); } Paragraph::Private::Private(Private const & p, Paragraph * owner) : owner_(owner), inset_owner_(p.inset_owner_), fontlist_(p.fontlist_), - params_(p.params_), changes_(p.changes_), insetlist_(p.insetlist_) + params_(p.params_), changes_(p.changes_), insetlist_(p.insetlist_), + layout_(p.layout_), begin_of_body_(p.begin_of_body_), text_(p.text_) { id_ = paragraph_id++; } @@ -212,15 +249,13 @@ bool Paragraph::isChanged(pos_type start, pos_type end) const } -bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const { +bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const +{ // keep the logic here in sync with the logic of eraseChars() - - if (!trackChanges) { + if (!trackChanges) return true; - } - - Change change = d->changes_.lookup(size()); + Change const change = d->changes_.lookup(size()); return change.type == Change::INSERTED && change.author == 0; } @@ -244,8 +279,8 @@ void Paragraph::setChange(Change const & change) if (change.type != Change::DELETED) { for (pos_type pos = 0; pos < size(); ++pos) { - if (isInset(pos)) - getInset(pos)->setChange(change); + if (Inset * inset = getInset(pos)) + inset->setChange(change); } } } @@ -254,22 +289,18 @@ void Paragraph::setChange(Change const & change) void Paragraph::setChange(pos_type pos, Change const & change) { BOOST_ASSERT(pos >= 0 && pos <= size()); - d->changes_.set(change, pos); // see comment in setChange(Change const &) above - - if (change.type != Change::DELETED && - pos < size() && isInset(pos)) { - getInset(pos)->setChange(change); - } + if (change.type != Change::DELETED && pos < size()) + if (Inset * inset = getInset(pos)) + inset->setChange(change); } Change const & Paragraph::lookupChange(pos_type pos) const { BOOST_ASSERT(pos >= 0 && pos <= size()); - return d->changes_.lookup(pos); } @@ -284,17 +315,15 @@ void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, switch (lookupChange(pos).type) { case Change::UNCHANGED: // accept changes in nested inset - if (pos < size() && isInset(pos)) - getInset(pos)->acceptChanges(bparams); - + if (Inset * inset = getInset(pos)) + inset->acceptChanges(bparams); break; case Change::INSERTED: d->changes_.set(Change(Change::UNCHANGED), pos); // also accept changes in nested inset - if (pos < size() && isInset(pos)) { - getInset(pos)->acceptChanges(bparams); - } + if (Inset * inset = getInset(pos)) + inset->acceptChanges(bparams); break; case Change::DELETED: @@ -322,9 +351,8 @@ void Paragraph::rejectChanges(BufferParams const & bparams, switch (lookupChange(pos).type) { case Change::UNCHANGED: // reject changes in nested inset - if (pos < size() && isInset(pos)) { - getInset(pos)->rejectChanges(bparams); - } + if (Inset * inset = getInset(pos)) + inset->rejectChanges(bparams); break; case Change::INSERTED: @@ -349,31 +377,23 @@ void Paragraph::rejectChanges(BufferParams const & bparams, } -Paragraph::value_type Paragraph::Private::getChar(pos_type pos) const -{ - BOOST_ASSERT(pos >= 0 && pos <= size()); - - return owner_->getChar(pos); -} - - -void Paragraph::Private::insertChar(pos_type pos, value_type c, +void Paragraph::Private::insertChar(pos_type pos, char_type c, Change const & change) { - BOOST_ASSERT(pos >= 0 && pos <= size()); + BOOST_ASSERT(pos >= 0 && pos <= int(text_.size())); // track change changes_.insert(change, pos); // This is actually very common when parsing buffers (and // maybe inserting ascii text) - if (pos == size()) { + if (pos == pos_type(text_.size())) { // when appending characters, no need to update tables - owner_->text_.push_back(c); + text_.push_back(c); return; } - owner_->text_.insert(owner_->text_.begin() + pos, c); + text_.insert(text_.begin() + pos, c); // Update the font table. fontlist_.increasePosAfterPos(pos); @@ -390,7 +410,7 @@ void Paragraph::insertInset(pos_type pos, Inset * inset, BOOST_ASSERT(pos >= 0 && pos <= size()); d->insertChar(pos, META_INSET, change); - BOOST_ASSERT(text_[pos] == META_INSET); + BOOST_ASSERT(d->text_[pos] == META_INSET); // Add a new entry in the insetlist_. d->insetlist_.insert(inset, pos); @@ -431,10 +451,10 @@ bool Paragraph::eraseChar(pos_type pos, bool trackChanges) d->changes_.erase(pos); // if it is an inset, delete the inset entry - if (text_[pos] == Paragraph::META_INSET) + if (d->text_[pos] == META_INSET) d->insetlist_.erase(pos); - text_.erase(text_.begin() + pos); + d->text_.erase(d->text_.begin() + pos); // Update the fontlist_ d->fontlist_.erase(pos); @@ -460,8 +480,8 @@ int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges) } -int Paragraph::Private::latexSurrogatePair(odocstream & os, value_type c, - value_type next, Encoding const & encoding) +int Paragraph::Private::latexSurrogatePair(odocstream & os, char_type c, + char_type next, Encoding const & encoding) { // Writing next here may circumvent a possible font change between // c and next. Since next is only output if it forms a surrogate pair @@ -477,22 +497,22 @@ int Paragraph::Private::latexSurrogatePair(odocstream & os, value_type c, } -bool Paragraph::Private::simpleTeXBlanks(Encoding const & encoding, +bool Paragraph::Private::simpleTeXBlanks(OutputParams const & runparams, odocstream & os, TexRow & texrow, - pos_type & i, + pos_type i, unsigned int & column, Font const & font, Layout const & style) { - if (style.pass_thru) + if (style.pass_thru || runparams.verbatim) return false; - if (i + 1 < size()) { - char_type next = getChar(i + 1); + if (i + 1 < int(text_.size())) { + char_type next = text_[i + 1]; if (Encodings::isCombiningChar(next)) { + Encoding const & encoding = *(runparams.encoding); // This space has an accent, so we must always output it. column += latexSurrogatePair(os, ' ', next, encoding) - 1; - ++i; return true; } } @@ -500,17 +520,17 @@ bool Paragraph::Private::simpleTeXBlanks(Encoding const & encoding, if (lyxrc.plaintext_linelen > 0 && column > lyxrc.plaintext_linelen && i - && getChar(i - 1) != ' ' - && (i + 1 < size()) + && text_[i - 1] != ' ' + && (i + 1 < int(text_.size())) // same in FreeSpacing mode && !owner_->isFreeSpacing() // In typewriter mode, we want to avoid // ! . ? : at the end of a line - && !(font.family() == Font::TYPEWRITER_FAMILY - && (getChar(i - 1) == '.' - || getChar(i - 1) == '?' - || getChar(i - 1) == ':' - || getChar(i - 1) == '!'))) { + && !(font.fontInfo().family() == TYPEWRITER_FAMILY + && (text_[i - 1] == '.' + || text_[i - 1] == '?' + || text_[i - 1] == ':' + || text_[i - 1] == '!'))) { os << '\n'; texrow.newline(); texrow.start(owner_->id(), i + 1); @@ -524,27 +544,35 @@ bool Paragraph::Private::simpleTeXBlanks(Encoding const & encoding, } -int Paragraph::Private::knownLangChars(odocstream & os, - value_type c, - string & preamble, - Change & runningChange, - Encoding const & encoding, - pos_type & i) -{ - // When the character is marked by the proper language, we simply - // get its code point in some encoding, otherwise we get the - // translation specified in the unicodesymbols file, which is - // something like "\textLANG{}". So, we have to retain - // "\textLANG{" for the first char but only "" for - // all subsequent chars. - docstring const latex1 = rtrim(encoding.latexChar(c), "}"); - int length = latex1.length(); - os << latex1; - while (i + 1 < size()) { - char_type next = getChar(i + 1); - // Stop here if next character belongs to another - // language or there is a change tracking status. - if (!Encodings::isKnownLangChar(next, preamble) || +int Paragraph::Private::writeScriptChars(odocstream & os, + docstring const & ltx, + Change & runningChange, + Encoding const & encoding, + pos_type & i) +{ + // FIXME: modifying i here is not very nice... + + // We only arrive here when a proper language for character text_[i] has + // not been specified (i.e., it could not be translated in the current + // latex encoding) and it belongs to a known script. + // Parameter ltx contains the latex translation of text_[i] as specified in + // the unicodesymbols file and is something like "\textXXX{}". + // The latex macro name "textXXX" specifies the script to which text_[i] + // belongs and we use it in order to check whether characters from the + // same script immediately follow, such that we can collect them in a + // single "\textXXX" macro. So, we have to retain "\textXXX{" + // for the first char but only "" for all subsequent chars. + docstring::size_type const brace1 = ltx.find_first_of(from_ascii("{")); + docstring::size_type const brace2 = ltx.find_last_of(from_ascii("}")); + string script = to_ascii(ltx.substr(1, brace1 - 1)); + int length = ltx.substr(0, brace2).length(); + os << ltx.substr(0, brace2); + int size = text_.size(); + while (i + 1 < size) { + char_type const next = text_[i + 1]; + // Stop here if next character belongs to another script + // or there is a change in change tracking status. + if (!Encodings::isKnownScriptChar(next, script) || runningChange != owner_->lookupChange(i + 1)) break; Font prev_font; @@ -559,27 +587,21 @@ int Paragraph::Private::knownLangChars(odocstream & os, if (cit->pos() >= i + 1) break; } - // Stop here if there is a font attribute change. + // Stop here if there is a font attribute or encoding change. if (found && cit != end && prev_font != cit->font()) break; - docstring const latex = rtrim(encoding.latexChar(next), "}"); - docstring::size_type const j = + docstring const latex = encoding.latexChar(next); + docstring::size_type const b1 = latex.find_first_of(from_ascii("{")); - if (j == docstring::npos) { - os << latex; - length += latex.length(); - } else { - os << latex.substr(j + 1); - length += latex.substr(j + 1).length(); - } + docstring::size_type const b2 = + latex.find_last_of(from_ascii("}")); + int const len = b2 - b1 - 1; + os << latex.substr(b1 + 1, len); + length += len; ++i; } - // When the proper language is set, we are simply passed a code - // point, so we should not try to close the \textLANG command. - if (prefixIs(latex1, from_ascii("\\" + preamble))) { - os << '}'; - ++length; - } + os << '}'; + ++length; return length; } @@ -589,14 +611,14 @@ bool Paragraph::Private::isTextAt(string const & str, pos_type pos) const pos_type const len = str.length(); // is the paragraph large enough? - if (pos + len > size()) + if (pos + len > int(text_.size())) return false; // does the wanted text start at point? for (string::size_type i = 0; i < str.length(); ++i) { // Caution: direct comparison of characters works only // because str is pure ASCII. - if (str[i] != owner_->text_[pos + i]) + if (str[i] != text_[pos + i]) return false; } @@ -604,7 +626,7 @@ bool Paragraph::Private::isTextAt(string const & str, pos_type pos) const } -void Paragraph::Private::simpleTeXSpecialChars(Buffer const & buf, +void Paragraph::Private::latexInset(Buffer const & buf, BufferParams const & bparams, odocstream & os, TexRow & texrow, @@ -616,321 +638,351 @@ void Paragraph::Private::simpleTeXSpecialChars(Buffer const & buf, Change & running_change, Layout const & style, pos_type & i, - unsigned int & column, - value_type const c) + unsigned int & column) { + Inset * inset = owner_->getInset(i); + BOOST_ASSERT(inset); + if (style.pass_thru) { - if (c != Paragraph::META_INSET) { - if (c != '\0') - // FIXME UNICODE: This can fail if c cannot - // be encoded in the current encoding. - os.put(c); - } else - owner_->getInset(i)->plaintext(buf, os, runparams); + inset->plaintext(buf, os, runparams); return; } - // Two major modes: LaTeX or plain - // Handle here those cases common to both modes - // and then split to handle the two modes separately. - switch (c) { - case Paragraph::META_INSET: { - Inset * inset = owner_->getInset(i); + // FIXME: move this to InsetNewline::latex + if (inset->lyxCode() == NEWLINE_CODE) { + // newlines are handled differently here than + // the default in simpleTeXSpecialChars(). + if (!style.newline_allowed) { + os << '\n'; + } else { + if (open_font) { + column += running_font.latexWriteEndChanges( + os, bparams, runparams, + basefont, basefont); + open_font = false; + } - // FIXME: remove this check - if (!inset) - break; + if (running_font.fontInfo().family() == TYPEWRITER_FAMILY) + os << '~'; - // FIXME: move this to InsetNewline::latex - if (inset->lyxCode() == NEWLINE_CODE) { - // newlines are handled differently here than - // the default in simpleTeXSpecialChars(). - if (!style.newline_allowed) { - os << '\n'; - } else { - if (open_font) { - column += running_font.latexWriteEndChanges( - os, bparams, runparams, - basefont, basefont); - open_font = false; - } + basefont = owner_->getLayoutFont(bparams, outerfont); + running_font = basefont; - if (running_font.family() == Font::TYPEWRITER_FAMILY) - os << '~'; + if (runparams.moving_arg) + os << "\\protect "; - basefont = owner_->getLayoutFont(bparams, outerfont); - running_font = basefont; + } + texrow.newline(); + texrow.start(owner_->id(), i + 1); + column = 0; + } - if (runparams.moving_arg) - os << "\\protect "; + if (owner_->lookupChange(i).type == Change::DELETED) { + if( ++runparams.inDeletedInset == 1) + runparams.changeOfDeletedInset = owner_->lookupChange(i); + } - os << "\\\\\n"; - } - texrow.newline(); - texrow.start(owner_->id(), i + 1); - column = 0; - break; - } + if (inset->canTrackChanges()) { + column += Changes::latexMarkChange(os, bparams, running_change, + Change(Change::UNCHANGED)); + running_change = Change(Change::UNCHANGED); + } - if (owner_->lookupChange(i).type == Change::DELETED) { - if( ++runparams.inDeletedInset == 1) - runparams.changeOfDeletedInset = owner_->lookupChange(i); - } + bool close = false; + odocstream::pos_type const len = os.tellp(); - if (inset->canTrackChanges()) { - column += Changes::latexMarkChange(os, bparams, running_change, - Change(Change::UNCHANGED)); - running_change = Change(Change::UNCHANGED); + if (inset->forceLTR() + && running_font.isRightToLeft() + // ERT is an exception, it should be output with no decorations at all + && inset->lyxCode() != ERT_CODE) { + if (running_font.language()->lang() == "farsi") + os << "\\beginL{}"; + else + os << "\\L{"; + 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->noFontChange()) { + bool closeLanguage = arabtex + || basefont.isRightToLeft() == running_font.isRightToLeft(); + unsigned int count = running_font.latexWriteEndChanges(os, + bparams, runparams, basefont, basefont, closeLanguage); + 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()); + // leave font open if language is still open + open_font = (running_font.language() == basefont.language()); + if (closeLanguage) + runparams.local_font = &basefont; } + } - bool close = false; - odocstream::pos_type const len = os.tellp(); + int tmp = inset->latex(buf, os, runparams); - if ((inset->lyxCode() == GRAPHICS_CODE - || inset->lyxCode() == MATH_CODE - || inset->lyxCode() == HYPERLINK_CODE) - && running_font.isRightToLeft()) { - if (running_font.language()->lang() == "farsi") - os << "\\beginL{}"; - else - os << "\\L{"; - close = true; - } + if (close) { + if (running_font.language()->lang() == "farsi") + os << "\\endL{}"; + else + os << '}'; + } -// 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->noFontChange()) { - bool closeLanguage = arabtex || - basefont.isRightToLeft() == running_font.isRightToLeft(); - unsigned int count = running_font.latexWriteEndChanges( - os, bparams, runparams, - basefont, basefont, closeLanguage); - 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()); - // leave font open if language is still open - open_font = (running_font.language() == basefont.language()); - if (closeLanguage) - runparams.local_font = &basefont; - } - } + if (tmp) { + for (int j = 0; j < tmp; ++j) + texrow.newline(); - int tmp = inset->latex(buf, os, runparams); + texrow.start(owner_->id(), i + 1); + column = 0; + } else { + column += os.tellp() - len; + } - if (close) { - if (running_font.language()->lang() == "farsi") - os << "\\endL{}"; - else - os << '}'; - } + if (owner_->lookupChange(i).type == Change::DELETED) + --runparams.inDeletedInset; +} - if (tmp) { - for (int j = 0; j < tmp; ++j) { - texrow.newline(); - } - texrow.start(owner_->id(), i + 1); - column = 0; - } else { - column += os.tellp() - len; - } - if (owner_->lookupChange(i).type == Change::DELETED) { - --runparams.inDeletedInset; - } +void Paragraph::Private::latexSpecialChar( + odocstream & os, + OutputParams & runparams, + Font & running_font, + Change & running_change, + Layout const & style, + pos_type & i, + unsigned int & column) +{ + char_type const c = text_[i]; + + if (style.pass_thru) { + if (c != '\0') + // FIXME UNICODE: This can fail if c cannot + // be encoded in the current encoding. + os.put(c); + return; } - break; - default: - // And now for the special cases within each mode + if (runparams.verbatim) { + os.put(c); + return; + } - switch (c) { - case '\\': - os << "\\textbackslash{}"; - column += 15; - break; + if (lyxrc.fontenc == "T1" && latexSpecialT1(c, os, i, column)) + return; - case '|': case '<': case '>': - // In T1 encoding, these characters exist - if (lyxrc.fontenc == "T1") { - os.put(c); - //... but we should avoid ligatures - if ((c == '>' || c == '<') - && i <= size() - 2 - && getChar(i + 1) == c) { - //os << "\\textcompwordmark{}"; - //column += 19; - // Jean-Marc, have a look at - // this. I think this works - // equally well: - os << "\\,{}"; - // Lgb - column += 3; - } - break; - } - // Typewriter font also has them - if (running_font.family() == Font::TYPEWRITER_FAMILY) { - os.put(c); - break; - } - // Otherwise, we use what LaTeX - // provides us. - switch (c) { - case '<': - os << "\\textless{}"; - column += 10; - break; - case '>': - os << "\\textgreater{}"; - column += 13; - break; - case '|': - os << "\\textbar{}"; - column += 9; - break; - } - break; + if (running_font.fontInfo().family() == TYPEWRITER_FAMILY + && latexSpecialTypewriter(c, os, i, column)) + return; - case '-': // "--" in Typewriter mode -> "-{}-" - if (i <= size() - 2 && - getChar(i + 1) == '-' && - running_font.family() == Font::TYPEWRITER_FAMILY) { - os << "-{}"; - column += 2; - } else { - os << '-'; - } - break; + // Otherwise, we use what LaTeX provides us. + switch (c) { + case '\\': + os << "\\textbackslash{}"; + column += 15; + break; + case '<': + os << "\\textless{}"; + column += 10; + break; + case '>': + os << "\\textgreater{}"; + column += 13; + break; + case '|': + os << "\\textbar{}"; + column += 9; + break; + case '-': + os << '-'; + break; + case '\"': + os << "\\char`\\\"{}"; + column += 9; + break; - case '\"': - os << "\\char`\\\"{}"; - column += 9; - break; + case '$': case '&': + case '%': case '#': case '{': + case '}': case '_': + os << '\\'; + os.put(c); + column += 1; + break; - case '$': case '&': - case '%': case '#': case '{': - case '}': case '_': - os << '\\'; - os.put(c); - column += 1; - break; + case '~': + os << "\\textasciitilde{}"; + column += 16; + break; - case '~': - os << "\\textasciitilde{}"; - column += 16; - break; + case '^': + os << "\\textasciicircum{}"; + column += 17; + break; - case '^': - os << "\\textasciicircum{}"; - column += 17; - break; + case '*': case '[': + // avoid being mistaken for optional arguments + os << '{'; + os.put(c); + os << '}'; + column += 2; + break; - case '*': case '[': - // avoid being mistaken for optional arguments - os << '{'; - os.put(c); - os << '}'; - column += 2; - break; + case ' ': + // Blanks are printed before font switching. + // Sure? I am not! (try nice-latex) + // I am sure it's correct. LyX might be smarter + // in the future, but for now, nothing wrong is + // written. (Asger) + break; - case ' ': - // Blanks are printed before font switching. - // Sure? I am not! (try nice-latex) - // I am sure it's correct. LyX might be smarter - // in the future, but for now, nothing wrong is - // written. (Asger) - break; + default: - default: + // LyX, LaTeX etc. + if (latexSpecialPhrase(os, i, column, runparams)) + return; - // I assume this is hack treating typewriter as verbatim - // FIXME UNICODE: This can fail if c cannot be encoded - // in the current encoding. - if (running_font.family() == Font::TYPEWRITER_FAMILY) { - if (c != '\0') { - os.put(c); - } + if (c == '\0') + return; + + Encoding const & encoding = *(runparams.encoding); + if (i + 1 < int(text_.size())) { + char_type next = text_[i + 1]; + if (Encodings::isCombiningChar(next)) { + column += latexSurrogatePair(os, c, next, encoding) - 1; + ++i; break; } + } + string script; + docstring const latex = encoding.latexChar(c); + if (Encodings::isKnownScriptChar(c, script) + && prefixIs(latex, from_ascii("\\" + script))) + column += writeScriptChars(os, latex, + running_change, encoding, i) - 1; + else if (latex.length() > 1 && latex[latex.length() - 1] != '}') { + // Prevent eating of a following + // space or command corruption by + // following characters + column += latex.length() + 1; + os << latex << "{}"; + } else { + column += latex.length() - 1; + os << latex; + } + break; + } +} - // LyX, LaTeX etc. - // FIXME: if we have "LaTeX" with a font - // change in the middle (before the 'T', then - // the "TeX" part is still special cased. - // Really we should only operate this on - // "words" for some definition of word +bool Paragraph::Private::latexSpecialT1(char_type const c, odocstream & os, + pos_type & i, unsigned int & column) +{ + switch (c) { + case '>': + case '<': + os.put(c); + // In T1 encoding, these characters exist + // but we should avoid ligatures + if (i + 1 >= int(text_.size()) || text_[i + 1] != c) + return true; + os << "\\,{}"; + column += 3; + // Alternative code: + //os << "\\textcompwordmark{}"; + //column += 19; + return true; + case '|': + os.put(c); + return true; + default: + return false; + } +} - size_t pnr = 0; - for (; pnr < phrases_nr; ++pnr) { - if (isTextAt(special_phrases[pnr].phrase, i)) { - os << special_phrases[pnr].macro; - i += special_phrases[pnr].phrase.length() - 1; - column += special_phrases[pnr].macro.length() - 1; - break; - } - } +bool Paragraph::Private::latexSpecialTypewriter(char_type const c, odocstream & os, + pos_type & i, unsigned int & column) +{ + switch (c) { + case '-': + if (i + 1 < int(text_.size()) && text_[i + 1] == '-') { + // "--" in Typewriter mode -> "-{}-" + os << "-{}"; + column += 2; + } else + os << '-'; + return true; - if (pnr == phrases_nr && c != '\0') { - Encoding const & encoding = *(runparams.encoding); - if (i + 1 < size()) { - char_type next = getChar(i + 1); - if (Encodings::isCombiningChar(next)) { - column += latexSurrogatePair(os, c, next, encoding) - 1; - ++i; - break; - } - } - string preamble; - if (Encodings::isKnownLangChar(c, preamble)) { - column += - knownLangChars(os, c, preamble, - running_change, - encoding, i) - 1; - break; - } - docstring const latex = encoding.latexChar(c); - if (latex.length() > 1 && - latex[latex.length() - 1] != '}') { - // Prevent eating of a following - // space or command corruption by - // following characters - column += latex.length() + 1; - os << latex << "{}"; - } else { - column += latex.length() - 1; - os << latex; - } - } - break; - } + // I assume this is hack treating typewriter as verbatim + // FIXME UNICODE: This can fail if c cannot be encoded + // in the current encoding. + + case '\0': + return true; + + // Those characters are not directly supported. + case '\\': + case '\"': + case '$': case '&': + case '%': case '#': case '{': + case '}': case '_': + case '~': + case '^': + case '*': case '[': + case ' ': + return false; + + default: + // With Typewriter font, these characters exist. + os.put(c); + return true; + } +} + + +bool Paragraph::Private::latexSpecialPhrase(odocstream & os, pos_type & i, + unsigned int & column, OutputParams & runparams) +{ + // FIXME: if we have "LaTeX" with a font + // change in the middle (before the 'T', then + // the "TeX" part is still special cased. + // Really we should only operate this on + // "words" for some definition of word + + for (size_t pnr = 0; pnr < phrases_nr; ++pnr) { + if (!isTextAt(special_phrases[pnr].phrase, i)) + continue; + if (runparams.moving_arg) + os << "\\protect"; + os << special_phrases[pnr].macro; + i += special_phrases[pnr].phrase.length() - 1; + column += special_phrases[pnr].macro.length() - 1; + return true; } + return false; } void Paragraph::Private::validate(LaTeXFeatures & features, Layout const & layout) const { - BufferParams const & bparams = features.bufferParams(); - // check the params. if (!params_.spacing().isDefault()) features.require("setspace"); @@ -939,47 +991,9 @@ void Paragraph::Private::validate(LaTeXFeatures & features, features.useLayout(layout.name()); // then the fonts - Language const * doc_language = bparams.language; - - FontList::const_iterator fcit = fontlist_.begin(); - FontList::const_iterator fend = fontlist_.end(); - for (; fcit != fend; ++fcit) { - if (fcit->font().noun() == Font::ON) { - LYXERR(Debug::LATEX) << "font.noun: " - << fcit->font().noun() - << endl; - features.require("noun"); - LYXERR(Debug::LATEX) << "Noun enabled. Font: " - << to_utf8(fcit->font().stateText(0)) - << endl; - } - switch (fcit->font().color()) { - case Color::none: - case Color::inherit: - case Color::ignore: - // probably we should put here all interface colors used for - // font displaying! For now I just add this ones I know of (Jug) - case Color::latex: - case Color::note: - break; - default: - features.require("color"); - LYXERR(Debug::LATEX) << "Color enabled. Font: " - << to_utf8(fcit->font().stateText(0)) - << endl; - } - - Language const * language = fcit->font().language(); - if (language->babel() != doc_language->babel() && - language != ignore_language && - language != latex_language) - { - features.useLanguage(language); - LYXERR(Debug::LATEX) << "Found language " - << language->lang() << endl; - } - } + fontlist_.validate(features); + // then the indentation if (!params_.leftIndent().zero()) features.require("ParagraphLeftIndent"); @@ -996,7 +1010,7 @@ void Paragraph::Private::validate(LaTeXFeatures & features, } // then the contents - for (pos_type i = 0; i < size() ; ++i) { + for (pos_type i = 0; i < int(text_.size()) ; ++i) { for (size_t pnr = 0; pnr < phrases_nr; ++pnr) { if (!special_phrases[pnr].builtin && isTextAt(special_phrases[pnr].phrase, i)) { @@ -1004,24 +1018,18 @@ void Paragraph::Private::validate(LaTeXFeatures & features, break; } } - Encodings::validate(getChar(i), features); + Encodings::validate(text_[i], features); } } - -} // namespace lyx - - ///////////////////////////////////////////////////////////////////// // // Paragraph // ///////////////////////////////////////////////////////////////////// -namespace lyx { - Paragraph::Paragraph() - : begin_of_body_(0), d(new Paragraph::Private(this)) + : d(new Paragraph::Private(this)) { itemdepth = 0; d->params_.clear(); @@ -1030,8 +1038,6 @@ Paragraph::Paragraph() Paragraph::Paragraph(Paragraph const & par) : itemdepth(par.itemdepth), - layout_(par.layout_), - text_(par.text_), begin_of_body_(par.begin_of_body_), d(new Paragraph::Private(*par.d, this)) { } @@ -1042,9 +1048,6 @@ Paragraph & Paragraph::operator=(Paragraph const & par) // needed as we will destroy the private part before copying it if (&par != this) { itemdepth = par.itemdepth; - layout_ = par.layout(); - text_ = par.text_; - begin_of_body_ = par.begin_of_body_; delete d; d = new Private(*par.d, this); @@ -1083,7 +1086,7 @@ void Paragraph::write(Buffer const & buf, ostream & os, params().write(os); - Font font1(Font::ALL_INHERIT, bparams.language); + Font font1(inherit_font, bparams.language); Change running_change = Change(Change::UNCHANGED); @@ -1105,12 +1108,10 @@ void Paragraph::write(Buffer const & buf, ostream & os, font1 = font2; } - value_type const c = getChar(i); + char_type const c = d->text_[i]; switch (c) { case META_INSET: - { - Inset const * inset = getInset(i); - if (inset) + if (Inset const * inset = getInset(i)) { if (inset->directWrite()) { // international char, let it write // code directly so it's shorter in @@ -1124,14 +1125,14 @@ void Paragraph::write(Buffer const & buf, ostream & os, os << "\n\\end_inset\n\n"; column = 0; } - } - break; + } + break; case '\\': os << "\n\\backslash\n"; column = 0; break; case '.': - if (i + 1 < size() && getChar(i + 1) == ' ') { + if (i + 1 < size() && d->text_[i + 1] == ' ') { os << ".\n"; column = 0; } else @@ -1145,11 +1146,9 @@ void Paragraph::write(Buffer const & buf, ostream & os, } // this check is to amend a bug. LyX sometimes // inserts '\0' this could cause problems. - if (c != '\0') { - std::vector tmp = ucs4_to_utf8(c); - tmp.push_back('\0'); - os << &tmp[0]; - } else + if (c != '\0') + os << to_utf8(docstring(1, c)); + else lyxerr << "ERROR (Paragraph::writeFile):" " NULL char in structure." << endl; ++column; @@ -1175,7 +1174,41 @@ void Paragraph::insert(pos_type start, docstring const & str, } -void Paragraph::insertChar(pos_type pos, Paragraph::value_type c, +void Paragraph::appendChar(char_type c, Font const & font, + Change const & change) +{ + // track change + d->changes_.insert(change, d->text_.size()); + // when appending characters, no need to update tables + d->text_.push_back(c); + setFont(d->text_.size() - 1, font); +} + + +void Paragraph::appendString(docstring const & s, Font const & font, + Change const & change) +{ + pos_type end = s.size(); + size_t oldsize = d->text_.size(); + size_t newsize = oldsize + end; + size_t capacity = d->text_.capacity(); + if (newsize >= capacity) + d->text_.reserve(max(capacity + 100, newsize)); + + // when appending characters, no need to update tables + d->text_.append(s); + + // FIXME: Optimize this! + for (pos_type i = 0; i != end; ++i) { + // track change + d->changes_.insert(change, i); + } + d->fontlist_.set(oldsize, font); + d->fontlist_.set(newsize - 1, font); +} + + +void Paragraph::insertChar(pos_type pos, char_type c, bool trackChanges) { d->insertChar(pos, c, Change(trackChanges ? @@ -1183,7 +1216,7 @@ void Paragraph::insertChar(pos_type pos, Paragraph::value_type c, } -void Paragraph::insertChar(pos_type pos, Paragraph::value_type c, +void Paragraph::insertChar(pos_type pos, char_type c, Font const & font, bool trackChanges) { d->insertChar(pos, c, Change(trackChanges ? @@ -1192,7 +1225,7 @@ void Paragraph::insertChar(pos_type pos, Paragraph::value_type c, } -void Paragraph::insertChar(pos_type pos, Paragraph::value_type c, +void Paragraph::insertChar(pos_type pos, char_type c, Font const & font, Change const & change) { d->insertChar(pos, c, change); @@ -1215,6 +1248,13 @@ bool Paragraph::insetAllowed(InsetCode code) } +void Paragraph::resetFonts(Font const & font) +{ + d->fontlist_.clear(); + d->fontlist_.set(0, font); + d->fontlist_.set(d->text_.size() - 1, font); +} + // Gets uninstantiated font setting at position. Font const Paragraph::getFontSettings(BufferParams const & bparams, pos_type pos) const @@ -1224,19 +1264,14 @@ Font const Paragraph::getFontSettings(BufferParams const & bparams, BOOST_ASSERT(pos <= size()); } - FontList::const_iterator cit = d->fontlist_.begin(); - FontList::const_iterator end = d->fontlist_.end(); - for (; cit != end; ++cit) - if (cit->pos() >= pos) - break; - - if (cit != end) + FontList::const_iterator cit = d->fontlist_.fontIterator(pos); + if (cit != d->fontlist_.end()) return cit->font(); if (pos == size() && !empty()) return getFontSettings(bparams, pos - 1); - return Font(Font::ALL_INHERIT, getParLanguage(bparams)); + return Font(inherit_font, getParLanguage(bparams)); } @@ -1250,11 +1285,11 @@ FontSpan Paragraph::fontSpan(pos_type pos) const for (; cit != end; ++cit) { if (cit->pos() >= pos) { if (pos >= beginOfBody()) - return FontSpan(std::max(start, beginOfBody()), + return FontSpan(max(start, beginOfBody()), cit->pos()); else return FontSpan(start, - std::min(beginOfBody() - 1, + min(beginOfBody() - 1, cit->pos())); } start = cit->pos() + 1; @@ -1273,7 +1308,7 @@ Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const if (!empty() && !d->fontlist_.empty()) return d->fontlist_.begin()->font(); - return Font(Font::ALL_INHERIT, bparams.language); + return Font(inherit_font, bparams.language); } @@ -1291,12 +1326,12 @@ Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos, pos_type const body_pos = beginOfBody(); if (pos < body_pos) - font.realize(layout_->labelfont); + font.fontInfo().realize(d->layout_->labelfont); else - font.realize(layout_->font); + font.fontInfo().realize(d->layout_->font); - font.realize(outerfont); - font.realize(bparams.getFont()); + font.fontInfo().realize(outerfont.fontInfo()); + font.fontInfo().realize(bparams.getFont().fontInfo()); return font; } @@ -1305,41 +1340,39 @@ Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos, Font const Paragraph::getLabelFont (BufferParams const & bparams, Font const & outerfont) const { - Font tmpfont = layout()->labelfont; - tmpfont.setLanguage(getParLanguage(bparams)); - tmpfont.realize(outerfont); - tmpfont.realize(bparams.getFont()); - return tmpfont; + FontInfo tmpfont = layout()->labelfont; + tmpfont.realize(outerfont.fontInfo()); + tmpfont.realize(bparams.getFont().fontInfo()); + return Font(tmpfont, getParLanguage(bparams)); } Font const Paragraph::getLayoutFont (BufferParams const & bparams, Font const & outerfont) const { - Font tmpfont = layout()->font; - tmpfont.setLanguage(getParLanguage(bparams)); - tmpfont.realize(outerfont); - tmpfont.realize(bparams.getFont()); - return tmpfont; + FontInfo tmpfont = layout()->font; + tmpfont.realize(outerfont.fontInfo()); + tmpfont.realize(bparams.getFont().fontInfo()); + return Font(tmpfont, getParLanguage(bparams)); } /// Returns the height of the highest font in range -Font_size Paragraph::highestFontInRange - (pos_type startpos, pos_type endpos, Font_size def_size) const +FontSize Paragraph::highestFontInRange + (pos_type startpos, pos_type endpos, FontSize def_size) const { return d->fontlist_.highestInRange(startpos, endpos, def_size); } -Paragraph::value_type +char_type Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const { - value_type c = getChar(pos); + char_type c = d->text_[pos]; if (!lyxrc.rtl_support) return c; - value_type uc = c; + char_type uc = c; switch (c) { case '(': uc = ')'; @@ -1467,7 +1500,7 @@ void Paragraph::setLabelWidthString(docstring const & s) docstring const Paragraph::translateIfPossible(docstring const & s, BufferParams const & bparams) const { - if (!support::isAscii(s) || s.empty()) { + if (!isAscii(s) || s.empty()) { // This must be a user defined layout. We cannot translate // this, since gettext accepts only ascii keys. return s; @@ -1529,14 +1562,14 @@ void Paragraph::applyLayout(LayoutPtr const & new_layout) pos_type Paragraph::beginOfBody() const { - return begin_of_body_; + return d->begin_of_body_; } void Paragraph::setBeginOfBody() { if (layout()->labeltype != LABEL_MANUAL) { - begin_of_body_ = 0; + d->begin_of_body_ = 0; return; } @@ -1550,11 +1583,11 @@ void Paragraph::setBeginOfBody() char_type previous_char = 0; char_type temp = 0; if (i < end) { - previous_char = text_[i]; + previous_char = d->text_[i]; if (!isNewline(i)) { ++i; while (i < end && previous_char != ' ') { - temp = text_[i]; + temp = d->text_[i]; if (isNewline(i)) break; ++i; @@ -1564,31 +1597,7 @@ void Paragraph::setBeginOfBody() } } - begin_of_body_ = i; -} - - -// returns -1 if inset not found -int Paragraph::getPositionOfInset(Inset const * inset) const -{ - // Find the entry. - InsetList::const_iterator it = d->insetlist_.begin(); - InsetList::const_iterator end = d->insetlist_.end(); - for (; it != end; ++it) - if (it->inset == inset) - return it->pos; - return -1; -} - - -InsetBibitem * Paragraph::bibitem() const -{ - if (!d->insetlist_.empty()) { - Inset * inset = d->insetlist_.begin()->inset; - if (inset->lyxCode() == BIBITEM_CODE) - return static_cast(inset); - } - return 0; + d->begin_of_body_ = i; } @@ -1650,21 +1659,20 @@ void adjust_row_column(string const & str, TexRow & texrow, int & column) } // namespace anon -// This could go to ParagraphParameters if we want to -int Paragraph::startTeXParParams(BufferParams const & bparams, +int Paragraph::Private::startTeXParParams(BufferParams const & bparams, odocstream & os, TexRow & texrow, bool moving_arg) const { int column = 0; - if (params().noindent()) { + if (params_.noindent()) { os << "\\noindent "; column += 10; } - LyXAlignment const curAlign = params().align(); + LyXAlignment const curAlign = params_.align(); - if (curAlign == layout()->align) + if (curAlign == layout_->align) return column; switch (curAlign) { @@ -1691,25 +1699,25 @@ int Paragraph::startTeXParParams(BufferParams const & bparams, break; case LYX_ALIGN_LEFT: { string output; - if (getParLanguage(bparams)->babel() != "hebrew") - output = corrected_env("\\begin", "flushleft", ownerCode()); + if (owner_->getParLanguage(bparams)->babel() != "hebrew") + output = corrected_env("\\begin", "flushleft", owner_->ownerCode()); else - output = corrected_env("\\begin", "flushright", ownerCode()); + output = corrected_env("\\begin", "flushright", owner_->ownerCode()); os << from_ascii(output); adjust_row_column(output, texrow, column); break; } case LYX_ALIGN_RIGHT: { string output; - if (getParLanguage(bparams)->babel() != "hebrew") - output = corrected_env("\\begin", "flushright", ownerCode()); + if (owner_->getParLanguage(bparams)->babel() != "hebrew") + output = corrected_env("\\begin", "flushright", owner_->ownerCode()); else - output = corrected_env("\\begin", "flushleft", ownerCode()); + output = corrected_env("\\begin", "flushleft", owner_->ownerCode()); os << from_ascii(output); adjust_row_column(output, texrow, column); break; } case LYX_ALIGN_CENTER: { string output; - output = corrected_env("\\begin", "center", ownerCode()); + output = corrected_env("\\begin", "center", owner_->ownerCode()); os << from_ascii(output); adjust_row_column(output, texrow, column); break; @@ -1720,14 +1728,13 @@ int Paragraph::startTeXParParams(BufferParams const & bparams, } -// This could go to ParagraphParameters if we want to -int Paragraph::endTeXParParams(BufferParams const & bparams, +int Paragraph::Private::endTeXParParams(BufferParams const & bparams, odocstream & os, TexRow & texrow, bool moving_arg) const { int column = 0; - switch (params().align()) { + switch (params_.align()) { case LYX_ALIGN_NONE: case LYX_ALIGN_BLOCK: case LYX_ALIGN_LAYOUT: @@ -1743,7 +1750,7 @@ int Paragraph::endTeXParParams(BufferParams const & bparams, break; } - switch (params().align()) { + switch (params_.align()) { case LYX_ALIGN_NONE: case LYX_ALIGN_BLOCK: case LYX_ALIGN_LAYOUT: @@ -1751,25 +1758,25 @@ int Paragraph::endTeXParParams(BufferParams const & bparams, break; case LYX_ALIGN_LEFT: { string output; - if (getParLanguage(bparams)->babel() != "hebrew") - output = corrected_env("\n\\par\\end", "flushleft", ownerCode()); + if (owner_->getParLanguage(bparams)->babel() != "hebrew") + output = corrected_env("\n\\par\\end", "flushleft", owner_->ownerCode()); else - output = corrected_env("\n\\par\\end", "flushright", ownerCode()); + output = corrected_env("\n\\par\\end", "flushright", owner_->ownerCode()); os << from_ascii(output); adjust_row_column(output, texrow, column); break; } case LYX_ALIGN_RIGHT: { string output; - if (getParLanguage(bparams)->babel() != "hebrew") - output = corrected_env("\n\\par\\end", "flushright", ownerCode()); + if (owner_->getParLanguage(bparams)->babel() != "hebrew") + output = corrected_env("\n\\par\\end", "flushright", owner_->ownerCode()); else - output = corrected_env("\n\\par\\end", "flushleft", ownerCode()); + output = corrected_env("\n\\par\\end", "flushleft", owner_->ownerCode()); os << from_ascii(output); adjust_row_column(output, texrow, column); break; } case LYX_ALIGN_CENTER: { string output; - output = corrected_env("\n\\par\\end", "center", ownerCode()); + output = corrected_env("\n\\par\\end", "center", owner_->ownerCode()); os << from_ascii(output); adjust_row_column(output, texrow, column); break; @@ -1781,13 +1788,13 @@ int Paragraph::endTeXParParams(BufferParams const & bparams, // This one spits out the text of the paragraph -bool Paragraph::simpleTeXOnePar(Buffer const & buf, +bool Paragraph::latex(Buffer const & buf, BufferParams const & bparams, Font const & outerfont, odocstream & os, TexRow & texrow, OutputParams const & runparams) const { - LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl; + LYXERR(Debug::LATEX, "SimpleTeXOnePar... " << this); bool return_value = false; @@ -1844,7 +1851,7 @@ bool Paragraph::simpleTeXOnePar(Buffer const & buf, ++column; } if (!asdefault) - column += startTeXParParams(bparams, os, texrow, + column += d->startTeXParParams(bparams, os, texrow, runparams.moving_arg); } @@ -1874,7 +1881,7 @@ bool Paragraph::simpleTeXOnePar(Buffer const & buf, } if (!asdefault) - column += startTeXParParams(bparams, os, + column += d->startTeXParParams(bparams, os, texrow, runparams.moving_arg); } @@ -1903,8 +1910,6 @@ bool Paragraph::simpleTeXOnePar(Buffer const & buf, ++column; - value_type const c = getChar(i); - // Fully instantiated font Font const font = getFont(bparams, i, outerfont); @@ -1922,18 +1927,30 @@ bool Paragraph::simpleTeXOnePar(Buffer const & buf, open_font = false; } - // Switch file encoding if necessary - if (runparams.encoding->package() == Encoding::inputenc && - font.language()->encoding()->package() == Encoding::inputenc) { - std::pair const enc_switch = switchEncoding(os, bparams, - runparams.moving_arg, *(runparams.encoding), - *(font.language()->encoding())); + // close babel's font environment before opening CJK. + if (!running_font.language()->babel().empty() && + font.language()->encoding()->package() == Encoding::CJK) { + string end_tag = subst(lyxrc.language_command_end, + "$$lang", + running_font.language()->babel()); + os << from_ascii(end_tag); + column += end_tag.length(); + } + + // Switch file encoding if necessary (and allowed) + if (!runparams.verbatim && + runparams.encoding->package() == Encoding::none && + font.language()->encoding()->package() == Encoding::none) { + pair const enc_switch = switchEncoding(os, bparams, + runparams, *(font.language()->encoding())); if (enc_switch.first) { column += enc_switch.second; runparams.encoding = font.language()->encoding(); } } + char_type const c = d->text_[i]; + // Do we need to change font? if ((font != running_font || font.language() != running_font.language()) && @@ -1956,21 +1973,22 @@ bool Paragraph::simpleTeXOnePar(Buffer const & buf, } if (c == ' ') { + // FIXME: integrate this case in latexSpecialChar // Do not print the separation of the optional argument // if style->pass_thru is false. This works because - // simpleTeXSpecialChars ignores spaces if + // latexSpecialChar ignores spaces if // style->pass_thru is false. if (i != body_pos - 1) { if (d->simpleTeXBlanks( - *(runparams.encoding), os, texrow, - i, column, font, *style)) + runparams, os, texrow, + i, column, font, *style)) { // A surrogate pair was output. We - // must not call simpleTeXSpecialChars - // in this iteration, since - // simpleTeXBlanks incremented i, and - // simpleTeXSpecialChars would output + // must not call latexSpecialChar + // in this iteration, since it would output // the combining character again. + ++i; continue; + } } } @@ -1978,10 +1996,33 @@ bool Paragraph::simpleTeXOnePar(Buffer const & buf, rp.free_spacing = style->free_spacing; rp.local_font = &font; rp.intitle = style->intitle; - d->simpleTeXSpecialChars(buf, bparams, os, + + // Two major modes: LaTeX or plain + // Handle here those cases common to both modes + // and then split to handle the two modes separately. + if (c == META_INSET) + d->latexInset(buf, bparams, os, texrow, rp, running_font, basefont, outerfont, open_font, - runningChange, *style, i, column, c); + runningChange, *style, i, column); + else { + try { + d->latexSpecialChar(os, rp, running_font, runningChange, + *style, i, column); + } catch (EncodingException & e) { + if (runparams.dryrun) { + os << "<" << _("LyX Warning: ") + << _("uncodable character") << " '"; + os.put(c); + os << "'>"; + } else { + // add location information and throw again. + e.par_id = id(); + e.pos = i; + throw(e); + } + } + } // Set the encoding to that returned from simpleTeXSpecialChars (see // comment for encoding member in OutputParams.h) @@ -2018,63 +2059,19 @@ bool Paragraph::simpleTeXOnePar(Buffer const & buf, } if (!asdefault) { - column += endTeXParParams(bparams, os, texrow, + column += d->endTeXParParams(bparams, os, texrow, runparams.moving_arg); } - LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl; + LYXERR(Debug::LATEX, "SimpleTeXOnePar...done " << this); return return_value; } -namespace { - -enum PAR_TAG { - PAR_NONE=0, - TT = 1, - SF = 2, - BF = 4, - IT = 8, - SL = 16, - EM = 32 -}; - - -string tag_name(PAR_TAG const & pt) { - switch (pt) { - case PAR_NONE: return "!-- --"; - case TT: return "tt"; - case SF: return "sf"; - case BF: return "bf"; - case IT: return "it"; - case SL: return "sl"; - case EM: return "em"; - } - return ""; -} - - -inline -void operator|=(PAR_TAG & p1, PAR_TAG const & p2) -{ - p1 = static_cast(p1 | p2); -} - - -inline -void reset(PAR_TAG & p1, PAR_TAG const & p2) -{ - p1 = static_cast(p1 & ~p2); -} - -} // anon - - bool Paragraph::emptyTag() const { for (pos_type i = 0; i < size(); ++i) { - if (isInset(i)) { - Inset const * inset = getInset(i); + if (Inset const * inset = getInset(i)) { InsetCode lyx_code = inset->lyxCode(); if (lyx_code != TOC_CODE && lyx_code != INCLUDE_CODE && @@ -2086,7 +2083,7 @@ bool Paragraph::emptyTag() const return false; } } else { - value_type c = getChar(i); + char_type c = d->text_[i]; if (c != ' ' && c != '\t') return false; } @@ -2098,15 +2095,14 @@ bool Paragraph::emptyTag() const string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const { for (pos_type i = 0; i < size(); ++i) { - if (isInset(i)) { - Inset const * inset = getInset(i); + if (Inset const * inset = getInset(i)) { InsetCode lyx_code = inset->lyxCode(); if (lyx_code == LABEL_CODE) { - string const id = static_cast(inset)->getContents(); - return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'"; + InsetLabel const * const il = static_cast(inset); + docstring const & id = il->getParam("name"); + return "id='" + to_utf8(sgml::cleanID(buf, runparams, id)) + "'"; } } - } return string(); } @@ -2116,11 +2112,10 @@ pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputPara { pos_type i; for (i = 0; i < size(); ++i) { - if (isInset(i)) { - Inset const * inset = getInset(i); + if (Inset const * inset = getInset(i)) { inset->docbook(buf, os, runparams); } else { - value_type c = getChar(i); + char_type c = d->text_[i]; if (c == ' ') break; os << sgml::escapeChar(c); @@ -2130,13 +2125,13 @@ pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputPara } -bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const +bool Paragraph::Private::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const { Font font_old; - - for (pos_type i = initial; i < size(); ++i) { - Font font = getFont(buf.params(), i, outerfont); - if (isInset(i)) + pos_type size = text_.size(); + for (pos_type i = initial; i < size; ++i) { + Font font = owner_->getFont(buf.params(), i, outerfont); + if (text_[i] == META_INSET) return false; if (i != initial && font != font_old) return false; @@ -2156,10 +2151,10 @@ void Paragraph::simpleDocBookOnePar(Buffer const & buf, bool emph_flag = false; LayoutPtr const & style = layout(); - Font font_old = + FontInfo font_old = style->labeltype == LABEL_MANUAL ? style->labelfont : style->font; - if (style->pass_thru && !onlyText(buf, outerfont, initial)) + if (style->pass_thru && !d->onlyText(buf, outerfont, initial)) os << "]]>"; // parsing main loop @@ -2167,8 +2162,8 @@ void Paragraph::simpleDocBookOnePar(Buffer const & buf, Font font = getFont(buf.params(), i, outerfont); // handle tag - if (font_old.emph() != font.emph()) { - if (font.emph() == Font::ON) { + if (font_old.emph() != font.fontInfo().emph()) { + if (font.fontInfo().emph() == FONT_ON) { os << ""; emph_flag = true; } else if (i != initial) { @@ -2177,18 +2172,17 @@ void Paragraph::simpleDocBookOnePar(Buffer const & buf, } } - if (isInset(i)) { - Inset const * inset = getInset(i); + if (Inset const * inset = getInset(i)) { inset->docbook(buf, os, runparams); } else { - value_type c = getChar(i); + char_type c = d->text_[i]; if (style->pass_thru) os.put(c); else os << sgml::escapeChar(c); } - font_old = font; + font_old = font.fontInfo(); } if (emph_flag) { @@ -2197,43 +2191,42 @@ void Paragraph::simpleDocBookOnePar(Buffer const & buf, if (style->free_spacing) os << '\n'; - if (style->pass_thru && !onlyText(buf, outerfont, initial)) + if (style->pass_thru && !d->onlyText(buf, outerfont, initial)) os << "lyxCode() == HFILL_CODE; + Inset const * inset = getInset(pos); + return inset && inset->lyxCode() == HFILL_CODE; } bool Paragraph::isNewline(pos_type pos) const { - return isInset(pos) - && getInset(pos)->lyxCode() == NEWLINE_CODE; + Inset const * inset = getInset(pos); + return inset && inset->lyxCode() == NEWLINE_CODE; } bool Paragraph::isLineSeparator(pos_type pos) const { - value_type const c = getChar(pos); - return isLineSeparatorChar(c) - || (c == Paragraph::META_INSET && getInset(pos) && - getInset(pos)->isLineSeparator()); + char_type const c = d->text_[pos]; + if (isLineSeparatorChar(c)) + return true; + Inset const * inset = getInset(pos); + return inset && inset->isLineSeparator(); } /// Used by the spellchecker bool Paragraph::isLetter(pos_type pos) const { - if (isInset(pos)) - return getInset(pos)->isLetter(); - else { - value_type const c = getChar(pos); - return isLetterChar(c) || isDigit(c); - } + if (Inset const * inset = getInset(pos)) + return inset->isLetter(); + char_type const c = d->text_[pos]; + return isLetterChar(c) || isDigit(c); } @@ -2303,7 +2296,7 @@ docstring const Paragraph::asString(Buffer const & buffer, os << params().labelString() << ' '; for (pos_type i = beg; i < end; ++i) { - value_type const c = getChar(i); + char_type const c = d->text_[i]; if (isPrintable(c)) os.put(c); else if (c == META_INSET) @@ -2328,13 +2321,13 @@ int Paragraph::id() const LayoutPtr const & Paragraph::layout() const { - return layout_; + return d->layout_; } void Paragraph::layout(LayoutPtr const & new_layout) { - layout_ = new_layout; + d->layout_ = new_layout; } @@ -2346,8 +2339,7 @@ Inset * Paragraph::inInset() const InsetCode Paragraph::ownerCode() const { - return d->inset_owner_ ? - d->inset_owner_->lyxCode() : NO_CODE; + return d->inset_owner_ ? d->inset_owner_->lyxCode() : NO_CODE; } @@ -2367,10 +2359,7 @@ bool Paragraph::isFreeSpacing() const { if (layout()->free_spacing) return true; - - // for now we just need this, later should we need this in some - // other way we can always add a function to Inset too. - return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE; + return d->inset_owner_ && d->inset_owner_->isFreeSpacing(); } @@ -2378,7 +2367,7 @@ bool Paragraph::allowEmpty() const { if (layout()->keepempty) return true; - return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE; + return d->inset_owner_ && d->inset_owner_->allowEmpty(); } @@ -2387,11 +2376,11 @@ char_type Paragraph::transformChar(char_type c, pos_type pos) const if (!Encodings::is_arabic(c)) return c; - value_type prev_char = ' '; - value_type next_char = ' '; + char_type prev_char = ' '; + char_type next_char = ' '; for (pos_type i = pos - 1; i >= 0; --i) { - value_type const par_char = getChar(i); + char_type const par_char = d->text_[i]; if (!Encodings::isComposeChar_arabic(par_char)) { prev_char = par_char; break; @@ -2399,7 +2388,7 @@ char_type Paragraph::transformChar(char_type c, pos_type pos) const } for (pos_type i = pos + 1, end = size(); i < end; ++i) { - value_type const par_char = getChar(i); + char_type const par_char = d->text_[i]; if (!Encodings::isComposeChar_arabic(par_char)) { next_char = par_char; break; @@ -2434,7 +2423,7 @@ int Paragraph::checkBiblio(bool track_changes) bool hasbibitem = !d->insetlist_.empty() // Insist on it being in pos 0 - && getChar(0) == Paragraph::META_INSET + && d->text_[0] == META_INSET && d->insetlist_.begin()->inset->lyxCode() == BIBITEM_CODE; docstring oldkey; @@ -2477,7 +2466,7 @@ int Paragraph::checkBiblio(bool track_changes) //There was no inset at the beginning, so we need to create one with //the key and label of the one we erased. - InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem"))); + InsetBibitem * inset(new InsetBibitem(InsetCommandParams(BIBITEM_CODE))); // restore values of previously deleted item in this par. if (!oldkey.empty()) inset->setParam("key", oldkey); @@ -2530,13 +2519,145 @@ Inset * Paragraph::releaseInset(pos_type pos) Inset * Paragraph::getInset(pos_type pos) { - return d->insetlist_.get(pos); + return (pos < pos_type(d->text_.size()) && d->text_[pos] == META_INSET) + ? d->insetlist_.get(pos) : 0; } Inset const * Paragraph::getInset(pos_type pos) const { - return d->insetlist_.get(pos); + return (pos < pos_type(d->text_.size()) && d->text_[pos] == META_INSET) + ? d->insetlist_.get(pos) : 0; +} + + +void Paragraph::changeCase(BufferParams const & bparams, pos_type pos, + pos_type & right, TextCase action) +{ + // process sequences of modified characters; in change + // tracking mode, this approach results in much better + // usability than changing case on a char-by-char basis + docstring changes; + + bool const trackChanges = bparams.trackChanges; + + bool capitalize = true; + + for (; pos < right; ++pos) { + char_type oldChar = d->text_[pos]; + char_type newChar = oldChar; + + // ignore insets and don't play with deleted text! + if (oldChar != META_INSET && !isDeleted(pos)) { + switch (action) { + case text_lowercase: + newChar = lowercase(oldChar); + break; + case text_capitalization: + if (capitalize) { + newChar = uppercase(oldChar); + capitalize = false; + } + break; + case text_uppercase: + newChar = uppercase(oldChar); + break; + } + } + + if (!isLetter(pos) || isDeleted(pos)) { + // permit capitalization again + capitalize = true; + } + + if (oldChar != newChar) + changes += newChar; + + if (oldChar == newChar || pos == right - 1) { + if (oldChar != newChar) { + // step behind the changing area + pos++; + } + int erasePos = pos - changes.size(); + for (size_t i = 0; i < changes.size(); i++) { + insertChar(pos, changes[i], + getFontSettings(bparams, + erasePos), + trackChanges); + if (!eraseChar(erasePos, trackChanges)) { + ++erasePos; + ++pos; // advance + ++right; // expand selection + } + } + changes.clear(); + } + } +} + + +bool Paragraph::find(docstring const & str, bool cs, bool mw, + pos_type pos, bool del) const +{ + int const strsize = str.length(); + int i = 0; + pos_type const parsize = d->text_.size(); + for (i = 0; pos + i < parsize; ++i) { + if (i >= strsize) + break; + if (cs && str[i] != d->text_[pos + i]) + break; + if (!cs && uppercase(str[i]) != uppercase(d->text_[pos + i])) + break; + if (!del && isDeleted(pos + i)) + break; + } + + if (i != strsize) + return false; + + // if necessary, check whether string matches word + if (mw) { + if (pos > 0 && isLetter(pos - 1)) + return false; + if (pos + strsize < parsize + && isLetter(pos + strsize)) + return false; + } + + return true; +} + + +char_type Paragraph::getChar(pos_type pos) const +{ + return d->text_[pos]; +} + + +pos_type Paragraph::size() const +{ + return d->text_.size(); +} + + +bool Paragraph::empty() const +{ + return d->text_.empty(); +} + + +bool Paragraph::isInset(pos_type pos) const +{ + return d->text_[pos] == META_INSET; +} + + +bool Paragraph::isSeparator(pos_type pos) const +{ + //FIXME: Are we sure this can be the only separator? + return d->text_[pos] == ' '; } + } // namespace lyx