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::suffixIs;
71 using support::rsplit;
74 /////////////////////////////////////////////////////////////////////
78 /////////////////////////////////////////////////////////////////////
84 class Paragraph::Pimpl {
87 Pimpl(Paragraph * owner);
88 /// "Copy constructor"
89 Pimpl(Pimpl const &, Paragraph * owner);
94 /// look up change at given pos
95 Change const & lookupChange(pos_type pos) const;
96 /// is there a change within the given range ?
97 bool isChanged(pos_type start, pos_type end) const;
98 /// will the paragraph be physically merged with the next
99 /// one if the imaginary end-of-par character is logically deleted?
100 bool isMergedOnEndOfParDeletion(bool trackChanges) const;
101 /// set change for the entire par
102 void setChange(Change const & change);
103 /// set change at given pos
104 void setChange(pos_type pos, Change const & change);
105 /// accept changes within the given range
106 void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end);
107 /// reject changes within the given range
108 void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end);
111 value_type getChar(pos_type pos) const;
113 void insertChar(pos_type pos, value_type c, Change const & change);
115 void insertInset(pos_type pos, Inset * inset, Change const & change);
116 /// (logically) erase the char at pos; return true if it was actually erased
117 bool eraseChar(pos_type pos, bool trackChanges);
118 /// (logically) erase the given range; return the number of chars actually erased
119 int eraseChars(pos_type start, pos_type end, bool trackChanges);
123 /** A font entry covers a range of positions. Notice that the
124 entries in the list are inserted in random order.
125 I don't think it's worth the effort to implement a more effective
126 datastructure, because the number of different fonts in a paragraph
128 Nevertheless, I decided to store fontlist using a sorted vector:
129 fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where
130 pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
131 and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
132 (font_1 covers the chars 0,...,pos_1) (Dekel)
137 FontTable(pos_type p, Font const & f)
141 pos_type pos() const { return pos_; }
143 void pos(pos_type p) { pos_ = p; }
145 Font const & font() const { return font_; }
147 void font(Font const & f) { font_ = f;}
149 /// End position of paragraph this font attribute covers
151 /** Font. Interpretation of the font values:
152 If a value is Font::INHERIT_*, it means that the font
153 attribute is inherited from either the layout of this
154 paragraph or, in the case of nested paragraphs, from the
155 layout in the environment one level up until completely
157 The values Font::IGNORE_* and Font::TOGGLE are NOT
158 allowed in these font tables.
163 friend class matchFT;
167 /// used by lower_bound and upper_bound
168 int operator()(FontTable const & a, FontTable const & b) const {
169 return a.pos() < b.pos();
174 typedef std::vector<FontTable> FontList;
178 /// Output the surrogate pair formed by \p c and \p next to \p os.
179 /// \return the number of characters written.
180 int latexSurrogatePair(odocstream & os, value_type c, value_type next,
182 /// Output a space in appropriate formatting (or a surrogate pair
183 /// if the next character is a combining character).
184 /// \return whether a surrogate pair was output.
185 bool simpleTeXBlanks(Encoding const &,
186 odocstream &, TexRow & texrow,
188 unsigned int & column,
190 Layout const & style);
192 void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
194 TexRow & texrow, OutputParams const &,
197 Font const & outerfont,
199 Change & running_change,
200 Layout const & style,
202 unsigned int & column, value_type const c);
205 void validate(LaTeXFeatures & features,
206 Layout const & layout) const;
211 static unsigned int paragraph_id;
213 ParagraphParameters params;
217 pos_type size() const { return owner_->size(); }
218 /// match a string against a particular point in the paragraph
219 bool isTextAt(std::string const & str, pos_type pos) const;
221 /// for recording and looking up changes
232 using std::upper_bound;
233 using std::lower_bound;
237 // Initialization of the counter for the paragraph id's,
238 unsigned int Paragraph::Pimpl::paragraph_id = 0;
242 struct special_phrase {
248 special_phrase const special_phrases[] = {
249 { "LyX", from_ascii("\\LyX{}"), false },
250 { "TeX", from_ascii("\\TeX{}"), true },
251 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
252 { "LaTeX", from_ascii("\\LaTeX{}"), true },
255 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
260 Paragraph::Pimpl::Pimpl(Paragraph * owner)
264 id_ = paragraph_id++;
268 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
269 : params(p.params), changes_(p.changes_), owner_(owner)
271 inset_owner = p.inset_owner;
272 fontlist = p.fontlist;
273 id_ = paragraph_id++;
277 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
279 BOOST_ASSERT(start >= 0 && start <= size());
280 BOOST_ASSERT(end > start && end <= size() + 1);
282 return changes_.isChanged(start, end);
286 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
287 // keep the logic here in sync with the logic of eraseChars()
293 Change change = changes_.lookup(size());
295 return change.type == Change::INSERTED && change.author == 0;
299 void Paragraph::Pimpl::setChange(Change const & change)
301 // beware of the imaginary end-of-par character!
302 changes_.set(change, 0, size() + 1);
305 * Propagate the change recursively - but not in case of DELETED!
307 * Imagine that your co-author makes changes in an existing inset. He
308 * sends your document to you and you come to the conclusion that the
309 * inset should go completely. If you erase it, LyX must not delete all
310 * text within the inset. Otherwise, the change tracked insertions of
311 * your co-author get lost and there is no way to restore them later.
313 * Conclusion: An inset's content should remain untouched if you delete it
316 if (change.type != Change::DELETED) {
317 for (pos_type pos = 0; pos < size(); ++pos) {
318 if (owner_->isInset(pos)) {
319 owner_->getInset(pos)->setChange(change);
326 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
328 BOOST_ASSERT(pos >= 0 && pos <= size());
330 changes_.set(change, pos);
332 // see comment in setChange(Change const &) above
334 if (change.type != Change::DELETED &&
335 pos < size() && owner_->isInset(pos)) {
336 owner_->getInset(pos)->setChange(change);
341 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
343 BOOST_ASSERT(pos >= 0 && pos <= size());
345 return changes_.lookup(pos);
349 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
351 BOOST_ASSERT(start >= 0 && start <= size());
352 BOOST_ASSERT(end > start && end <= size() + 1);
354 for (pos_type pos = start; pos < end; ++pos) {
355 switch (lookupChange(pos).type) {
356 case Change::UNCHANGED:
357 // accept changes in nested inset
358 if (pos < size() && owner_->isInset(pos)) {
359 owner_->getInset(pos)->acceptChanges(bparams);
364 case Change::INSERTED:
365 changes_.set(Change(Change::UNCHANGED), pos);
366 // also accept changes in nested inset
367 if (pos < size() && owner_->isInset(pos)) {
368 owner_->getInset(pos)->acceptChanges(bparams);
372 case Change::DELETED:
373 // Suppress access to non-existent
374 // "end-of-paragraph char"
376 eraseChar(pos, false);
387 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
389 BOOST_ASSERT(start >= 0 && start <= size());
390 BOOST_ASSERT(end > start && end <= size() + 1);
392 for (pos_type pos = start; pos < end; ++pos) {
393 switch (lookupChange(pos).type) {
394 case Change::UNCHANGED:
395 // reject changes in nested inset
396 if (pos < size() && owner_->isInset(pos)) {
397 owner_->getInset(pos)->rejectChanges(bparams);
401 case Change::INSERTED:
402 // Suppress access to non-existent
403 // "end-of-paragraph char"
405 eraseChar(pos, false);
411 case Change::DELETED:
412 changes_.set(Change(Change::UNCHANGED), pos);
414 // Do NOT reject changes within a deleted inset!
415 // There may be insertions of a co-author inside of it!
423 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
425 BOOST_ASSERT(pos >= 0 && pos <= size());
427 return owner_->getChar(pos);
431 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
433 BOOST_ASSERT(pos >= 0 && pos <= size());
436 changes_.insert(change, pos);
438 // This is actually very common when parsing buffers (and
439 // maybe inserting ascii text)
441 // when appending characters, no need to update tables
442 owner_->text_.push_back(c);
446 owner_->text_.insert(owner_->text_.begin() + pos, c);
448 // Update the font table.
449 FontTable search_font(pos, Font());
450 for (FontList::iterator it
451 = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
452 it != fontlist.end(); ++it)
454 it->pos(it->pos() + 1);
458 owner_->insetlist.increasePosAfterPos(pos);
462 void Paragraph::Pimpl::insertInset(pos_type pos, Inset * inset,
463 Change const & change)
466 BOOST_ASSERT(pos >= 0 && pos <= size());
468 insertChar(pos, META_INSET, change);
469 BOOST_ASSERT(owner_->text_[pos] == META_INSET);
471 // Add a new entry in the insetlist.
472 owner_->insetlist.insert(inset, pos);
476 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
478 BOOST_ASSERT(pos >= 0 && pos <= size());
480 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
483 Change change = changes_.lookup(pos);
485 // set the character to DELETED if
486 // a) it was previously unchanged or
487 // b) it was inserted by a co-author
489 if (change.type == Change::UNCHANGED ||
490 (change.type == Change::INSERTED && change.author != 0)) {
491 setChange(pos, Change(Change::DELETED));
495 if (change.type == Change::DELETED)
499 // Don't physically access the imaginary end-of-paragraph character.
500 // eraseChar() can only mark it as DELETED. A physical deletion of
501 // end-of-par must be handled externally.
509 // if it is an inset, delete the inset entry
510 if (owner_->text_[pos] == Paragraph::META_INSET) {
511 owner_->insetlist.erase(pos);
514 owner_->text_.erase(owner_->text_.begin() + pos);
516 // Erase entries in the tables.
517 FontTable search_font(pos, Font());
519 FontList::iterator it =
520 lower_bound(fontlist.begin(),
522 search_font, matchFT());
523 if (it != fontlist.end() && it->pos() == pos &&
525 (it != fontlist.begin()
526 && boost::prior(it)->pos() == pos - 1))) {
527 // If it is a multi-character font
528 // entry, we just make it smaller
529 // (see update below), otherwise we
531 unsigned int const i = it - fontlist.begin();
532 fontlist.erase(fontlist.begin() + i);
533 it = fontlist.begin() + i;
534 if (i > 0 && i < fontlist.size() &&
535 fontlist[i - 1].font() == fontlist[i].font()) {
536 fontlist.erase(fontlist.begin() + i - 1);
537 it = fontlist.begin() + i - 1;
541 // Update all other entries
542 FontList::iterator fend = fontlist.end();
543 for (; it != fend; ++it)
544 it->pos(it->pos() - 1);
546 // Update the insetlist
547 owner_->insetlist.decreasePosAfterPos(pos);
553 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
555 BOOST_ASSERT(start >= 0 && start <= size());
556 BOOST_ASSERT(end >= start && end <= size() + 1);
559 for (pos_type count = end - start; count; --count) {
560 if (!eraseChar(i, trackChanges))
567 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
568 value_type next, Encoding const & encoding)
570 // Writing next here may circumvent a possible font change between
571 // c and next. Since next is only output if it forms a surrogate pair
572 // with c we can ignore this:
573 // A font change inside a surrogate pair does not make sense and is
574 // hopefully impossible to input.
575 // FIXME: change tracking
576 // Is this correct WRT change tracking?
577 docstring const latex1 = encoding.latexChar(next);
578 docstring const latex2 = encoding.latexChar(c);
579 os << latex1 << '{' << latex2 << '}';
580 return latex1.length() + latex2.length() + 2;
584 bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding,
585 odocstream & os, TexRow & texrow,
587 unsigned int & column,
589 Layout const & style)
594 if (i < size() - 1) {
595 char_type next = getChar(i + 1);
596 if (Encodings::isCombiningChar(next)) {
597 // This space has an accent, so we must always output it.
598 column += latexSurrogatePair(os, ' ', next, encoding) - 1;
604 if (lyxrc.plaintext_linelen > 0
605 && column > lyxrc.plaintext_linelen
607 && getChar(i - 1) != ' '
609 // same in FreeSpacing mode
610 && !owner_->isFreeSpacing()
611 // In typewriter mode, we want to avoid
612 // ! . ? : at the end of a line
613 && !(font.family() == Font::TYPEWRITER_FAMILY
614 && (getChar(i - 1) == '.'
615 || getChar(i - 1) == '?'
616 || getChar(i - 1) == ':'
617 || getChar(i - 1) == '!'))) {
620 texrow.start(owner_->id(), i + 1);
622 } else if (style.free_spacing) {
631 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
633 pos_type const len = str.length();
635 // is the paragraph large enough?
636 if (pos + len > size())
639 // does the wanted text start at point?
640 for (string::size_type i = 0; i < str.length(); ++i) {
641 // Caution: direct comparison of characters works only
642 // because str is pure ASCII.
643 if (str[i] != owner_->text_[pos + i])
647 // is there a font change in middle of the word?
648 FontList::const_iterator cit = fontlist.begin();
649 FontList::const_iterator end = fontlist.end();
650 for (; cit != end; ++cit) {
651 if (cit->pos() >= pos)
654 if (cit != end && pos + len - 1 > cit->pos())
661 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
662 BufferParams const & bparams,
665 OutputParams const & runparams,
668 Font const & outerfont,
670 Change & running_change,
671 Layout const & style,
673 unsigned int & column,
676 if (style.pass_thru) {
677 if (c != Paragraph::META_INSET) {
679 // FIXME UNICODE: This can fail if c cannot
680 // be encoded in the current encoding.
683 owner_->getInset(i)->plaintext(buf, os, runparams);
687 // Two major modes: LaTeX or plain
688 // Handle here those cases common to both modes
689 // and then split to handle the two modes separately.
691 case Paragraph::META_INSET: {
692 Inset * inset = owner_->getInset(i);
694 // FIXME: remove this check
698 // FIXME: move this to InsetNewline::latex
699 if (inset->lyxCode() == Inset::NEWLINE_CODE) {
700 // newlines are handled differently here than
701 // the default in simpleTeXSpecialChars().
702 if (!style.newline_allowed) {
706 column += running_font.latexWriteEndChanges(
707 os, bparams, runparams,
712 if (running_font.family() == Font::TYPEWRITER_FAMILY)
715 basefont = owner_->getLayoutFont(bparams, outerfont);
716 running_font = basefont;
718 if (runparams.moving_arg)
724 texrow.start(owner_->id(), i + 1);
729 if (inset->canTrackChanges()) {
730 column += Changes::latexMarkChange(os, bparams, running_change,
731 Change(Change::UNCHANGED));
732 running_change = Change(Change::UNCHANGED);
736 odocstream::pos_type const len = os.tellp();
738 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
739 || inset->lyxCode() == Inset::MATH_CODE
740 || inset->lyxCode() == Inset::URL_CODE)
741 && running_font.isRightToLeft()) {
742 if (running_font.language()->lang() == "farsi")
750 #warning Bug: we can have an empty font change here!
751 // if there has just been a font change, we are going to close it
752 // right now, which means stupid latex code like \textsf{}. AFAIK,
753 // this does not harm dvi output. A minor bug, thus (JMarc)
755 // some insets cannot be inside a font change command
756 if (open_font && inset->noFontChange()) {
757 column += running_font.latexWriteEndChanges(
758 os, bparams, runparams,
761 basefont = owner_->getLayoutFont(bparams, outerfont);
762 running_font = basefont;
765 int tmp = inset->latex(buf, os, runparams);
768 if (running_font.language()->lang() == "farsi")
775 for (int j = 0; j < tmp; ++j) {
778 texrow.start(owner_->id(), i + 1);
781 column += os.tellp() - len;
787 // And now for the special cases within each mode
791 os << "\\textbackslash{}";
795 case '|': case '<': case '>':
796 // In T1 encoding, these characters exist
797 if (lyxrc.fontenc == "T1") {
799 //... but we should avoid ligatures
800 if ((c == '>' || c == '<')
802 && getChar(i + 1) == c) {
803 //os << "\\textcompwordmark{}";
805 // Jean-Marc, have a look at
806 // this. I think this works
814 // Typewriter font also has them
815 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
819 // Otherwise, we use what LaTeX
823 os << "\\textless{}";
827 os << "\\textgreater{}";
837 case '-': // "--" in Typewriter mode -> "-{}-"
838 if (i <= size() - 2 &&
839 getChar(i + 1) == '-' &&
840 running_font.family() == Font::TYPEWRITER_FAMILY) {
849 os << "\\char`\\\"{}";
854 case '%': case '#': case '{':
862 os << "\\textasciitilde{}";
867 os << "\\textasciicircum{}";
872 // avoid being mistaken for optional arguments
880 // Blanks are printed before font switching.
881 // Sure? I am not! (try nice-latex)
882 // I am sure it's correct. LyX might be smarter
883 // in the future, but for now, nothing wrong is
889 // I assume this is hack treating typewriter as verbatim
890 // FIXME UNICODE: This can fail if c cannot be encoded
891 // in the current encoding.
892 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
901 // FIXME: if we have "LaTeX" with a font
902 // change in the middle (before the 'T', then
903 // the "TeX" part is still special cased.
904 // Really we should only operate this on
905 // "words" for some definition of word
909 for (; pnr < phrases_nr; ++pnr) {
910 if (isTextAt(special_phrases[pnr].phrase, i)) {
911 os << special_phrases[pnr].macro;
912 i += special_phrases[pnr].phrase.length() - 1;
913 column += special_phrases[pnr].macro.length() - 1;
918 if (pnr == phrases_nr && c != '\0') {
919 Encoding const & encoding = *(runparams.encoding);
920 if (i < size() - 1) {
921 char_type next = getChar(i + 1);
922 if (Encodings::isCombiningChar(next)) {
923 column += latexSurrogatePair(os, c, next, encoding) - 1;
928 docstring const latex = encoding.latexChar(c);
929 if (latex.length() > 1 &&
930 latex[latex.length() - 1] != '}') {
931 // Prevent eating of a following
932 // space or command corruption by
933 // following characters
934 column += latex.length() + 1;
937 column += latex.length() - 1;
947 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
948 Layout const & layout) const
950 BufferParams const & bparams = features.bufferParams();
953 if (!params.spacing().isDefault())
954 features.require("setspace");
957 features.useLayout(layout.name());
960 Language const * doc_language = bparams.language;
962 FontList::const_iterator fcit = fontlist.begin();
963 FontList::const_iterator fend = fontlist.end();
964 for (; fcit != fend; ++fcit) {
965 if (fcit->font().noun() == Font::ON) {
966 LYXERR(Debug::LATEX) << "font.noun: "
967 << fcit->font().noun()
969 features.require("noun");
970 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
971 << to_utf8(fcit->font().stateText(0))
974 switch (fcit->font().color()) {
978 // probably we should put here all interface colors used for
979 // font displaying! For now I just add this ones I know of (Jug)
984 features.require("color");
985 LYXERR(Debug::LATEX) << "Color enabled. Font: "
986 << to_utf8(fcit->font().stateText(0))
990 Language const * language = fcit->font().language();
991 if (language->babel() != doc_language->babel() &&
992 language != ignore_language &&
993 language != latex_language)
995 features.useLanguage(language);
996 LYXERR(Debug::LATEX) << "Found language "
997 << language->lang() << endl;
1001 if (!params.leftIndent().zero())
1002 features.require("ParagraphLeftIndent");
1005 InsetList::const_iterator icit = owner_->insetlist.begin();
1006 InsetList::const_iterator iend = owner_->insetlist.end();
1007 for (; icit != iend; ++icit) {
1009 icit->inset->validate(features);
1010 if (layout.needprotect &&
1011 icit->inset->lyxCode() == Inset::FOOT_CODE)
1012 features.require("NeedLyXFootnoteCode");
1016 // then the contents
1017 for (pos_type i = 0; i < size() ; ++i) {
1018 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1019 if (!special_phrases[pnr].builtin
1020 && isTextAt(special_phrases[pnr].phrase, i)) {
1021 features.require(special_phrases[pnr].phrase);
1025 Encodings::validate(getChar(i), features);
1033 /////////////////////////////////////////////////////////////////////
1037 /////////////////////////////////////////////////////////////////////
1041 Paragraph::Paragraph()
1042 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1049 Paragraph::Paragraph(Paragraph const & par)
1050 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1051 layout_(par.layout_),
1052 text_(par.text_), begin_of_body_(par.begin_of_body_),
1053 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1055 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1056 InsetList::iterator it = insetlist.begin();
1057 InsetList::iterator end = insetlist.end();
1058 for (; it != end; ++it)
1059 it->inset = it->inset->clone().release();
1063 Paragraph & Paragraph::operator=(Paragraph const & par)
1065 // needed as we will destroy the pimpl_ before copying it
1067 itemdepth = par.itemdepth;
1069 insetlist = par.insetlist;
1070 InsetList::iterator it = insetlist.begin();
1071 InsetList::iterator end = insetlist.end();
1072 for (; it != end; ++it)
1073 it->inset = it->inset->clone().release();
1075 layout_ = par.layout();
1077 begin_of_body_ = par.begin_of_body_;
1080 pimpl_ = new Pimpl(*par.pimpl_, this);
1086 Paragraph::~Paragraph()
1090 //lyxerr << "Paragraph::paragraph_id = "
1091 // << Paragraph::paragraph_id << endl;
1095 void Paragraph::write(Buffer const & buf, ostream & os,
1096 BufferParams const & bparams,
1097 depth_type & dth) const
1099 // The beginning or end of a deeper (i.e. nested) area?
1100 if (dth != params().depth()) {
1101 if (params().depth() > dth) {
1102 while (params().depth() > dth) {
1103 os << "\n\\begin_deeper";
1107 while (params().depth() < dth) {
1108 os << "\n\\end_deeper";
1114 // First write the layout
1115 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1119 Font font1(Font::ALL_INHERIT, bparams.language);
1121 Change running_change = Change(Change::UNCHANGED);
1124 for (pos_type i = 0; i <= size(); ++i) {
1126 Change change = pimpl_->lookupChange(i);
1127 Changes::lyxMarkChange(os, column, running_change, change);
1128 running_change = change;
1133 // Write font changes
1134 Font font2 = getFontSettings(bparams, i);
1135 if (font2 != font1) {
1136 font2.lyxWriteChanges(font1, os);
1141 value_type const c = getChar(i);
1145 Inset const * inset = getInset(i);
1147 if (inset->directWrite()) {
1148 // international char, let it write
1149 // code directly so it's shorter in
1151 inset->write(buf, os);
1155 os << "\\begin_inset ";
1156 inset->write(buf, os);
1157 os << "\n\\end_inset\n\n";
1163 os << "\n\\backslash\n";
1167 if (i + 1 < size() && getChar(i + 1) == ' ') {
1174 if ((column > 70 && c == ' ')
1179 // this check is to amend a bug. LyX sometimes
1180 // inserts '\0' this could cause problems.
1182 std::vector<char> tmp = ucs4_to_utf8(c);
1183 tmp.push_back('\0');
1186 lyxerr << "ERROR (Paragraph::writeFile):"
1187 " NULL char in structure." << endl;
1193 os << "\n\\end_layout\n";
1197 void Paragraph::validate(LaTeXFeatures & features) const
1199 pimpl_->validate(features, *layout());
1203 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1205 return pimpl_->eraseChar(pos, trackChanges);
1209 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1211 return pimpl_->eraseChars(start, end, trackChanges);
1215 void Paragraph::insert(pos_type start, docstring const & str,
1216 Font const & font, Change const & change)
1218 for (size_t i = 0, n = str.size(); i != n ; ++i)
1219 insertChar(start + i, str[i], font, change);
1223 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1226 pimpl_->insertChar(pos, c, Change(trackChanges ?
1227 Change::INSERTED : Change::UNCHANGED));
1231 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1232 Font const & font, bool trackChanges)
1234 pimpl_->insertChar(pos, c, Change(trackChanges ?
1235 Change::INSERTED : Change::UNCHANGED));
1240 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1241 Font const & font, Change const & change)
1243 pimpl_->insertChar(pos, c, change);
1248 void Paragraph::insertInset(pos_type pos, Inset * inset,
1249 Change const & change)
1251 pimpl_->insertInset(pos, inset, change);
1255 void Paragraph::insertInset(pos_type pos, Inset * inset,
1256 Font const & font, Change const & change)
1258 pimpl_->insertInset(pos, inset, change);
1259 // Set the font/language of the inset...
1261 // ... as well as the font/language of the text inside the inset
1262 // FIXME: This is far from perfect. It basically overrides work being done
1263 // in the InsetText constructor. Also, it doesn't work for Tables
1264 // (precisely because each cell's font/language is set in the Table's
1265 // constructor, so by now it's too late). The long-term solution should
1266 // be moving current_font into Cursor, and getting rid of all this...
1267 // (see http://thread.gmane.org/gmane.editors.lyx.devel/88869/focus=88944)
1268 if (inset->asTextInset()) {
1269 inset->asTextInset()->text_.current_font = font;
1270 inset->asTextInset()->text_.real_current_font = font;
1275 bool Paragraph::insetAllowed(Inset_code code)
1277 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1281 // Gets uninstantiated font setting at position.
1282 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1286 lyxerr << " pos: " << pos << " size: " << size() << endl;
1287 BOOST_ASSERT(pos <= size());
1290 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1291 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1292 for (; cit != end; ++cit)
1293 if (cit->pos() >= pos)
1299 if (pos == size() && !empty())
1300 return getFontSettings(bparams, pos - 1);
1302 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1306 FontSpan Paragraph::fontSpan(pos_type pos) const
1308 BOOST_ASSERT(pos <= size());
1311 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1312 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1313 for (; cit != end; ++cit) {
1314 if (cit->pos() >= pos) {
1315 if (pos >= beginOfBody())
1316 return FontSpan(std::max(start, beginOfBody()),
1319 return FontSpan(start,
1320 std::min(beginOfBody() - 1,
1323 start = cit->pos() + 1;
1326 // This should not happen, but if so, we take no chances.
1327 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1329 return FontSpan(pos, pos);
1333 // Gets uninstantiated font setting at position 0
1334 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1336 if (!empty() && !pimpl_->fontlist.empty())
1337 return pimpl_->fontlist[0].font();
1339 return Font(Font::ALL_INHERIT, bparams.language);
1343 // Gets the fully instantiated font at a given position in a paragraph
1344 // This is basically the same function as Text::GetFont() in text2.cpp.
1345 // The difference is that this one is used for generating the LaTeX file,
1346 // and thus cosmetic "improvements" are disallowed: This has to deliver
1347 // the true picture of the buffer. (Asger)
1348 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1349 Font const & outerfont) const
1351 BOOST_ASSERT(pos >= 0);
1353 Layout_ptr const & lout = layout();
1355 pos_type const body_pos = beginOfBody();
1359 layoutfont = lout->labelfont;
1361 layoutfont = lout->font;
1363 Font font = getFontSettings(bparams, pos);
1364 font.realize(layoutfont);
1365 font.realize(outerfont);
1366 font.realize(bparams.getFont());
1372 Font const Paragraph::getLabelFont
1373 (BufferParams const & bparams, Font const & outerfont) const
1375 Font tmpfont = layout()->labelfont;
1376 tmpfont.setLanguage(getParLanguage(bparams));
1377 tmpfont.realize(outerfont);
1378 tmpfont.realize(bparams.getFont());
1383 Font const Paragraph::getLayoutFont
1384 (BufferParams const & bparams, Font const & outerfont) const
1386 Font tmpfont = layout()->font;
1387 tmpfont.setLanguage(getParLanguage(bparams));
1388 tmpfont.realize(outerfont);
1389 tmpfont.realize(bparams.getFont());
1394 /// Returns the height of the highest font in range
1395 Font_size Paragraph::highestFontInRange
1396 (pos_type startpos, pos_type endpos, Font_size def_size) const
1398 if (pimpl_->fontlist.empty())
1401 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1402 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1403 for (; end_it != end; ++end_it) {
1404 if (end_it->pos() >= endpos)
1411 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1412 for (; cit != end; ++cit) {
1413 if (cit->pos() >= startpos)
1417 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1418 for (; cit != end_it; ++cit) {
1419 Font::FONT_SIZE size = cit->font().size();
1420 if (size == Font::INHERIT_SIZE)
1422 if (size > maxsize && size <= Font::SIZE_HUGER)
1429 Paragraph::value_type
1430 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1432 value_type c = getChar(pos);
1433 if (!lyxrc.rtl_support)
1463 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1470 void Paragraph::setFont(pos_type pos, Font const & font)
1472 BOOST_ASSERT(pos <= size());
1474 // First, reduce font against layout/label font
1475 // Update: The setCharFont() routine in text2.cpp already
1476 // reduces font, so we don't need to do that here. (Asger)
1477 // No need to simplify this because it will disappear
1478 // in a new kernel. (Asger)
1479 // Next search font table
1481 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1482 Pimpl::FontList::iterator it = beg;
1483 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1484 for (; it != endit; ++it) {
1485 if (it->pos() >= pos)
1488 size_t const i = distance(beg, it);
1489 bool notfound = (it == endit);
1491 if (!notfound && pimpl_->fontlist[i].font() == font)
1494 bool begin = pos == 0 || notfound ||
1495 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1496 // Is position pos is a beginning of a font block?
1497 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1498 // Is position pos is the end of a font block?
1499 if (begin && end) { // A single char block
1500 if (i + 1 < pimpl_->fontlist.size() &&
1501 pimpl_->fontlist[i + 1].font() == font) {
1502 // Merge the singleton block with the next block
1503 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1504 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1505 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1506 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1507 // Merge the singleton block with the previous block
1508 pimpl_->fontlist[i - 1].pos(pos);
1509 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1511 pimpl_->fontlist[i].font(font);
1513 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1514 pimpl_->fontlist[i - 1].pos(pos);
1516 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1517 Pimpl::FontTable(pos, font));
1519 pimpl_->fontlist[i].pos(pos - 1);
1520 if (!(i + 1 < pimpl_->fontlist.size() &&
1521 pimpl_->fontlist[i + 1].font() == font))
1522 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1523 Pimpl::FontTable(pos, font));
1524 } else { // The general case. The block is splitted into 3 blocks
1525 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1526 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1527 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1528 Pimpl::FontTable(pos, font));
1533 void Paragraph::makeSameLayout(Paragraph const & par)
1535 layout(par.layout());
1537 params() = par.params();
1541 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1543 if (isFreeSpacing())
1549 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1550 if (eraseChar(pos, trackChanges))
1556 return count > 0 || pos > 0;
1560 bool Paragraph::hasSameLayout(Paragraph const & par) const
1562 return par.layout() == layout() && params().sameLayout(par.params());
1566 depth_type Paragraph::getDepth() const
1568 return params().depth();
1572 depth_type Paragraph::getMaxDepthAfter() const
1574 if (layout()->isEnvironment())
1575 return params().depth() + 1;
1577 return params().depth();
1581 char Paragraph::getAlign() const
1583 if (params().align() == LYX_ALIGN_LAYOUT)
1584 return layout()->align;
1586 return params().align();
1590 docstring const & Paragraph::getLabelstring() const
1592 return params().labelString();
1596 // the next two functions are for the manual labels
1597 docstring const Paragraph::getLabelWidthString() const
1599 if (!params().labelWidthString().empty())
1600 return params().labelWidthString();
1602 return _("Senseless with this layout!");
1606 void Paragraph::setLabelWidthString(docstring const & s)
1608 params().labelWidthString(s);
1612 docstring const Paragraph::translateIfPossible(docstring const & s,
1613 BufferParams const & bparams) const
1615 if (!support::isAscii(s) || s.empty()) {
1616 // This must be a user defined layout. We cannot translate
1617 // this, since gettext accepts only ascii keys.
1620 // Probably standard layout, try to translate
1621 Messages & m = getMessages(getParLanguage(bparams)->code());
1622 return m.get(to_ascii(s));
1626 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1627 BufferParams const & bparams, bool process_appendix) const
1629 TextClass const & tclass = bparams.getTextClass();
1632 if (process_appendix && params().appendix())
1633 fmt = translateIfPossible(layout->labelstring_appendix(),
1636 fmt = translateIfPossible(layout->labelstring(), bparams);
1638 // handle 'inherited level parts' in 'fmt',
1639 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1640 size_t const i = fmt.find('@', 0);
1641 if (i != docstring::npos) {
1642 size_t const j = fmt.find('@', i + 1);
1643 if (j != docstring::npos) {
1644 docstring parent(fmt, i + 1, j - i - 1);
1645 docstring label = expandLabel(tclass[parent], bparams);
1646 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1650 return tclass.counters().counterLabel(fmt);
1654 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1657 LyXAlignment const oldAlign = params().align();
1658 // FIXME The first check is due to the fact that LYX_ALIGN_LAYOUT
1659 // is not required to be possible. A fix is on the way.
1660 if ((oldAlign != LYX_ALIGN_LAYOUT) &&
1661 !(oldAlign & layout()->alignpossible)) {
1662 frontend::Alert::warning(_("Alignment not permitted"),
1663 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1664 params().align(LYX_ALIGN_LAYOUT);
1669 pos_type Paragraph::beginOfBody() const
1671 return begin_of_body_;
1675 void Paragraph::setBeginOfBody()
1677 if (layout()->labeltype != LABEL_MANUAL) {
1682 // Unroll the first two cycles of the loop
1683 // and remember the previous character to
1684 // remove unnecessary getChar() calls
1686 pos_type end = size();
1687 if (i < end && !isNewline(i)) {
1689 char_type previous_char = 0;
1692 previous_char = text_[i];
1693 if (!isNewline(i)) {
1695 while (i < end && previous_char != ' ') {
1700 previous_char = temp;
1710 // returns -1 if inset not found
1711 int Paragraph::getPositionOfInset(Inset const * inset) const
1714 InsetList::const_iterator it = insetlist.begin();
1715 InsetList::const_iterator end = insetlist.end();
1716 for (; it != end; ++it)
1717 if (it->inset == inset)
1723 InsetBibitem * Paragraph::bibitem() const
1725 if (!insetlist.empty()) {
1726 Inset * inset = insetlist.begin()->inset;
1727 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1728 return static_cast<InsetBibitem *>(inset);
1734 bool Paragraph::forceDefaultParagraphs() const
1736 return inInset() && inInset()->forceDefaultParagraphs(0);
1742 // paragraphs inside floats need different alignment tags to avoid
1745 bool noTrivlistCentering(Inset::Code code)
1747 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1751 string correction(string const & orig)
1753 if (orig == "flushleft")
1754 return "raggedright";
1755 if (orig == "flushright")
1756 return "raggedleft";
1757 if (orig == "center")
1763 string const corrected_env(string const & suffix, string const & env,
1766 string output = suffix + "{";
1767 if (noTrivlistCentering(code))
1768 output += correction(env);
1772 if (suffix == "\\begin")
1778 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1780 if (!contains(str, "\n"))
1781 column += str.size();
1785 column = rsplit(str, tmp, '\n').size();
1792 // This could go to ParagraphParameters if we want to
1793 int Paragraph::startTeXParParams(BufferParams const & bparams,
1794 odocstream & os, TexRow & texrow,
1795 bool moving_arg) const
1799 if (params().noindent()) {
1800 os << "\\noindent ";
1804 LyXAlignment const curAlign = params().align();
1806 if (curAlign == layout()->align)
1810 case LYX_ALIGN_NONE:
1811 case LYX_ALIGN_BLOCK:
1812 case LYX_ALIGN_LAYOUT:
1813 case LYX_ALIGN_SPECIAL:
1815 case LYX_ALIGN_LEFT:
1816 case LYX_ALIGN_RIGHT:
1817 case LYX_ALIGN_CENTER:
1826 case LYX_ALIGN_NONE:
1827 case LYX_ALIGN_BLOCK:
1828 case LYX_ALIGN_LAYOUT:
1829 case LYX_ALIGN_SPECIAL:
1831 case LYX_ALIGN_LEFT: {
1833 if (getParLanguage(bparams)->babel() != "hebrew")
1834 output = corrected_env("\\begin", "flushleft", ownerCode());
1836 output = corrected_env("\\begin", "flushright", ownerCode());
1837 os << from_ascii(output);
1838 adjust_row_column(output, texrow, column);
1840 } case LYX_ALIGN_RIGHT: {
1842 if (getParLanguage(bparams)->babel() != "hebrew")
1843 output = corrected_env("\\begin", "flushright", ownerCode());
1845 output = corrected_env("\\begin", "flushleft", ownerCode());
1846 os << from_ascii(output);
1847 adjust_row_column(output, texrow, column);
1849 } case LYX_ALIGN_CENTER: {
1851 output = corrected_env("\\begin", "center", ownerCode());
1852 os << from_ascii(output);
1853 adjust_row_column(output, texrow, column);
1862 // This could go to ParagraphParameters if we want to
1863 int Paragraph::endTeXParParams(BufferParams const & bparams,
1864 odocstream & os, TexRow & texrow,
1865 bool moving_arg) const
1869 switch (params().align()) {
1870 case LYX_ALIGN_NONE:
1871 case LYX_ALIGN_BLOCK:
1872 case LYX_ALIGN_LAYOUT:
1873 case LYX_ALIGN_SPECIAL:
1875 case LYX_ALIGN_LEFT:
1876 case LYX_ALIGN_RIGHT:
1877 case LYX_ALIGN_CENTER:
1885 switch (params().align()) {
1886 case LYX_ALIGN_NONE:
1887 case LYX_ALIGN_BLOCK:
1888 case LYX_ALIGN_LAYOUT:
1889 case LYX_ALIGN_SPECIAL:
1891 case LYX_ALIGN_LEFT: {
1893 if (getParLanguage(bparams)->babel() != "hebrew")
1894 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1896 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1897 os << from_ascii(output);
1898 adjust_row_column(output, texrow, column);
1900 } case LYX_ALIGN_RIGHT: {
1902 if (getParLanguage(bparams)->babel() != "hebrew")
1903 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1905 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1906 os << from_ascii(output);
1907 adjust_row_column(output, texrow, column);
1909 } case LYX_ALIGN_CENTER: {
1911 output = corrected_env("\n\\par\\end", "center", ownerCode());
1912 os << from_ascii(output);
1913 adjust_row_column(output, texrow, column);
1922 // This one spits out the text of the paragraph
1923 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1924 BufferParams const & bparams,
1925 Font const & outerfont,
1926 odocstream & os, TexRow & texrow,
1927 OutputParams const & runparams) const
1929 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1931 bool return_value = false;
1935 // well we have to check if we are in an inset with unlimited
1936 // length (all in one row) if that is true then we don't allow
1937 // any special options in the paragraph and also we don't allow
1938 // any environment other than the default layout of the text class
1940 bool asdefault = forceDefaultParagraphs();
1943 style = bparams.getTextClass().defaultLayout();
1948 // Current base font for all inherited font changes, without any
1949 // change caused by an individual character, except for the language:
1950 // It is set to the language of the first character.
1951 // As long as we are in the label, this font is the base font of the
1952 // label. Before the first body character it is set to the base font
1956 // Maybe we have to create a optional argument.
1957 pos_type body_pos = beginOfBody();
1958 unsigned int column = 0;
1961 // the optional argument is kept in curly brackets in
1962 // case it contains a ']'
1965 basefont = getLabelFont(bparams, outerfont);
1967 basefont = getLayoutFont(bparams, outerfont);
1970 // Which font is currently active?
1971 Font running_font(basefont);
1972 // Do we have an open font change?
1973 bool open_font = false;
1975 Change runningChange = Change(Change::UNCHANGED);
1977 texrow.start(id(), 0);
1979 // if the paragraph is empty, the loop will not be entered at all
1981 if (style->isCommand()) {
1986 column += startTeXParParams(bparams, os, texrow,
1987 runparams.moving_arg);
1990 for (pos_type i = 0; i < size(); ++i) {
1991 // First char in paragraph or after label?
1992 if (i == body_pos) {
1995 column += running_font.latexWriteEndChanges(
1996 os, bparams, runparams,
1997 basefont, basefont);
2000 basefont = getLayoutFont(bparams, outerfont);
2001 running_font = basefont;
2003 column += Changes::latexMarkChange(os, bparams,
2004 runningChange, Change(Change::UNCHANGED));
2005 runningChange = Change(Change::UNCHANGED);
2010 if (style->isCommand()) {
2016 column += startTeXParParams(bparams, os,
2018 runparams.moving_arg);
2021 Change const & change = pimpl_->lookupChange(i);
2023 if (bparams.outputChanges && runningChange != change) {
2025 column += running_font.latexWriteEndChanges(
2026 os, bparams, runparams, basefont, basefont);
2029 basefont = getLayoutFont(bparams, outerfont);
2030 running_font = basefont;
2032 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2033 runningChange = change;
2036 // do not output text which is marked deleted
2037 // if change tracking output is disabled
2038 if (!bparams.outputChanges && change.type == Change::DELETED) {
2044 value_type const c = getChar(i);
2046 // Fully instantiated font
2047 Font const font = getFont(bparams, i, outerfont);
2049 Font const last_font = running_font;
2051 // Do we need to close the previous font?
2053 (font != running_font ||
2054 font.language() != running_font.language()))
2056 column += running_font.latexWriteEndChanges(
2057 os, bparams, runparams, basefont,
2058 (i == body_pos-1) ? basefont : font);
2059 running_font = basefont;
2063 // Switch file encoding if necessary
2064 if (runparams.encoding->package() == Encoding::inputenc &&
2065 font.language()->encoding()->package() == Encoding::inputenc) {
2066 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2067 runparams.moving_arg, *(runparams.encoding),
2068 *(font.language()->encoding()));
2069 if (enc_switch.first) {
2070 column += enc_switch.second;
2071 runparams.encoding = font.language()->encoding();
2075 // Do we need to change font?
2076 if ((font != running_font ||
2077 font.language() != running_font.language()) &&
2080 odocstringstream ods;
2081 column += font.latexWriteStartChanges(ods, bparams,
2082 runparams, basefont,
2084 running_font = font;
2086 docstring fontchange = ods.str();
2087 // check if the fontchange ends with a trailing blank
2088 // (like "\small " (see bug 3382)
2089 if (suffixIs(fontchange, ' ') && c == ' ')
2090 os << fontchange.substr(0, fontchange.size() - 1)
2091 << from_ascii("{}");
2097 // Do not print the separation of the optional argument
2098 // if style->pass_thru is false. This works because
2099 // simpleTeXSpecialChars ignores spaces if
2100 // style->pass_thru is false.
2101 if (i != body_pos - 1) {
2102 if (pimpl_->simpleTeXBlanks(
2103 *(runparams.encoding), os, texrow,
2104 i, column, font, *style))
2105 // A surrogate pair was output. We
2106 // must not call simpleTeXSpecialChars
2107 // in this iteration, since
2108 // simpleTeXBlanks incremented i, and
2109 // simpleTeXSpecialChars would output
2110 // the combining character again.
2115 OutputParams rp = runparams;
2116 rp.free_spacing = style->free_spacing;
2117 rp.local_font = &font;
2118 rp.intitle = style->intitle;
2119 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2120 texrow, rp, running_font,
2121 basefont, outerfont, open_font,
2122 runningChange, *style, i, column, c);
2125 // If we have an open font definition, we have to close it
2127 #ifdef FIXED_LANGUAGE_END_DETECTION
2130 .latexWriteEndChanges(os, bparams, runparams,
2132 next_->getFont(bparams, 0, outerfont));
2134 running_font.latexWriteEndChanges(os, bparams,
2135 runparams, basefont, basefont);
2138 #ifdef WITH_WARNINGS
2139 //#warning For now we ALWAYS have to close the foreign font settings if they are
2140 //#warning there as we start another \selectlanguage with the next paragraph if
2141 //#warning we are in need of this. This should be fixed sometime (Jug)
2143 running_font.latexWriteEndChanges(os, bparams, runparams,
2144 basefont, basefont);
2148 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2150 // Needed if there is an optional argument but no contents.
2151 if (body_pos > 0 && body_pos == size()) {
2153 return_value = false;
2157 column += endTeXParParams(bparams, os, texrow,
2158 runparams.moving_arg);
2161 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2162 return return_value;
2179 string tag_name(PAR_TAG const & pt) {
2181 case PAR_NONE: return "!-- --";
2182 case TT: return "tt";
2183 case SF: return "sf";
2184 case BF: return "bf";
2185 case IT: return "it";
2186 case SL: return "sl";
2187 case EM: return "em";
2194 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2196 p1 = static_cast<PAR_TAG>(p1 | p2);
2201 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2203 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2209 bool Paragraph::emptyTag() const
2211 for (pos_type i = 0; i < size(); ++i) {
2213 Inset const * inset = getInset(i);
2214 Inset::Code lyx_code = inset->lyxCode();
2215 if (lyx_code != Inset::TOC_CODE &&
2216 lyx_code != Inset::INCLUDE_CODE &&
2217 lyx_code != Inset::GRAPHICS_CODE &&
2218 lyx_code != Inset::ERT_CODE &&
2219 lyx_code != Inset::LISTINGS_CODE &&
2220 lyx_code != Inset::FLOAT_CODE &&
2221 lyx_code != Inset::TABULAR_CODE) {
2225 value_type c = getChar(i);
2226 if (c != ' ' && c != '\t')
2234 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2236 for (pos_type i = 0; i < size(); ++i) {
2238 Inset const * inset = getInset(i);
2239 Inset::Code lyx_code = inset->lyxCode();
2240 if (lyx_code == Inset::LABEL_CODE) {
2241 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2242 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2251 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2254 for (i = 0; i < size(); ++i) {
2256 Inset const * inset = getInset(i);
2257 inset->docbook(buf, os, runparams);
2259 value_type c = getChar(i);
2262 os << sgml::escapeChar(c);
2269 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2273 for (pos_type i = initial; i < size(); ++i) {
2274 Font font = getFont(buf.params(), i, outerfont);
2277 if (i != initial && font != font_old)
2286 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2288 OutputParams const & runparams,
2289 Font const & outerfont,
2290 pos_type initial) const
2292 bool emph_flag = false;
2294 Layout_ptr const & style = layout();
2296 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2298 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2301 // parsing main loop
2302 for (pos_type i = initial; i < size(); ++i) {
2303 Font font = getFont(buf.params(), i, outerfont);
2305 // handle <emphasis> tag
2306 if (font_old.emph() != font.emph()) {
2307 if (font.emph() == Font::ON) {
2310 } else if (i != initial) {
2311 os << "</emphasis>";
2317 Inset const * inset = getInset(i);
2318 inset->docbook(buf, os, runparams);
2320 value_type c = getChar(i);
2322 if (style->pass_thru)
2325 os << sgml::escapeChar(c);
2331 os << "</emphasis>";
2334 if (style->free_spacing)
2336 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2341 bool Paragraph::isNewline(pos_type pos) const
2344 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2348 bool Paragraph::isLineSeparator(pos_type pos) const
2350 value_type const c = getChar(pos);
2351 return isLineSeparatorChar(c)
2352 || (c == Paragraph::META_INSET && getInset(pos) &&
2353 getInset(pos)->isLineSeparator());
2357 /// Used by the spellchecker
2358 bool Paragraph::isLetter(pos_type pos) const
2361 return getInset(pos)->isLetter();
2363 value_type const c = getChar(pos);
2364 return isLetterChar(c) || isDigit(c);
2370 Paragraph::getParLanguage(BufferParams const & bparams) const
2373 return getFirstFontSettings(bparams).language();
2374 #ifdef WITH_WARNINGS
2375 #warning FIXME we should check the prev par as well (Lgb)
2377 return bparams.language;
2381 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2383 return lyxrc.rtl_support
2384 && getParLanguage(bparams)->rightToLeft()
2385 && ownerCode() != Inset::ERT_CODE
2386 && ownerCode() != Inset::LISTINGS_CODE;
2390 void Paragraph::changeLanguage(BufferParams const & bparams,
2391 Language const * from, Language const * to)
2393 // change language including dummy font change at the end
2394 for (pos_type i = 0; i <= size(); ++i) {
2395 Font font = getFontSettings(bparams, i);
2396 if (font.language() == from) {
2397 font.setLanguage(to);
2404 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2406 Language const * doc_language = bparams.language;
2407 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2408 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2410 for (; cit != end; ++cit)
2411 if (cit->font().language() != ignore_language &&
2412 cit->font().language() != latex_language &&
2413 cit->font().language() != doc_language)
2419 // Convert the paragraph to a string.
2420 // Used for building the table of contents
2421 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2423 return asString(buffer, 0, size(), label);
2427 docstring const Paragraph::asString(Buffer const & buffer,
2428 pos_type beg, pos_type end, bool label) const
2431 odocstringstream os;
2433 if (beg == 0 && label && !params().labelString().empty())
2434 os << params().labelString() << ' ';
2436 for (pos_type i = beg; i < end; ++i) {
2437 value_type const c = getChar(i);
2440 else if (c == META_INSET)
2441 getInset(i)->textString(buffer, os);
2448 void Paragraph::setInsetOwner(Inset * inset)
2450 pimpl_->inset_owner = inset;
2454 Change const & Paragraph::lookupChange(pos_type pos) const
2456 BOOST_ASSERT(pos <= size());
2457 return pimpl_->lookupChange(pos);
2461 bool Paragraph::isChanged(pos_type start, pos_type end) const
2463 return pimpl_->isChanged(start, end);
2467 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2469 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2473 void Paragraph::setChange(Change const & change)
2475 pimpl_->setChange(change);
2479 void Paragraph::setChange(pos_type pos, Change const & change)
2481 pimpl_->setChange(pos, change);
2485 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2487 return pimpl_->acceptChanges(bparams, start, end);
2491 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2493 return pimpl_->rejectChanges(bparams, start, end);
2497 int Paragraph::id() const
2503 Layout_ptr const & Paragraph::layout() const
2509 void Paragraph::layout(Layout_ptr const & new_layout)
2511 layout_ = new_layout;
2515 Inset * Paragraph::inInset() const
2517 return pimpl_->inset_owner;
2521 Inset::Code Paragraph::ownerCode() const
2523 return pimpl_->inset_owner
2524 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2528 ParagraphParameters & Paragraph::params()
2530 return pimpl_->params;
2534 ParagraphParameters const & Paragraph::params() const
2536 return pimpl_->params;
2540 bool Paragraph::isFreeSpacing() const
2542 if (layout()->free_spacing)
2545 // for now we just need this, later should we need this in some
2546 // other way we can always add a function to Inset too.
2547 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2551 bool Paragraph::allowEmpty() const
2553 if (layout()->keepempty)
2555 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2559 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2561 if (!Encodings::is_arabic(c))
2564 value_type prev_char = ' ';
2565 value_type next_char = ' ';
2567 for (pos_type i = pos - 1; i >= 0; --i) {
2568 value_type const par_char = getChar(i);
2569 if (!Encodings::isComposeChar_arabic(par_char)) {
2570 prev_char = par_char;
2575 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2576 value_type const par_char = getChar(i);
2577 if (!Encodings::isComposeChar_arabic(par_char)) {
2578 next_char = par_char;
2583 if (Encodings::is_arabic(next_char)) {
2584 if (Encodings::is_arabic(prev_char) &&
2585 !Encodings::is_arabic_special(prev_char))
2586 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2588 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2590 if (Encodings::is_arabic(prev_char) &&
2591 !Encodings::is_arabic_special(prev_char))
2592 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2594 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2599 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2604 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2606 // expand at the end of a row only if there is another hfill on the same row
2607 if (pos == row.endpos() - 1) {
2608 for (pos_type i = row.pos(); i < pos; i++) {
2615 // expand at the beginning of a row only if it is the first row of a paragraph
2616 if (pos == row.pos()) {
2620 // do not expand in some labels
2621 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2624 // if there is anything between the first char of the row and
2625 // the specified position that is neither a newline nor an hfill,
2626 // the hfill will be expanded, otherwise it won't
2627 for (pos_type i = row.pos(); i < pos; i++) {
2628 if (!isNewline(i) && !isHfill(i))
2635 int Paragraph::checkBiblio(bool track_changes)
2638 //This is getting more and more a mess. ...We really should clean
2639 //up this bibitem issue for 1.6. See also bug 2743.
2641 // Add bibitem insets if necessary
2642 if (layout()->labeltype != LABEL_BIBLIO)
2645 bool hasbibitem = !insetlist.empty()
2646 // Insist on it being in pos 0
2647 && getChar(0) == Paragraph::META_INSET
2648 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2653 // remove a bibitem in pos != 0
2654 // restore it later in pos 0 if necessary
2655 // (e.g. if a user inserts contents _before_ the item)
2656 // we're assuming there's only one of these, which there
2658 int erasedInsetPosition = -1;
2659 InsetList::iterator it = insetlist.begin();
2660 InsetList::iterator end = insetlist.end();
2661 for (; it != end; ++it)
2662 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2664 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2665 oldkey = olditem->getParam("key");
2666 oldlabel = olditem->getParam("label");
2667 erasedInsetPosition = it->pos;
2668 eraseChar(erasedInsetPosition, track_changes);
2672 //There was an InsetBibitem at the beginning, and we didn't
2673 //have to erase one.
2674 if (hasbibitem && erasedInsetPosition < 0)
2677 //There was an InsetBibitem at the beginning and we did have to
2678 //erase one. So we give its properties to the beginning inset.
2680 InsetBibitem * inset =
2681 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2682 if (!oldkey.empty())
2683 inset->setParam("key", oldkey);
2684 inset->setParam("label", oldlabel);
2685 return -erasedInsetPosition;
2688 //There was no inset at the beginning, so we need to create one with
2689 //the key and label of the one we erased.
2690 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2691 // restore values of previously deleted item in this par.
2692 if (!oldkey.empty())
2693 inset->setParam("key", oldkey);
2694 inset->setParam("label", oldlabel);
2695 insertInset(0, static_cast<Inset *>(inset),
2696 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2702 void Paragraph::checkAuthors(AuthorList const & authorList)
2704 pimpl_->changes_.checkAuthors(authorList);