X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=a69169c41e3773f711297e982ac2949c2b8a4f16;hb=818249f69fbb59f8ecae17213e046069f53de438;hp=7d1ef37f250ce3325a91bcfb668b800527d00345;hpb=c1e79cde53176fb29060cad2b5a387a01cb46198;p=lyx.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 7d1ef37f25..a69169c41e 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -25,7 +25,7 @@ #include "BufferParams.h" #include "Changes.h" #include "Counters.h" -#include "Encoding.h" +#include "BufferEncodings.h" #include "InsetList.h" #include "Language.h" #include "LaTeXFeatures.h" @@ -43,7 +43,6 @@ #include "TextClass.h" #include "TexRow.h" #include "Text.h" -#include "VSpace.h" #include "WordLangTuple.h" #include "WordList.h" @@ -51,6 +50,7 @@ #include "insets/InsetBibitem.h" #include "insets/InsetLabel.h" +#include "insets/InsetSpecialChar.h" #include "support/debug.h" #include "support/docstring_list.h" @@ -69,8 +69,209 @@ using namespace lyx::support; namespace lyx { namespace { + /// Inset identifier (above 0x10ffff, for ucs-4) char_type const META_INSET = 0x200001; + +} + + +///////////////////////////////////////////////////////////////////// +// +// SpellResultRange +// +///////////////////////////////////////////////////////////////////// + +class SpellResultRange { +public: + SpellResultRange(FontSpan range, SpellChecker::Result result) + : range_(range), result_(result) + {} + /// + FontSpan const & range() const { return range_; } + /// + void range(FontSpan const & r) { range_ = r; } + /// + SpellChecker::Result result() const { return result_; } + /// + void result(SpellChecker::Result r) { result_ = r; } + /// + bool contains(pos_type pos) const { return range_.contains(pos); } + /// + bool covered(FontSpan const & r) const + { + // 1. first of new range inside current range or + // 2. last of new range inside current range or + // 3. first of current range inside new range or + // 4. last of current range inside new range + //FIXME: is this the same as !range_.intersect(r).empty() ? + return range_.contains(r.first) || range_.contains(r.last) || + r.contains(range_.first) || r.contains(range_.last); + } + /// + void shift(pos_type pos, int offset) + { + if (range_.first > pos) { + range_.first += offset; + range_.last += offset; + } else if (range_.last >= pos) { + range_.last += offset; + } + } +private: + FontSpan range_ ; + SpellChecker::Result result_ ; +}; + + +///////////////////////////////////////////////////////////////////// +// +// SpellCheckerState +// +///////////////////////////////////////////////////////////////////// + +class SpellCheckerState { +public: + SpellCheckerState() + { + needs_refresh_ = true; + current_change_number_ = 0; + } + + void setRange(FontSpan const & fp, SpellChecker::Result state) + { + Ranges result; + RangesIterator et = ranges_.end(); + RangesIterator it = ranges_.begin(); + for (; it != et; ++it) { + if (!it->covered(fp)) + result.push_back(SpellResultRange(it->range(), it->result())); + else if (state == SpellChecker::WORD_OK) { + // trim or split the current misspelled range + // store misspelled ranges only + FontSpan range = it->range(); + if (fp.first > range.first) { + // misspelled area in front of WORD_OK + range.last = fp.first - 1; + result.push_back(SpellResultRange(range, it->result())); + range = it->range(); + } + if (fp.last < range.last) { + // misspelled area after WORD_OK range + range.first = fp.last + 1; + result.push_back(SpellResultRange(range, it->result())); + } + } + } + ranges_ = result; + if (state != SpellChecker::WORD_OK) + ranges_.push_back(SpellResultRange(fp, state)); + } + + void increasePosAfterPos(pos_type pos) + { + correctRangesAfterPos(pos, 1); + needsRefresh(pos); + } + + void decreasePosAfterPos(pos_type pos) + { + correctRangesAfterPos(pos, -1); + needsRefresh(pos); + } + + void refreshLast(pos_type pos) + { + if (pos < refresh_.last) + refresh_.last = pos; + } + + SpellChecker::Result getState(pos_type pos) const + { + SpellChecker::Result result = SpellChecker::WORD_OK; + RangesIterator et = ranges_.end(); + RangesIterator it = ranges_.begin(); + for (; it != et; ++it) { + if(it->contains(pos)) { + return it->result(); + } + } + return result; + } + + FontSpan const & getRange(pos_type pos) const + { + /// empty span to indicate mismatch + static FontSpan empty_; + RangesIterator et = ranges_.end(); + RangesIterator it = ranges_.begin(); + for (; it != et; ++it) { + if(it->contains(pos)) { + return it->range(); + } + } + return empty_; + } + + bool needsRefresh() const + { + return needs_refresh_; + } + + SpellChecker::ChangeNumber currentChangeNumber() const + { + return current_change_number_; + } + + void refreshRange(pos_type & first, pos_type & last) const + { + first = refresh_.first; + last = refresh_.last; + } + + void needsRefresh(pos_type pos) + { + if (needs_refresh_ && pos != -1) { + if (pos < refresh_.first) + refresh_.first = pos; + if (pos > refresh_.last) + refresh_.last = pos; + } else if (pos != -1) { + // init request check for neighbour positions too + refresh_.first = pos > 0 ? pos - 1 : 0; + // no need for special end of paragraph check + refresh_.last = pos + 1; + } + needs_refresh_ = pos != -1; + } + + void needsCompleteRefresh(SpellChecker::ChangeNumber change_number) + { + needs_refresh_ = true; + refresh_.first = 0; + refresh_.last = -1; + current_change_number_ = change_number; + } +private: + typedef vector Ranges; + typedef Ranges::const_iterator RangesIterator; + Ranges ranges_; + /// the area of the paragraph with pending spell check + FontSpan refresh_; + bool needs_refresh_; + /// spell state cache version number + SpellChecker::ChangeNumber current_change_number_; + + + void correctRangesAfterPos(pos_type pos, int offset) + { + RangesIterator et = ranges_.end(); + Ranges::iterator it = ranges_.begin(); + for (; it != et; ++it) { + it->shift(pos, offset); + } + } + }; ///////////////////////////////////////////////////////////////////// @@ -81,6 +282,10 @@ char_type const META_INSET = 0x200001; class Paragraph::Private { + // Enforce our own "copy" constructor by declaring the standard one and + // the assignment operator private without implementing them. + Private(Private const &); + Private & operator=(Private const &); public: /// Private(Paragraph * owner, Layout const & layout); @@ -94,14 +299,14 @@ public: /// Output the surrogate pair formed by \p c and \p next to \p os. /// \return the number of characters written. - int latexSurrogatePair(odocstream & os, char_type c, char_type next, + int latexSurrogatePair(otexstream & os, char_type c, char_type next, OutputParams 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(OutputParams const &, - odocstream &, TexRow & texrow, + otexstream &, pos_type i, unsigned int & column, Font const & font, @@ -110,21 +315,21 @@ public: /// 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, + int writeScriptChars(otexstream & os, docstring const & ltx, Change const &, Encoding const &, pos_type & i); /// This could go to ParagraphParameters if we want to. - int startTeXParParams(BufferParams const &, odocstream &, TexRow &, + int startTeXParParams(BufferParams const &, otexstream &, OutputParams const &) const; /// This could go to ParagraphParameters if we want to. - int endTeXParParams(BufferParams const &, odocstream &, TexRow &, - OutputParams const &) const; + bool endTeXParParams(BufferParams const &, otexstream &, + OutputParams const &) const; /// void latexInset(BufferParams const &, - odocstream &, - TexRow & texrow, OutputParams &, + otexstream &, + OutputParams &, Font & running_font, Font & basefont, Font const & outerfont, @@ -136,32 +341,28 @@ public: /// void latexSpecialChar( - odocstream & os, + otexstream & os, + BufferParams const & bparams, OutputParams const & runparams, Font const & running_font, Change const & running_change, Layout const & style, pos_type & i, + pos_type end_pos, unsigned int & column); /// bool latexSpecialT1( char_type const c, - odocstream & os, + otexstream & os, pos_type i, unsigned int & column); /// - bool latexSpecialTypewriter( + bool latexSpecialT3( char_type const c, - odocstream & os, + otexstream & os, pos_type i, unsigned int & column); - /// - bool latexSpecialPhrase( - odocstream & os, - pos_type & i, - unsigned int & column, - OutputParams const & runparams); /// void validate(LaTeXFeatures & features) const; @@ -170,15 +371,94 @@ public: 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; + /// a vector of speller skip positions + typedef vector SkipPositions; + typedef SkipPositions::const_iterator SkipPositionsIterator; + + void appendSkipPosition(SkipPositions & skips, pos_type const pos) const; + + Language * getSpellLanguage(pos_type const from) const; + + Language * locateSpellRange(pos_type & from, pos_type & to, + SkipPositions & skips) const; + + bool hasSpellerChange() const + { + SpellChecker::ChangeNumber speller_change_number = 0; + if (theSpellChecker()) + speller_change_number = theSpellChecker()->changeNumber(); + return speller_change_number > speller_state_.currentChangeNumber(); + } + + bool ignoreWord(docstring const & word) const ; + + void setMisspelled(pos_type from, pos_type to, SpellChecker::Result state) + { + pos_type textsize = owner_->size(); + // check for sane arguments + if (to <= from || from >= textsize) + return; + FontSpan fp = FontSpan(from, to - 1); + speller_state_.setRange(fp, state); + } + void requestSpellCheck(pos_type pos) + { + if (pos == -1) + speller_state_.needsCompleteRefresh(speller_state_.currentChangeNumber()); + else + speller_state_.needsRefresh(pos); + } + + void readySpellCheck() + { + speller_state_.needsRefresh(-1); + } + + bool needsSpellCheck() const + { + return speller_state_.needsRefresh(); + } + + void rangeOfSpellCheck(pos_type & first, pos_type & last) const + { + speller_state_.refreshRange(first, last); + if (last == -1) { + last = owner_->size(); + return; + } + pos_type endpos = last; + owner_->locateWord(first, endpos, WHOLE_WORD); + if (endpos < last) { + endpos = last; + owner_->locateWord(last, endpos, WHOLE_WORD); + } + last = endpos; + } + + int countSkips(SkipPositionsIterator & it, SkipPositionsIterator const et, + int & start) const + { + int numskips = 0; + while (it != et && it->first < start) { + int skip = it->last - it->first + 1; + start += skip; + numskips += skip; + ++it; + } + return numskips; + } + + void markMisspelledWords(pos_type const & first, pos_type const & last, + SpellChecker::Result result, + docstring const & word, + SkipPositions const & skips); InsetCode ownerCode() const { return inset_owner_ ? inset_owner_->lyxCode() : NO_CODE; } - + /// Which Paragraph owns us? Paragraph * owner_; @@ -206,35 +486,18 @@ public: typedef docstring TextContainer; /// TextContainer text_; - + typedef set Words; + typedef map LangWordsMap; /// - map words_; + LangWordsMap words_; /// Layout const * layout_; + /// + SpellCheckerState speller_state_; }; -namespace { - -struct special_phrase { - string phrase; - docstring macro; - bool builtin; -}; - -special_phrase const special_phrases[] = { - { "LyX", from_ascii("\\LyX{}"), false }, - { "TeX", from_ascii("\\TeX{}"), true }, - { "LaTeX2e", from_ascii("\\LaTeXe{}"), true }, - { "LaTeX", from_ascii("\\LaTeX{}"), true }, -}; - -size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase); - -} // namespace anon - - Paragraph::Private::Private(Paragraph * owner, Layout const & layout) : owner_(owner), inset_owner_(0), id_(-1), begin_of_body_(0), layout_(&layout) { @@ -250,12 +513,13 @@ Paragraph::Private::Private(Paragraph * owner, Layout const & layout) static int paragraph_id = -1; Paragraph::Private::Private(Private const & p, Paragraph * owner) - : owner_(owner), inset_owner_(p.inset_owner_), fontlist_(p.fontlist_), + : owner_(owner), inset_owner_(p.inset_owner_), fontlist_(p.fontlist_), params_(p.params_), changes_(p.changes_), insetlist_(p.insetlist_), begin_of_body_(p.begin_of_body_), text_(p.text_), words_(p.words_), layout_(p.layout_) { id_ = ++paragraph_id; + requestSpellCheck(p.text_.size()); } @@ -285,20 +549,21 @@ Paragraph::Private::Private(Private const & p, Paragraph * owner, // Add a new entry in the fontlist_. fontlist_.set(fcit->pos() - beg, fcit->font()); } + requestSpellCheck(p.text_.size()); } void Paragraph::addChangesToToc(DocIterator const & cdit, - Buffer const & buf) const + Buffer const & buf, bool output_active) const { - d->changes_.addToToc(cdit, buf); + d->changes_.addToToc(cdit, buf, output_active); } bool Paragraph::isDeleted(pos_type start, pos_type end) const { - LASSERT(start >= 0 && start <= size(), /**/); - LASSERT(end > start && end <= size() + 1, /**/); + LASSERT(start >= 0 && start <= size(), return false); + LASSERT(end > start && end <= size() + 1, return false); return d->changes_.isDeleted(start, end); } @@ -306,8 +571,8 @@ bool Paragraph::isDeleted(pos_type start, pos_type end) const bool Paragraph::isChanged(pos_type start, pos_type end) const { - LASSERT(start >= 0 && start <= size(), /**/); - LASSERT(end > start && end <= size() + 1, /**/); + LASSERT(start >= 0 && start <= size(), return false); + LASSERT(end > start && end <= size() + 1, return false); return d->changes_.isChanged(start, end); } @@ -319,7 +584,7 @@ bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const if (!trackChanges) return true; - Change const change = d->changes_.lookup(size()); + Change const & change = d->changes_.lookup(size()); return change.inserted() && change.currentAuthor(); } @@ -352,7 +617,7 @@ void Paragraph::setChange(Change const & change) void Paragraph::setChange(pos_type pos, Change const & change) { - LASSERT(pos >= 0 && pos <= size(), /**/); + LASSERT(pos >= 0 && pos <= size(), return); d->changes_.set(change, pos); // see comment in setChange(Change const &) above @@ -364,15 +629,15 @@ void Paragraph::setChange(pos_type pos, Change const & change) Change const & Paragraph::lookupChange(pos_type pos) const { - LASSERT(pos >= 0 && pos <= size(), /**/); + LBUFERR(pos >= 0 && pos <= size()); return d->changes_.lookup(pos); } void Paragraph::acceptChanges(pos_type start, pos_type end) { - LASSERT(start >= 0 && start <= size(), /**/); - LASSERT(end > start && end <= size() + 1, /**/); + LASSERT(start >= 0 && start <= size(), return); + LASSERT(end > start && end <= size() + 1, return); for (pos_type pos = start; pos < end; ++pos) { switch (lookupChange(pos).type) { @@ -406,8 +671,8 @@ void Paragraph::acceptChanges(pos_type start, pos_type end) void Paragraph::rejectChanges(pos_type start, pos_type end) { - LASSERT(start >= 0 && start <= size(), /**/); - LASSERT(end > start && end <= size() + 1, /**/); + LASSERT(start >= 0 && start <= size(), return); + LASSERT(end > start && end <= size() + 1, return); for (pos_type pos = start; pos < end; ++pos) { switch (lookupChange(pos).type) { @@ -442,7 +707,7 @@ void Paragraph::rejectChanges(pos_type start, pos_type end) void Paragraph::Private::insertChar(pos_type pos, char_type c, Change const & change) { - LASSERT(pos >= 0 && pos <= int(text_.size()), /**/); + LASSERT(pos >= 0 && pos <= int(text_.size()), return); // track change changes_.insert(change, pos); @@ -452,6 +717,8 @@ void Paragraph::Private::insertChar(pos_type pos, char_type c, if (pos == pos_type(text_.size())) { // when appending characters, no need to update tables text_.push_back(c); + // but we want spell checking + requestSpellCheck(pos); return; } @@ -462,14 +729,17 @@ void Paragraph::Private::insertChar(pos_type pos, char_type c, // Update the insets insetlist_.increasePosAfterPos(pos); + + // Update list of misspelled positions + speller_state_.increasePosAfterPos(pos); } bool Paragraph::insertInset(pos_type pos, Inset * inset, - Change const & change) + Font const & font, Change const & change) { - LASSERT(inset, /**/); - LASSERT(pos >= 0 && pos <= size(), /**/); + LASSERT(inset, return false); + LASSERT(pos >= 0 && pos <= size(), return false); // Paragraph::insertInset() can be used in cut/copy/paste operation where // d->inset_owner_ is not set yet. @@ -477,10 +747,14 @@ bool Paragraph::insertInset(pos_type pos, Inset * inset, return false; d->insertChar(pos, META_INSET, change); - LASSERT(d->text_[pos] == META_INSET, /**/); + LASSERT(d->text_[pos] == META_INSET, return false); // Add a new entry in the insetlist_. d->insetlist_.insert(inset, pos); + + // Some insets require run of spell checker + requestSpellCheck(pos); + setFont(pos, font); return true; } @@ -501,6 +775,8 @@ bool Paragraph::eraseChar(pos_type pos, bool trackChanges) if (!change.changed() || (change.inserted() && !change.currentAuthor())) { setChange(pos, Change(Change::DELETED)); + // request run of spell checker + requestSpellCheck(pos); return false; } @@ -530,14 +806,18 @@ bool Paragraph::eraseChar(pos_type pos, bool trackChanges) // Update the insetlist_ d->insetlist_.decreasePosAfterPos(pos); + // Update list of misspelled positions + d->speller_state_.decreasePosAfterPos(pos); + d->speller_state_.refreshLast(size()); + return true; } int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges) { - LASSERT(start >= 0 && start <= size(), /**/); - LASSERT(end >= start && end <= size() + 1, /**/); + LASSERT(start >= 0 && start <= size(), return 0); + LASSERT(end >= start && end <= size() + 1, return 0); pos_type i = start; for (pos_type count = end - start; count; --count) { @@ -548,7 +828,7 @@ int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges) } -int Paragraph::Private::latexSurrogatePair(odocstream & os, char_type c, +int Paragraph::Private::latexSurrogatePair(otexstream & os, char_type c, char_type next, OutputParams const & runparams) { // Writing next here may circumvent a possible font change between @@ -559,8 +839,14 @@ int Paragraph::Private::latexSurrogatePair(odocstream & os, char_type c, // FIXME: change tracking // Is this correct WRT change tracking? Encoding const & encoding = *(runparams.encoding); - docstring const latex1 = encoding.latexChar(next); - docstring const latex2 = encoding.latexChar(c); + docstring latex1 = encoding.latexChar(next).first; + if (runparams.inIPA) { + string const tipashortcut = Encodings::TIPAShortcut(next); + if (!tipashortcut.empty()) { + latex1 = from_ascii(tipashortcut); + } + } + docstring const latex2 = encoding.latexChar(c).first; if (docstring(1, next) == latex1) { // the encoding supports the combination os << latex2 << latex1; @@ -577,13 +863,13 @@ int Paragraph::Private::latexSurrogatePair(odocstream & os, char_type c, bool Paragraph::Private::simpleTeXBlanks(OutputParams const & runparams, - odocstream & os, TexRow & texrow, + otexstream & os, pos_type i, unsigned int & column, Font const & font, Layout const & style) { - if (style.pass_thru || runparams.verbatim) + if (style.pass_thru || runparams.pass_thru) return false; if (i + 1 < int(text_.size())) { @@ -610,8 +896,7 @@ bool Paragraph::Private::simpleTeXBlanks(OutputParams const & runparams, || text_[i - 1] == ':' || text_[i - 1] == '!'))) { os << '\n'; - texrow.newline(); - texrow.start(owner_->id(), i + 1); + os.texrow().start(owner_->id(), i + 1); column = 0; } else if (style.free_spacing) { os << '~'; @@ -622,7 +907,7 @@ bool Paragraph::Private::simpleTeXBlanks(OutputParams const & runparams, } -int Paragraph::Private::writeScriptChars(odocstream & os, +int Paragraph::Private::writeScriptChars(otexstream & os, docstring const & ltx, Change const & runningChange, Encoding const & encoding, @@ -677,7 +962,7 @@ int Paragraph::Private::writeScriptChars(odocstream & os, // 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 const latex = encoding.latexChar(next).first; docstring::size_type const b1 = latex.find_first_of(from_ascii("{")); docstring::size_type const b2 = @@ -695,50 +980,30 @@ int Paragraph::Private::writeScriptChars(odocstream & os, } -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 > 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] != text_[pos + i]) - return false; - } - - return fontlist_.hasChangeInRange(pos, len); -} - - -void Paragraph::Private::latexInset( - BufferParams const & bparams, - odocstream & os, - TexRow & texrow, - OutputParams & runparams, - Font & running_font, - Font & basefont, - Font const & outerfont, - bool & open_font, - Change & running_change, - Layout const & style, - pos_type & i, - unsigned int & column) +void Paragraph::Private::latexInset(BufferParams const & bparams, + otexstream & os, + OutputParams & runparams, + Font & running_font, + Font & basefont, + Font const & outerfont, + bool & open_font, + Change & running_change, + Layout const & style, + pos_type & i, + unsigned int & column) { Inset * inset = owner_->getInset(i); - LASSERT(inset, /**/); + LBUFERR(inset); if (style.pass_thru) { - inset->plaintext(os, runparams); + odocstringstream ods; + inset->plaintext(ods, runparams); + os << ods.str(); return; } // FIXME: move this to InsetNewline::latex - if (inset->lyxCode() == NEWLINE_CODE) { + if (inset->lyxCode() == NEWLINE_CODE || inset->lyxCode() == SEPARATOR_CODE) { // newlines are handled differently here than // the default in simpleTeXSpecialChars(). if (!style.newline_allowed) { @@ -761,8 +1026,7 @@ void Paragraph::Private::latexInset( os << "\\protect "; } - texrow.newline(); - texrow.start(owner_->id(), i + 1); + os.texrow().start(owner_->id(), i + 1); column = 0; } @@ -778,14 +1042,15 @@ void Paragraph::Private::latexInset( } bool close = false; - odocstream::pos_type const len = os.tellp(); + odocstream::pos_type const len = os.os().tellp(); if (inset->forceLTR() + && !runparams.use_polyglossia && 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") + if (running_font.language()->lang() == "farsi") os << "\\beginL{}"; else os << "\\L{"; @@ -804,16 +1069,16 @@ void Paragraph::Private::latexInset( // 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()) { + if (open_font && !inset->inheritFont()) { 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, + // 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 + // 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); @@ -827,10 +1092,12 @@ void Paragraph::Private::latexInset( } } - int tmp; + int prev_rows = os.texrow().rows(); try { - tmp = inset->latex(os, runparams); + runparams.lastid = id_; + runparams.lastpos = i; + inset->latex(os, runparams); } catch (EncodingException & e) { // add location information and throw again. e.par_id = id_; @@ -845,14 +1112,11 @@ void Paragraph::Private::latexInset( os << '}'; } - if (tmp) { - for (int j = 0; j < tmp; ++j) - texrow.newline(); - - texrow.start(owner_->id(), i + 1); + if (os.texrow().rows() > prev_rows) { + os.texrow().start(owner_->id(), i + 1); column = 0; } else { - column += os.tellp() - len; + column += (unsigned int)(os.os().tellp() - len); } if (owner_->isDeleted(i)) @@ -860,43 +1124,43 @@ void Paragraph::Private::latexInset( } -void Paragraph::Private::latexSpecialChar( - odocstream & os, - OutputParams const & runparams, - Font const & running_font, - Change const & 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. +void Paragraph::Private::latexSpecialChar(otexstream & os, + BufferParams const & bparams, + OutputParams const & runparams, + Font const & running_font, + Change const & running_change, + Layout const & style, + pos_type & i, + pos_type end_pos, + unsigned int & column) +{ + // With polyglossia, brackets and stuff need not be reversed + // in RTL scripts (see bug #8251) + char_type const c = (runparams.use_polyglossia) ? + owner_->getUChar(bparams, i) : text_[i]; + + if (style.pass_thru || runparams.pass_thru + || contains(style.pass_thru_chars, c) + || contains(runparams.pass_thru_chars, c)) { + if (c != '\0') { + Encoding const * const enc = runparams.encoding; + if (enc && !enc->encodable(c)) + throw EncodingException(c); os.put(c); + } return; } - if (runparams.verbatim) { - // FIXME UNICODE: This can fail if c cannot - // be encoded in the current encoding. - os.put(c); + // TIPA uses its own T3 encoding + if (runparams.inIPA && latexSpecialT3(c, os, i, column)) return; - } - // If T1 font encoding is used, use the special // characters it provides. - // NOTE: some languages reset the font encoding - // internally - if (!running_font.language()->internalFontEncoding() - && lyxrc.fontenc == "T1" && latexSpecialT1(c, os, i, column)) - return; - - // \tt font needs special treatment - if (running_font.fontInfo().family() == TYPEWRITER_FAMILY - && latexSpecialTypewriter(c, os, i, column)) + // NOTE: Some languages reset the font encoding internally. + // If we are using such a language, we do not output + // special T1 chars. + if (!runparams.inIPA && !running_font.language()->internalFontEncoding() + && bparams.font_encoding() == "T1" && latexSpecialT1(c, os, i, column)) return; // Otherwise, we use what LaTeX provides us. @@ -919,6 +1183,14 @@ void Paragraph::Private::latexSpecialChar( break; case '-': os << '-'; + if (i + 1 < end_pos && text_[i+1] == '-') { + // Prevent "--" becoming an endash and "---" becoming + // an emdash. + // Within \ttfamily, "--" is merged to "-" (no endash) + // so we avoid this rather irritating ligature as well + os << "{}"; + column += 2; + } break; case '\"': os << "\\char`\\\"{}"; @@ -962,16 +1234,13 @@ void Paragraph::Private::latexSpecialChar( break; default: - // LyX, LaTeX etc. - if (latexSpecialPhrase(os, i, column, runparams)) - return; - if (c == '\0') return; Encoding const & encoding = *(runparams.encoding); + char_type next = '\0'; if (i + 1 < int(text_.size())) { - char_type next = text_[i + 1]; + next = text_[i + 1]; if (Encodings::isCombiningChar(next)) { column += latexSurrogatePair(os, c, next, runparams) - 1; ++i; @@ -979,27 +1248,57 @@ void Paragraph::Private::latexSpecialChar( } } string script; - docstring const latex = encoding.latexChar(c); + pair latex = encoding.latexChar(c); + docstring nextlatex; + bool nexttipas = false; + string nexttipashortcut; + if (next != '\0' && next != META_INSET && encoding.encodable(next)) { + nextlatex = encoding.latexChar(next).first; + if (runparams.inIPA) { + nexttipashortcut = Encodings::TIPAShortcut(next); + nexttipas = !nexttipashortcut.empty(); + } + } + bool tipas = false; + if (runparams.inIPA) { + string const tipashortcut = Encodings::TIPAShortcut(c); + if (!tipashortcut.empty()) { + latex.first = from_ascii(tipashortcut); + latex.second = false; + tipas = true; + } + } if (Encodings::isKnownScriptChar(c, script) - && prefixIs(latex, from_ascii("\\" + script))) - column += writeScriptChars(os, latex, + && prefixIs(latex.first, from_ascii("\\" + script))) + column += writeScriptChars(os, latex.first, running_change, encoding, i) - 1; - else if (latex.length() > 1 && latex[latex.length() - 1] != '}') { + else if (latex.second + && ((!prefixIs(nextlatex, '\\') + && !prefixIs(nextlatex, '{') + && !prefixIs(nextlatex, '}')) + || (nexttipas + && !prefixIs(from_ascii(nexttipashortcut), '\\'))) + && !tipas) { // Prevent eating of a following // space or command corruption by // following characters - column += latex.length() + 1; - os << latex << "{}"; + if (next == ' ' || next == '\0') { + column += latex.first.length() + 1; + os << latex.first << "{}"; + } else { + column += latex.first.length(); + os << latex.first << " "; + } } else { - column += latex.length() - 1; - os << latex; + column += latex.first.length() - 1; + os << latex.first; } break; } } -bool Paragraph::Private::latexSpecialT1(char_type const c, odocstream & os, +bool Paragraph::Private::latexSpecialT1(char_type const c, otexstream & os, pos_type i, unsigned int & column) { switch (c) { @@ -1027,70 +1326,75 @@ bool Paragraph::Private::latexSpecialT1(char_type const c, odocstream & os, } -bool Paragraph::Private::latexSpecialTypewriter(char_type const c, odocstream & os, - pos_type i, unsigned int & column) +bool Paragraph::Private::latexSpecialT3(char_type const c, otexstream & os, + pos_type /*i*/, unsigned int & column) { switch (c) { - case '-': - // within \ttfamily, "--" is merged to "-" (no endash) - // so we avoid this rather irritating ligature - if (i + 1 < int(text_.size()) && text_[i + 1] == '-') { - os << "-{}"; - column += 2; - } else - os << '-'; + case '*': + case '[': + case ']': + case '\"': + os.put(c); + return true; + case '|': + os << "\\textvertline{}"; + column += 14; return true; - - // everything else has to be checked separately - // (depending on the encoding) default: return false; } } -bool Paragraph::Private::latexSpecialPhrase(odocstream & os, pos_type & i, - unsigned int & column, OutputParams const & 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) const { if (layout_->inpreamble && inset_owner_) { + bool const is_command = layout_->latextype == LATEX_COMMAND; Buffer const & buf = inset_owner_->buffer(); - BufferParams const & bp = buf.params(); + BufferParams const & bp = features.runparams().is_child + ? buf.masterParams() : buf.params(); Font f; - TexRow tr; + TexRow texrow; + // Using a string stream here circumvents the encoding + // switching machinery of odocstream. Therefore the + // output is wrong if this paragraph contains content + // that needs to switch encoding. odocstringstream ods; - owner_->latex(bp, f, ods, tr, features.runparams()); - docstring d = ods.str(); - if (!d.empty()) - features.addPreambleSnippet(to_utf8(d)); + otexstream os(ods, texrow); + if (is_command) { + os << '\\' << from_ascii(layout_->latexname()); + // we have to provide all the optional arguments here, even though + // the last one is the only one we care about. + // Separate handling of optional argument inset. + if (!layout_->latexargs().empty()) { + OutputParams rp = features.runparams(); + rp.local_font = &owner_->getFirstFontSettings(bp); + latexArgInsets(*owner_, os, rp, layout_->latexargs()); + } + os << from_ascii(layout_->latexparam()); + } + docstring::size_type const length = ods.str().length(); + // this will output "{" at the beginning, but not at the end + owner_->latex(bp, f, os, features.runparams(), 0, -1, true); + if (ods.str().length() > length) { + if (is_command) { + ods << '}'; + if (!layout_->postcommandargs().empty()) { + OutputParams rp = features.runparams(); + rp.local_font = &owner_->getFirstFontSettings(bp); + latexArgInsets(*owner_, os, rp, layout_->postcommandargs(), "post:"); + } + } + string const snippet = to_utf8(ods.str()); + features.addPreambleSnippet(snippet); + } } - - if (features.runparams().flavor == OutputParams::HTML + + if (features.runparams().flavor == OutputParams::HTML && layout_->htmltitle()) { - features.setHTMLTitle(owner_->asString(AS_STR_INSETS)); + features.setHTMLTitle(owner_->asString(AS_STR_INSETS | AS_STR_SKIPDELETE)); } - + // check the params. if (!params_.spacing().isDefault()) features.require("setspace"); @@ -1113,20 +1417,13 @@ void Paragraph::Private::validate(LaTeXFeatures & features) const icit->inset->validate(features); if (layout_->needprotect && icit->inset->lyxCode() == FOOT_CODE) - features.require("NeedLyXFootnoteCode"); + features.require("footmisc"); } } // then the contents 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)) { - features.require(special_phrases[pnr].phrase); - break; - } - } - Encodings::validate(text_[i], features); + BufferEncodings::validate(text_[i], features); } } @@ -1140,7 +1437,7 @@ namespace { Layout const emptyParagraphLayout; } -Paragraph::Paragraph() +Paragraph::Paragraph() : d(new Paragraph::Private(this, emptyParagraphLayout)) { itemdepth = 0; @@ -1232,7 +1529,7 @@ void Paragraph::write(ostream & os, BufferParams const & bparams, int column = 0; for (pos_type i = 0; i <= size(); ++i) { - Change const change = lookupChange(i); + Change const & change = lookupChange(i); if (change != running_change) flushString(os, write_buffer); Changes::lyxMarkChange(os, bparams, column, running_change, change); @@ -1241,9 +1538,8 @@ void Paragraph::write(ostream & os, BufferParams const & bparams, if (i == size()) break; - // Write font changes (ignore spelling markers) + // Write font changes Font font2 = getFontSettings(bparams, i); - font2.setMisspelled(false); if (font2 != font1) { flushString(os, write_buffer); font2.lyxWriteChanges(font1, os); @@ -1269,6 +1565,9 @@ void Paragraph::write(ostream & os, BufferParams const & bparams, os << "\n\\end_inset\n\n"; column = 0; } + // FIXME This can be removed again once the mystery + // crash has been resolved. + os << flush; } break; case '\\': @@ -1304,6 +1603,9 @@ void Paragraph::write(ostream & os, BufferParams const & bparams, flushString(os, write_buffer); os << "\n\\end_layout\n"; + // FIXME This can be removed again once the mystery + // crash has been resolved. + os << flush; } @@ -1329,6 +1631,7 @@ void Paragraph::appendChar(char_type c, Font const & font, // when appending characters, no need to update tables d->text_.push_back(c); setFont(d->text_.size() - 1, font); + d->requestSpellCheck(d->text_.size() - 1); } @@ -1349,6 +1652,7 @@ void Paragraph::appendString(docstring const & s, Font const & font, for (size_t i = oldsize; i != newsize; ++i) { // track change d->changes_.insert(change, i); + d->requestSpellCheck(i); } d->fontlist_.set(oldsize, font); d->fontlist_.set(newsize - 1, font); @@ -1380,16 +1684,6 @@ void Paragraph::insertChar(pos_type pos, char_type c, } -bool Paragraph::insertInset(pos_type pos, Inset * inset, - Font const & font, Change const & change) -{ - bool const success = insertInset(pos, inset, change); - // Set the font/language of the inset... - setFont(pos, font); - return success; -} - - void Paragraph::resetFonts(Font const & font) { d->fontlist_.clear(); @@ -1403,7 +1697,7 @@ Font const & Paragraph::getFontSettings(BufferParams const & bparams, { if (pos > size()) { LYXERR0("pos: " << pos << " size: " << size()); - LASSERT(pos <= size(), /**/); + LBUFERR(false); } FontList::const_iterator cit = d->fontlist_.fontIterator(pos); @@ -1428,9 +1722,9 @@ Font const & Paragraph::getFontSettings(BufferParams const & bparams, FontSpan Paragraph::fontSpan(pos_type pos) const { - LASSERT(pos <= size(), /**/); - pos_type start = 0; + LBUFERR(pos < size()); + pos_type start = 0; FontList::const_iterator cit = d->fontlist_.begin(); FontList::const_iterator end = d->fontlist_.end(); for (; cit != end; ++cit) { @@ -1447,8 +1741,8 @@ FontSpan Paragraph::fontSpan(pos_type pos) const } // This should not happen, but if so, we take no chances. - // LYXERR0("Paragraph::getEndPosOfFontSpan: This should not happen!"); - return FontSpan(pos, pos); + LYXERR0("Paragraph::fontSpan: position not found in fontinfo table!"); + LASSERT(false, return FontSpan(pos, pos)); } @@ -1479,7 +1773,7 @@ Font const & Paragraph::getFirstFontSettings(BufferParams const & bparams) const Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos, Font const & outerfont) const { - LASSERT(pos >= 0, /**/); + LBUFERR(pos >= 0); Font font = getFontSettings(bparams, pos); @@ -1528,16 +1822,23 @@ FontSize Paragraph::highestFontInRange char_type Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const { char_type c = d->text_[pos]; - if (!lyxrc.rtl_support) + if (!getFontSettings(bparams, pos).isRightToLeft()) return c; + // FIXME: The arabic special casing is due to the difference of arabic + // round brackets input introduced in r18599. Check if this should be + // unified with Hebrew or at least if all bracket types should be + // handled the same (file format change in either case). + string const & lang = getFontSettings(bparams, pos).language()->lang(); + bool const arabic = lang == "arabic_arabtex" || lang == "arabic_arabi" + || lang == "farsi"; char_type uc = c; switch (c) { case '(': - uc = ')'; + uc = arabic ? c : ')'; break; case ')': - uc = '('; + uc = arabic ? c : '('; break; case '[': uc = ']'; @@ -1558,20 +1859,19 @@ char_type Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const uc = '<'; break; } - if (uc != c && getFontSettings(bparams, pos).isRightToLeft()) - return uc; - return c; + + return uc; } void Paragraph::setFont(pos_type pos, Font const & font) { - LASSERT(pos <= size(), /**/); + LASSERT(pos <= size(), return); // First, reduce font against layout/label font // Update: The setCharFont() routine in text2.cpp already // reduces font, so we don't need to do that here. (Asger) - + d->fontlist_.set(pos, font); } @@ -1656,14 +1956,14 @@ void Paragraph::setLabelWidthString(docstring const & s) } -docstring Paragraph::expandLabel(Layout const & layout, +docstring Paragraph::expandLabel(Layout const & layout, BufferParams const & bparams) const -{ - return expandParagraphLabel(layout, bparams, true); +{ + return expandParagraphLabel(layout, bparams, true); } -docstring Paragraph::expandDocBookLabel(Layout const & layout, +docstring Paragraph::expandDocBookLabel(Layout const & layout, BufferParams const & bparams) const { return expandParagraphLabel(layout, bparams, false); @@ -1678,8 +1978,7 @@ docstring Paragraph::expandParagraphLabel(Layout const & layout, bool const in_appendix = process_appendix && d->params_.appendix(); docstring fmt = translateIfPossible(layout.labelstring(in_appendix), lang); - if (fmt.empty() && layout.labeltype == LABEL_COUNTER - && !layout.counter.empty()) + if (fmt.empty() && !layout.counter.empty()) return tclass.counters().theCounter(layout.counter, lang); // handle 'inherited level parts' in 'fmt', @@ -1693,7 +1992,7 @@ docstring Paragraph::expandParagraphLabel(Layout const & layout, if (tclass.hasLayout(parent)) docstring label = expandParagraphLabel(tclass[parent], bparams, process_appendix); - fmt = docstring(fmt, 0, i) + label + fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos); } } @@ -1706,9 +2005,9 @@ void Paragraph::applyLayout(Layout const & new_layout) { d->layout_ = &new_layout; LyXAlignment const oldAlign = d->params_.align(); - + if (!(oldAlign & d->layout_->alignpossible)) { - frontend::Alert::warning(_("Alignment not permitted"), + frontend::Alert::warning(_("Alignment not permitted"), _("The new layout does not permit the alignment previously used.\nSetting to default.")); d->params_.align(LYX_ALIGN_LAYOUT); } @@ -1733,17 +2032,17 @@ void Paragraph::setBeginOfBody() // remove unnecessary getChar() calls pos_type i = 0; pos_type end = size(); - if (i < end && !isNewline(i)) { + if (i < end && !(isNewline(i) || isEnvSeparator(i))) { ++i; char_type previous_char = 0; char_type temp = 0; if (i < end) { previous_char = d->text_[i]; - if (!isNewline(i)) { + if (!(isNewline(i) || isEnvSeparator(i))) { ++i; while (i < end && previous_char != ' ') { temp = d->text_[i]; - if (isNewline(i)) + if (isNewline(i) || isEnvSeparator(i)) break; ++i; previous_char = temp; @@ -1768,6 +2067,11 @@ bool Paragraph::usePlainLayout() const } +bool Paragraph::isPassThru() const +{ + return inInset().isPassThru() || d->layout_->pass_thru; +} + namespace { // paragraphs inside floats need different alignment tags to avoid @@ -1793,53 +2097,57 @@ string correction(string const & orig) } -string const corrected_env(string const & suffix, string const & env, - InsetCode code, bool const lastpar) +bool corrected_env(otexstream & os, string const & suffix, string const & env, + InsetCode code, bool const lastpar, int & col) { - string output = suffix + "{"; + string macro = suffix + "{"; if (noTrivlistCentering(code)) { if (lastpar) { // the last paragraph in non-trivlist-aligned // context is special (to avoid unwanted whitespace) - if (suffix == "\\begin") - return "\\" + correction(env) + "{}"; - return string(); + if (suffix == "\\begin") { + macro = "\\" + correction(env) + "{}"; + os << from_ascii(macro); + col += macro.size(); + return true; + } + return false; } - output += correction(env); + macro += correction(env); } else - output += env; - output += "}"; - if (suffix == "\\begin") - output += "\n"; - return output; -} - - -void adjust_row_column(string const & str, TexRow & texrow, int & column) -{ - if (!contains(str, "\n")) - column += str.size(); - else { - string tmp; - texrow.newline(); - column = rsplit(str, tmp, '\n').size(); + macro += env; + macro += "}"; + if (suffix == "\\par\\end") { + os << breakln; + col = 0; + } + os << from_ascii(macro); + col += macro.size(); + if (suffix == "\\begin") { + os << breakln; + col = 0; } + return true; } } // namespace anon int Paragraph::Private::startTeXParParams(BufferParams const & bparams, - odocstream & os, TexRow & texrow, - OutputParams const & runparams) const + otexstream & os, OutputParams const & runparams) const { int column = 0; - if (params_.noindent()) { + bool canindent = + (bparams.paragraph_separation == BufferParams::ParagraphIndentSeparation) ? + (layout_->toggle_indent != ITOGGLE_NEVER) : + (layout_->toggle_indent == ITOGGLE_ALWAYS); + + if (canindent && params_.noindent() && !layout_->pass_thru) { os << "\\noindent "; column += 10; } - + LyXAlignment const curAlign = params_.align(); if (curAlign == layout_->align) @@ -1850,6 +2158,7 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, case LYX_ALIGN_BLOCK: case LYX_ALIGN_LAYOUT: case LYX_ALIGN_SPECIAL: + case LYX_ALIGN_DECIMAL: break; case LYX_ALIGN_LEFT: case LYX_ALIGN_RIGHT: @@ -1870,30 +2179,23 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, case LYX_ALIGN_BLOCK: case LYX_ALIGN_LAYOUT: case LYX_ALIGN_SPECIAL: + case LYX_ALIGN_DECIMAL: break; case LYX_ALIGN_LEFT: { - string output; if (owner_->getParLanguage(bparams)->babel() != "hebrew") - output = corrected_env(begin_tag, "flushleft", code, lastpar); + corrected_env(os, begin_tag, "flushleft", code, lastpar, column); else - output = corrected_env(begin_tag, "flushright", code, lastpar); - os << from_ascii(output); - adjust_row_column(output, texrow, column); + corrected_env(os, begin_tag, "flushright", code, lastpar, column); break; } case LYX_ALIGN_RIGHT: { string output; if (owner_->getParLanguage(bparams)->babel() != "hebrew") - output = corrected_env(begin_tag, "flushright", code, lastpar); + corrected_env(os, begin_tag, "flushright", code, lastpar, column); else - output = corrected_env(begin_tag, "flushleft", code, lastpar); - os << from_ascii(output); - adjust_row_column(output, texrow, column); + corrected_env(os, begin_tag, "flushleft", code, lastpar, column); break; } case LYX_ALIGN_CENTER: { - string output; - output = corrected_env(begin_tag, "center", code, lastpar); - os << from_ascii(output); - adjust_row_column(output, texrow, column); + corrected_env(os, begin_tag, "center", code, lastpar, column); break; } } @@ -1902,34 +2204,32 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, } -int Paragraph::Private::endTeXParParams(BufferParams const & bparams, - odocstream & os, TexRow & texrow, - OutputParams const & runparams) const +bool Paragraph::Private::endTeXParParams(BufferParams const & bparams, + otexstream & os, OutputParams const & runparams) const { - int column = 0; - LyXAlignment const curAlign = params_.align(); if (curAlign == layout_->align) - return column; + return false; switch (curAlign) { case LYX_ALIGN_NONE: case LYX_ALIGN_BLOCK: case LYX_ALIGN_LAYOUT: case LYX_ALIGN_SPECIAL: + case LYX_ALIGN_DECIMAL: break; case LYX_ALIGN_LEFT: case LYX_ALIGN_RIGHT: case LYX_ALIGN_CENTER: - if (runparams.moving_arg) { + if (runparams.moving_arg) os << "\\protect"; - column = 8; - } break; } - string const end_tag = "\n\\par\\end"; + bool output = false; + int col = 0; + string const end_tag = "\\par\\end"; InsetCode code = ownerCode(); bool const lastpar = runparams.isLastPar; @@ -1938,59 +2238,49 @@ int Paragraph::Private::endTeXParParams(BufferParams const & bparams, case LYX_ALIGN_BLOCK: case LYX_ALIGN_LAYOUT: case LYX_ALIGN_SPECIAL: + case LYX_ALIGN_DECIMAL: break; case LYX_ALIGN_LEFT: { - string output; if (owner_->getParLanguage(bparams)->babel() != "hebrew") - output = corrected_env(end_tag, "flushleft", code, lastpar); + output = corrected_env(os, end_tag, "flushleft", code, lastpar, col); else - output = corrected_env(end_tag, "flushright", code, lastpar); - os << from_ascii(output); - adjust_row_column(output, texrow, column); + output = corrected_env(os, end_tag, "flushright", code, lastpar, col); break; } case LYX_ALIGN_RIGHT: { - string output; if (owner_->getParLanguage(bparams)->babel() != "hebrew") - output = corrected_env(end_tag, "flushright", code, lastpar); + output = corrected_env(os, end_tag, "flushright", code, lastpar, col); else - output = corrected_env(end_tag, "flushleft", code, lastpar); - os << from_ascii(output); - adjust_row_column(output, texrow, column); + output = corrected_env(os, end_tag, "flushleft", code, lastpar, col); break; } case LYX_ALIGN_CENTER: { - string output; - output = corrected_env(end_tag, "center", code, lastpar); - os << from_ascii(output); - adjust_row_column(output, texrow, column); + corrected_env(os, end_tag, "center", code, lastpar, col); break; } } - return column; + return output || lastpar; } // This one spits out the text of the paragraph -bool Paragraph::latex(BufferParams const & bparams, +void Paragraph::latex(BufferParams const & bparams, Font const & outerfont, - odocstream & os, TexRow & texrow, + otexstream & os, OutputParams const & runparams, - int start_pos, int end_pos) const + int start_pos, int end_pos, bool force) const { LYXERR(Debug::LATEX, "Paragraph::latex... " << this); - if (layout().inpreamble) - return true; - - bool return_value = false; - - bool const allowcust = allowParagraphCustomization(); - // FIXME This check should not be needed. Perhaps issue an // error if it triggers. Layout const & style = inInset().forcePlainLayout() ? bparams.documentClass().plainLayout() : *d->layout_; + if (!force && style.inpreamble) + return; + + bool const allowcust = allowParagraphCustomization(); + // Current base font for all inherited font changes, without any // change caused by an individual character, except for the language: // It is set to the language of the first character. @@ -2006,6 +2296,10 @@ bool Paragraph::latex(BufferParams const & bparams, if (body_pos > 0) { // the optional argument is kept in curly brackets in // case it contains a ']' + // This is not strictly needed, but if this is changed it + // would be a file format change, and tex2lyx would need + // to be adjusted, since it unconditionally removes the + // braces when it parses \item. os << "[{"; column += 2; basefont = getLabelFont(bparams, outerfont); @@ -2022,7 +2316,7 @@ bool Paragraph::latex(BufferParams const & bparams, Encoding const * const prev_encoding = runparams.encoding; - texrow.start(id(), 0); + os.texrow().start(id(), 0); // if the paragraph is empty, the loop will not be entered at all if (empty()) { @@ -2030,9 +2324,12 @@ bool Paragraph::latex(BufferParams const & bparams, os << '{'; ++column; } + if (!style.leftdelim().empty()) { + os << style.leftdelim(); + column += style.leftdelim().size(); + } if (allowcust) - column += d->startTeXParParams(bparams, os, texrow, - runparams); + column += d->startTeXParParams(bparams, os, runparams); } for (pos_type i = 0; i < size(); ++i) { @@ -2061,16 +2358,20 @@ bool Paragraph::latex(BufferParams const & bparams, ++column; } + if (!style.leftdelim().empty()) { + os << style.leftdelim(); + column += style.leftdelim().size(); + } + if (allowcust) column += d->startTeXParParams(bparams, os, - texrow, runparams); } - Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset - : lookupChange(i); + Change const & change = runparams.inDeletedInset + ? runparams.changeOfDeletedInset : lookupChange(i); - if (bparams.outputChanges && runningChange != change) { + if (bparams.output_changes && runningChange != change) { if (open_font) { column += running_font.latexWriteEndChanges( os, bparams, runparams, basefont, basefont); @@ -2086,7 +2387,7 @@ bool Paragraph::latex(BufferParams const & bparams, // do not output text which is marked deleted // if change tracking output is disabled - if (!bparams.outputChanges && change.deleted()) { + if (!bparams.output_changes && change.deleted()) { continue; } @@ -2109,22 +2410,27 @@ bool Paragraph::latex(BufferParams const & bparams, open_font = false; } + string const running_lang = runparams.use_polyglossia ? + running_font.language()->polyglossia() : running_font.language()->babel(); // close babel's font environment before opening CJK. - if (!running_font.language()->babel().empty() && + string const lang_end_command = runparams.use_polyglossia ? + "\\end{$$lang}" : lyxrc.language_command_end; + if (!running_lang.empty() && font.language()->encoding()->package() == Encoding::CJK) { - string end_tag = subst(lyxrc.language_command_end, + string end_tag = subst(lang_end_command, "$$lang", - running_font.language()->babel()); + running_lang); os << from_ascii(end_tag); column += end_tag.length(); } // Switch file encoding if necessary (and allowed) - if (!runparams.verbatim && + if (!runparams.pass_thru && !style.pass_thru && runparams.encoding->package() != Encoding::none && font.language()->encoding()->package() != Encoding::none) { - pair const enc_switch = switchEncoding(os, bparams, - runparams, *(font.language()->encoding())); + pair const enc_switch = + switchEncoding(os.os(), bparams, runparams, + *(font.language()->encoding())); if (enc_switch.first) { column += enc_switch.second; runparams.encoding = font.language()->encoding(); @@ -2153,7 +2459,7 @@ bool Paragraph::latex(BufferParams const & bparams, // check if the fontchange ends with a trailing blank // (like "\small " (see bug 3382) else if (suffixIs(fontchange, ' ') && c == ' ') - os << fontchange.substr(0, fontchange.size() - 1) + os << fontchange.substr(0, fontchange.size() - 1) << from_ascii("{}"); else os << fontchange; @@ -2167,8 +2473,7 @@ bool Paragraph::latex(BufferParams const & bparams, // latexSpecialChar ignores spaces if // style.pass_thru is false. if (i != body_pos - 1) { - if (d->simpleTeXBlanks( - runparams, os, texrow, + if (d->simpleTeXBlanks(runparams, os, i, column, font, style)) { // A surrogate pair was output. We // must not call latexSpecialChar @@ -2190,16 +2495,15 @@ bool Paragraph::latex(BufferParams const & bparams, // and then split to handle the two modes separately. if (c == META_INSET) { if (i >= start_pos && (end_pos == -1 || i < end_pos)) { - d->latexInset(bparams, os, - texrow, rp, running_font, + d->latexInset(bparams, os, rp, running_font, basefont, outerfont, open_font, runningChange, style, i, column); } } else { if (i >= start_pos && (end_pos == -1 || i < end_pos)) { try { - d->latexSpecialChar(os, rp, running_font, runningChange, - style, i, column); + d->latexSpecialChar(os, bparams, rp, running_font, runningChange, + style, i, end_pos, column); } catch (EncodingException & e) { if (runparams.dryrun) { os << "<" << _("LyX Warning: ") @@ -2225,9 +2529,8 @@ bool Paragraph::latex(BufferParams const & bparams, if (open_font) { #ifdef FIXED_LANGUAGE_END_DETECTION if (next_) { - running_font - .latexWriteEndChanges(os, bparams, runparams, - basefont, + running_font.latexWriteEndChanges(os, bparams, + runparams, basefont, next_->getFont(bparams, 0, outerfont)); } else { running_font.latexWriteEndChanges(os, bparams, @@ -2248,18 +2551,21 @@ bool Paragraph::latex(BufferParams const & bparams, // Needed if there is an optional argument but no contents. if (body_pos > 0 && body_pos == size()) { os << "}]~"; - return_value = false; } - if (allowcust && d->endTeXParParams(bparams, os, texrow, runparams) + if (!style.rightdelim().empty()) { + os << style.rightdelim(); + column += style.rightdelim().size(); + } + + if (allowcust && d->endTeXParParams(bparams, os, runparams) && runparams.encoding != prev_encoding) { runparams.encoding = prev_encoding; - if (!bparams.useXetex) + if (!runparams.isFullUnicode()) os << setEncoding(prev_encoding->iconvName()); } LYXERR(Debug::LATEX, "Paragraph::latex... done " << this); - return return_value; } @@ -2413,6 +2719,22 @@ void Paragraph::simpleDocBookOnePar(Buffer const & buf, } +namespace { +void doFontSwitch(vector & tagsToOpen, + vector & tagsToClose, + bool & flag, FontState curstate, html::FontTypes type) +{ + if (curstate == FONT_ON) { + tagsToOpen.push_back(html::FontTag(type)); + flag = true; + } else if (flag) { + tagsToClose.push_back(html::EndFontTag(type)); + flag = false; + } +} +} + + docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, XHTMLStream & xs, OutputParams const & runparams, @@ -2421,95 +2743,322 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, { docstring retval; + // track whether we have opened these tags bool emph_flag = false; bool bold_flag = false; - string closing_tag; + bool noun_flag = false; + bool ubar_flag = false; + bool dbar_flag = false; + bool sout_flag = false; + bool wave_flag = false; + // shape tags + bool shap_flag = false; + // family tags + bool faml_flag = false; + // size tags + bool size_flag = false; Layout const & style = *d->layout_; - if (!runparams.for_toc && runparams.html_make_pars) { - // generate a magic label for this paragraph - string const attr = "id='" + magicLabel() + "'"; - xs << html::CompTag("a", attr); - } + xs.startParagraph(allowEmpty()); FontInfo font_old = style.labeltype == LABEL_MANUAL ? style.labelfont : style.font; + FontShape curr_fs = INHERIT_SHAPE; + FontFamily curr_fam = INHERIT_FAMILY; + FontSize curr_size = FONT_SIZE_INHERIT; + + string const default_family = + buf.masterBuffer()->params().fonts_default_family; + + vector tagsToOpen; + vector tagsToClose; + // parsing main loop for (pos_type i = initial; i < size(); ++i) { // let's not show deleted material in the output if (isDeleted(i)) continue; - - Font font = getFont(buf.params(), i, outerfont); + + Font const font = getFont(buf.masterBuffer()->params(), i, outerfont); // emphasis - if (font_old.emph() != font.fontInfo().emph()) { - if (font.fontInfo().emph() == FONT_ON) { - xs << html::StartTag("em"); - emph_flag = true; - } else if (emph_flag && i != initial) { - xs << html::EndTag("em"); - emph_flag = false; + FontState curstate = font.fontInfo().emph(); + if (font_old.emph() != curstate) + doFontSwitch(tagsToOpen, tagsToClose, emph_flag, curstate, html::FT_EMPH); + + // noun + curstate = font.fontInfo().noun(); + if (font_old.noun() != curstate) + doFontSwitch(tagsToOpen, tagsToClose, noun_flag, curstate, html::FT_NOUN); + + // underbar + curstate = font.fontInfo().underbar(); + if (font_old.underbar() != curstate) + doFontSwitch(tagsToOpen, tagsToClose, ubar_flag, curstate, html::FT_UBAR); + + // strikeout + curstate = font.fontInfo().strikeout(); + if (font_old.strikeout() != curstate) + doFontSwitch(tagsToOpen, tagsToClose, sout_flag, curstate, html::FT_SOUT); + + // double underbar + curstate = font.fontInfo().uuline(); + if (font_old.uuline() != curstate) + doFontSwitch(tagsToOpen, tagsToClose, dbar_flag, curstate, html::FT_DBAR); + + // wavy line + curstate = font.fontInfo().uwave(); + if (font_old.uwave() != curstate) + doFontSwitch(tagsToOpen, tagsToClose, wave_flag, curstate, html::FT_WAVE); + + // bold + // a little hackish, but allows us to reuse what we have. + curstate = (font.fontInfo().series() == BOLD_SERIES ? FONT_ON : FONT_OFF); + if (font_old.series() != font.fontInfo().series()) + doFontSwitch(tagsToOpen, tagsToClose, bold_flag, curstate, html::FT_BOLD); + + // Font shape + curr_fs = font.fontInfo().shape(); + FontShape old_fs = font_old.shape(); + if (old_fs != curr_fs) { + if (shap_flag) { + switch (old_fs) { + case ITALIC_SHAPE: + tagsToClose.push_back(html::EndFontTag(html::FT_ITALIC)); + break; + case SLANTED_SHAPE: + tagsToClose.push_back(html::EndFontTag(html::FT_SLANTED)); + break; + case SMALLCAPS_SHAPE: + tagsToClose.push_back(html::EndFontTag(html::FT_SMALLCAPS)); + break; + case UP_SHAPE: + case INHERIT_SHAPE: + break; + default: + // the other tags are for internal use + LATTEST(false); + break; + } + shap_flag = false; + } + switch (curr_fs) { + case ITALIC_SHAPE: + tagsToOpen.push_back(html::FontTag(html::FT_ITALIC)); + shap_flag = true; + break; + case SLANTED_SHAPE: + tagsToOpen.push_back(html::FontTag(html::FT_SLANTED)); + shap_flag = true; + break; + case SMALLCAPS_SHAPE: + tagsToOpen.push_back(html::FontTag(html::FT_SMALLCAPS)); + shap_flag = true; + break; + case UP_SHAPE: + case INHERIT_SHAPE: + break; + default: + // the other tags are for internal use + LATTEST(false); + break; } } - // bold - if (font_old.series() != font.fontInfo().series()) { - if (font.fontInfo().series() == BOLD_SERIES) { - xs << html::StartTag("strong"); - bold_flag = true; - } else if (bold_flag && i != initial) { - xs << html::EndTag("strong"); - bold_flag = false; + + // Font family + curr_fam = font.fontInfo().family(); + FontFamily old_fam = font_old.family(); + if (old_fam != curr_fam) { + if (faml_flag) { + switch (old_fam) { + case ROMAN_FAMILY: + tagsToClose.push_back(html::EndFontTag(html::FT_ROMAN)); + break; + case SANS_FAMILY: + tagsToClose.push_back(html::EndFontTag(html::FT_SANS)); + break; + case TYPEWRITER_FAMILY: + tagsToClose.push_back(html::EndFontTag(html::FT_TYPE)); + break; + case INHERIT_FAMILY: + break; + default: + // the other tags are for internal use + LATTEST(false); + break; + } + faml_flag = false; + } + switch (curr_fam) { + case ROMAN_FAMILY: + // we will treat a "default" font family as roman, since we have + // no other idea what to do. + if (default_family != "rmdefault" && default_family != "default") { + tagsToOpen.push_back(html::FontTag(html::FT_ROMAN)); + faml_flag = true; + } + break; + case SANS_FAMILY: + if (default_family != "sfdefault") { + tagsToOpen.push_back(html::FontTag(html::FT_SANS)); + faml_flag = true; + } + break; + case TYPEWRITER_FAMILY: + if (default_family != "ttdefault") { + tagsToOpen.push_back(html::FontTag(html::FT_TYPE)); + faml_flag = true; + } + break; + case INHERIT_FAMILY: + break; + default: + // the other tags are for internal use + LATTEST(false); + break; } } + + // Font size + curr_size = font.fontInfo().size(); + FontSize old_size = font_old.size(); + if (old_size != curr_size) { + if (size_flag) { + switch (old_size) { + case FONT_SIZE_TINY: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_TINY)); + break; + case FONT_SIZE_SCRIPT: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_SCRIPT)); + break; + case FONT_SIZE_FOOTNOTE: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_FOOTNOTE)); + break; + case FONT_SIZE_SMALL: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_SMALL)); + break; + case FONT_SIZE_LARGE: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_LARGE)); + break; + case FONT_SIZE_LARGER: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_LARGER)); + break; + case FONT_SIZE_LARGEST: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_LARGEST)); + break; + case FONT_SIZE_HUGE: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_HUGE)); + break; + case FONT_SIZE_HUGER: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_HUGER)); + break; + case FONT_SIZE_INCREASE: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_INCREASE)); + break; + case FONT_SIZE_DECREASE: + tagsToClose.push_back(html::EndFontTag(html::FT_SIZE_DECREASE)); + break; + case FONT_SIZE_INHERIT: + case FONT_SIZE_NORMAL: + break; + default: + // the other tags are for internal use + LATTEST(false); + break; + } + size_flag = false; + } + switch (curr_size) { + case FONT_SIZE_TINY: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_TINY)); + size_flag = true; + break; + case FONT_SIZE_SCRIPT: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_SCRIPT)); + size_flag = true; + break; + case FONT_SIZE_FOOTNOTE: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_FOOTNOTE)); + size_flag = true; + break; + case FONT_SIZE_SMALL: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_SMALL)); + size_flag = true; + break; + case FONT_SIZE_LARGE: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_LARGE)); + size_flag = true; + break; + case FONT_SIZE_LARGER: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_LARGER)); + size_flag = true; + break; + case FONT_SIZE_LARGEST: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_LARGEST)); + size_flag = true; + break; + case FONT_SIZE_HUGE: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_HUGE)); + size_flag = true; + break; + case FONT_SIZE_HUGER: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_HUGER)); + size_flag = true; + break; + case FONT_SIZE_INCREASE: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_INCREASE)); + size_flag = true; + break; + case FONT_SIZE_DECREASE: + tagsToOpen.push_back(html::FontTag(html::FT_SIZE_DECREASE)); + size_flag = true; + break; + case FONT_SIZE_NORMAL: + case FONT_SIZE_INHERIT: + break; + default: + // the other tags are for internal use + LATTEST(false); + break; + } + } + // FIXME XHTML // Other such tags? What about the other text ranges? + vector::const_iterator cit = tagsToClose.begin(); + vector::const_iterator cen = tagsToClose.end(); + for (; cit != cen; ++cit) + xs << *cit; + + vector::const_iterator sit = tagsToOpen.begin(); + vector::const_iterator sen = tagsToOpen.end(); + for (; sit != sen; ++sit) + xs << *sit; + + tagsToClose.clear(); + tagsToOpen.clear(); + Inset const * inset = getInset(i); if (inset) { - InsetCommand const * ic = inset->asInsetCommand(); - InsetLayout const & il = inset->getLayout(); - InsetMath const * im = inset->asInsetMath(); - if (!runparams.for_toc - || im || il.isInToc() || (ic && ic->isInToc())) { + if (!runparams.for_toc || inset->isInToc()) { OutputParams np = runparams; - if (!il.htmlisblock()) + np.local_font = &font; + if (!inset->getLayout().htmlisblock()) np.html_in_par = true; retval += inset->xhtml(xs, np); } } else { - char_type c = d->text_[i]; - - if (style.pass_thru) - xs << c; - else if (c == '-') { - docstring str; - int j = i + 1; - if (j < size() && d->text_[j] == '-') { - j += 1; - if (j < size() && d->text_[j] == '-') { - str += from_ascii("—"); - i += 2; - } else { - str += from_ascii("–"); - i += 1; - } - } - else - str += c; - // We don't want to escape the entities. Note that - // it is safe to do this, since str can otherwise - // only be "-". E.g., it can't be "<". - xs << XHTMLStream::NextRaw() << str; - } else - xs << c; + char_type c = getUChar(buf.masterBuffer()->params(), i); + xs << c; } font_old = font.fontInfo(); } xs.closeFontTags(); + xs.endParagraph(); return retval; } @@ -2517,8 +3066,7 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, bool Paragraph::isHfill(pos_type pos) const { Inset const * inset = getInset(pos); - return inset && (inset->lyxCode() == SPACE_CODE && - inset->isStretchableSpace()); + return inset && inset->isHfill(); } @@ -2529,6 +3077,13 @@ bool Paragraph::isNewline(pos_type pos) const } +bool Paragraph::isEnvSeparator(pos_type pos) const +{ + Inset const * inset = getInset(pos); + return inset && inset->lyxCode() == SEPARATOR_CODE; +} + + bool Paragraph::isLineSeparator(pos_type pos) const { char_type const c = d->text_[pos]; @@ -2541,13 +3096,45 @@ bool Paragraph::isLineSeparator(pos_type pos) const bool Paragraph::isWordSeparator(pos_type pos) const { + if (pos == size()) + return true; if (Inset const * inset = getInset(pos)) return !inset->isLetter(); + // if we have a hard hyphen (no en- or emdash) or apostrophe + // we pass this to the spell checker + // FIXME: this method is subject to change, visit + // https://bugzilla.mozilla.org/show_bug.cgi?id=355178 + // to get an impression how complex this is. + if (isHardHyphenOrApostrophe(pos)) + return false; char_type const c = d->text_[pos]; - // We want to pass the ' and escape chars to the spellchecker - static docstring const quote = from_utf8(lyxrc.spellchecker_esc_chars + '\''); - return (!isLetterChar(c) && !isDigit(c) && !contains(quote, c)) - || pos == size(); + // We want to pass the escape chars to the spellchecker + docstring const escape_chars = from_utf8(lyxrc.spellchecker_esc_chars); + return !isLetterChar(c) && !isDigitASCII(c) && !contains(escape_chars, c); +} + + +bool Paragraph::isHardHyphenOrApostrophe(pos_type pos) const +{ + pos_type const psize = size(); + if (pos >= psize) + return false; + char_type const c = d->text_[pos]; + if (c != '-' && c != '\'') + return false; + int nextpos = pos + 1; + int prevpos = pos > 0 ? pos - 1 : 0; + if ((nextpos == psize || isSpace(nextpos)) + && (pos == 0 || isSpace(prevpos))) + return false; + return true; +} + + +bool Paragraph::isSameSpellRange(pos_type pos1, pos_type pos2) const +{ + return pos1 == pos2 + || d->speller_state_.getRange(pos1) == d->speller_state_.getRange(pos2); } @@ -2556,7 +3143,7 @@ bool Paragraph::isChar(pos_type pos) const if (Inset const * inset = getInset(pos)) return inset->isChar(); char_type const c = d->text_[pos]; - return !isLetterChar(c) && !isDigit(c) && !lyx::isSpace(c); + return !isLetterChar(c) && !isDigitASCII(c) && !lyx::isSpace(c); } @@ -2581,8 +3168,7 @@ Paragraph::getParLanguage(BufferParams const & bparams) const bool Paragraph::isRTL(BufferParams const & bparams) const { - return lyxrc.rtl_support - && getParLanguage(bparams)->rightToLeft() + return getParLanguage(bparams)->rightToLeft() && !inInset().getLayout().forceLTR(); } @@ -2596,6 +3182,7 @@ void Paragraph::changeLanguage(BufferParams const & bparams, if (font.language() == from) { font.setLanguage(to); setFont(i, font); + d->requestSpellCheck(i); } } } @@ -2603,7 +3190,7 @@ void Paragraph::changeLanguage(BufferParams const & bparams, bool Paragraph::isMultiLingual(BufferParams const & bparams) const { - Language const * doc_language = bparams.language; + Language const * doc_language = bparams.language; FontList::const_iterator cit = d->fontlist_.begin(); FontList::const_iterator end = d->fontlist_.end(); @@ -2616,30 +3203,49 @@ bool Paragraph::isMultiLingual(BufferParams const & bparams) const } +void Paragraph::getLanguages(std::set & languages) const +{ + FontList::const_iterator cit = d->fontlist_.begin(); + FontList::const_iterator end = d->fontlist_.end(); + + for (; cit != end; ++cit) { + Language const * lang = cit->font().language(); + if (lang != ignore_language && + lang != latex_language) + languages.insert(lang); + } +} + + docstring Paragraph::asString(int options) const { return asString(0, size(), options); } -docstring Paragraph::asString(pos_type beg, pos_type end, int options) const +docstring Paragraph::asString(pos_type beg, pos_type end, int options, const OutputParams *runparams) const { odocstringstream os; - if (beg == 0 - && options & AS_STR_LABEL - && !d->params_.labelString().empty()) + if (beg == 0 + && options & AS_STR_LABEL + && !d->params_.labelString().empty()) os << d->params_.labelString() << ' '; for (pos_type i = beg; i < end; ++i) { + if ((options & AS_STR_SKIPDELETE) && isDeleted(i)) + continue; char_type const c = d->text_[i]; if (isPrintable(c) || c == '\t' || (c == '\n' && (options & AS_STR_NEWLINES))) os.put(c); else if (c == META_INSET && (options & AS_STR_INSETS)) { - getInset(i)->tocString(os); - if (getInset(i)->asInsetMath()) - os << " "; + if (c == META_INSET && (options & AS_STR_PLAINTEXT)) { + LASSERT(runparams != 0, return docstring()); + getInset(i)->plaintext(os, *runparams); + } else { + getInset(i)->toString(os); + } } } @@ -2647,26 +3253,21 @@ docstring Paragraph::asString(pos_type beg, pos_type end, int options) const } -docstring Paragraph::stringify(pos_type beg, pos_type end, int options, OutputParams & runparams) const +void Paragraph::forOutliner(docstring & os, size_t maxlen) const { - odocstringstream os; - - if (beg == 0 - && options & AS_STR_LABEL - && !d->params_.labelString().empty()) - os << d->params_.labelString() << ' '; - - for (pos_type i = beg; i < end; ++i) { + if (!d->params_.labelString().empty()) + os += d->params_.labelString() + ' '; + for (pos_type i = 0; i < size() && os.length() < maxlen; ++i) { + if (isDeleted(i)) + continue; char_type const c = d->text_[i]; - if (isPrintable(c) || c == '\t' - || (c == '\n' && (options & AS_STR_NEWLINES))) - os.put(c); - else if (c == META_INSET && (options & AS_STR_INSETS)) { - getInset(i)->plaintext(os, runparams); - } + if (isPrintable(c)) + os += c; + else if (c == '\t' || c == '\n') + os += ' '; + else if (c == META_INSET) + getInset(i)->forOutliner(os, maxlen); } - - return os.str(); } @@ -2701,14 +3302,14 @@ void Paragraph::setLayout(Layout const & layout) void Paragraph::setDefaultLayout(DocumentClass const & tc) -{ - setLayout(tc.defaultLayout()); +{ + setLayout(tc.defaultLayout()); } void Paragraph::setPlainLayout(DocumentClass const & tc) -{ - setLayout(tc.plainLayout()); +{ + setLayout(tc.plainLayout()); } @@ -2723,8 +3324,7 @@ void Paragraph::setPlainOrDefaultLayout(DocumentClass const & tclass) Inset const & Paragraph::inInset() const { - LASSERT(d->inset_owner_, throw ExceptionMessage(BufferException, - _("Memory problem"), _("Paragraph not properly initialized"))); + LBUFERR(d->inset_owner_); return *d->inset_owner_; } @@ -2757,111 +3357,61 @@ bool Paragraph::allowEmpty() const } -char_type Paragraph::transformChar(char_type c, pos_type pos) const +bool Paragraph::brokenBiblio() const { - if (!Encodings::isArabicChar(c)) - return c; - - char_type prev_char = ' '; - char_type next_char = ' '; - - for (pos_type i = pos - 1; i >= 0; --i) { - char_type const par_char = d->text_[i]; - if (!Encodings::isArabicComposeChar(par_char)) { - prev_char = par_char; - break; - } - } - - for (pos_type i = pos + 1, end = size(); i < end; ++i) { - char_type const par_char = d->text_[i]; - if (!Encodings::isArabicComposeChar(par_char)) { - next_char = par_char; - break; - } - } - - if (Encodings::isArabicChar(next_char)) { - if (Encodings::isArabicChar(prev_char) && - !Encodings::isArabicSpecialChar(prev_char)) - return Encodings::transformChar(c, Encodings::FORM_MEDIAL); - else - return Encodings::transformChar(c, Encodings::FORM_INITIAL); - } else { - if (Encodings::isArabicChar(prev_char) && - !Encodings::isArabicSpecialChar(prev_char)) - return Encodings::transformChar(c, Encodings::FORM_FINAL); - else - return Encodings::transformChar(c, Encodings::FORM_ISOLATED); - } + // there is a problem if there is no bibitem at position 0 or + // if there is another bibitem in the paragraph. + return d->layout_->labeltype == LABEL_BIBLIO + && (d->insetlist_.find(BIBITEM_CODE) != 0 + || d->insetlist_.find(BIBITEM_CODE, 1) > 0); } -int Paragraph::checkBiblio(Buffer const & buffer) +int Paragraph::fixBiblio(Buffer const & buffer) { - // FIXME From JS: - // This is getting more and more a mess. ...We really should clean - // up this bibitem issue for 1.6. See also bug 2743. + // FIXME: What about the case where paragraph is not BIBLIO + // but there is an InsetBibitem? + // FIXME: when there was already an inset at 0, the return value is 1, + // which does not tell whether another inset has been remove; the + // cursor cannot be correctly updated. - // Add bibitem insets if necessary if (d->layout_->labeltype != LABEL_BIBLIO) return 0; - bool hasbibitem = !d->insetlist_.empty() - // Insist on it being in pos 0 - && d->text_[0] == META_INSET - && d->insetlist_.begin()->inset->lyxCode() == BIBITEM_CODE; - - bool track_changes = buffer.params().trackChanges; - - docstring oldkey; - docstring oldlabel; - - // remove a bibitem in pos != 0 - // restore it later in pos 0 if necessary - // (e.g. if a user inserts contents _before_ the item) - // we're assuming there's only one of these, which there - // should be. - int erasedInsetPosition = -1; - InsetList::iterator it = d->insetlist_.begin(); - InsetList::iterator end = d->insetlist_.end(); - for (; it != end; ++it) - if (it->inset->lyxCode() == BIBITEM_CODE - && it->pos > 0) { - InsetBibitem * olditem = static_cast(it->inset); - oldkey = olditem->getParam("key"); - oldlabel = olditem->getParam("label"); - erasedInsetPosition = it->pos; - eraseChar(erasedInsetPosition, track_changes); - break; - } + bool const track_changes = buffer.params().track_changes; + int bibitem_pos = d->insetlist_.find(BIBITEM_CODE); + bool const hasbibitem0 = bibitem_pos == 0; - // There was an InsetBibitem at the beginning, and we didn't - // have to erase one. - if (hasbibitem && erasedInsetPosition < 0) + if (hasbibitem0) { + bibitem_pos = d->insetlist_.find(BIBITEM_CODE, 1); + // There was an InsetBibitem at pos 0, and no other one => OK + if (bibitem_pos == -1) return 0; + // there is a bibitem at the 0 position, but since + // there is a second one, we copy the second on the + // first. We're assuming there are at most two of + // these, which there should be. + // FIXME: why does it make sense to do that rather + // than keep the first? (JMarc) + Inset * inset = releaseInset(bibitem_pos); + d->insetlist_.begin()->inset = inset; + return -bibitem_pos; + } + + // We need to create an inset at the beginning + Inset * inset = 0; + if (bibitem_pos > 0) { + // there was one somewhere in the paragraph, let's move it + inset = d->insetlist_.release(bibitem_pos); + eraseChar(bibitem_pos, track_changes); + } else + // make a fresh one + inset = new InsetBibitem(const_cast(&buffer), + InsetCommandParams(BIBITEM_CODE)); - // There was an InsetBibitem at the beginning and we did have to - // erase one. So we give its properties to the beginning inset. - if (hasbibitem) { - InsetBibitem * inset = - static_cast(d->insetlist_.begin()->inset); - if (!oldkey.empty()) - inset->setParam("key", oldkey); - inset->setParam("label", oldlabel); - return -erasedInsetPosition; - } - - // 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(const_cast(&buffer), InsetCommandParams(BIBITEM_CODE)); - // restore values of previously deleted item in this par. - if (!oldkey.empty()) - inset->setParam("key", oldkey); - inset->setParam("label", oldlabel); - insertInset(0, static_cast(inset), - Change(track_changes ? Change::INSERTED : Change::UNCHANGED)); + Font font(inherit_font, buffer.params().language); + insertInset(0, inset, font, Change(track_changes ? Change::INSERTED + : Change::UNCHANGED)); return 1; } @@ -2932,9 +3482,11 @@ void Paragraph::changeCase(BufferParams const & bparams, pos_type pos, // 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; + // We also need to track the current font, since font + // changes within sequences can occur. + vector > changes; - bool const trackChanges = bparams.trackChanges; + bool const trackChanges = bparams.track_changes; bool capitalize = true; @@ -2966,7 +3518,7 @@ void Paragraph::changeCase(BufferParams const & bparams, pos_type pos, } if (oldChar != newChar) { - changes += newChar; + changes.push_back(make_pair(newChar, getFontSettings(bparams, pos))); if (pos != right - 1) continue; // step behind the changing area @@ -2975,9 +3527,8 @@ void Paragraph::changeCase(BufferParams const & bparams, pos_type pos, int erasePos = pos - changes.size(); for (size_t i = 0; i < changes.size(); i++) { - insertChar(pos, changes[i], - getFontSettings(bparams, - erasePos), + insertChar(pos, changes[i].first, + changes[i].second, trackChanges); if (!eraseChar(erasePos, trackChanges)) { ++erasePos; @@ -2990,36 +3541,44 @@ void Paragraph::changeCase(BufferParams const & bparams, pos_type pos, } -bool Paragraph::find(docstring const & str, bool cs, bool mw, - pos_type pos, bool del) const +int Paragraph::find(docstring const & str, bool cs, bool mw, + pos_type start_pos, bool del) const { + pos_type pos = start_pos; 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]) + for (i = 0; i < strsize && pos < parsize; ++i, ++pos) { + // Ignore "invisible" letters such as ligature breaks + // and hyphenation chars while searching + while (pos < parsize - 1 && isInset(pos)) { + odocstringstream os; + getInset(pos)->toString(os); + if (!getInset(pos)->isLetter() || !os.str().empty()) + break; + pos++; + } + if (cs && str[i] != d->text_[pos]) break; - if (!cs && uppercase(str[i]) != uppercase(d->text_[pos + i])) + if (!cs && uppercase(str[i]) != uppercase(d->text_[pos])) break; - if (!del && isDeleted(pos + i)) + if (!del && isDeleted(pos)) break; } if (i != strsize) - return false; + return 0; // if necessary, check whether string matches word if (mw) { - if (pos > 0 && !isWordSeparator(pos - 1)) - return false; - if (pos + strsize < parsize - && !isWordSeparator(pos + strsize)) - return false; + if (start_pos > 0 && !isWordSeparator(start_pos - 1)) + return 0; + if (pos < parsize + && !isWordSeparator(pos)) + return 0; } - return true; + return pos - start_pos; } @@ -3056,11 +3615,13 @@ bool Paragraph::isSeparator(pos_type pos) const void Paragraph::deregisterWords() { - map::const_iterator itl; - Private::Words::const_iterator it; - for (itl = d->words_.begin(); itl != d->words_.end(); ++itl) { + Private::LangWordsMap::const_iterator itl = d->words_.begin(); + Private::LangWordsMap::const_iterator ite = d->words_.end(); + for (; itl != ite; ++itl) { WordList * wl = theWordList(itl->first); - for (it = (itl->second).begin(); it != (itl->second).end(); ++it) + Private::Words::const_iterator it = (itl->second).begin(); + Private::Words::const_iterator et = (itl->second).end(); + for (; it != et; ++it) wl->remove(*it); } d->words_.clear(); @@ -3106,31 +3667,41 @@ void Paragraph::locateWord(pos_type & from, pos_type & to, void Paragraph::collectWords() { - pos_type n = size(); - for (pos_type pos = 0; pos < n; ++pos) { + for (pos_type pos = 0; pos < size(); ++pos) { if (isWordSeparator(pos)) continue; pos_type from = pos; locateWord(from, pos, WHOLE_WORD); - if (pos - from >= 6) { - docstring word = asString(from, pos, AS_STR_NONE); - FontList::const_iterator cit = d->fontlist_.fontIterator(pos); - if (cit == d->fontlist_.end()) - return; - Language const * lang = cit->font().language(); - d->words_[*lang].insert(word); - } + // Work around MSVC warning: The statement + // if (pos < from + lyxrc.completion_minlength) + // triggers a signed vs. unsigned warning. + // I don't know why this happens, it could be a MSVC bug, or + // related to LLP64 (windows) vs. LP64 (unix) programming + // model, or the C++ standard might be ambigous in the section + // defining the "usual arithmetic conversions". However, using + // a temporary variable is safe and works on all compilers. + pos_type const endpos = from + lyxrc.completion_minlength; + if (pos < endpos) + continue; + FontList::const_iterator cit = d->fontlist_.fontIterator(from); + if (cit == d->fontlist_.end()) + return; + Language const * lang = cit->font().language(); + docstring const word = asString(from, pos, AS_STR_NONE); + d->words_[lang->lang()].insert(word); } } void Paragraph::registerWords() { - map::const_iterator itl; - Private::Words::const_iterator it; - for (itl = d->words_.begin(); itl != d->words_.end(); ++itl) { + Private::LangWordsMap::const_iterator itl = d->words_.begin(); + Private::LangWordsMap::const_iterator ite = d->words_.end(); + for (; itl != ite; ++itl) { WordList * wl = theWordList(itl->first); - for (it = (itl->second).begin(); it != (itl->second).end(); ++it) + Private::Words::const_iterator it = (itl->second).begin(); + Private::Words::const_iterator et = (itl->second).end(); + for (; it != et; ++it) wl->insert(*it); } } @@ -3144,58 +3715,279 @@ void Paragraph::updateWords() } -bool Paragraph::spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, - docstring_list & suggestions, bool do_suggestion) const +void Paragraph::Private::appendSkipPosition(SkipPositions & skips, pos_type const pos) const { + SkipPositionsIterator begin = skips.begin(); + SkipPositions::iterator end = skips.end(); + if (pos > 0 && begin < end) { + --end; + if (end->last == pos - 1) { + end->last = pos; + return; + } + } + skips.insert(end, FontSpan(pos, pos)); +} + + +Language * Paragraph::Private::locateSpellRange( + pos_type & from, pos_type & to, + SkipPositions & skips) const +{ + // skip leading white space + while (from < to && owner_->isWordSeparator(from)) + ++from; + // don't check empty range + if (from >= to) + return 0; + // get current language + Language * lang = getSpellLanguage(from); + pos_type last = from; + bool samelang = true; + bool sameinset = true; + while (last < to && samelang && sameinset) { + // hop to end of word + while (last < to && !owner_->isWordSeparator(last)) { + if (owner_->getInset(last)) { + appendSkipPosition(skips, last); + } else if (owner_->isDeleted(last)) { + appendSkipPosition(skips, last); + } + ++last; + } + // hop to next word while checking for insets + while (sameinset && last < to && owner_->isWordSeparator(last)) { + if (Inset const * inset = owner_->getInset(last)) + sameinset = inset->isChar() && inset->isLetter(); + if (sameinset && owner_->isDeleted(last)) { + appendSkipPosition(skips, last); + } + if (sameinset) + last++; + } + if (sameinset && last < to) { + // now check for language change + samelang = lang == getSpellLanguage(last); + } + } + // if language change detected backstep is needed + if (!samelang) + --last; + to = last; + return lang; +} + + +Language * Paragraph::Private::getSpellLanguage(pos_type const from) const +{ + Language * lang = + const_cast(owner_->getFontSettings( + inset_owner_->buffer().params(), from).language()); + if (lang == inset_owner_->buffer().params().language + && !lyxrc.spellchecker_alt_lang.empty()) { + string lang_code; + string const lang_variety = + split(lyxrc.spellchecker_alt_lang, lang_code, '-'); + lang->setCode(lang_code); + lang->setVariety(lang_variety); + } + return lang; +} + + +void Paragraph::requestSpellCheck(pos_type pos) +{ + d->requestSpellCheck(pos); +} + + +bool Paragraph::needsSpellCheck() const +{ + SpellChecker::ChangeNumber speller_change_number = 0; + if (theSpellChecker()) + speller_change_number = theSpellChecker()->changeNumber(); + if (speller_change_number > d->speller_state_.currentChangeNumber()) { + d->speller_state_.needsCompleteRefresh(speller_change_number); + } + return d->needsSpellCheck(); +} + + +bool Paragraph::Private::ignoreWord(docstring const & word) const +{ + // Ignore words with digits + // FIXME: make this customizable + // (note that some checkers ignore words with digits by default) + docstring::const_iterator cit = word.begin(); + docstring::const_iterator const end = word.end(); + for (; cit != end; ++cit) { + if (isNumber((*cit))) + return true; + } + return false; +} + + +SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, + WordLangTuple & wl, docstring_list & suggestions, + bool do_suggestion, bool check_learned) const +{ + SpellChecker::Result result = SpellChecker::WORD_OK; SpellChecker * speller = theSpellChecker(); if (!speller) - return false; + return result; + + if (!d->layout_->spellcheck || !inInset().allowSpellCheck()) + return result; locateWord(from, to, WHOLE_WORD); - if (from == to || from >= pos_type(d->text_.size())) - return false; + if (from == to || from >= size()) + return result; + + docstring word = asString(from, to, AS_STR_INSETS | AS_STR_SKIPDELETE); + Language * lang = d->getSpellLanguage(from); + + wl = WordLangTuple(word, lang); + + if (word.empty()) + return result; + + if (needsSpellCheck() || check_learned) { + pos_type end = to; + if (!d->ignoreWord(word)) { + bool const trailing_dot = to < size() && d->text_[to] == '.'; + result = speller->check(wl); + if (SpellChecker::misspelled(result) && trailing_dot) { + wl = WordLangTuple(word.append(from_ascii(".")), lang); + result = speller->check(wl); + if (!SpellChecker::misspelled(result)) { + LYXERR(Debug::GUI, "misspelled word is correct with dot: \"" << + word << "\" [" << + from << ".." << to << "]"); + } else { + // spell check with dot appended failed too + // restore original word/lang value + word = asString(from, to, AS_STR_INSETS | AS_STR_SKIPDELETE); + wl = WordLangTuple(word, lang); + } + } + } + if (!SpellChecker::misspelled(result)) { + // area up to the begin of the next word is not misspelled + while (end < size() && isWordSeparator(end)) + ++end; + } + d->setMisspelled(from, end, result); + } else { + result = d->speller_state_.getState(from); + } - docstring word = asString(from, to, AS_STR_INSETS); - string lang_code; - string lang_variety; - if (!lyxrc.spellchecker_alt_lang.empty()) - lang_variety = split(lyxrc.spellchecker_alt_lang, lang_code, '-'); - else { - lang_code = getFontSettings( - d->inset_owner_->buffer().params(), from).language()->code(); - lang_variety = getFontSettings( - d->inset_owner_->buffer().params(), from).language()->variety(); - } - wl = WordLangTuple(word, lang_code, lang_variety); - SpellChecker::Result res = speller->check(wl); - // Just ignore any error that the spellchecker reports. - // FIXME: we should through out an exception and catch it in the GUI to - // display the error. - if (!speller->error().empty()) - return false; + if (do_suggestion) + suggestions.clear(); - bool const misspelled = res != SpellChecker::OK - && res != SpellChecker::IGNORED_WORD; + if (SpellChecker::misspelled(result)) { + LYXERR(Debug::GUI, "misspelled word: \"" << + word << "\" [" << + from << ".." << to << "]"); + if (do_suggestion) + speller->suggest(wl, suggestions); + } + return result; +} - if (lyxrc.spellcheck_continuously) - d->fontlist_.setMisspelled(from, to, misspelled); - if (misspelled && do_suggestion) - speller->suggest(wl, suggestions); - else - suggestions.clear(); +void Paragraph::Private::markMisspelledWords( + pos_type const & first, pos_type const & last, + SpellChecker::Result result, + docstring const & word, + SkipPositions const & skips) +{ + if (!SpellChecker::misspelled(result)) { + setMisspelled(first, last, SpellChecker::WORD_OK); + return; + } + int snext = first; + SpellChecker * speller = theSpellChecker(); + // locate and enumerate the error positions + int nerrors = speller->numMisspelledWords(); + int numskipped = 0; + SkipPositionsIterator it = skips.begin(); + SkipPositionsIterator et = skips.end(); + for (int index = 0; index < nerrors; ++index) { + int wstart; + int wlen = 0; + speller->misspelledWord(index, wstart, wlen); + /// should not happen if speller supports range checks + if (!wlen) continue; + docstring const misspelled = word.substr(wstart, wlen); + wstart += first + numskipped; + if (snext < wstart) { + /// mark the range of correct spelling + numskipped += countSkips(it, et, wstart); + setMisspelled(snext, + wstart - 1, SpellChecker::WORD_OK); + } + snext = wstart + wlen; + numskipped += countSkips(it, et, snext); + /// mark the range of misspelling + setMisspelled(wstart, snext, result); + LYXERR(Debug::GUI, "misspelled word: \"" << + misspelled << "\" [" << + wstart << ".." << (snext-1) << "]"); + ++snext; + } + if (snext <= last) { + /// mark the range of correct spelling at end + setMisspelled(snext, last, SpellChecker::WORD_OK); + } +} - return misspelled; + +void Paragraph::spellCheck() const +{ + SpellChecker * speller = theSpellChecker(); + if (!speller || empty() ||!needsSpellCheck()) + return; + pos_type start; + pos_type endpos; + d->rangeOfSpellCheck(start, endpos); + if (speller->canCheckParagraph()) { + // loop until we leave the range + for (pos_type first = start; first < endpos; ) { + pos_type last = endpos; + Private::SkipPositions skips; + Language * lang = d->locateSpellRange(first, last, skips); + if (first >= endpos) + break; + // start the spell checker on the unit of meaning + docstring word = asString(first, last, AS_STR_INSETS + AS_STR_SKIPDELETE); + WordLangTuple wl = WordLangTuple(word, lang); + SpellChecker::Result result = word.size() ? + speller->check(wl) : SpellChecker::WORD_OK; + d->markMisspelledWords(first, last, result, word, skips); + first = ++last; + } + } else { + static docstring_list suggestions; + pos_type to = endpos; + while (start < endpos) { + WordLangTuple wl; + spellCheck(start, to, wl, suggestions, false); + start = to + 1; + } + } + d->readySpellCheck(); } -bool Paragraph::isMisspelled(pos_type pos) const +bool Paragraph::isMisspelled(pos_type pos, bool check_boundary) const { - pos_type from = pos; - pos_type to = pos; - WordLangTuple wl; - docstring_list suggestions; - return spellCheck(from, to, wl, suggestions, false); + bool result = SpellChecker::misspelled(d->speller_state_.getState(pos)); + if (result || pos <= 0 || pos > size()) + return result; + if (check_boundary && (pos == size() || isWordSeparator(pos))) + result = SpellChecker::misspelled(d->speller_state_.getState(pos - 1)); + return result; }