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"
44 #include "frontends/alert.h"
45 #include "frontends/FontMetrics.h"
47 #include "insets/InsetBibitem.h"
48 #include "insets/InsetOptArg.h"
50 #include "support/lstrings.h"
51 #include "support/textutils.h"
52 #include "support/convert.h"
53 #include "support/unicode.h"
55 #include <boost/bind.hpp>
56 #include <boost/next_prior.hpp>
68 using support::contains;
69 using support::suffixIs;
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 &,
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 & 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 (lookupChange(i).type == Change::DELETED) {
729 if( ++runparams.inDeletedInset == 1)
730 runparams.changeOfDeletedInset = lookupChange(i);
733 if (inset->canTrackChanges()) {
734 column += Changes::latexMarkChange(os, bparams, running_change,
735 Change(Change::UNCHANGED));
736 running_change = Change(Change::UNCHANGED);
740 odocstream::pos_type const len = os.tellp();
742 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
743 || inset->lyxCode() == Inset::MATH_CODE
744 || inset->lyxCode() == Inset::URL_CODE)
745 && running_font.isRightToLeft()) {
746 if (running_font.language()->lang() == "farsi")
753 // FIXME: Bug: we can have an empty font change here!
754 // if there has just been a font change, we are going to close it
755 // right now, which means stupid latex code like \textsf{}. AFAIK,
756 // this does not harm dvi output. A minor bug, thus (JMarc)
757 // Some insets cannot be inside a font change command.
758 // However, even such insets *can* be placed in \L or \R
759 // or their equivalents (for RTL language switches), so we don't
760 // close the language in those cases.
761 // ArabTeX, though, cannot handle this special behavior, it seems.
762 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
763 running_font.language()->lang() == "arabic_arabtex";
764 if (open_font && inset->noFontChange()) {
765 bool closeLanguage = arabtex ||
766 basefont.isRightToLeft() == running_font.isRightToLeft();
767 unsigned int count = running_font.latexWriteEndChanges(
768 os, bparams, runparams,
769 basefont, basefont, closeLanguage);
771 // if any font properties were closed, update the running_font,
772 // making sure, however, to leave the language as it was
774 // FIXME: probably a better way to keep track of the old
775 // language, than copying the entire font?
776 Font const copy_font(running_font);
777 basefont = owner_->getLayoutFont(bparams, outerfont);
778 running_font = basefont;
780 running_font.setLanguage(copy_font.language());
781 // leave font open if language is still open
782 open_font = (running_font.language() == basefont.language());
784 runparams.local_font = &basefont;
788 int tmp = inset->latex(buf, os, runparams);
791 if (running_font.language()->lang() == "farsi")
798 for (int j = 0; j < tmp; ++j) {
801 texrow.start(owner_->id(), i + 1);
804 column += os.tellp() - len;
807 if (lookupChange(i).type == Change::DELETED) {
808 --runparams.inDeletedInset;
814 // And now for the special cases within each mode
818 os << "\\textbackslash{}";
822 case '|': case '<': case '>':
823 // In T1 encoding, these characters exist
824 if (lyxrc.fontenc == "T1") {
826 //... but we should avoid ligatures
827 if ((c == '>' || c == '<')
829 && getChar(i + 1) == c) {
830 //os << "\\textcompwordmark{}";
832 // Jean-Marc, have a look at
833 // this. I think this works
841 // Typewriter font also has them
842 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
846 // Otherwise, we use what LaTeX
850 os << "\\textless{}";
854 os << "\\textgreater{}";
864 case '-': // "--" in Typewriter mode -> "-{}-"
865 if (i <= size() - 2 &&
866 getChar(i + 1) == '-' &&
867 running_font.family() == Font::TYPEWRITER_FAMILY) {
876 os << "\\char`\\\"{}";
881 case '%': case '#': case '{':
889 os << "\\textasciitilde{}";
894 os << "\\textasciicircum{}";
899 // avoid being mistaken for optional arguments
907 // Blanks are printed before font switching.
908 // Sure? I am not! (try nice-latex)
909 // I am sure it's correct. LyX might be smarter
910 // in the future, but for now, nothing wrong is
916 // I assume this is hack treating typewriter as verbatim
917 // FIXME UNICODE: This can fail if c cannot be encoded
918 // in the current encoding.
919 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
928 // FIXME: if we have "LaTeX" with a font
929 // change in the middle (before the 'T', then
930 // the "TeX" part is still special cased.
931 // Really we should only operate this on
932 // "words" for some definition of word
936 for (; pnr < phrases_nr; ++pnr) {
937 if (isTextAt(special_phrases[pnr].phrase, i)) {
938 os << special_phrases[pnr].macro;
939 i += special_phrases[pnr].phrase.length() - 1;
940 column += special_phrases[pnr].macro.length() - 1;
945 if (pnr == phrases_nr && c != '\0') {
946 Encoding const & encoding = *(runparams.encoding);
947 if (i < size() - 1) {
948 char_type next = getChar(i + 1);
949 if (Encodings::isCombiningChar(next)) {
950 column += latexSurrogatePair(os, c, next, encoding) - 1;
955 docstring const latex = encoding.latexChar(c);
956 if (latex.length() > 1 &&
957 latex[latex.length() - 1] != '}') {
958 // Prevent eating of a following
959 // space or command corruption by
960 // following characters
961 column += latex.length() + 1;
964 column += latex.length() - 1;
974 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
975 Layout const & layout) const
977 BufferParams const & bparams = features.bufferParams();
980 if (!params.spacing().isDefault())
981 features.require("setspace");
984 features.useLayout(layout.name());
987 Language const * doc_language = bparams.language;
989 FontList::const_iterator fcit = fontlist.begin();
990 FontList::const_iterator fend = fontlist.end();
991 for (; fcit != fend; ++fcit) {
992 if (fcit->font().noun() == Font::ON) {
993 LYXERR(Debug::LATEX) << "font.noun: "
994 << fcit->font().noun()
996 features.require("noun");
997 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
998 << to_utf8(fcit->font().stateText(0))
1001 switch (fcit->font().color()) {
1003 case Color::inherit:
1005 // probably we should put here all interface colors used for
1006 // font displaying! For now I just add this ones I know of (Jug)
1011 features.require("color");
1012 LYXERR(Debug::LATEX) << "Color enabled. Font: "
1013 << to_utf8(fcit->font().stateText(0))
1017 Language const * language = fcit->font().language();
1018 if (language->babel() != doc_language->babel() &&
1019 language != ignore_language &&
1020 language != latex_language)
1022 features.useLanguage(language);
1023 LYXERR(Debug::LATEX) << "Found language "
1024 << language->lang() << endl;
1028 if (!params.leftIndent().zero())
1029 features.require("ParagraphLeftIndent");
1032 InsetList::const_iterator icit = owner_->insetlist.begin();
1033 InsetList::const_iterator iend = owner_->insetlist.end();
1034 for (; icit != iend; ++icit) {
1036 icit->inset->validate(features);
1037 if (layout.needprotect &&
1038 icit->inset->lyxCode() == Inset::FOOT_CODE)
1039 features.require("NeedLyXFootnoteCode");
1043 // then the contents
1044 for (pos_type i = 0; i < size() ; ++i) {
1045 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1046 if (!special_phrases[pnr].builtin
1047 && isTextAt(special_phrases[pnr].phrase, i)) {
1048 features.require(special_phrases[pnr].phrase);
1052 Encodings::validate(getChar(i), features);
1060 /////////////////////////////////////////////////////////////////////
1064 /////////////////////////////////////////////////////////////////////
1068 Paragraph::Paragraph()
1069 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1076 Paragraph::Paragraph(Paragraph const & par)
1077 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1078 layout_(par.layout_),
1079 text_(par.text_), begin_of_body_(par.begin_of_body_),
1080 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1082 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1083 InsetList::iterator it = insetlist.begin();
1084 InsetList::iterator end = insetlist.end();
1085 for (; it != end; ++it)
1086 it->inset = it->inset->clone().release();
1090 Paragraph & Paragraph::operator=(Paragraph const & par)
1092 // needed as we will destroy the pimpl_ before copying it
1094 itemdepth = par.itemdepth;
1096 insetlist = par.insetlist;
1097 InsetList::iterator it = insetlist.begin();
1098 InsetList::iterator end = insetlist.end();
1099 for (; it != end; ++it)
1100 it->inset = it->inset->clone().release();
1102 layout_ = par.layout();
1104 begin_of_body_ = par.begin_of_body_;
1107 pimpl_ = new Pimpl(*par.pimpl_, this);
1113 Paragraph::~Paragraph()
1117 //lyxerr << "Paragraph::paragraph_id = "
1118 // << Paragraph::paragraph_id << endl;
1122 void Paragraph::write(Buffer const & buf, ostream & os,
1123 BufferParams const & bparams,
1124 depth_type & dth) const
1126 // The beginning or end of a deeper (i.e. nested) area?
1127 if (dth != params().depth()) {
1128 if (params().depth() > dth) {
1129 while (params().depth() > dth) {
1130 os << "\n\\begin_deeper";
1134 while (params().depth() < dth) {
1135 os << "\n\\end_deeper";
1141 // First write the layout
1142 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1146 Font font1(Font::ALL_INHERIT, bparams.language);
1148 Change running_change = Change(Change::UNCHANGED);
1151 for (pos_type i = 0; i <= size(); ++i) {
1153 Change change = pimpl_->lookupChange(i);
1154 Changes::lyxMarkChange(os, column, running_change, change);
1155 running_change = change;
1160 // Write font changes
1161 Font font2 = getFontSettings(bparams, i);
1162 if (font2 != font1) {
1163 font2.lyxWriteChanges(font1, os);
1168 value_type const c = getChar(i);
1172 Inset const * inset = getInset(i);
1174 if (inset->directWrite()) {
1175 // international char, let it write
1176 // code directly so it's shorter in
1178 inset->write(buf, os);
1182 os << "\\begin_inset ";
1183 inset->write(buf, os);
1184 os << "\n\\end_inset\n\n";
1190 os << "\n\\backslash\n";
1194 if (i + 1 < size() && getChar(i + 1) == ' ') {
1201 if ((column > 70 && c == ' ')
1206 // this check is to amend a bug. LyX sometimes
1207 // inserts '\0' this could cause problems.
1209 std::vector<char> tmp = ucs4_to_utf8(c);
1210 tmp.push_back('\0');
1213 lyxerr << "ERROR (Paragraph::writeFile):"
1214 " NULL char in structure." << endl;
1220 os << "\n\\end_layout\n";
1224 void Paragraph::validate(LaTeXFeatures & features) const
1226 pimpl_->validate(features, *layout());
1230 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1232 return pimpl_->eraseChar(pos, trackChanges);
1236 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1238 return pimpl_->eraseChars(start, end, trackChanges);
1242 void Paragraph::insert(pos_type start, docstring const & str,
1243 Font const & font, Change const & change)
1245 for (size_t i = 0, n = str.size(); i != n ; ++i)
1246 insertChar(start + i, str[i], font, change);
1250 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1253 pimpl_->insertChar(pos, c, Change(trackChanges ?
1254 Change::INSERTED : Change::UNCHANGED));
1258 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1259 Font const & font, bool trackChanges)
1261 pimpl_->insertChar(pos, c, Change(trackChanges ?
1262 Change::INSERTED : Change::UNCHANGED));
1267 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1268 Font const & font, Change const & change)
1270 pimpl_->insertChar(pos, c, change);
1275 void Paragraph::insertInset(pos_type pos, Inset * inset,
1276 Change const & change)
1278 pimpl_->insertInset(pos, inset, change);
1282 void Paragraph::insertInset(pos_type pos, Inset * inset,
1283 Font const & font, Change const & change)
1285 pimpl_->insertInset(pos, inset, change);
1286 // Set the font/language of the inset...
1288 // ... as well as the font/language of the text inside the inset
1289 // FIXME: This is far from perfect. It basically overrides work being done
1290 // in the InsetText constructor. Also, it doesn't work for Tables
1291 // (precisely because each cell's font/language is set in the Table's
1292 // constructor, so by now it's too late). The long-term solution should
1293 // be moving current_font into Cursor, and getting rid of all this...
1294 // (see http://thread.gmane.org/gmane.editors.lyx.devel/88869/focus=88944)
1295 if (inset->asTextInset()) {
1296 inset->asTextInset()->text_.current_font = font;
1297 inset->asTextInset()->text_.real_current_font = font;
1302 bool Paragraph::insetAllowed(Inset_code code)
1304 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1308 // Gets uninstantiated font setting at position.
1309 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1313 lyxerr << " pos: " << pos << " size: " << size() << endl;
1314 BOOST_ASSERT(pos <= size());
1317 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1318 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1319 for (; cit != end; ++cit)
1320 if (cit->pos() >= pos)
1326 if (pos == size() && !empty())
1327 return getFontSettings(bparams, pos - 1);
1329 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1333 FontSpan Paragraph::fontSpan(pos_type pos) const
1335 BOOST_ASSERT(pos <= size());
1338 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1339 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1340 for (; cit != end; ++cit) {
1341 if (cit->pos() >= pos) {
1342 if (pos >= beginOfBody())
1343 return FontSpan(std::max(start, beginOfBody()),
1346 return FontSpan(start,
1347 std::min(beginOfBody() - 1,
1350 start = cit->pos() + 1;
1353 // This should not happen, but if so, we take no chances.
1354 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1356 return FontSpan(pos, pos);
1360 // Gets uninstantiated font setting at position 0
1361 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1363 if (!empty() && !pimpl_->fontlist.empty())
1364 return pimpl_->fontlist[0].font();
1366 return Font(Font::ALL_INHERIT, bparams.language);
1370 // Gets the fully instantiated font at a given position in a paragraph
1371 // This is basically the same function as Text::GetFont() in text2.cpp.
1372 // The difference is that this one is used for generating the LaTeX file,
1373 // and thus cosmetic "improvements" are disallowed: This has to deliver
1374 // the true picture of the buffer. (Asger)
1375 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1376 Font const & outerfont) const
1378 BOOST_ASSERT(pos >= 0);
1380 Font font = getFontSettings(bparams, pos);
1382 pos_type const body_pos = beginOfBody();
1384 font.realize(layout_->labelfont);
1386 font.realize(layout_->font);
1388 font.realize(outerfont);
1389 font.realize(bparams.getFont());
1395 Font const Paragraph::getLabelFont
1396 (BufferParams const & bparams, Font const & outerfont) const
1398 Font tmpfont = layout()->labelfont;
1399 tmpfont.setLanguage(getParLanguage(bparams));
1400 tmpfont.realize(outerfont);
1401 tmpfont.realize(bparams.getFont());
1406 Font const Paragraph::getLayoutFont
1407 (BufferParams const & bparams, Font const & outerfont) const
1409 Font tmpfont = layout()->font;
1410 tmpfont.setLanguage(getParLanguage(bparams));
1411 tmpfont.realize(outerfont);
1412 tmpfont.realize(bparams.getFont());
1417 /// Returns the height of the highest font in range
1418 Font_size Paragraph::highestFontInRange
1419 (pos_type startpos, pos_type endpos, Font_size def_size) const
1421 if (pimpl_->fontlist.empty())
1424 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1425 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1426 for (; end_it != end; ++end_it) {
1427 if (end_it->pos() >= endpos)
1434 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1435 for (; cit != end; ++cit) {
1436 if (cit->pos() >= startpos)
1440 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1441 for (; cit != end_it; ++cit) {
1442 Font::FONT_SIZE size = cit->font().size();
1443 if (size == Font::INHERIT_SIZE)
1445 if (size > maxsize && size <= Font::SIZE_HUGER)
1452 Paragraph::value_type
1453 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1455 value_type c = getChar(pos);
1456 if (!lyxrc.rtl_support)
1486 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1493 void Paragraph::setFont(pos_type pos, Font const & font)
1495 BOOST_ASSERT(pos <= size());
1497 // First, reduce font against layout/label font
1498 // Update: The setCharFont() routine in text2.cpp already
1499 // reduces font, so we don't need to do that here. (Asger)
1500 // No need to simplify this because it will disappear
1501 // in a new kernel. (Asger)
1502 // Next search font table
1504 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1505 Pimpl::FontList::iterator it = beg;
1506 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1507 for (; it != endit; ++it) {
1508 if (it->pos() >= pos)
1511 size_t const i = distance(beg, it);
1512 bool notfound = (it == endit);
1514 if (!notfound && pimpl_->fontlist[i].font() == font)
1517 bool begin = pos == 0 || notfound ||
1518 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1519 // Is position pos is a beginning of a font block?
1520 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1521 // Is position pos is the end of a font block?
1522 if (begin && end) { // A single char block
1523 if (i + 1 < pimpl_->fontlist.size() &&
1524 pimpl_->fontlist[i + 1].font() == font) {
1525 // Merge the singleton block with the next block
1526 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1527 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1528 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1529 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1530 // Merge the singleton block with the previous block
1531 pimpl_->fontlist[i - 1].pos(pos);
1532 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1534 pimpl_->fontlist[i].font(font);
1536 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1537 pimpl_->fontlist[i - 1].pos(pos);
1539 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1540 Pimpl::FontTable(pos, font));
1542 pimpl_->fontlist[i].pos(pos - 1);
1543 if (!(i + 1 < pimpl_->fontlist.size() &&
1544 pimpl_->fontlist[i + 1].font() == font))
1545 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1546 Pimpl::FontTable(pos, font));
1547 } else { // The general case. The block is splitted into 3 blocks
1548 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1549 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1550 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1551 Pimpl::FontTable(pos, font));
1556 void Paragraph::makeSameLayout(Paragraph const & par)
1558 layout(par.layout());
1560 params() = par.params();
1564 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1566 if (isFreeSpacing())
1572 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1573 if (eraseChar(pos, trackChanges))
1579 return count > 0 || pos > 0;
1583 bool Paragraph::hasSameLayout(Paragraph const & par) const
1585 return par.layout() == layout() && params().sameLayout(par.params());
1589 depth_type Paragraph::getDepth() const
1591 return params().depth();
1595 depth_type Paragraph::getMaxDepthAfter() const
1597 if (layout()->isEnvironment())
1598 return params().depth() + 1;
1600 return params().depth();
1604 char Paragraph::getAlign() const
1606 if (params().align() == LYX_ALIGN_LAYOUT)
1607 return layout()->align;
1609 return params().align();
1613 docstring const & Paragraph::getLabelstring() const
1615 return params().labelString();
1619 // the next two functions are for the manual labels
1620 docstring const Paragraph::getLabelWidthString() const
1622 if (!params().labelWidthString().empty())
1623 return params().labelWidthString();
1625 return _("Senseless with this layout!");
1629 void Paragraph::setLabelWidthString(docstring const & s)
1631 params().labelWidthString(s);
1635 docstring const Paragraph::translateIfPossible(docstring const & s,
1636 BufferParams const & bparams) const
1638 if (!support::isAscii(s) || s.empty()) {
1639 // This must be a user defined layout. We cannot translate
1640 // this, since gettext accepts only ascii keys.
1643 // Probably standard layout, try to translate
1644 Messages & m = getMessages(getParLanguage(bparams)->code());
1645 return m.get(to_ascii(s));
1649 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1650 BufferParams const & bparams, bool process_appendix) const
1652 TextClass const & tclass = bparams.getTextClass();
1655 if (process_appendix && params().appendix())
1656 fmt = translateIfPossible(layout->labelstring_appendix(),
1659 fmt = translateIfPossible(layout->labelstring(), bparams);
1661 if (fmt.empty() && layout->labeltype == LABEL_COUNTER
1662 && !layout->counter.empty())
1663 fmt = "\\the" + layout->counter;
1665 // handle 'inherited level parts' in 'fmt',
1666 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1667 size_t const i = fmt.find('@', 0);
1668 if (i != docstring::npos) {
1669 size_t const j = fmt.find('@', i + 1);
1670 if (j != docstring::npos) {
1671 docstring parent(fmt, i + 1, j - i - 1);
1672 docstring label = expandLabel(tclass[parent], bparams,
1674 fmt = docstring(fmt, 0, i) + label
1675 + docstring(fmt, j + 1, docstring::npos);
1679 return tclass.counters().counterLabel(fmt);
1683 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1686 LyXAlignment const oldAlign = params().align();
1688 if (!(oldAlign & layout()->alignpossible)) {
1689 frontend::Alert::warning(_("Alignment not permitted"),
1690 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1691 params().align(LYX_ALIGN_LAYOUT);
1696 pos_type Paragraph::beginOfBody() const
1698 return begin_of_body_;
1702 void Paragraph::setBeginOfBody()
1704 if (layout()->labeltype != LABEL_MANUAL) {
1709 // Unroll the first two cycles of the loop
1710 // and remember the previous character to
1711 // remove unnecessary getChar() calls
1713 pos_type end = size();
1714 if (i < end && !isNewline(i)) {
1716 char_type previous_char = 0;
1719 previous_char = text_[i];
1720 if (!isNewline(i)) {
1722 while (i < end && previous_char != ' ') {
1727 previous_char = temp;
1737 // returns -1 if inset not found
1738 int Paragraph::getPositionOfInset(Inset const * inset) const
1741 InsetList::const_iterator it = insetlist.begin();
1742 InsetList::const_iterator end = insetlist.end();
1743 for (; it != end; ++it)
1744 if (it->inset == inset)
1750 InsetBibitem * Paragraph::bibitem() const
1752 if (!insetlist.empty()) {
1753 Inset * inset = insetlist.begin()->inset;
1754 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1755 return static_cast<InsetBibitem *>(inset);
1761 bool Paragraph::forceDefaultParagraphs() const
1763 return inInset() && inInset()->forceDefaultParagraphs(0);
1769 // paragraphs inside floats need different alignment tags to avoid
1772 bool noTrivlistCentering(Inset::Code code)
1774 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1778 string correction(string const & orig)
1780 if (orig == "flushleft")
1781 return "raggedright";
1782 if (orig == "flushright")
1783 return "raggedleft";
1784 if (orig == "center")
1790 string const corrected_env(string const & suffix, string const & env,
1793 string output = suffix + "{";
1794 if (noTrivlistCentering(code))
1795 output += correction(env);
1799 if (suffix == "\\begin")
1805 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1807 if (!contains(str, "\n"))
1808 column += str.size();
1812 column = rsplit(str, tmp, '\n').size();
1819 // This could go to ParagraphParameters if we want to
1820 int Paragraph::startTeXParParams(BufferParams const & bparams,
1821 odocstream & os, TexRow & texrow,
1822 bool moving_arg) const
1826 if (params().noindent()) {
1827 os << "\\noindent ";
1831 LyXAlignment const curAlign = params().align();
1833 if (curAlign == layout()->align)
1837 case LYX_ALIGN_NONE:
1838 case LYX_ALIGN_BLOCK:
1839 case LYX_ALIGN_LAYOUT:
1840 case LYX_ALIGN_SPECIAL:
1842 case LYX_ALIGN_LEFT:
1843 case LYX_ALIGN_RIGHT:
1844 case LYX_ALIGN_CENTER:
1853 case LYX_ALIGN_NONE:
1854 case LYX_ALIGN_BLOCK:
1855 case LYX_ALIGN_LAYOUT:
1856 case LYX_ALIGN_SPECIAL:
1858 case LYX_ALIGN_LEFT: {
1860 if (getParLanguage(bparams)->babel() != "hebrew")
1861 output = corrected_env("\\begin", "flushleft", ownerCode());
1863 output = corrected_env("\\begin", "flushright", ownerCode());
1864 os << from_ascii(output);
1865 adjust_row_column(output, texrow, column);
1867 } case LYX_ALIGN_RIGHT: {
1869 if (getParLanguage(bparams)->babel() != "hebrew")
1870 output = corrected_env("\\begin", "flushright", ownerCode());
1872 output = corrected_env("\\begin", "flushleft", ownerCode());
1873 os << from_ascii(output);
1874 adjust_row_column(output, texrow, column);
1876 } case LYX_ALIGN_CENTER: {
1878 output = corrected_env("\\begin", "center", ownerCode());
1879 os << from_ascii(output);
1880 adjust_row_column(output, texrow, column);
1889 // This could go to ParagraphParameters if we want to
1890 int Paragraph::endTeXParParams(BufferParams const & bparams,
1891 odocstream & os, TexRow & texrow,
1892 bool moving_arg) const
1896 switch (params().align()) {
1897 case LYX_ALIGN_NONE:
1898 case LYX_ALIGN_BLOCK:
1899 case LYX_ALIGN_LAYOUT:
1900 case LYX_ALIGN_SPECIAL:
1902 case LYX_ALIGN_LEFT:
1903 case LYX_ALIGN_RIGHT:
1904 case LYX_ALIGN_CENTER:
1912 switch (params().align()) {
1913 case LYX_ALIGN_NONE:
1914 case LYX_ALIGN_BLOCK:
1915 case LYX_ALIGN_LAYOUT:
1916 case LYX_ALIGN_SPECIAL:
1918 case LYX_ALIGN_LEFT: {
1920 if (getParLanguage(bparams)->babel() != "hebrew")
1921 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1923 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1924 os << from_ascii(output);
1925 adjust_row_column(output, texrow, column);
1927 } case LYX_ALIGN_RIGHT: {
1929 if (getParLanguage(bparams)->babel() != "hebrew")
1930 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1932 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1933 os << from_ascii(output);
1934 adjust_row_column(output, texrow, column);
1936 } case LYX_ALIGN_CENTER: {
1938 output = corrected_env("\n\\par\\end", "center", ownerCode());
1939 os << from_ascii(output);
1940 adjust_row_column(output, texrow, column);
1949 // This one spits out the text of the paragraph
1950 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1951 BufferParams const & bparams,
1952 Font const & outerfont,
1953 odocstream & os, TexRow & texrow,
1954 OutputParams const & runparams) const
1956 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1958 bool return_value = false;
1962 // well we have to check if we are in an inset with unlimited
1963 // length (all in one row) if that is true then we don't allow
1964 // any special options in the paragraph and also we don't allow
1965 // any environment other than the default layout of the text class
1967 bool asdefault = forceDefaultParagraphs();
1970 style = bparams.getTextClass().defaultLayout();
1975 // Current base font for all inherited font changes, without any
1976 // change caused by an individual character, except for the language:
1977 // It is set to the language of the first character.
1978 // As long as we are in the label, this font is the base font of the
1979 // label. Before the first body character it is set to the base font
1983 // Maybe we have to create a optional argument.
1984 pos_type body_pos = beginOfBody();
1985 unsigned int column = 0;
1988 // the optional argument is kept in curly brackets in
1989 // case it contains a ']'
1992 basefont = getLabelFont(bparams, outerfont);
1994 basefont = getLayoutFont(bparams, outerfont);
1997 // Which font is currently active?
1998 Font running_font(basefont);
1999 // Do we have an open font change?
2000 bool open_font = false;
2002 Change runningChange = Change(Change::UNCHANGED);
2004 texrow.start(id(), 0);
2006 // if the paragraph is empty, the loop will not be entered at all
2008 if (style->isCommand()) {
2013 column += startTeXParParams(bparams, os, texrow,
2014 runparams.moving_arg);
2017 for (pos_type i = 0; i < size(); ++i) {
2018 // First char in paragraph or after label?
2019 if (i == body_pos) {
2022 column += running_font.latexWriteEndChanges(
2023 os, bparams, runparams,
2024 basefont, basefont);
2027 basefont = getLayoutFont(bparams, outerfont);
2028 running_font = basefont;
2030 column += Changes::latexMarkChange(os, bparams,
2031 runningChange, Change(Change::UNCHANGED));
2032 runningChange = Change(Change::UNCHANGED);
2037 if (style->isCommand()) {
2043 column += startTeXParParams(bparams, os,
2045 runparams.moving_arg);
2048 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2049 : pimpl_->lookupChange(i);
2051 if (bparams.outputChanges && runningChange != change) {
2053 column += running_font.latexWriteEndChanges(
2054 os, bparams, runparams, basefont, basefont);
2057 basefont = getLayoutFont(bparams, outerfont);
2058 running_font = basefont;
2060 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2061 runningChange = change;
2064 // do not output text which is marked deleted
2065 // if change tracking output is disabled
2066 if (!bparams.outputChanges && change.type == Change::DELETED) {
2072 value_type const c = getChar(i);
2074 // Fully instantiated font
2075 Font const font = getFont(bparams, i, outerfont);
2077 Font const last_font = running_font;
2079 // Do we need to close the previous font?
2081 (font != running_font ||
2082 font.language() != running_font.language()))
2084 column += running_font.latexWriteEndChanges(
2085 os, bparams, runparams, basefont,
2086 (i == body_pos-1) ? basefont : font);
2087 running_font = basefont;
2091 // Switch file encoding if necessary
2092 if (runparams.encoding->package() == Encoding::inputenc &&
2093 font.language()->encoding()->package() == Encoding::inputenc) {
2094 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2095 runparams.moving_arg, *(runparams.encoding),
2096 *(font.language()->encoding()));
2097 if (enc_switch.first) {
2098 column += enc_switch.second;
2099 runparams.encoding = font.language()->encoding();
2103 // Do we need to change font?
2104 if ((font != running_font ||
2105 font.language() != running_font.language()) &&
2108 odocstringstream ods;
2109 column += font.latexWriteStartChanges(ods, bparams,
2110 runparams, basefont,
2112 running_font = font;
2114 docstring fontchange = ods.str();
2115 // check if the fontchange ends with a trailing blank
2116 // (like "\small " (see bug 3382)
2117 if (suffixIs(fontchange, ' ') && c == ' ')
2118 os << fontchange.substr(0, fontchange.size() - 1)
2119 << from_ascii("{}");
2125 // Do not print the separation of the optional argument
2126 // if style->pass_thru is false. This works because
2127 // simpleTeXSpecialChars ignores spaces if
2128 // style->pass_thru is false.
2129 if (i != body_pos - 1) {
2130 if (pimpl_->simpleTeXBlanks(
2131 *(runparams.encoding), os, texrow,
2132 i, column, font, *style))
2133 // A surrogate pair was output. We
2134 // must not call simpleTeXSpecialChars
2135 // in this iteration, since
2136 // simpleTeXBlanks incremented i, and
2137 // simpleTeXSpecialChars would output
2138 // the combining character again.
2143 OutputParams rp = runparams;
2144 rp.free_spacing = style->free_spacing;
2145 rp.local_font = &font;
2146 rp.intitle = style->intitle;
2147 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2148 texrow, rp, running_font,
2149 basefont, outerfont, open_font,
2150 runningChange, *style, i, column, c);
2152 // Set the encoding to that returned from simpleTeXSpecialChars (see
2153 // comment for encoding member in OutputParams.h)
2154 runparams.encoding = rp.encoding;
2157 // If we have an open font definition, we have to close it
2159 #ifdef FIXED_LANGUAGE_END_DETECTION
2162 .latexWriteEndChanges(os, bparams, runparams,
2164 next_->getFont(bparams, 0, outerfont));
2166 running_font.latexWriteEndChanges(os, bparams,
2167 runparams, basefont, basefont);
2170 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2171 //FIXME: there as we start another \selectlanguage with the next paragraph if
2172 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2173 running_font.latexWriteEndChanges(os, bparams, runparams,
2174 basefont, basefont);
2178 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2180 // Needed if there is an optional argument but no contents.
2181 if (body_pos > 0 && body_pos == size()) {
2183 return_value = false;
2187 column += endTeXParParams(bparams, os, texrow,
2188 runparams.moving_arg);
2191 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2192 return return_value;
2209 string tag_name(PAR_TAG const & pt) {
2211 case PAR_NONE: return "!-- --";
2212 case TT: return "tt";
2213 case SF: return "sf";
2214 case BF: return "bf";
2215 case IT: return "it";
2216 case SL: return "sl";
2217 case EM: return "em";
2224 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2226 p1 = static_cast<PAR_TAG>(p1 | p2);
2231 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2233 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2239 bool Paragraph::emptyTag() const
2241 for (pos_type i = 0; i < size(); ++i) {
2243 Inset const * inset = getInset(i);
2244 Inset::Code lyx_code = inset->lyxCode();
2245 if (lyx_code != Inset::TOC_CODE &&
2246 lyx_code != Inset::INCLUDE_CODE &&
2247 lyx_code != Inset::GRAPHICS_CODE &&
2248 lyx_code != Inset::ERT_CODE &&
2249 lyx_code != Inset::LISTINGS_CODE &&
2250 lyx_code != Inset::FLOAT_CODE &&
2251 lyx_code != Inset::TABULAR_CODE) {
2255 value_type c = getChar(i);
2256 if (c != ' ' && c != '\t')
2264 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2266 for (pos_type i = 0; i < size(); ++i) {
2268 Inset const * inset = getInset(i);
2269 Inset::Code lyx_code = inset->lyxCode();
2270 if (lyx_code == Inset::LABEL_CODE) {
2271 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2272 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2281 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2284 for (i = 0; i < size(); ++i) {
2286 Inset const * inset = getInset(i);
2287 inset->docbook(buf, os, runparams);
2289 value_type c = getChar(i);
2292 os << sgml::escapeChar(c);
2299 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2303 for (pos_type i = initial; i < size(); ++i) {
2304 Font font = getFont(buf.params(), i, outerfont);
2307 if (i != initial && font != font_old)
2316 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2318 OutputParams const & runparams,
2319 Font const & outerfont,
2320 pos_type initial) const
2322 bool emph_flag = false;
2324 LayoutPtr const & style = layout();
2326 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2328 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2331 // parsing main loop
2332 for (pos_type i = initial; i < size(); ++i) {
2333 Font font = getFont(buf.params(), i, outerfont);
2335 // handle <emphasis> tag
2336 if (font_old.emph() != font.emph()) {
2337 if (font.emph() == Font::ON) {
2340 } else if (i != initial) {
2341 os << "</emphasis>";
2347 Inset const * inset = getInset(i);
2348 inset->docbook(buf, os, runparams);
2350 value_type c = getChar(i);
2352 if (style->pass_thru)
2355 os << sgml::escapeChar(c);
2361 os << "</emphasis>";
2364 if (style->free_spacing)
2366 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2371 bool Paragraph::isNewline(pos_type pos) const
2374 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2378 bool Paragraph::isLineSeparator(pos_type pos) const
2380 value_type const c = getChar(pos);
2381 return isLineSeparatorChar(c)
2382 || (c == Paragraph::META_INSET && getInset(pos) &&
2383 getInset(pos)->isLineSeparator());
2387 /// Used by the spellchecker
2388 bool Paragraph::isLetter(pos_type pos) const
2391 return getInset(pos)->isLetter();
2393 value_type const c = getChar(pos);
2394 return isLetterChar(c) || isDigit(c);
2400 Paragraph::getParLanguage(BufferParams const & bparams) const
2403 return getFirstFontSettings(bparams).language();
2404 // FIXME: we should check the prev par as well (Lgb)
2405 return bparams.language;
2409 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2411 return lyxrc.rtl_support
2412 && getParLanguage(bparams)->rightToLeft()
2413 && ownerCode() != Inset::ERT_CODE
2414 && ownerCode() != Inset::LISTINGS_CODE;
2418 void Paragraph::changeLanguage(BufferParams const & bparams,
2419 Language const * from, Language const * to)
2421 // change language including dummy font change at the end
2422 for (pos_type i = 0; i <= size(); ++i) {
2423 Font font = getFontSettings(bparams, i);
2424 if (font.language() == from) {
2425 font.setLanguage(to);
2432 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2434 Language const * doc_language = bparams.language;
2435 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2436 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2438 for (; cit != end; ++cit)
2439 if (cit->font().language() != ignore_language &&
2440 cit->font().language() != latex_language &&
2441 cit->font().language() != doc_language)
2447 // Convert the paragraph to a string.
2448 // Used for building the table of contents
2449 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2451 return asString(buffer, 0, size(), label);
2455 docstring const Paragraph::asString(Buffer const & buffer,
2456 pos_type beg, pos_type end, bool label) const
2459 odocstringstream os;
2461 if (beg == 0 && label && !params().labelString().empty())
2462 os << params().labelString() << ' ';
2464 for (pos_type i = beg; i < end; ++i) {
2465 value_type const c = getChar(i);
2468 else if (c == META_INSET)
2469 getInset(i)->textString(buffer, os);
2476 void Paragraph::setInsetOwner(Inset * inset)
2478 pimpl_->inset_owner = inset;
2482 Change const & Paragraph::lookupChange(pos_type pos) const
2484 BOOST_ASSERT(pos <= size());
2485 return pimpl_->lookupChange(pos);
2489 bool Paragraph::isChanged(pos_type start, pos_type end) const
2491 return pimpl_->isChanged(start, end);
2495 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2497 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2501 void Paragraph::setChange(Change const & change)
2503 pimpl_->setChange(change);
2507 void Paragraph::setChange(pos_type pos, Change const & change)
2509 pimpl_->setChange(pos, change);
2513 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2515 return pimpl_->acceptChanges(bparams, start, end);
2519 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2521 return pimpl_->rejectChanges(bparams, start, end);
2525 int Paragraph::id() const
2531 LayoutPtr const & Paragraph::layout() const
2537 void Paragraph::layout(LayoutPtr const & new_layout)
2539 layout_ = new_layout;
2543 Inset * Paragraph::inInset() const
2545 return pimpl_->inset_owner;
2549 Inset::Code Paragraph::ownerCode() const
2551 return pimpl_->inset_owner
2552 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2556 ParagraphParameters & Paragraph::params()
2558 return pimpl_->params;
2562 ParagraphParameters const & Paragraph::params() const
2564 return pimpl_->params;
2568 bool Paragraph::isFreeSpacing() const
2570 if (layout()->free_spacing)
2573 // for now we just need this, later should we need this in some
2574 // other way we can always add a function to Inset too.
2575 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2579 bool Paragraph::allowEmpty() const
2581 if (layout()->keepempty)
2583 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2587 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2589 if (!Encodings::is_arabic(c))
2592 value_type prev_char = ' ';
2593 value_type next_char = ' ';
2595 for (pos_type i = pos - 1; i >= 0; --i) {
2596 value_type const par_char = getChar(i);
2597 if (!Encodings::isComposeChar_arabic(par_char)) {
2598 prev_char = par_char;
2603 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2604 value_type const par_char = getChar(i);
2605 if (!Encodings::isComposeChar_arabic(par_char)) {
2606 next_char = par_char;
2611 if (Encodings::is_arabic(next_char)) {
2612 if (Encodings::is_arabic(prev_char) &&
2613 !Encodings::is_arabic_special(prev_char))
2614 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2616 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2618 if (Encodings::is_arabic(prev_char) &&
2619 !Encodings::is_arabic_special(prev_char))
2620 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2622 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2627 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2632 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2634 // expand at the end of a row only if there is another hfill on the same row
2635 if (pos == row.endpos() - 1) {
2636 for (pos_type i = row.pos(); i < pos; i++) {
2643 // expand at the beginning of a row only if it is the first row of a paragraph
2644 if (pos == row.pos()) {
2648 // do not expand in some labels
2649 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2652 // if there is anything between the first char of the row and
2653 // the specified position that is neither a newline nor an hfill,
2654 // the hfill will be expanded, otherwise it won't
2655 for (pos_type i = row.pos(); i < pos; i++) {
2656 if (!isNewline(i) && !isHfill(i))
2663 int Paragraph::checkBiblio(bool track_changes)
2666 //This is getting more and more a mess. ...We really should clean
2667 //up this bibitem issue for 1.6. See also bug 2743.
2669 // Add bibitem insets if necessary
2670 if (layout()->labeltype != LABEL_BIBLIO)
2673 bool hasbibitem = !insetlist.empty()
2674 // Insist on it being in pos 0
2675 && getChar(0) == Paragraph::META_INSET
2676 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2681 // remove a bibitem in pos != 0
2682 // restore it later in pos 0 if necessary
2683 // (e.g. if a user inserts contents _before_ the item)
2684 // we're assuming there's only one of these, which there
2686 int erasedInsetPosition = -1;
2687 InsetList::iterator it = insetlist.begin();
2688 InsetList::iterator end = insetlist.end();
2689 for (; it != end; ++it)
2690 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2692 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2693 oldkey = olditem->getParam("key");
2694 oldlabel = olditem->getParam("label");
2695 erasedInsetPosition = it->pos;
2696 eraseChar(erasedInsetPosition, track_changes);
2700 //There was an InsetBibitem at the beginning, and we didn't
2701 //have to erase one.
2702 if (hasbibitem && erasedInsetPosition < 0)
2705 //There was an InsetBibitem at the beginning and we did have to
2706 //erase one. So we give its properties to the beginning inset.
2708 InsetBibitem * inset =
2709 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2710 if (!oldkey.empty())
2711 inset->setParam("key", oldkey);
2712 inset->setParam("label", oldlabel);
2713 return -erasedInsetPosition;
2716 //There was no inset at the beginning, so we need to create one with
2717 //the key and label of the one we erased.
2718 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2719 // restore values of previously deleted item in this par.
2720 if (!oldkey.empty())
2721 inset->setParam("key", oldkey);
2722 inset->setParam("label", oldlabel);
2723 insertInset(0, static_cast<Inset *>(inset),
2724 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2730 void Paragraph::checkAuthors(AuthorList const & authorList)
2732 pimpl_->changes_.checkAuthors(authorList);