X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=b5f31281f4da9ee0dff6d11b8825bdb8a68a63f1;hb=3256dbc78a76f947080dc8e3414abc60e4b5c5af;hp=de4021db3be9e3f178119f467327a28d08e7c7c9;hpb=be01974475eef7690005fd64d375337bfcd42811;p=lyx.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index de4021db3b..b5f31281f4 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -21,175 +21,102 @@ #include "Buffer.h" #include "BufferParams.h" +#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 "Row.h" -#include "Messages.h" #include "OutputParams.h" #include "output_latex.h" #include "paragraph_funcs.h" #include "ParagraphParameters.h" -#include "rowpainter.h" #include "sgml.h" +#include "TextClass.h" #include "TexRow.h" #include "VSpace.h" +#include "frontends/alert.h" #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 -#include #include +#include -using std::distance; -using std::endl; -using std::string; -using std::ostream; +using namespace std; +using namespace lyx::support; namespace lyx { -using support::contains; -using support::rsplit; - +namespace { +/// Inset identifier (above 0x10ffff, for ucs-4) +char_type const META_INSET = 0x200001; +}; ///////////////////////////////////////////////////////////////////// // -// Paragraph::Pimpl +// Paragraph::Private // ///////////////////////////////////////////////////////////////////// -class Encoding; -class Layout; - - -class Paragraph::Pimpl { +class Paragraph::Private +{ public: /// - Pimpl(Paragraph * owner); + Private(Paragraph * owner); /// "Copy constructor" - Pimpl(Pimpl const &, Paragraph * owner); - - // - // Change tracking - // - /// look up change at given pos - Change const & lookupChange(pos_type pos) const; - /// is there a change within the given range ? - bool isChanged(pos_type start, pos_type end) const; - /// will the paragraph be physically merged with the next - /// one if the imaginary end-of-par character is logically deleted? - bool isMergedOnEndOfParDeletion(bool trackChanges) const; - /// set change for the entire par - void setChange(Change const & change); - /// set change at given pos - void setChange(pos_type pos, Change const & change); - /// accept changes within the given range - void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end); - /// reject changes within the given range - void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end); - - /// - value_type getChar(pos_type pos) const; - /// - void insertChar(pos_type pos, value_type c, Change const & change); - /// - void insertInset(pos_type pos, Inset * inset, Change const & change); - /// (logically) erase the char at pos; return true if it was actually erased - bool eraseChar(pos_type pos, bool trackChanges); - /// (logically) erase the given range; return the number of chars actually erased - int eraseChars(pos_type start, pos_type end, bool trackChanges); - /// - Inset * inset_owner; - - /** A font entry covers a range of positions. Notice that the - entries in the list are inserted in random order. - I don't think it's worth the effort to implement a more effective - datastructure, because the number of different fonts in a paragraph - is limited. (Asger) - Nevertheless, I decided to store fontlist using a sorted vector: - fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where - pos_1 < pos_2 < ..., font_{i-1} != font_i for all i, - and font_i covers the chars in positions pos_{i-1}+1,...,pos_i - (font_1 covers the chars 0,...,pos_1) (Dekel) - */ - class FontTable { - public: - /// - FontTable(pos_type p, Font const & f) - : pos_(p), font_(f) - {} - /// - pos_type pos() const { return pos_; } - /// - void pos(pos_type p) { pos_ = p; } - /// - Font const & font() const { return font_; } - /// - void font(Font const & f) { font_ = f;} - private: - /// End position of paragraph this font attribute covers - pos_type pos_; - /** Font. Interpretation of the font values: - If a value is Font::INHERIT_*, it means that the font - attribute is inherited from either the layout of this - paragraph or, in the case of nested paragraphs, from the - layout in the environment one level up until completely - resolved. - The values Font::IGNORE_* and Font::TOGGLE are NOT - allowed in these font tables. - */ - Font font_; - }; - /// - friend class matchFT; - /// - class matchFT { - public: - /// used by lower_bound and upper_bound - int operator()(FontTable const & a, FontTable const & b) const { - return a.pos() < b.pos(); - } - }; + Private(Private const &, Paragraph * owner); /// - typedef std::vector FontList; - /// - FontList fontlist; + 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 unicode chars, belonging to the same script as + /// specified by the latex macro \p ltx, to \p os starting from \p i. + /// \return the number of characters written. + int writeScriptChars(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 const &, + TexRow & texrow, OutputParams &, Font & running_font, Font & basefont, Font const & outerfont, @@ -197,43 +124,84 @@ 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; + /// 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(string const & str, pos_type pos) const; + + /// Which Paragraph owns us? + Paragraph * owner_; + + /// In which Inset? + Inset * inset_owner_; + + /// + FontList fontlist_; + /// unsigned int id_; /// static unsigned int paragraph_id; /// - ParagraphParameters params; - -private: - /// - pos_type size() const { return owner_->size(); } - /// match a string against a particular point in the paragraph - bool isTextAt(std::string const & str, pos_type pos) const; + ParagraphParameters params_; /// for recording and looking up changes Changes changes_; - /// Who owns us? - Paragraph * owner_; -}; - + /// + 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, -unsigned int Paragraph::Pimpl::paragraph_id = 0; +unsigned int Paragraph::Private::paragraph_id = 0; namespace { @@ -255,49 +223,47 @@ size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase); } // namespace anon -Paragraph::Pimpl::Pimpl(Paragraph * owner) - : owner_(owner) +Paragraph::Private::Private(Paragraph * owner) + : owner_(owner), inset_owner_(0), begin_of_body_(0) { - inset_owner = 0; id_ = paragraph_id++; + text_.reserve(100); } -Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner) - : params(p.params), changes_(p.changes_), owner_(owner) +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_), + layout_(p.layout_), begin_of_body_(p.begin_of_body_), text_(p.text_) { - inset_owner = p.inset_owner; - fontlist = p.fontlist; id_ = paragraph_id++; } -bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const +bool Paragraph::isChanged(pos_type start, pos_type end) const { BOOST_ASSERT(start >= 0 && start <= size()); BOOST_ASSERT(end > start && end <= size() + 1); - return changes_.isChanged(start, end); + return d->changes_.isChanged(start, end); } -bool Paragraph::Pimpl::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 = changes_.lookup(size()); + Change const change = d->changes_.lookup(size()); return change.type == Change::INSERTED && change.author == 0; } -void Paragraph::Pimpl::setChange(Change const & change) +void Paragraph::setChange(Change const & change) { // beware of the imaginary end-of-par character! - changes_.set(change, 0, size() + 1); + d->changes_.set(change, 0, size() + 1); /* * Propagate the change recursively - but not in case of DELETED! @@ -313,38 +279,34 @@ void Paragraph::Pimpl::setChange(Change const & change) if (change.type != Change::DELETED) { for (pos_type pos = 0; pos < size(); ++pos) { - if (owner_->isInset(pos)) { - owner_->getInset(pos)->setChange(change); - } + if (Inset * inset = getInset(pos)) + inset->setChange(change); } } } -void Paragraph::Pimpl::setChange(pos_type pos, Change const & change) +void Paragraph::setChange(pos_type pos, Change const & change) { BOOST_ASSERT(pos >= 0 && pos <= size()); - - changes_.set(change, pos); + d->changes_.set(change, pos); // see comment in setChange(Change const &) above - - if (change.type != Change::DELETED && - pos < size() && owner_->isInset(pos)) { - owner_->getInset(pos)->setChange(change); - } + if (change.type != Change::DELETED && pos < size()) + if (Inset * inset = getInset(pos)) + inset->setChange(change); } -Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const +Change const & Paragraph::lookupChange(pos_type pos) const { BOOST_ASSERT(pos >= 0 && pos <= size()); - - return changes_.lookup(pos); + return d->changes_.lookup(pos); } -void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end) +void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, + pos_type end) { BOOST_ASSERT(start >= 0 && start <= size()); BOOST_ASSERT(end > start && end <= size() + 1); @@ -353,18 +315,15 @@ void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type star switch (lookupChange(pos).type) { case Change::UNCHANGED: // accept changes in nested inset - if (pos < size() && owner_->isInset(pos)) { - owner_->getInset(pos)->acceptChanges(bparams); - } - + if (Inset * inset = getInset(pos)) + inset->acceptChanges(bparams); break; case Change::INSERTED: - changes_.set(Change(Change::UNCHANGED), pos); + d->changes_.set(Change(Change::UNCHANGED), pos); // also accept changes in nested inset - if (pos < size() && owner_->isInset(pos)) { - owner_->getInset(pos)->acceptChanges(bparams); - } + if (Inset * inset = getInset(pos)) + inset->acceptChanges(bparams); break; case Change::DELETED: @@ -382,7 +341,8 @@ void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type star } -void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end) +void Paragraph::rejectChanges(BufferParams const & bparams, + pos_type start, pos_type end) { BOOST_ASSERT(start >= 0 && start <= size()); BOOST_ASSERT(end > start && end <= size() + 1); @@ -391,9 +351,8 @@ void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type star switch (lookupChange(pos).type) { case Change::UNCHANGED: // reject changes in nested inset - if (pos < size() && owner_->isInset(pos)) { - owner_->getInset(pos)->rejectChanges(bparams); - } + if (Inset * inset = getInset(pos)) + inset->rejectChanges(bparams); break; case Change::INSERTED: @@ -407,7 +366,7 @@ void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type star break; case Change::DELETED: - changes_.set(Change(Change::UNCHANGED), pos); + d->changes_.set(Change(Change::UNCHANGED), pos); // Do NOT reject changes within a deleted inset! // There may be insertions of a co-author inside of it! @@ -418,67 +377,54 @@ void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type star } -Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const +void Paragraph::Private::insertChar(pos_type pos, char_type c, + Change const & change) { - BOOST_ASSERT(pos >= 0 && pos <= size()); - - return owner_->getChar(pos); -} - - -void Paragraph::Pimpl::insertChar(pos_type pos, value_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. - FontTable search_font(pos, Font()); - for (FontList::iterator it - = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT()); - it != fontlist.end(); ++it) - { - it->pos(it->pos() + 1); - } + fontlist_.increasePosAfterPos(pos); // Update the insets - owner_->insetlist.increasePosAfterPos(pos); + insetlist_.increasePosAfterPos(pos); } -void Paragraph::Pimpl::insertInset(pos_type pos, Inset * inset, +void Paragraph::insertInset(pos_type pos, Inset * inset, Change const & change) { BOOST_ASSERT(inset); BOOST_ASSERT(pos >= 0 && pos <= size()); - insertChar(pos, META_INSET, change); - BOOST_ASSERT(owner_->text_[pos] == META_INSET); + d->insertChar(pos, META_INSET, change); + BOOST_ASSERT(d->text_[pos] == META_INSET); - // Add a new entry in the insetlist. - owner_->insetlist.insert(inset, pos); + // Add a new entry in the insetlist_. + d->insetlist_.insert(inset, pos); } -bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges) +bool Paragraph::eraseChar(pos_type pos, bool trackChanges) { BOOST_ASSERT(pos >= 0 && pos <= size()); // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion() if (trackChanges) { - Change change = changes_.lookup(pos); + Change change = d->changes_.lookup(pos); // set the character to DELETED if // a) it was previously unchanged or @@ -502,53 +448,25 @@ bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges) } // track change - changes_.erase(pos); + d->changes_.erase(pos); // if it is an inset, delete the inset entry - if (owner_->text_[pos] == Paragraph::META_INSET) { - owner_->insetlist.erase(pos); - } - - owner_->text_.erase(owner_->text_.begin() + pos); - - // Erase entries in the tables. - FontTable search_font(pos, Font()); - - FontList::iterator it = - lower_bound(fontlist.begin(), - fontlist.end(), - search_font, matchFT()); - if (it != fontlist.end() && it->pos() == pos && - (pos == 0 || - (it != fontlist.begin() - && boost::prior(it)->pos() == pos - 1))) { - // If it is a multi-character font - // entry, we just make it smaller - // (see update below), otherwise we - // should delete it. - unsigned int const i = it - fontlist.begin(); - fontlist.erase(fontlist.begin() + i); - it = fontlist.begin() + i; - if (i > 0 && i < fontlist.size() && - fontlist[i - 1].font() == fontlist[i].font()) { - fontlist.erase(fontlist.begin() + i - 1); - it = fontlist.begin() + i - 1; - } - } + if (d->text_[pos] == META_INSET) + d->insetlist_.erase(pos); + + d->text_.erase(d->text_.begin() + pos); - // Update all other entries - FontList::iterator fend = fontlist.end(); - for (; it != fend; ++it) - it->pos(it->pos() - 1); + // Update the fontlist_ + d->fontlist_.erase(pos); - // Update the insetlist - owner_->insetlist.decreasePosAfterPos(pos); + // Update the insetlist_ + d->insetlist_.decreasePosAfterPos(pos); return true; } -int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges) +int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges) { BOOST_ASSERT(start >= 0 && start <= size()); BOOST_ASSERT(end >= start && end <= size() + 1); @@ -562,8 +480,8 @@ int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges } -int Paragraph::Pimpl::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 @@ -579,22 +497,22 @@ int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c, } -bool Paragraph::Pimpl::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 < size() - 1) { - 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; } } @@ -602,17 +520,17 @@ bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding, if (lyxrc.plaintext_linelen > 0 && column > lyxrc.plaintext_linelen && i - && getChar(i - 1) != ' ' - && (i < size() - 1) + && 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); @@ -626,41 +544,93 @@ bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding, } -bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const +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; + bool found = false; + FontList::const_iterator cit = fontlist_.begin(); + FontList::const_iterator end = fontlist_.end(); + for (; cit != end; ++cit) { + if (cit->pos() >= i && !found) { + prev_font = cit->font(); + found = true; + } + if (cit->pos() >= i + 1) + break; + } + // Stop here if there is a font attribute or encoding change. + if (found && cit != end && prev_font != cit->font()) + break; + docstring const latex = encoding.latexChar(next); + docstring::size_type const b1 = + latex.find_first_of(from_ascii("{")); + docstring::size_type const b2 = + latex.find_last_of(from_ascii("}")); + int const len = b2 - b1 - 1; + os << latex.substr(b1 + 1, len); + length += len; + ++i; + } + os << '}'; + ++length; + return length; +} + + +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; } - // is there a font change in middle of the word? - FontList::const_iterator cit = fontlist.begin(); - FontList::const_iterator end = fontlist.end(); - for (; cit != end; ++cit) { - if (cit->pos() >= pos) - break; - } - if (cit != end && pos + len - 1 > cit->pos()) - return false; - - return true; + return fontlist_.hasChangeInRange(pos, len); } -void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf, +void Paragraph::Private::latexInset(Buffer const & buf, BufferParams const & bparams, odocstream & os, TexRow & texrow, - OutputParams const & runparams, + OutputParams & runparams, Font & running_font, Font & basefont, Font const & outerfont, @@ -668,351 +638,379 @@ void Paragraph::Pimpl::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: remove this check - if (!inset) - break; - - // FIXME: move this to InsetNewline::latex - if (inset->lyxCode() == Inset::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: 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; + } - if (running_font.family() == Font::TYPEWRITER_FAMILY) - os << '~'; + if (running_font.fontInfo().family() == TYPEWRITER_FAMILY) + os << '~'; - basefont = owner_->getLayoutFont(bparams, outerfont); - running_font = basefont; + basefont = owner_->getLayoutFont(bparams, outerfont); + running_font = basefont; - if (runparams.moving_arg) - os << "\\protect "; + if (runparams.moving_arg) + os << "\\protect "; - os << "\\\\\n"; - } - texrow.newline(); - texrow.start(owner_->id(), i + 1); - column = 0; - break; } + texrow.newline(); + texrow.start(owner_->id(), i + 1); + column = 0; + } - 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->lyxCode() == Inset::GRAPHICS_CODE - || inset->lyxCode() == Inset::MATH_CODE - || inset->lyxCode() == Inset::URL_CODE) - && running_font.isRightToLeft()) { - if (running_font.language()->lang() == "farsi") - os << "\\beginL{}"; - else - os << "\\L{"; - close = true; - } + bool close = false; + odocstream::pos_type const len = os.tellp(); -#ifdef WITH_WARNINGS -#warning 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) -#endif - // some insets cannot be inside a font change command - if (open_font && inset->noFontChange()) { - column += running_font.latexWriteEndChanges( - os, bparams, runparams, - basefont, basefont); - open_font = false; + 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; } + } - int tmp = inset->latex(buf, os, runparams); + int tmp = inset->latex(buf, os, runparams); - if (close) { - if (running_font.language()->lang() == "farsi") - os << "\\endL{}"; - else - os << '}'; - } - - 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 (close) { + if (running_font.language()->lang() == "farsi") + os << "\\endL{}"; + else + os << '}'; } - break; - default: - // And now for the special cases within each mode + if (tmp) { + for (int j = 0; j < tmp; ++j) + texrow.newline(); - switch (c) { - case '\\': - os << "\\textbackslash{}"; - column += 15; - break; + texrow.start(owner_->id(), i + 1); + column = 0; + } else { + column += os.tellp() - len; + } - 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 (owner_->lookupChange(i).type == Change::DELETED) + --runparams.inDeletedInset; +} - case '-': // "--" in Typewriter mode -> "-{}-" - if (i <= size() - 2 && - getChar(i + 1) == '-' && - running_font.family() == Font::TYPEWRITER_FAMILY) { - os << "-{}"; - column += 2; - } else { - os << '-'; - } - break; - case '\"': - os << "\\char`\\\"{}"; - column += 9; - break; +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]; - case '$': case '&': - case '%': case '#': case '{': - case '}': case '_': - os << '\\'; + if (style.pass_thru) { + if (c != '\0') + // FIXME UNICODE: This can fail if c cannot + // be encoded in the current encoding. os.put(c); - column += 1; - break; + return; + } - case '~': - os << "\\textasciitilde{}"; - column += 16; - break; + if (runparams.verbatim) { + os.put(c); + return; + } - case '^': - os << "\\textasciicircum{}"; - column += 17; - break; + if (lyxrc.fontenc == "T1" && latexSpecialT1(c, os, i, column)) + return; - case '*': case '[': - // avoid being mistaken for optional arguments - os << '{'; - os.put(c); - os << '}'; - column += 2; - break; + if (running_font.fontInfo().family() == TYPEWRITER_FAMILY + && latexSpecialTypewriter(c, os, i, column)) + return; - 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; + // 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; - default: + case '$': case '&': + case '%': case '#': case '{': + case '}': case '_': + os << '\\'; + os.put(c); + column += 1; + break; - // 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); - } - break; - } + case '~': + os << "\\textasciitilde{}"; + column += 16; + break; - // LyX, LaTeX etc. + case '^': + os << "\\textasciicircum{}"; + column += 17; + break; - // 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 + case '*': case '[': + // avoid being mistaken for optional arguments + os << '{'; + os.put(c); + os << '}'; + column += 2; + break; - size_t pnr = 0; + 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; - 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; - } - } + default: - if (pnr == phrases_nr && c != '\0') { - Encoding const & encoding = *(runparams.encoding); - if (i < size() - 1) { - char_type next = getChar(i + 1); - if (Encodings::isCombiningChar(next)) { - column += latexSurrogatePair(os, c, next, encoding) - 1; - ++i; - 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; - } + // LyX, LaTeX etc. + if (latexSpecialPhrase(os, i, column, runparams)) + return; + + 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; } - 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; } } -void Paragraph::Pimpl::validate(LaTeXFeatures & features, - Layout const & layout) const +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; + } +} + + +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; + + // 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) { - BufferParams const & bparams = features.bufferParams(); + // 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 +{ // check the params. - if (!params.spacing().isDefault()) + if (!params_.spacing().isDefault()) features.require("setspace"); // then the layouts 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; - } + fontlist_.validate(features); - 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; - } - } - - if (!params.leftIndent().zero()) + // then the indentation + if (!params_.leftIndent().zero()) features.require("ParagraphLeftIndent"); // then the insets - InsetList::const_iterator icit = owner_->insetlist.begin(); - InsetList::const_iterator iend = owner_->insetlist.end(); + InsetList::const_iterator icit = insetlist_.begin(); + InsetList::const_iterator iend = insetlist_.end(); for (; icit != iend; ++icit) { if (icit->inset) { icit->inset->validate(features); if (layout.needprotect && - icit->inset->lyxCode() == Inset::FOOT_CODE) + icit->inset->lyxCode() == FOOT_CODE) features.require("NeedLyXFootnoteCode"); } } // 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)) { @@ -1020,62 +1018,39 @@ void Paragraph::Pimpl::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), pimpl_(new Paragraph::Pimpl(this)) + : d(new Paragraph::Private(this)) { itemdepth = 0; - params().clear(); + d->params_.clear(); } Paragraph::Paragraph(Paragraph const & par) - : itemdepth(par.itemdepth), insetlist(par.insetlist), - layout_(par.layout_), - text_(par.text_), begin_of_body_(par.begin_of_body_), - pimpl_(new Paragraph::Pimpl(*par.pimpl_, this)) -{ - //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl; - InsetList::iterator it = insetlist.begin(); - InsetList::iterator end = insetlist.end(); - for (; it != end; ++it) - it->inset = it->inset->clone().release(); + : itemdepth(par.itemdepth), + d(new Paragraph::Private(*par.d, this)) +{ } Paragraph & Paragraph::operator=(Paragraph const & par) { - // needed as we will destroy the pimpl_ before copying it + // needed as we will destroy the private part before copying it if (&par != this) { itemdepth = par.itemdepth; - insetlist = par.insetlist; - InsetList::iterator it = insetlist.begin(); - InsetList::iterator end = insetlist.end(); - for (; it != end; ++it) - it->inset = it->inset->clone().release(); - - layout_ = par.layout(); - text_ = par.text_; - begin_of_body_ = par.begin_of_body_; - - delete pimpl_; - pimpl_ = new Pimpl(*par.pimpl_, this); + delete d; + d = new Private(*par.d, this); } return *this; } @@ -1083,10 +1058,7 @@ Paragraph & Paragraph::operator=(Paragraph const & par) Paragraph::~Paragraph() { - delete pimpl_; - // - //lyxerr << "Paragraph::paragraph_id = " - // << Paragraph::paragraph_id << endl; + delete d; } @@ -1110,18 +1082,18 @@ void Paragraph::write(Buffer const & buf, ostream & os, } // First write the layout - os << "\n\\begin_layout " << layout()->name() << '\n'; + os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n'; params().write(os); - Font font1(Font::ALL_INHERIT, bparams.language); + Font font1(inherit_font, bparams.language); Change running_change = Change(Change::UNCHANGED); int column = 0; for (pos_type i = 0; i <= size(); ++i) { - Change change = pimpl_->lookupChange(i); + Change change = lookupChange(i); Changes::lyxMarkChange(os, column, running_change, change); running_change = change; @@ -1136,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 @@ -1155,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 @@ -1176,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; @@ -1194,88 +1162,99 @@ void Paragraph::write(Buffer const & buf, ostream & os, void Paragraph::validate(LaTeXFeatures & features) const { - pimpl_->validate(features, *layout()); + d->validate(features, *layout()); } -bool Paragraph::eraseChar(pos_type pos, bool trackChanges) +void Paragraph::insert(pos_type start, docstring const & str, + Font const & font, Change const & change) { - return pimpl_->eraseChar(pos, trackChanges); + for (size_t i = 0, n = str.size(); i != n ; ++i) + insertChar(start + i, str[i], font, change); } -int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges) +void Paragraph::appendChar(char_type c, Font const & font, + Change const & change) { - return pimpl_->eraseChars(start, end, trackChanges); + // 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::insert(pos_type start, docstring const & str, - Font const & font, Change const & change) +void Paragraph::appendString(docstring const & s, Font const & font, + Change const & change) { - for (size_t i = 0, n = str.size(); i != n ; ++i) - insertChar(start + i, str[i], font, 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, Paragraph::value_type c, +void Paragraph::insertChar(pos_type pos, char_type c, bool trackChanges) { - pimpl_->insertChar(pos, c, Change(trackChanges ? + d->insertChar(pos, c, Change(trackChanges ? Change::INSERTED : Change::UNCHANGED)); } -void Paragraph::insertChar(pos_type pos, Paragraph::value_type c, +void Paragraph::insertChar(pos_type pos, char_type c, Font const & font, bool trackChanges) { - pimpl_->insertChar(pos, c, Change(trackChanges ? + d->insertChar(pos, c, Change(trackChanges ? Change::INSERTED : Change::UNCHANGED)); setFont(pos, font); } -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) { - pimpl_->insertChar(pos, c, change); + d->insertChar(pos, c, change); setFont(pos, font); } -void Paragraph::insertInset(pos_type pos, Inset * inset, - Change const & change) -{ - pimpl_->insertInset(pos, inset, change); -} - - void Paragraph::insertInset(pos_type pos, Inset * inset, Font const & font, Change const & change) { - pimpl_->insertInset(pos, inset, change); + insertInset(pos, inset, change); // Set the font/language of the inset... setFont(pos, font); - // ... as well as the font/language of the text inside the inset - // FIXME: This is far from perfect. It basically overrides work being done - // in the InsetText constructor. Also, it doesn't work for Tables - // (precisely because each cell's font/language is set in the Table's - // constructor, so by now it's too late). The long-term solution should - // be moving current_font into Cursor, and getting rid of all this... - // (see http://thread.gmane.org/gmane.editors.lyx.devel/88869/focus=88944) - if (inset->asTextInset()) { - inset->asTextInset()->text_.current_font = font; - inset->asTextInset()->text_.real_current_font = font; - } } -bool Paragraph::insetAllowed(Inset_code code) +bool Paragraph::insetAllowed(InsetCode code) { - return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code); + return !d->inset_owner_ || d->inset_owner_->insetAllowed(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 @@ -1285,19 +1264,14 @@ Font const Paragraph::getFontSettings(BufferParams const & bparams, BOOST_ASSERT(pos <= size()); } - Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin(); - Pimpl::FontList::const_iterator end = pimpl_->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)); } @@ -1306,16 +1280,16 @@ FontSpan Paragraph::fontSpan(pos_type pos) const BOOST_ASSERT(pos <= size()); pos_type start = 0; - Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin(); - Pimpl::FontList::const_iterator end = pimpl_->fontlist.end(); + FontList::const_iterator cit = d->fontlist_.begin(); + FontList::const_iterator end = d->fontlist_.end(); 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; @@ -1331,10 +1305,10 @@ FontSpan Paragraph::fontSpan(pos_type pos) const // Gets uninstantiated font setting at position 0 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const { - if (!empty() && !pimpl_->fontlist.empty()) - return pimpl_->fontlist[0].font(); + if (!empty() && !d->fontlist_.empty()) + return d->fontlist_.begin()->font(); - return Font(Font::ALL_INHERIT, bparams.language); + return Font(inherit_font, bparams.language); } @@ -1348,20 +1322,16 @@ Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos, { BOOST_ASSERT(pos >= 0); - Layout_ptr const & lout = layout(); + Font font = getFontSettings(bparams, pos); pos_type const body_pos = beginOfBody(); - - Font layoutfont; if (pos < body_pos) - layoutfont = lout->labelfont; + font.fontInfo().realize(d->layout_->labelfont); else - layoutfont = lout->font; + font.fontInfo().realize(d->layout_->font); - Font font = getFontSettings(bparams, pos); - font.realize(layoutfont); - font.realize(outerfont); - font.realize(bparams.getFont()); + font.fontInfo().realize(outerfont.fontInfo()); + font.fontInfo().realize(bparams.getFont().fontInfo()); return font; } @@ -1370,68 +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 { - if (pimpl_->fontlist.empty()) - return def_size; - - Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin(); - Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end(); - for (; end_it != end; ++end_it) { - if (end_it->pos() >= endpos) - break; - } - - if (end_it != end) - ++end_it; - - Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin(); - for (; cit != end; ++cit) { - if (cit->pos() >= startpos) - break; - } - - Font::FONT_SIZE maxsize = Font::SIZE_TINY; - for (; cit != end_it; ++cit) { - Font::FONT_SIZE size = cit->font().size(); - if (size == Font::INHERIT_SIZE) - size = def_size; - if (size > maxsize && size <= Font::SIZE_HUGER) - maxsize = size; - } - return maxsize; + 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 = ')'; @@ -1472,59 +1413,8 @@ void Paragraph::setFont(pos_type pos, Font const & font) // First, reduce font against layout/label font // Update: The setCharFont() routine in text2.cpp already // reduces font, so we don't need to do that here. (Asger) - // No need to simplify this because it will disappear - // in a new kernel. (Asger) - // Next search font table - - Pimpl::FontList::iterator beg = pimpl_->fontlist.begin(); - Pimpl::FontList::iterator it = beg; - Pimpl::FontList::iterator endit = pimpl_->fontlist.end(); - for (; it != endit; ++it) { - if (it->pos() >= pos) - break; - } - size_t const i = distance(beg, it); - bool notfound = (it == endit); - - if (!notfound && pimpl_->fontlist[i].font() == font) - return; - - bool begin = pos == 0 || notfound || - (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1); - // Is position pos is a beginning of a font block? - bool end = !notfound && pimpl_->fontlist[i].pos() == pos; - // Is position pos is the end of a font block? - if (begin && end) { // A single char block - if (i + 1 < pimpl_->fontlist.size() && - pimpl_->fontlist[i + 1].font() == font) { - // Merge the singleton block with the next block - pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i); - if (i > 0 && pimpl_->fontlist[i - 1].font() == font) - pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1); - } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) { - // Merge the singleton block with the previous block - pimpl_->fontlist[i - 1].pos(pos); - pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i); - } else - pimpl_->fontlist[i].font(font); - } else if (begin) { - if (i > 0 && pimpl_->fontlist[i - 1].font() == font) - pimpl_->fontlist[i - 1].pos(pos); - else - pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i, - Pimpl::FontTable(pos, font)); - } else if (end) { - pimpl_->fontlist[i].pos(pos - 1); - if (!(i + 1 < pimpl_->fontlist.size() && - pimpl_->fontlist[i + 1].font() == font)) - pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1, - Pimpl::FontTable(pos, font)); - } else { // The general case. The block is splitted into 3 blocks - pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i, - Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font())); - pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1, - Pimpl::FontTable(pos, font)); - } + + d->fontlist_.set(pos, font); } @@ -1532,7 +1422,7 @@ void Paragraph::makeSameLayout(Paragraph const & par) { layout(par.layout()); // move to pimpl? - params() = par.params(); + d->params_ = par.params(); } @@ -1557,7 +1447,7 @@ bool Paragraph::stripLeadingSpaces(bool trackChanges) bool Paragraph::hasSameLayout(Paragraph const & par) const { - return par.layout() == layout() && params().sameLayout(par.params()); + return par.layout() == layout() && d->params_.sameLayout(par.params()); } @@ -1594,7 +1484,7 @@ docstring const & Paragraph::getLabelstring() const // the next two functions are for the manual labels docstring const Paragraph::getLabelWidthString() const { - if (!params().labelWidthString().empty()) + if (layout()->margintype == MARGIN_MANUAL) return params().labelWidthString(); else return _("Senseless with this layout!"); @@ -1610,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; @@ -1621,7 +1511,7 @@ docstring const Paragraph::translateIfPossible(docstring const & s, } -docstring Paragraph::expandLabel(Layout_ptr const & layout, +docstring Paragraph::expandLabel(LayoutPtr const & layout, BufferParams const & bparams, bool process_appendix) const { TextClass const & tclass = bparams.getTextClass(); @@ -1633,6 +1523,10 @@ docstring Paragraph::expandLabel(Layout_ptr const & layout, else fmt = translateIfPossible(layout->labelstring(), bparams); + if (fmt.empty() && layout->labeltype == LABEL_COUNTER + && !layout->counter.empty()) + fmt = "\\the" + layout->counter; + // handle 'inherited level parts' in 'fmt', // i.e. the stuff between '@' in '@Section@.\arabic{subsection}' size_t const i = fmt.find('@', 0); @@ -1640,9 +1534,12 @@ docstring Paragraph::expandLabel(Layout_ptr const & layout, size_t const j = fmt.find('@', i + 1); if (j != docstring::npos) { docstring parent(fmt, i + 1, j - i - 1); - // FIXME UNICODE - docstring label = expandLabel(tclass[to_utf8(parent)], bparams); - fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos); + docstring label = from_ascii("??"); + if (tclass.hasLayout(parent)) + docstring label = expandLabel(tclass[parent], bparams, + process_appendix); + fmt = docstring(fmt, 0, i) + label + + docstring(fmt, j + 1, docstring::npos); } } @@ -1650,25 +1547,29 @@ docstring Paragraph::expandLabel(Layout_ptr const & layout, } -void Paragraph::applyLayout(Layout_ptr const & new_layout) +void Paragraph::applyLayout(LayoutPtr const & new_layout) { layout(new_layout); - params().labelWidthString(docstring()); - params().align(LYX_ALIGN_LAYOUT); - params().spacing(Spacing(Spacing::Default)); + LyXAlignment const oldAlign = params().align(); + + if (!(oldAlign & layout()->alignpossible)) { + frontend::Alert::warning(_("Alignment not permitted"), + _("The new layout does not permit the alignment previously used.\nSetting to default.")); + params().align(LYX_ALIGN_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; } @@ -1682,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; @@ -1696,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 = insetlist.begin(); - InsetList::const_iterator end = insetlist.end(); - for (; it != end; ++it) - if (it->inset == inset) - return it->pos; - return -1; -} - - -InsetBibitem * Paragraph::bibitem() const -{ - if (!insetlist.empty()) { - Inset * inset = insetlist.begin()->inset; - if (inset->lyxCode() == Inset::BIBITEM_CODE) - return static_cast(inset); - } - return 0; + d->begin_of_body_ = i; } @@ -1735,9 +1612,9 @@ namespace { // paragraphs inside floats need different alignment tags to avoid // unwanted space -bool noTrivlistCentering(Inset::Code code) +bool noTrivlistCentering(InsetCode code) { - return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE; + return code == FLOAT_CODE || code == WRAP_CODE; } @@ -1754,7 +1631,7 @@ string correction(string const & orig) string const corrected_env(string const & suffix, string const & env, - Inset::Code code) + InsetCode code) { string output = suffix + "{"; if (noTrivlistCentering(code)) @@ -1782,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) { @@ -1823,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; @@ -1852,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: @@ -1875,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: @@ -1883,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; @@ -1913,17 +1788,17 @@ 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; - Layout_ptr style; + LayoutPtr style; // well we have to check if we are in an inset with unlimited // length (all in one row) if that is true then we don't allow @@ -1976,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); } @@ -2006,12 +1881,13 @@ bool Paragraph::simpleTeXOnePar(Buffer const & buf, } if (!asdefault) - column += startTeXParParams(bparams, os, + column += d->startTeXParParams(bparams, os, texrow, runparams.moving_arg); } - Change const & change = pimpl_->lookupChange(i); + Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset + : lookupChange(i); if (bparams.outputChanges && runningChange != change) { if (open_font) { @@ -2034,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); @@ -2053,46 +1927,68 @@ 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()) && i != body_pos - 1) { - column += font.latexWriteStartChanges(os, bparams, + odocstringstream ods; + column += font.latexWriteStartChanges(ods, bparams, runparams, basefont, last_font); running_font = font; open_font = true; + docstring fontchange = ods.str(); + // check if the fontchange ends with a trailing blank + // (like "\small " (see bug 3382) + if (suffixIs(fontchange, ' ') && c == ' ') + os << fontchange.substr(0, fontchange.size() - 1) + << from_ascii("{}"); + else + os << fontchange; } 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 (pimpl_->simpleTeXBlanks( - *(runparams.encoding), os, texrow, - i, column, font, *style)) + if (d->simpleTeXBlanks( + 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; + } } } @@ -2100,10 +1996,37 @@ bool Paragraph::simpleTeXOnePar(Buffer const & buf, rp.free_spacing = style->free_spacing; rp.local_font = &font; rp.intitle = style->intitle; - pimpl_->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) + runparams.encoding = rp.encoding; } // If we have an open font definition, we have to close it @@ -2119,11 +2042,9 @@ bool Paragraph::simpleTeXOnePar(Buffer const & buf, runparams, basefont, basefont); } #else -#ifdef WITH_WARNINGS -//#warning For now we ALWAYS have to close the foreign font settings if they are -//#warning there as we start another \selectlanguage with the next paragraph if -//#warning we are in need of this. This should be fixed sometime (Jug) -#endif +//FIXME: For now we ALWAYS have to close the foreign font settings if they are +//FIXME: there as we start another \selectlanguage with the next paragraph if +//FIXME: we are in need of this. This should be fixed sometime (Jug) running_font.latexWriteEndChanges(os, bparams, runparams, basefont, basefont); #endif @@ -2138,75 +2059,31 @@ 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); - Inset::Code lyx_code = inset->lyxCode(); - if (lyx_code != Inset::TOC_CODE && - lyx_code != Inset::INCLUDE_CODE && - lyx_code != Inset::GRAPHICS_CODE && - lyx_code != Inset::ERT_CODE && - lyx_code != Inset::LISTINGS_CODE && - lyx_code != Inset::FLOAT_CODE && - lyx_code != Inset::TABULAR_CODE) { + if (Inset const * inset = getInset(i)) { + InsetCode lyx_code = inset->lyxCode(); + if (lyx_code != TOC_CODE && + lyx_code != INCLUDE_CODE && + lyx_code != GRAPHICS_CODE && + lyx_code != ERT_CODE && + lyx_code != LISTINGS_CODE && + lyx_code != FLOAT_CODE && + lyx_code != TABULAR_CODE) { return false; } } else { - value_type c = getChar(i); + char_type c = d->text_[i]; if (c != ' ' && c != '\t') return false; } @@ -2218,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); - Inset::Code lyx_code = inset->lyxCode(); - if (lyx_code == Inset::LABEL_CODE) { - string const id = static_cast(inset)->getContents(); - return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'"; + if (Inset const * inset = getInset(i)) { + InsetCode lyx_code = inset->lyxCode(); + if (lyx_code == LABEL_CODE) { + InsetLabel const * const il = static_cast(inset); + docstring const & id = il->getParam("name"); + return "id='" + to_utf8(sgml::cleanID(buf, runparams, id)) + "'"; } } - } return string(); } @@ -2236,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); @@ -2250,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; @@ -2275,11 +2150,11 @@ void Paragraph::simpleDocBookOnePar(Buffer const & buf, { bool emph_flag = false; - Layout_ptr const & style = layout(); - Font font_old = + LayoutPtr const & style = layout(); + 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 @@ -2287,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) { @@ -2297,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) { @@ -2317,36 +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; +} + + bool Paragraph::isNewline(pos_type pos) const { - return isInset(pos) - && getInset(pos)->lyxCode() == Inset::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); } @@ -2355,19 +2235,17 @@ Paragraph::getParLanguage(BufferParams const & bparams) const { if (!empty()) return getFirstFontSettings(bparams).language(); -#ifdef WITH_WARNINGS -#warning FIXME we should check the prev par as well (Lgb) -#endif + // FIXME: we should check the prev par as well (Lgb) return bparams.language; } -bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const +bool Paragraph::isRTL(BufferParams const & bparams) const { return lyxrc.rtl_support && getParLanguage(bparams)->rightToLeft() - && ownerCode() != Inset::ERT_CODE - && ownerCode() != Inset::LISTINGS_CODE; + && ownerCode() != ERT_CODE + && ownerCode() != LISTINGS_CODE; } @@ -2388,8 +2266,8 @@ void Paragraph::changeLanguage(BufferParams const & bparams, bool Paragraph::isMultiLingual(BufferParams const & bparams) const { Language const * doc_language = bparams.language; - Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin(); - Pimpl::FontList::const_iterator end = pimpl_->fontlist.end(); + FontList::const_iterator cit = d->fontlist_.begin(); + FontList::const_iterator end = d->fontlist_.end(); for (; cit != end; ++cit) if (cit->font().language() != ignore_language && @@ -2418,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) @@ -2431,93 +2309,49 @@ docstring const Paragraph::asString(Buffer const & buffer, void Paragraph::setInsetOwner(Inset * inset) { - pimpl_->inset_owner = inset; -} - - -Change const & Paragraph::lookupChange(pos_type pos) const -{ - BOOST_ASSERT(pos <= size()); - return pimpl_->lookupChange(pos); -} - - -bool Paragraph::isChanged(pos_type start, pos_type end) const -{ - return pimpl_->isChanged(start, end); -} - - -bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const -{ - return pimpl_->isMergedOnEndOfParDeletion(trackChanges); -} - - -void Paragraph::setChange(Change const & change) -{ - pimpl_->setChange(change); -} - - -void Paragraph::setChange(pos_type pos, Change const & change) -{ - pimpl_->setChange(pos, change); -} - - -void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end) -{ - return pimpl_->acceptChanges(bparams, start, end); -} - - -void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end) -{ - return pimpl_->rejectChanges(bparams, start, end); + d->inset_owner_ = inset; } int Paragraph::id() const { - return pimpl_->id_; + return d->id_; } -Layout_ptr const & Paragraph::layout() const +LayoutPtr const & Paragraph::layout() const { - return layout_; + return d->layout_; } -void Paragraph::layout(Layout_ptr const & new_layout) +void Paragraph::layout(LayoutPtr const & new_layout) { - layout_ = new_layout; + d->layout_ = new_layout; } Inset * Paragraph::inInset() const { - return pimpl_->inset_owner; + return d->inset_owner_; } -Inset::Code Paragraph::ownerCode() const +InsetCode Paragraph::ownerCode() const { - return pimpl_->inset_owner - ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE; + return d->inset_owner_ ? d->inset_owner_->lyxCode() : NO_CODE; } ParagraphParameters & Paragraph::params() { - return pimpl_->params; + return d->params_; } ParagraphParameters const & Paragraph::params() const { - return pimpl_->params; + return d->params_; } @@ -2525,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() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE; + return d->inset_owner_ && d->inset_owner_->isFreeSpacing(); } @@ -2536,7 +2367,7 @@ bool Paragraph::allowEmpty() const { if (layout()->keepempty) return true; - return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE; + return d->inset_owner_ && d->inset_owner_->allowEmpty(); } @@ -2545,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; @@ -2557,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; @@ -2580,42 +2411,6 @@ char_type Paragraph::transformChar(char_type c, pos_type pos) const } -bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const -{ - if (!isHfill(pos)) - return false; - - BOOST_ASSERT(pos >= row.pos() && pos < row.endpos()); - - // expand at the end of a row only if there is another hfill on the same row - if (pos == row.endpos() - 1) { - for (pos_type i = row.pos(); i < pos; i++) { - if (isHfill(i)) - return true; - } - return false; - } - - // expand at the beginning of a row only if it is the first row of a paragraph - if (pos == row.pos()) { - return pos == 0; - } - - // do not expand in some labels - if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody()) - return false; - - // if there is anything between the first char of the row and - // the specified position that is neither a newline nor an hfill, - // the hfill will be expanded, otherwise it won't - for (pos_type i = row.pos(); i < pos; i++) { - if (!isNewline(i) && !isHfill(i)) - return true; - } - return false; -} - - int Paragraph::checkBiblio(bool track_changes) { //FIXME From JS: @@ -2626,10 +2421,10 @@ int Paragraph::checkBiblio(bool track_changes) if (layout()->labeltype != LABEL_BIBLIO) return 0; - bool hasbibitem = !insetlist.empty() + bool hasbibitem = !d->insetlist_.empty() // Insist on it being in pos 0 - && getChar(0) == Paragraph::META_INSET - && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE; + && d->text_[0] == META_INSET + && d->insetlist_.begin()->inset->lyxCode() == BIBITEM_CODE; docstring oldkey; docstring oldlabel; @@ -2640,10 +2435,10 @@ int Paragraph::checkBiblio(bool track_changes) // we're assuming there's only one of these, which there // should be. int erasedInsetPosition = -1; - InsetList::iterator it = insetlist.begin(); - InsetList::iterator end = insetlist.end(); + InsetList::iterator it = d->insetlist_.begin(); + InsetList::iterator end = d->insetlist_.end(); for (; it != end; ++it) - if (it->inset->lyxCode() == Inset::BIBITEM_CODE + if (it->inset->lyxCode() == BIBITEM_CODE && it->pos > 0) { InsetBibitem * olditem = static_cast(it->inset); oldkey = olditem->getParam("key"); @@ -2662,7 +2457,7 @@ int Paragraph::checkBiblio(bool track_changes) //erase one. So we give its properties to the beginning inset. if (hasbibitem) { InsetBibitem * inset = - static_cast(insetlist.begin()->inset); + static_cast(d->insetlist_.begin()->inset); if (!oldkey.empty()) inset->setParam("key", oldkey); inset->setParam("label", oldlabel); @@ -2671,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); @@ -2682,4 +2477,187 @@ int Paragraph::checkBiblio(bool track_changes) return 1; } + +void Paragraph::checkAuthors(AuthorList const & authorList) +{ + d->changes_.checkAuthors(authorList); +} + + +bool Paragraph::isUnchanged(pos_type pos) const +{ + return lookupChange(pos).type == Change::UNCHANGED; +} + + +bool Paragraph::isInserted(pos_type pos) const +{ + return lookupChange(pos).type == Change::INSERTED; +} + + +bool Paragraph::isDeleted(pos_type pos) const +{ + return lookupChange(pos).type == Change::DELETED; +} + + +InsetList const & Paragraph::insetList() const +{ + return d->insetlist_; +} + + +Inset * Paragraph::releaseInset(pos_type pos) +{ + Inset * inset = d->insetlist_.release(pos); + /// does not honour change tracking! + eraseChar(pos, false); + return inset; +} + + +Inset * Paragraph::getInset(pos_type 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 (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