3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Jean-Marc Lasgouttes
9 * \author Angus Leeming
11 * \author André Pönitz
13 * \author Jürgen Vigna
15 * Full author contact details are available in file CREDITS.
20 #include "Paragraph.h"
23 #include "BufferParams.h"
29 #include "LaTeXFeatures.h"
36 #include "OutputParams.h"
37 #include "output_latex.h"
38 #include "paragraph_funcs.h"
39 #include "ParagraphParameters.h"
40 #include "rowpainter.h"
45 #include "frontends/alert.h"
46 #include "frontends/FontMetrics.h"
48 #include "insets/InsetBibitem.h"
49 #include "insets/InsetOptArg.h"
51 #include "support/lstrings.h"
52 #include "support/textutils.h"
53 #include "support/convert.h"
54 #include "support/unicode.h"
56 #include <boost/bind.hpp>
57 #include <boost/next_prior.hpp>
69 using support::contains;
70 using support::rsplit;
73 /////////////////////////////////////////////////////////////////////
77 /////////////////////////////////////////////////////////////////////
83 class Paragraph::Pimpl {
86 Pimpl(Paragraph * owner);
87 /// "Copy constructor"
88 Pimpl(Pimpl const &, Paragraph * owner);
93 /// look up change at given pos
94 Change const & lookupChange(pos_type pos) const;
95 /// is there a change within the given range ?
96 bool isChanged(pos_type start, pos_type end) const;
97 /// will the paragraph be physically merged with the next
98 /// one if the imaginary end-of-par character is logically deleted?
99 bool isMergedOnEndOfParDeletion(bool trackChanges) const;
100 /// set change for the entire par
101 void setChange(Change const & change);
102 /// set change at given pos
103 void setChange(pos_type pos, Change const & change);
104 /// accept changes within the given range
105 void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end);
106 /// reject changes within the given range
107 void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end);
110 value_type getChar(pos_type pos) const;
112 void insertChar(pos_type pos, value_type c, Change const & change);
114 void insertInset(pos_type pos, Inset * inset, Change const & change);
115 /// (logically) erase the char at pos; return true if it was actually erased
116 bool eraseChar(pos_type pos, bool trackChanges);
117 /// (logically) erase the given range; return the number of chars actually erased
118 int eraseChars(pos_type start, pos_type end, bool trackChanges);
122 /** A font entry covers a range of positions. Notice that the
123 entries in the list are inserted in random order.
124 I don't think it's worth the effort to implement a more effective
125 datastructure, because the number of different fonts in a paragraph
127 Nevertheless, I decided to store fontlist using a sorted vector:
128 fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where
129 pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
130 and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
131 (font_1 covers the chars 0,...,pos_1) (Dekel)
136 FontTable(pos_type p, Font const & f)
140 pos_type pos() const { return pos_; }
142 void pos(pos_type p) { pos_ = p; }
144 Font const & font() const { return font_; }
146 void font(Font const & f) { font_ = f;}
148 /// End position of paragraph this font attribute covers
150 /** Font. Interpretation of the font values:
151 If a value is Font::INHERIT_*, it means that the font
152 attribute is inherited from either the layout of this
153 paragraph or, in the case of nested paragraphs, from the
154 layout in the environment one level up until completely
156 The values Font::IGNORE_* and Font::TOGGLE are NOT
157 allowed in these font tables.
162 friend class matchFT;
166 /// used by lower_bound and upper_bound
167 int operator()(FontTable const & a, FontTable const & b) const {
168 return a.pos() < b.pos();
173 typedef std::vector<FontTable> FontList;
177 /// Output the surrogate pair formed by \p c and \p next to \p os.
178 /// \return the number of characters written.
179 int latexSurrogatePair(odocstream & os, value_type c, value_type next,
181 /// Output a space in appropriate formatting (or a surrogate pair
182 /// if the next character is a combining character).
183 /// \return whether a surrogate pair was output.
184 bool simpleTeXBlanks(Encoding const &,
185 odocstream &, TexRow & texrow,
187 unsigned int & column,
189 Layout const & style);
191 void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
193 TexRow & texrow, OutputParams const &,
196 Font const & outerfont,
198 Change & running_change,
199 Layout const & style,
201 unsigned int & column, value_type const c);
204 void validate(LaTeXFeatures & features,
205 Layout const & layout) const;
210 static unsigned int paragraph_id;
212 ParagraphParameters params;
216 pos_type size() const { return owner_->size(); }
217 /// match a string against a particular point in the paragraph
218 bool isTextAt(std::string const & str, pos_type pos) const;
220 /// for recording and looking up changes
231 using std::upper_bound;
232 using std::lower_bound;
236 // Initialization of the counter for the paragraph id's,
237 unsigned int Paragraph::Pimpl::paragraph_id = 0;
241 struct special_phrase {
247 special_phrase const special_phrases[] = {
248 { "LyX", from_ascii("\\LyX{}"), false },
249 { "TeX", from_ascii("\\TeX{}"), true },
250 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
251 { "LaTeX", from_ascii("\\LaTeX{}"), true },
254 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
259 Paragraph::Pimpl::Pimpl(Paragraph * owner)
263 id_ = paragraph_id++;
267 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
268 : params(p.params), changes_(p.changes_), owner_(owner)
270 inset_owner = p.inset_owner;
271 fontlist = p.fontlist;
272 id_ = paragraph_id++;
276 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
278 BOOST_ASSERT(start >= 0 && start <= size());
279 BOOST_ASSERT(end > start && end <= size() + 1);
281 return changes_.isChanged(start, end);
285 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
286 // keep the logic here in sync with the logic of eraseChars()
292 Change change = changes_.lookup(size());
294 return change.type == Change::INSERTED && change.author == 0;
298 void Paragraph::Pimpl::setChange(Change const & change)
300 // beware of the imaginary end-of-par character!
301 changes_.set(change, 0, size() + 1);
304 * Propagate the change recursively - but not in case of DELETED!
306 * Imagine that your co-author makes changes in an existing inset. He
307 * sends your document to you and you come to the conclusion that the
308 * inset should go completely. If you erase it, LyX must not delete all
309 * text within the inset. Otherwise, the change tracked insertions of
310 * your co-author get lost and there is no way to restore them later.
312 * Conclusion: An inset's content should remain untouched if you delete it
315 if (change.type != Change::DELETED) {
316 for (pos_type pos = 0; pos < size(); ++pos) {
317 if (owner_->isInset(pos)) {
318 owner_->getInset(pos)->setChange(change);
325 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
327 BOOST_ASSERT(pos >= 0 && pos <= size());
329 changes_.set(change, pos);
331 // see comment in setChange(Change const &) above
333 if (change.type != Change::DELETED &&
334 pos < size() && owner_->isInset(pos)) {
335 owner_->getInset(pos)->setChange(change);
340 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
342 BOOST_ASSERT(pos >= 0 && pos <= size());
344 return changes_.lookup(pos);
348 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
350 BOOST_ASSERT(start >= 0 && start <= size());
351 BOOST_ASSERT(end > start && end <= size() + 1);
353 for (pos_type pos = start; pos < end; ++pos) {
354 switch (lookupChange(pos).type) {
355 case Change::UNCHANGED:
356 // accept changes in nested inset
357 if (pos < size() && owner_->isInset(pos)) {
358 owner_->getInset(pos)->acceptChanges(bparams);
363 case Change::INSERTED:
364 changes_.set(Change(Change::UNCHANGED), pos);
365 // also accept changes in nested inset
366 if (pos < size() && owner_->isInset(pos)) {
367 owner_->getInset(pos)->acceptChanges(bparams);
371 case Change::DELETED:
372 // Suppress access to non-existent
373 // "end-of-paragraph char"
375 eraseChar(pos, false);
386 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
388 BOOST_ASSERT(start >= 0 && start <= size());
389 BOOST_ASSERT(end > start && end <= size() + 1);
391 for (pos_type pos = start; pos < end; ++pos) {
392 switch (lookupChange(pos).type) {
393 case Change::UNCHANGED:
394 // reject changes in nested inset
395 if (pos < size() && owner_->isInset(pos)) {
396 owner_->getInset(pos)->rejectChanges(bparams);
400 case Change::INSERTED:
401 // Suppress access to non-existent
402 // "end-of-paragraph char"
404 eraseChar(pos, false);
410 case Change::DELETED:
411 changes_.set(Change(Change::UNCHANGED), pos);
413 // Do NOT reject changes within a deleted inset!
414 // There may be insertions of a co-author inside of it!
422 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
424 BOOST_ASSERT(pos >= 0 && pos <= size());
426 return owner_->getChar(pos);
430 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
432 BOOST_ASSERT(pos >= 0 && pos <= size());
435 changes_.insert(change, pos);
437 // This is actually very common when parsing buffers (and
438 // maybe inserting ascii text)
440 // when appending characters, no need to update tables
441 owner_->text_.push_back(c);
445 owner_->text_.insert(owner_->text_.begin() + pos, c);
447 // Update the font table.
448 FontTable search_font(pos, Font());
449 for (FontList::iterator it
450 = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
451 it != fontlist.end(); ++it)
453 it->pos(it->pos() + 1);
457 owner_->insetlist.increasePosAfterPos(pos);
461 void Paragraph::Pimpl::insertInset(pos_type pos, Inset * inset,
462 Change const & change)
465 BOOST_ASSERT(pos >= 0 && pos <= size());
467 insertChar(pos, META_INSET, change);
468 BOOST_ASSERT(owner_->text_[pos] == META_INSET);
470 // Add a new entry in the insetlist.
471 owner_->insetlist.insert(inset, pos);
475 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
477 BOOST_ASSERT(pos >= 0 && pos <= size());
479 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
482 Change change = changes_.lookup(pos);
484 // set the character to DELETED if
485 // a) it was previously unchanged or
486 // b) it was inserted by a co-author
488 if (change.type == Change::UNCHANGED ||
489 (change.type == Change::INSERTED && change.author != 0)) {
490 setChange(pos, Change(Change::DELETED));
494 if (change.type == Change::DELETED)
498 // Don't physically access the imaginary end-of-paragraph character.
499 // eraseChar() can only mark it as DELETED. A physical deletion of
500 // end-of-par must be handled externally.
508 // if it is an inset, delete the inset entry
509 if (owner_->text_[pos] == Paragraph::META_INSET) {
510 owner_->insetlist.erase(pos);
513 owner_->text_.erase(owner_->text_.begin() + pos);
515 // Erase entries in the tables.
516 FontTable search_font(pos, Font());
518 FontList::iterator it =
519 lower_bound(fontlist.begin(),
521 search_font, matchFT());
522 if (it != fontlist.end() && it->pos() == pos &&
524 (it != fontlist.begin()
525 && boost::prior(it)->pos() == pos - 1))) {
526 // If it is a multi-character font
527 // entry, we just make it smaller
528 // (see update below), otherwise we
530 unsigned int const i = it - fontlist.begin();
531 fontlist.erase(fontlist.begin() + i);
532 it = fontlist.begin() + i;
533 if (i > 0 && i < fontlist.size() &&
534 fontlist[i - 1].font() == fontlist[i].font()) {
535 fontlist.erase(fontlist.begin() + i - 1);
536 it = fontlist.begin() + i - 1;
540 // Update all other entries
541 FontList::iterator fend = fontlist.end();
542 for (; it != fend; ++it)
543 it->pos(it->pos() - 1);
545 // Update the insetlist
546 owner_->insetlist.decreasePosAfterPos(pos);
552 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
554 BOOST_ASSERT(start >= 0 && start <= size());
555 BOOST_ASSERT(end >= start && end <= size() + 1);
558 for (pos_type count = end - start; count; --count) {
559 if (!eraseChar(i, trackChanges))
566 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
567 value_type next, Encoding const & encoding)
569 // Writing next here may circumvent a possible font change between
570 // c and next. Since next is only output if it forms a surrogate pair
571 // with c we can ignore this:
572 // A font change inside a surrogate pair does not make sense and is
573 // hopefully impossible to input.
574 // FIXME: change tracking
575 // Is this correct WRT change tracking?
576 docstring const latex1 = encoding.latexChar(next);
577 docstring const latex2 = encoding.latexChar(c);
578 os << latex1 << '{' << latex2 << '}';
579 return latex1.length() + latex2.length() + 2;
583 bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding,
584 odocstream & os, TexRow & texrow,
586 unsigned int & column,
588 Layout const & style)
593 if (i < size() - 1) {
594 char_type next = getChar(i + 1);
595 if (Encodings::isCombiningChar(next)) {
596 // This space has an accent, so we must always output it.
597 column += latexSurrogatePair(os, ' ', next, encoding) - 1;
603 if (lyxrc.plaintext_linelen > 0
604 && column > lyxrc.plaintext_linelen
606 && getChar(i - 1) != ' '
608 // same in FreeSpacing mode
609 && !owner_->isFreeSpacing()
610 // In typewriter mode, we want to avoid
611 // ! . ? : at the end of a line
612 && !(font.family() == Font::TYPEWRITER_FAMILY
613 && (getChar(i - 1) == '.'
614 || getChar(i - 1) == '?'
615 || getChar(i - 1) == ':'
616 || getChar(i - 1) == '!'))) {
619 texrow.start(owner_->id(), i + 1);
621 } else if (style.free_spacing) {
630 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
632 pos_type const len = str.length();
634 // is the paragraph large enough?
635 if (pos + len > size())
638 // does the wanted text start at point?
639 for (string::size_type i = 0; i < str.length(); ++i) {
640 // Caution: direct comparison of characters works only
641 // because str is pure ASCII.
642 if (str[i] != owner_->text_[pos + i])
646 // is there a font change in middle of the word?
647 FontList::const_iterator cit = fontlist.begin();
648 FontList::const_iterator end = fontlist.end();
649 for (; cit != end; ++cit) {
650 if (cit->pos() >= pos)
653 if (cit != end && pos + len - 1 > cit->pos())
660 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
661 BufferParams const & bparams,
664 OutputParams const & runparams,
667 Font const & outerfont,
669 Change & running_change,
670 Layout const & style,
672 unsigned int & column,
675 if (style.pass_thru) {
676 if (c != Paragraph::META_INSET) {
678 // FIXME UNICODE: This can fail if c cannot
679 // be encoded in the current encoding.
682 owner_->getInset(i)->plaintext(buf, os, runparams);
686 // Two major modes: LaTeX or plain
687 // Handle here those cases common to both modes
688 // and then split to handle the two modes separately.
690 case Paragraph::META_INSET: {
691 Inset * inset = owner_->getInset(i);
693 // FIXME: remove this check
697 // FIXME: move this to InsetNewline::latex
698 if (inset->lyxCode() == Inset::NEWLINE_CODE) {
699 // newlines are handled differently here than
700 // the default in simpleTeXSpecialChars().
701 if (!style.newline_allowed) {
705 column += running_font.latexWriteEndChanges(
706 os, bparams, runparams,
711 if (running_font.family() == Font::TYPEWRITER_FAMILY)
714 basefont = owner_->getLayoutFont(bparams, outerfont);
715 running_font = basefont;
717 if (runparams.moving_arg)
723 texrow.start(owner_->id(), i + 1);
728 if (inset->canTrackChanges()) {
729 column += Changes::latexMarkChange(os, bparams, running_change,
730 Change(Change::UNCHANGED));
731 running_change = Change(Change::UNCHANGED);
735 odocstream::pos_type const len = os.tellp();
737 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
738 || inset->lyxCode() == Inset::MATH_CODE
739 || inset->lyxCode() == Inset::URL_CODE)
740 && running_font.isRightToLeft()) {
741 if (running_font.language()->lang() == "farsi")
749 #warning Bug: we can have an empty font change here!
750 // if there has just been a font change, we are going to close it
751 // right now, which means stupid latex code like \textsf{}. AFAIK,
752 // this does not harm dvi output. A minor bug, thus (JMarc)
754 // some insets cannot be inside a font change command
755 if (open_font && inset->noFontChange()) {
756 column += running_font.latexWriteEndChanges(
757 os, bparams, runparams,
760 basefont = owner_->getLayoutFont(bparams, outerfont);
761 running_font = basefont;
764 int tmp = inset->latex(buf, os, runparams);
767 if (running_font.language()->lang() == "farsi")
774 for (int j = 0; j < tmp; ++j) {
777 texrow.start(owner_->id(), i + 1);
780 column += os.tellp() - len;
786 // And now for the special cases within each mode
790 os << "\\textbackslash{}";
794 case '|': case '<': case '>':
795 // In T1 encoding, these characters exist
796 if (lyxrc.fontenc == "T1") {
798 //... but we should avoid ligatures
799 if ((c == '>' || c == '<')
801 && getChar(i + 1) == c) {
802 //os << "\\textcompwordmark{}";
804 // Jean-Marc, have a look at
805 // this. I think this works
813 // Typewriter font also has them
814 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
818 // Otherwise, we use what LaTeX
822 os << "\\textless{}";
826 os << "\\textgreater{}";
836 case '-': // "--" in Typewriter mode -> "-{}-"
837 if (i <= size() - 2 &&
838 getChar(i + 1) == '-' &&
839 running_font.family() == Font::TYPEWRITER_FAMILY) {
848 os << "\\char`\\\"{}";
853 case '%': case '#': case '{':
861 os << "\\textasciitilde{}";
866 os << "\\textasciicircum{}";
871 // avoid being mistaken for optional arguments
879 // Blanks are printed before font switching.
880 // Sure? I am not! (try nice-latex)
881 // I am sure it's correct. LyX might be smarter
882 // in the future, but for now, nothing wrong is
888 // I assume this is hack treating typewriter as verbatim
889 // FIXME UNICODE: This can fail if c cannot be encoded
890 // in the current encoding.
891 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
900 // FIXME: if we have "LaTeX" with a font
901 // change in the middle (before the 'T', then
902 // the "TeX" part is still special cased.
903 // Really we should only operate this on
904 // "words" for some definition of word
908 for (; pnr < phrases_nr; ++pnr) {
909 if (isTextAt(special_phrases[pnr].phrase, i)) {
910 os << special_phrases[pnr].macro;
911 i += special_phrases[pnr].phrase.length() - 1;
912 column += special_phrases[pnr].macro.length() - 1;
917 if (pnr == phrases_nr && c != '\0') {
918 Encoding const & encoding = *(runparams.encoding);
919 if (i < size() - 1) {
920 char_type next = getChar(i + 1);
921 if (Encodings::isCombiningChar(next)) {
922 column += latexSurrogatePair(os, c, next, encoding) - 1;
927 docstring const latex = encoding.latexChar(c);
928 if (latex.length() > 1 &&
929 latex[latex.length() - 1] != '}') {
930 // Prevent eating of a following
931 // space or command corruption by
932 // following characters
933 column += latex.length() + 1;
936 column += latex.length() - 1;
946 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
947 Layout const & layout) const
949 BufferParams const & bparams = features.bufferParams();
952 if (!params.spacing().isDefault())
953 features.require("setspace");
956 features.useLayout(layout.name());
959 Language const * doc_language = bparams.language;
961 FontList::const_iterator fcit = fontlist.begin();
962 FontList::const_iterator fend = fontlist.end();
963 for (; fcit != fend; ++fcit) {
964 if (fcit->font().noun() == Font::ON) {
965 LYXERR(Debug::LATEX) << "font.noun: "
966 << fcit->font().noun()
968 features.require("noun");
969 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
970 << to_utf8(fcit->font().stateText(0))
973 switch (fcit->font().color()) {
977 // probably we should put here all interface colors used for
978 // font displaying! For now I just add this ones I know of (Jug)
983 features.require("color");
984 LYXERR(Debug::LATEX) << "Color enabled. Font: "
985 << to_utf8(fcit->font().stateText(0))
989 Language const * language = fcit->font().language();
990 if (language->babel() != doc_language->babel() &&
991 language != ignore_language &&
992 language != latex_language)
994 features.useLanguage(language);
995 LYXERR(Debug::LATEX) << "Found language "
996 << language->lang() << endl;
1000 if (!params.leftIndent().zero())
1001 features.require("ParagraphLeftIndent");
1004 InsetList::const_iterator icit = owner_->insetlist.begin();
1005 InsetList::const_iterator iend = owner_->insetlist.end();
1006 for (; icit != iend; ++icit) {
1008 icit->inset->validate(features);
1009 if (layout.needprotect &&
1010 icit->inset->lyxCode() == Inset::FOOT_CODE)
1011 features.require("NeedLyXFootnoteCode");
1015 // then the contents
1016 for (pos_type i = 0; i < size() ; ++i) {
1017 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1018 if (!special_phrases[pnr].builtin
1019 && isTextAt(special_phrases[pnr].phrase, i)) {
1020 features.require(special_phrases[pnr].phrase);
1024 Encodings::validate(getChar(i), features);
1032 /////////////////////////////////////////////////////////////////////
1036 /////////////////////////////////////////////////////////////////////
1040 Paragraph::Paragraph()
1041 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1048 Paragraph::Paragraph(Paragraph const & par)
1049 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1050 layout_(par.layout_),
1051 text_(par.text_), begin_of_body_(par.begin_of_body_),
1052 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1054 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1055 InsetList::iterator it = insetlist.begin();
1056 InsetList::iterator end = insetlist.end();
1057 for (; it != end; ++it)
1058 it->inset = it->inset->clone().release();
1062 Paragraph & Paragraph::operator=(Paragraph const & par)
1064 // needed as we will destroy the pimpl_ before copying it
1066 itemdepth = par.itemdepth;
1068 insetlist = par.insetlist;
1069 InsetList::iterator it = insetlist.begin();
1070 InsetList::iterator end = insetlist.end();
1071 for (; it != end; ++it)
1072 it->inset = it->inset->clone().release();
1074 layout_ = par.layout();
1076 begin_of_body_ = par.begin_of_body_;
1079 pimpl_ = new Pimpl(*par.pimpl_, this);
1085 Paragraph::~Paragraph()
1089 //lyxerr << "Paragraph::paragraph_id = "
1090 // << Paragraph::paragraph_id << endl;
1094 void Paragraph::write(Buffer const & buf, ostream & os,
1095 BufferParams const & bparams,
1096 depth_type & dth) const
1098 // The beginning or end of a deeper (i.e. nested) area?
1099 if (dth != params().depth()) {
1100 if (params().depth() > dth) {
1101 while (params().depth() > dth) {
1102 os << "\n\\begin_deeper";
1106 while (params().depth() < dth) {
1107 os << "\n\\end_deeper";
1113 // First write the layout
1114 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1118 Font font1(Font::ALL_INHERIT, bparams.language);
1120 Change running_change = Change(Change::UNCHANGED);
1123 for (pos_type i = 0; i <= size(); ++i) {
1125 Change change = pimpl_->lookupChange(i);
1126 Changes::lyxMarkChange(os, column, running_change, change);
1127 running_change = change;
1132 // Write font changes
1133 Font font2 = getFontSettings(bparams, i);
1134 if (font2 != font1) {
1135 font2.lyxWriteChanges(font1, os);
1140 value_type const c = getChar(i);
1144 Inset const * inset = getInset(i);
1146 if (inset->directWrite()) {
1147 // international char, let it write
1148 // code directly so it's shorter in
1150 inset->write(buf, os);
1154 os << "\\begin_inset ";
1155 inset->write(buf, os);
1156 os << "\n\\end_inset\n\n";
1162 os << "\n\\backslash\n";
1166 if (i + 1 < size() && getChar(i + 1) == ' ') {
1173 if ((column > 70 && c == ' ')
1178 // this check is to amend a bug. LyX sometimes
1179 // inserts '\0' this could cause problems.
1181 std::vector<char> tmp = ucs4_to_utf8(c);
1182 tmp.push_back('\0');
1185 lyxerr << "ERROR (Paragraph::writeFile):"
1186 " NULL char in structure." << endl;
1192 os << "\n\\end_layout\n";
1196 void Paragraph::validate(LaTeXFeatures & features) const
1198 pimpl_->validate(features, *layout());
1202 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1204 return pimpl_->eraseChar(pos, trackChanges);
1208 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1210 return pimpl_->eraseChars(start, end, trackChanges);
1214 void Paragraph::insert(pos_type start, docstring const & str,
1215 Font const & font, Change const & change)
1217 for (size_t i = 0, n = str.size(); i != n ; ++i)
1218 insertChar(start + i, str[i], font, change);
1222 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1225 pimpl_->insertChar(pos, c, Change(trackChanges ?
1226 Change::INSERTED : Change::UNCHANGED));
1230 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1231 Font const & font, bool trackChanges)
1233 pimpl_->insertChar(pos, c, Change(trackChanges ?
1234 Change::INSERTED : Change::UNCHANGED));
1239 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1240 Font const & font, Change const & change)
1242 pimpl_->insertChar(pos, c, change);
1247 void Paragraph::insertInset(pos_type pos, Inset * inset,
1248 Change const & change)
1250 pimpl_->insertInset(pos, inset, change);
1254 void Paragraph::insertInset(pos_type pos, Inset * inset,
1255 Font const & font, Change const & change)
1257 pimpl_->insertInset(pos, inset, change);
1258 // Set the font/language of the inset...
1260 // ... as well as the font/language of the text inside the inset
1261 // FIXME: This is far from perfect. It basically overrides work being done
1262 // in the InsetText constructor. Also, it doesn't work for Tables
1263 // (precisely because each cell's font/language is set in the Table's
1264 // constructor, so by now it's too late). The long-term solution should
1265 // be moving current_font into Cursor, and getting rid of all this...
1266 // (see http://thread.gmane.org/gmane.editors.lyx.devel/88869/focus=88944)
1267 if (inset->asTextInset()) {
1268 inset->asTextInset()->text_.current_font = font;
1269 inset->asTextInset()->text_.real_current_font = font;
1274 bool Paragraph::insetAllowed(Inset_code code)
1276 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1280 // Gets uninstantiated font setting at position.
1281 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1285 lyxerr << " pos: " << pos << " size: " << size() << endl;
1286 BOOST_ASSERT(pos <= size());
1289 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1290 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1291 for (; cit != end; ++cit)
1292 if (cit->pos() >= pos)
1298 if (pos == size() && !empty())
1299 return getFontSettings(bparams, pos - 1);
1301 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1305 FontSpan Paragraph::fontSpan(pos_type pos) const
1307 BOOST_ASSERT(pos <= size());
1310 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1311 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1312 for (; cit != end; ++cit) {
1313 if (cit->pos() >= pos) {
1314 if (pos >= beginOfBody())
1315 return FontSpan(std::max(start, beginOfBody()),
1318 return FontSpan(start,
1319 std::min(beginOfBody() - 1,
1322 start = cit->pos() + 1;
1325 // This should not happen, but if so, we take no chances.
1326 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1328 return FontSpan(pos, pos);
1332 // Gets uninstantiated font setting at position 0
1333 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1335 if (!empty() && !pimpl_->fontlist.empty())
1336 return pimpl_->fontlist[0].font();
1338 return Font(Font::ALL_INHERIT, bparams.language);
1342 // Gets the fully instantiated font at a given position in a paragraph
1343 // This is basically the same function as Text::GetFont() in text2.cpp.
1344 // The difference is that this one is used for generating the LaTeX file,
1345 // and thus cosmetic "improvements" are disallowed: This has to deliver
1346 // the true picture of the buffer. (Asger)
1347 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1348 Font const & outerfont) const
1350 BOOST_ASSERT(pos >= 0);
1352 Layout_ptr const & lout = layout();
1354 pos_type const body_pos = beginOfBody();
1358 layoutfont = lout->labelfont;
1360 layoutfont = lout->font;
1362 Font font = getFontSettings(bparams, pos);
1363 font.realize(layoutfont);
1364 font.realize(outerfont);
1365 font.realize(bparams.getFont());
1371 Font const Paragraph::getLabelFont
1372 (BufferParams const & bparams, Font const & outerfont) const
1374 Font tmpfont = layout()->labelfont;
1375 tmpfont.setLanguage(getParLanguage(bparams));
1376 tmpfont.realize(outerfont);
1377 tmpfont.realize(bparams.getFont());
1382 Font const Paragraph::getLayoutFont
1383 (BufferParams const & bparams, Font const & outerfont) const
1385 Font tmpfont = layout()->font;
1386 tmpfont.setLanguage(getParLanguage(bparams));
1387 tmpfont.realize(outerfont);
1388 tmpfont.realize(bparams.getFont());
1393 /// Returns the height of the highest font in range
1394 Font_size Paragraph::highestFontInRange
1395 (pos_type startpos, pos_type endpos, Font_size def_size) const
1397 if (pimpl_->fontlist.empty())
1400 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1401 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1402 for (; end_it != end; ++end_it) {
1403 if (end_it->pos() >= endpos)
1410 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1411 for (; cit != end; ++cit) {
1412 if (cit->pos() >= startpos)
1416 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1417 for (; cit != end_it; ++cit) {
1418 Font::FONT_SIZE size = cit->font().size();
1419 if (size == Font::INHERIT_SIZE)
1421 if (size > maxsize && size <= Font::SIZE_HUGER)
1428 Paragraph::value_type
1429 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1431 value_type c = getChar(pos);
1432 if (!lyxrc.rtl_support)
1462 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1469 void Paragraph::setFont(pos_type pos, Font const & font)
1471 BOOST_ASSERT(pos <= size());
1473 // First, reduce font against layout/label font
1474 // Update: The setCharFont() routine in text2.cpp already
1475 // reduces font, so we don't need to do that here. (Asger)
1476 // No need to simplify this because it will disappear
1477 // in a new kernel. (Asger)
1478 // Next search font table
1480 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1481 Pimpl::FontList::iterator it = beg;
1482 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1483 for (; it != endit; ++it) {
1484 if (it->pos() >= pos)
1487 size_t const i = distance(beg, it);
1488 bool notfound = (it == endit);
1490 if (!notfound && pimpl_->fontlist[i].font() == font)
1493 bool begin = pos == 0 || notfound ||
1494 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1495 // Is position pos is a beginning of a font block?
1496 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1497 // Is position pos is the end of a font block?
1498 if (begin && end) { // A single char block
1499 if (i + 1 < pimpl_->fontlist.size() &&
1500 pimpl_->fontlist[i + 1].font() == font) {
1501 // Merge the singleton block with the next block
1502 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1503 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1504 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1505 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1506 // Merge the singleton block with the previous block
1507 pimpl_->fontlist[i - 1].pos(pos);
1508 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1510 pimpl_->fontlist[i].font(font);
1512 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1513 pimpl_->fontlist[i - 1].pos(pos);
1515 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1516 Pimpl::FontTable(pos, font));
1518 pimpl_->fontlist[i].pos(pos - 1);
1519 if (!(i + 1 < pimpl_->fontlist.size() &&
1520 pimpl_->fontlist[i + 1].font() == font))
1521 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1522 Pimpl::FontTable(pos, font));
1523 } else { // The general case. The block is splitted into 3 blocks
1524 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1525 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1526 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1527 Pimpl::FontTable(pos, font));
1532 void Paragraph::makeSameLayout(Paragraph const & par)
1534 layout(par.layout());
1536 params() = par.params();
1540 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1542 if (isFreeSpacing())
1548 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1549 if (eraseChar(pos, trackChanges))
1555 return count > 0 || pos > 0;
1559 bool Paragraph::hasSameLayout(Paragraph const & par) const
1561 return par.layout() == layout() && params().sameLayout(par.params());
1565 depth_type Paragraph::getDepth() const
1567 return params().depth();
1571 depth_type Paragraph::getMaxDepthAfter() const
1573 if (layout()->isEnvironment())
1574 return params().depth() + 1;
1576 return params().depth();
1580 char Paragraph::getAlign() const
1582 if (params().align() == LYX_ALIGN_LAYOUT)
1583 return layout()->align;
1585 return params().align();
1589 docstring const & Paragraph::getLabelstring() const
1591 return params().labelString();
1595 // the next two functions are for the manual labels
1596 docstring const Paragraph::getLabelWidthString() const
1598 if (!params().labelWidthString().empty())
1599 return params().labelWidthString();
1601 return _("Senseless with this layout!");
1605 void Paragraph::setLabelWidthString(docstring const & s)
1607 params().labelWidthString(s);
1611 docstring const Paragraph::translateIfPossible(docstring const & s,
1612 BufferParams const & bparams) const
1614 if (!support::isAscii(s) || s.empty()) {
1615 // This must be a user defined layout. We cannot translate
1616 // this, since gettext accepts only ascii keys.
1619 // Probably standard layout, try to translate
1620 Messages & m = getMessages(getParLanguage(bparams)->code());
1621 return m.get(to_ascii(s));
1625 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1626 BufferParams const & bparams, bool process_appendix) const
1628 TextClass const & tclass = bparams.getTextClass();
1631 if (process_appendix && params().appendix())
1632 fmt = translateIfPossible(layout->labelstring_appendix(),
1635 fmt = translateIfPossible(layout->labelstring(), bparams);
1637 // handle 'inherited level parts' in 'fmt',
1638 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1639 size_t const i = fmt.find('@', 0);
1640 if (i != docstring::npos) {
1641 size_t const j = fmt.find('@', i + 1);
1642 if (j != docstring::npos) {
1643 docstring parent(fmt, i + 1, j - i - 1);
1644 docstring label = expandLabel(tclass[parent], bparams);
1645 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1649 return tclass.counters().counterLabel(fmt);
1653 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1656 LyXAlignment const oldAlign = params().align();
1657 // FIXME The first check is due to the fact that LYX_ALIGN_LAYOUT
1658 // is not required to be possible. A fix is on the way.
1659 if ((oldAlign != LYX_ALIGN_LAYOUT) &&
1660 !(oldAlign & layout()->alignpossible)) {
1661 frontend::Alert::warning(_("Alignment not permitted"),
1662 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1663 params().align(LYX_ALIGN_LAYOUT);
1668 pos_type Paragraph::beginOfBody() const
1670 return begin_of_body_;
1674 void Paragraph::setBeginOfBody()
1676 if (layout()->labeltype != LABEL_MANUAL) {
1681 // Unroll the first two cycles of the loop
1682 // and remember the previous character to
1683 // remove unnecessary getChar() calls
1685 pos_type end = size();
1686 if (i < end && !isNewline(i)) {
1688 char_type previous_char = 0;
1691 previous_char = text_[i];
1692 if (!isNewline(i)) {
1694 while (i < end && previous_char != ' ') {
1699 previous_char = temp;
1709 // returns -1 if inset not found
1710 int Paragraph::getPositionOfInset(Inset const * inset) const
1713 InsetList::const_iterator it = insetlist.begin();
1714 InsetList::const_iterator end = insetlist.end();
1715 for (; it != end; ++it)
1716 if (it->inset == inset)
1722 InsetBibitem * Paragraph::bibitem() const
1724 if (!insetlist.empty()) {
1725 Inset * inset = insetlist.begin()->inset;
1726 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1727 return static_cast<InsetBibitem *>(inset);
1733 bool Paragraph::forceDefaultParagraphs() const
1735 return inInset() && inInset()->forceDefaultParagraphs(0);
1741 // paragraphs inside floats need different alignment tags to avoid
1744 bool noTrivlistCentering(Inset::Code code)
1746 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1750 string correction(string const & orig)
1752 if (orig == "flushleft")
1753 return "raggedright";
1754 if (orig == "flushright")
1755 return "raggedleft";
1756 if (orig == "center")
1762 string const corrected_env(string const & suffix, string const & env,
1765 string output = suffix + "{";
1766 if (noTrivlistCentering(code))
1767 output += correction(env);
1771 if (suffix == "\\begin")
1777 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1779 if (!contains(str, "\n"))
1780 column += str.size();
1784 column = rsplit(str, tmp, '\n').size();
1791 // This could go to ParagraphParameters if we want to
1792 int Paragraph::startTeXParParams(BufferParams const & bparams,
1793 odocstream & os, TexRow & texrow,
1794 bool moving_arg) const
1798 if (params().noindent()) {
1799 os << "\\noindent ";
1803 LyXAlignment const curAlign = params().align();
1805 if (curAlign == layout()->align)
1809 case LYX_ALIGN_NONE:
1810 case LYX_ALIGN_BLOCK:
1811 case LYX_ALIGN_LAYOUT:
1812 case LYX_ALIGN_SPECIAL:
1814 case LYX_ALIGN_LEFT:
1815 case LYX_ALIGN_RIGHT:
1816 case LYX_ALIGN_CENTER:
1825 case LYX_ALIGN_NONE:
1826 case LYX_ALIGN_BLOCK:
1827 case LYX_ALIGN_LAYOUT:
1828 case LYX_ALIGN_SPECIAL:
1830 case LYX_ALIGN_LEFT: {
1832 if (getParLanguage(bparams)->babel() != "hebrew")
1833 output = corrected_env("\\begin", "flushleft", ownerCode());
1835 output = corrected_env("\\begin", "flushright", ownerCode());
1836 os << from_ascii(output);
1837 adjust_row_column(output, texrow, column);
1839 } case LYX_ALIGN_RIGHT: {
1841 if (getParLanguage(bparams)->babel() != "hebrew")
1842 output = corrected_env("\\begin", "flushright", ownerCode());
1844 output = corrected_env("\\begin", "flushleft", ownerCode());
1845 os << from_ascii(output);
1846 adjust_row_column(output, texrow, column);
1848 } case LYX_ALIGN_CENTER: {
1850 output = corrected_env("\\begin", "center", ownerCode());
1851 os << from_ascii(output);
1852 adjust_row_column(output, texrow, column);
1861 // This could go to ParagraphParameters if we want to
1862 int Paragraph::endTeXParParams(BufferParams const & bparams,
1863 odocstream & os, TexRow & texrow,
1864 bool moving_arg) const
1868 switch (params().align()) {
1869 case LYX_ALIGN_NONE:
1870 case LYX_ALIGN_BLOCK:
1871 case LYX_ALIGN_LAYOUT:
1872 case LYX_ALIGN_SPECIAL:
1874 case LYX_ALIGN_LEFT:
1875 case LYX_ALIGN_RIGHT:
1876 case LYX_ALIGN_CENTER:
1884 switch (params().align()) {
1885 case LYX_ALIGN_NONE:
1886 case LYX_ALIGN_BLOCK:
1887 case LYX_ALIGN_LAYOUT:
1888 case LYX_ALIGN_SPECIAL:
1890 case LYX_ALIGN_LEFT: {
1892 if (getParLanguage(bparams)->babel() != "hebrew")
1893 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1895 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1896 os << from_ascii(output);
1897 adjust_row_column(output, texrow, column);
1899 } case LYX_ALIGN_RIGHT: {
1901 if (getParLanguage(bparams)->babel() != "hebrew")
1902 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1904 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1905 os << from_ascii(output);
1906 adjust_row_column(output, texrow, column);
1908 } case LYX_ALIGN_CENTER: {
1910 output = corrected_env("\n\\par\\end", "center", ownerCode());
1911 os << from_ascii(output);
1912 adjust_row_column(output, texrow, column);
1921 // This one spits out the text of the paragraph
1922 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1923 BufferParams const & bparams,
1924 Font const & outerfont,
1925 odocstream & os, TexRow & texrow,
1926 OutputParams const & runparams) const
1928 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1930 bool return_value = false;
1934 // well we have to check if we are in an inset with unlimited
1935 // length (all in one row) if that is true then we don't allow
1936 // any special options in the paragraph and also we don't allow
1937 // any environment other than the default layout of the text class
1939 bool asdefault = forceDefaultParagraphs();
1942 style = bparams.getTextClass().defaultLayout();
1947 // Current base font for all inherited font changes, without any
1948 // change caused by an individual character, except for the language:
1949 // It is set to the language of the first character.
1950 // As long as we are in the label, this font is the base font of the
1951 // label. Before the first body character it is set to the base font
1955 // Maybe we have to create a optional argument.
1956 pos_type body_pos = beginOfBody();
1957 unsigned int column = 0;
1960 // the optional argument is kept in curly brackets in
1961 // case it contains a ']'
1964 basefont = getLabelFont(bparams, outerfont);
1966 basefont = getLayoutFont(bparams, outerfont);
1969 // Which font is currently active?
1970 Font running_font(basefont);
1971 // Do we have an open font change?
1972 bool open_font = false;
1974 Change runningChange = Change(Change::UNCHANGED);
1976 texrow.start(id(), 0);
1978 // if the paragraph is empty, the loop will not be entered at all
1980 if (style->isCommand()) {
1985 column += startTeXParParams(bparams, os, texrow,
1986 runparams.moving_arg);
1989 for (pos_type i = 0; i < size(); ++i) {
1990 // First char in paragraph or after label?
1991 if (i == body_pos) {
1994 column += running_font.latexWriteEndChanges(
1995 os, bparams, runparams,
1996 basefont, basefont);
1999 basefont = getLayoutFont(bparams, outerfont);
2000 running_font = basefont;
2002 column += Changes::latexMarkChange(os, bparams,
2003 runningChange, Change(Change::UNCHANGED));
2004 runningChange = Change(Change::UNCHANGED);
2009 if (style->isCommand()) {
2015 column += startTeXParParams(bparams, os,
2017 runparams.moving_arg);
2020 Change const & change = pimpl_->lookupChange(i);
2022 if (bparams.outputChanges && runningChange != change) {
2024 column += running_font.latexWriteEndChanges(
2025 os, bparams, runparams, basefont, basefont);
2028 basefont = getLayoutFont(bparams, outerfont);
2029 running_font = basefont;
2031 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2032 runningChange = change;
2035 // do not output text which is marked deleted
2036 // if change tracking output is disabled
2037 if (!bparams.outputChanges && change.type == Change::DELETED) {
2043 value_type const c = getChar(i);
2045 // Fully instantiated font
2046 Font const font = getFont(bparams, i, outerfont);
2048 Font const last_font = running_font;
2050 // Do we need to close the previous font?
2052 (font != running_font ||
2053 font.language() != running_font.language()))
2055 column += running_font.latexWriteEndChanges(
2056 os, bparams, runparams, basefont,
2057 (i == body_pos-1) ? basefont : font);
2058 running_font = basefont;
2062 // Switch file encoding if necessary
2063 if (runparams.encoding->package() == Encoding::inputenc &&
2064 font.language()->encoding()->package() == Encoding::inputenc) {
2065 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2066 runparams.moving_arg, *(runparams.encoding),
2067 *(font.language()->encoding()));
2068 if (enc_switch.first) {
2069 column += enc_switch.second;
2070 runparams.encoding = font.language()->encoding();
2074 // Do we need to change font?
2075 if ((font != running_font ||
2076 font.language() != running_font.language()) &&
2079 column += font.latexWriteStartChanges(os, bparams,
2080 runparams, basefont,
2082 running_font = font;
2087 // Do not print the separation of the optional argument
2088 // if style->pass_thru is false. This works because
2089 // simpleTeXSpecialChars ignores spaces if
2090 // style->pass_thru is false.
2091 if (i != body_pos - 1) {
2092 if (pimpl_->simpleTeXBlanks(
2093 *(runparams.encoding), os, texrow,
2094 i, column, font, *style))
2095 // A surrogate pair was output. We
2096 // must not call simpleTeXSpecialChars
2097 // in this iteration, since
2098 // simpleTeXBlanks incremented i, and
2099 // simpleTeXSpecialChars would output
2100 // the combining character again.
2105 OutputParams rp = runparams;
2106 rp.free_spacing = style->free_spacing;
2107 rp.local_font = &font;
2108 rp.intitle = style->intitle;
2109 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2110 texrow, rp, running_font,
2111 basefont, outerfont, open_font,
2112 runningChange, *style, i, column, c);
2115 // If we have an open font definition, we have to close it
2117 #ifdef FIXED_LANGUAGE_END_DETECTION
2120 .latexWriteEndChanges(os, bparams, runparams,
2122 next_->getFont(bparams, 0, outerfont));
2124 running_font.latexWriteEndChanges(os, bparams,
2125 runparams, basefont, basefont);
2128 #ifdef WITH_WARNINGS
2129 //#warning For now we ALWAYS have to close the foreign font settings if they are
2130 //#warning there as we start another \selectlanguage with the next paragraph if
2131 //#warning we are in need of this. This should be fixed sometime (Jug)
2133 running_font.latexWriteEndChanges(os, bparams, runparams,
2134 basefont, basefont);
2138 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2140 // Needed if there is an optional argument but no contents.
2141 if (body_pos > 0 && body_pos == size()) {
2143 return_value = false;
2147 column += endTeXParParams(bparams, os, texrow,
2148 runparams.moving_arg);
2151 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2152 return return_value;
2169 string tag_name(PAR_TAG const & pt) {
2171 case PAR_NONE: return "!-- --";
2172 case TT: return "tt";
2173 case SF: return "sf";
2174 case BF: return "bf";
2175 case IT: return "it";
2176 case SL: return "sl";
2177 case EM: return "em";
2184 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2186 p1 = static_cast<PAR_TAG>(p1 | p2);
2191 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2193 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2199 bool Paragraph::emptyTag() const
2201 for (pos_type i = 0; i < size(); ++i) {
2203 Inset const * inset = getInset(i);
2204 Inset::Code lyx_code = inset->lyxCode();
2205 if (lyx_code != Inset::TOC_CODE &&
2206 lyx_code != Inset::INCLUDE_CODE &&
2207 lyx_code != Inset::GRAPHICS_CODE &&
2208 lyx_code != Inset::ERT_CODE &&
2209 lyx_code != Inset::LISTINGS_CODE &&
2210 lyx_code != Inset::FLOAT_CODE &&
2211 lyx_code != Inset::TABULAR_CODE) {
2215 value_type c = getChar(i);
2216 if (c != ' ' && c != '\t')
2224 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2226 for (pos_type i = 0; i < size(); ++i) {
2228 Inset const * inset = getInset(i);
2229 Inset::Code lyx_code = inset->lyxCode();
2230 if (lyx_code == Inset::LABEL_CODE) {
2231 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2232 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2241 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2244 for (i = 0; i < size(); ++i) {
2246 Inset const * inset = getInset(i);
2247 inset->docbook(buf, os, runparams);
2249 value_type c = getChar(i);
2252 os << sgml::escapeChar(c);
2259 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2263 for (pos_type i = initial; i < size(); ++i) {
2264 Font font = getFont(buf.params(), i, outerfont);
2267 if (i != initial && font != font_old)
2276 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2278 OutputParams const & runparams,
2279 Font const & outerfont,
2280 pos_type initial) const
2282 bool emph_flag = false;
2284 Layout_ptr const & style = layout();
2286 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2288 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2291 // parsing main loop
2292 for (pos_type i = initial; i < size(); ++i) {
2293 Font font = getFont(buf.params(), i, outerfont);
2295 // handle <emphasis> tag
2296 if (font_old.emph() != font.emph()) {
2297 if (font.emph() == Font::ON) {
2300 } else if (i != initial) {
2301 os << "</emphasis>";
2307 Inset const * inset = getInset(i);
2308 inset->docbook(buf, os, runparams);
2310 value_type c = getChar(i);
2312 if (style->pass_thru)
2315 os << sgml::escapeChar(c);
2321 os << "</emphasis>";
2324 if (style->free_spacing)
2326 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2331 bool Paragraph::isNewline(pos_type pos) const
2334 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2338 bool Paragraph::isLineSeparator(pos_type pos) const
2340 value_type const c = getChar(pos);
2341 return isLineSeparatorChar(c)
2342 || (c == Paragraph::META_INSET && getInset(pos) &&
2343 getInset(pos)->isLineSeparator());
2347 /// Used by the spellchecker
2348 bool Paragraph::isLetter(pos_type pos) const
2351 return getInset(pos)->isLetter();
2353 value_type const c = getChar(pos);
2354 return isLetterChar(c) || isDigit(c);
2360 Paragraph::getParLanguage(BufferParams const & bparams) const
2363 return getFirstFontSettings(bparams).language();
2364 #ifdef WITH_WARNINGS
2365 #warning FIXME we should check the prev par as well (Lgb)
2367 return bparams.language;
2371 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2373 return lyxrc.rtl_support
2374 && getParLanguage(bparams)->rightToLeft()
2375 && ownerCode() != Inset::ERT_CODE
2376 && ownerCode() != Inset::LISTINGS_CODE;
2380 void Paragraph::changeLanguage(BufferParams const & bparams,
2381 Language const * from, Language const * to)
2383 // change language including dummy font change at the end
2384 for (pos_type i = 0; i <= size(); ++i) {
2385 Font font = getFontSettings(bparams, i);
2386 if (font.language() == from) {
2387 font.setLanguage(to);
2394 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2396 Language const * doc_language = bparams.language;
2397 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2398 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2400 for (; cit != end; ++cit)
2401 if (cit->font().language() != ignore_language &&
2402 cit->font().language() != latex_language &&
2403 cit->font().language() != doc_language)
2409 // Convert the paragraph to a string.
2410 // Used for building the table of contents
2411 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2413 return asString(buffer, 0, size(), label);
2417 docstring const Paragraph::asString(Buffer const & buffer,
2418 pos_type beg, pos_type end, bool label) const
2421 odocstringstream os;
2423 if (beg == 0 && label && !params().labelString().empty())
2424 os << params().labelString() << ' ';
2426 for (pos_type i = beg; i < end; ++i) {
2427 value_type const c = getChar(i);
2430 else if (c == META_INSET)
2431 getInset(i)->textString(buffer, os);
2438 void Paragraph::setInsetOwner(Inset * inset)
2440 pimpl_->inset_owner = inset;
2444 Change const & Paragraph::lookupChange(pos_type pos) const
2446 BOOST_ASSERT(pos <= size());
2447 return pimpl_->lookupChange(pos);
2451 bool Paragraph::isChanged(pos_type start, pos_type end) const
2453 return pimpl_->isChanged(start, end);
2457 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2459 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2463 void Paragraph::setChange(Change const & change)
2465 pimpl_->setChange(change);
2469 void Paragraph::setChange(pos_type pos, Change const & change)
2471 pimpl_->setChange(pos, change);
2475 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2477 return pimpl_->acceptChanges(bparams, start, end);
2481 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2483 return pimpl_->rejectChanges(bparams, start, end);
2487 int Paragraph::id() const
2493 Layout_ptr const & Paragraph::layout() const
2499 void Paragraph::layout(Layout_ptr const & new_layout)
2501 layout_ = new_layout;
2505 Inset * Paragraph::inInset() const
2507 return pimpl_->inset_owner;
2511 Inset::Code Paragraph::ownerCode() const
2513 return pimpl_->inset_owner
2514 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2518 ParagraphParameters & Paragraph::params()
2520 return pimpl_->params;
2524 ParagraphParameters const & Paragraph::params() const
2526 return pimpl_->params;
2530 bool Paragraph::isFreeSpacing() const
2532 if (layout()->free_spacing)
2535 // for now we just need this, later should we need this in some
2536 // other way we can always add a function to Inset too.
2537 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2541 bool Paragraph::allowEmpty() const
2543 if (layout()->keepempty)
2545 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2549 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2551 if (!Encodings::is_arabic(c))
2554 value_type prev_char = ' ';
2555 value_type next_char = ' ';
2557 for (pos_type i = pos - 1; i >= 0; --i) {
2558 value_type const par_char = getChar(i);
2559 if (!Encodings::isComposeChar_arabic(par_char)) {
2560 prev_char = par_char;
2565 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2566 value_type const par_char = getChar(i);
2567 if (!Encodings::isComposeChar_arabic(par_char)) {
2568 next_char = par_char;
2573 if (Encodings::is_arabic(next_char)) {
2574 if (Encodings::is_arabic(prev_char) &&
2575 !Encodings::is_arabic_special(prev_char))
2576 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2578 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2580 if (Encodings::is_arabic(prev_char) &&
2581 !Encodings::is_arabic_special(prev_char))
2582 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2584 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2589 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2594 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2596 // expand at the end of a row only if there is another hfill on the same row
2597 if (pos == row.endpos() - 1) {
2598 for (pos_type i = row.pos(); i < pos; i++) {
2605 // expand at the beginning of a row only if it is the first row of a paragraph
2606 if (pos == row.pos()) {
2610 // do not expand in some labels
2611 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2614 // if there is anything between the first char of the row and
2615 // the specified position that is neither a newline nor an hfill,
2616 // the hfill will be expanded, otherwise it won't
2617 for (pos_type i = row.pos(); i < pos; i++) {
2618 if (!isNewline(i) && !isHfill(i))
2625 int Paragraph::checkBiblio(bool track_changes)
2628 //This is getting more and more a mess. ...We really should clean
2629 //up this bibitem issue for 1.6. See also bug 2743.
2631 // Add bibitem insets if necessary
2632 if (layout()->labeltype != LABEL_BIBLIO)
2635 bool hasbibitem = !insetlist.empty()
2636 // Insist on it being in pos 0
2637 && getChar(0) == Paragraph::META_INSET
2638 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2643 // remove a bibitem in pos != 0
2644 // restore it later in pos 0 if necessary
2645 // (e.g. if a user inserts contents _before_ the item)
2646 // we're assuming there's only one of these, which there
2648 int erasedInsetPosition = -1;
2649 InsetList::iterator it = insetlist.begin();
2650 InsetList::iterator end = insetlist.end();
2651 for (; it != end; ++it)
2652 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2654 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2655 oldkey = olditem->getParam("key");
2656 oldlabel = olditem->getParam("label");
2657 erasedInsetPosition = it->pos;
2658 eraseChar(erasedInsetPosition, track_changes);
2662 //There was an InsetBibitem at the beginning, and we didn't
2663 //have to erase one.
2664 if (hasbibitem && erasedInsetPosition < 0)
2667 //There was an InsetBibitem at the beginning and we did have to
2668 //erase one. So we give its properties to the beginning inset.
2670 InsetBibitem * inset =
2671 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2672 if (!oldkey.empty())
2673 inset->setParam("key", oldkey);
2674 inset->setParam("label", oldlabel);
2675 return -erasedInsetPosition;
2678 //There was no inset at the beginning, so we need to create one with
2679 //the key and label of the one we erased.
2680 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2681 // restore values of previously deleted item in this par.
2682 if (!oldkey.empty())
2683 inset->setParam("key", oldkey);
2684 inset->setParam("label", oldlabel);
2685 insertInset(0, static_cast<Inset *>(inset),
2686 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2692 void Paragraph::checkAuthors(AuthorList const & authorList)
2694 pimpl_->changes_.checkAuthors(authorList);