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 &,
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 & 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 (lookupChange(i).type == Change::DELETED) {
730 if( ++runparams.inDeletedInset == 1)
731 runparams.changeOfDeletedInset = lookupChange(i);
734 if (inset->canTrackChanges()) {
735 column += Changes::latexMarkChange(os, bparams, running_change,
736 Change(Change::UNCHANGED));
737 running_change = Change(Change::UNCHANGED);
741 odocstream::pos_type const len = os.tellp();
743 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
744 || inset->lyxCode() == Inset::MATH_CODE
745 || inset->lyxCode() == Inset::URL_CODE)
746 && running_font.isRightToLeft()) {
747 if (running_font.language()->lang() == "farsi")
755 #warning Bug: we can have an empty font change here!
756 // if there has just been a font change, we are going to close it
757 // right now, which means stupid latex code like \textsf{}. AFAIK,
758 // this does not harm dvi output. A minor bug, thus (JMarc)
760 // Some insets cannot be inside a font change command.
761 // However, even such insets *can* be placed in \L or \R
762 // or their equivalents (for RTL language switches), so we don't
763 // close the language in those cases.
764 // ArabTeX, though, cannot handle this special behavior, it seems.
765 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
766 running_font.language()->lang() == "arabic_arabtex";
767 if (open_font && inset->noFontChange()) {
768 bool closeLanguage = arabtex ||
769 basefont.isRightToLeft() == running_font.isRightToLeft();
770 unsigned int count = running_font.latexWriteEndChanges(
771 os, bparams, runparams,
772 basefont, basefont, closeLanguage);
774 // if any font properties were closed, update the running_font,
775 // making sure, however, to leave the language as it was
777 // FIXME: probably a better way to keep track of the old
778 // language, than copying the entire font?
779 Font const copy_font(running_font);
780 basefont = owner_->getLayoutFont(bparams, outerfont);
781 running_font = basefont;
783 running_font.setLanguage(copy_font.language());
784 // leave font open if language is still open
785 open_font = (running_font.language() == basefont.language());
787 runparams.local_font = &basefont;
791 int tmp = inset->latex(buf, os, runparams);
794 if (running_font.language()->lang() == "farsi")
801 for (int j = 0; j < tmp; ++j) {
804 texrow.start(owner_->id(), i + 1);
807 column += os.tellp() - len;
810 if (lookupChange(i).type == Change::DELETED) {
811 --runparams.inDeletedInset;
817 // And now for the special cases within each mode
821 os << "\\textbackslash{}";
825 case '|': case '<': case '>':
826 // In T1 encoding, these characters exist
827 if (lyxrc.fontenc == "T1") {
829 //... but we should avoid ligatures
830 if ((c == '>' || c == '<')
832 && getChar(i + 1) == c) {
833 //os << "\\textcompwordmark{}";
835 // Jean-Marc, have a look at
836 // this. I think this works
844 // Typewriter font also has them
845 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
849 // Otherwise, we use what LaTeX
853 os << "\\textless{}";
857 os << "\\textgreater{}";
867 case '-': // "--" in Typewriter mode -> "-{}-"
868 if (i <= size() - 2 &&
869 getChar(i + 1) == '-' &&
870 running_font.family() == Font::TYPEWRITER_FAMILY) {
879 os << "\\char`\\\"{}";
884 case '%': case '#': case '{':
892 os << "\\textasciitilde{}";
897 os << "\\textasciicircum{}";
902 // avoid being mistaken for optional arguments
910 // Blanks are printed before font switching.
911 // Sure? I am not! (try nice-latex)
912 // I am sure it's correct. LyX might be smarter
913 // in the future, but for now, nothing wrong is
919 // I assume this is hack treating typewriter as verbatim
920 // FIXME UNICODE: This can fail if c cannot be encoded
921 // in the current encoding.
922 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
931 // FIXME: if we have "LaTeX" with a font
932 // change in the middle (before the 'T', then
933 // the "TeX" part is still special cased.
934 // Really we should only operate this on
935 // "words" for some definition of word
939 for (; pnr < phrases_nr; ++pnr) {
940 if (isTextAt(special_phrases[pnr].phrase, i)) {
941 os << special_phrases[pnr].macro;
942 i += special_phrases[pnr].phrase.length() - 1;
943 column += special_phrases[pnr].macro.length() - 1;
948 if (pnr == phrases_nr && c != '\0') {
949 Encoding const & encoding = *(runparams.encoding);
950 if (i < size() - 1) {
951 char_type next = getChar(i + 1);
952 if (Encodings::isCombiningChar(next)) {
953 column += latexSurrogatePair(os, c, next, encoding) - 1;
958 docstring const latex = encoding.latexChar(c);
959 if (latex.length() > 1 &&
960 latex[latex.length() - 1] != '}') {
961 // Prevent eating of a following
962 // space or command corruption by
963 // following characters
964 column += latex.length() + 1;
967 column += latex.length() - 1;
977 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
978 Layout const & layout) const
980 BufferParams const & bparams = features.bufferParams();
983 if (!params.spacing().isDefault())
984 features.require("setspace");
987 features.useLayout(layout.name());
990 Language const * doc_language = bparams.language;
992 FontList::const_iterator fcit = fontlist.begin();
993 FontList::const_iterator fend = fontlist.end();
994 for (; fcit != fend; ++fcit) {
995 if (fcit->font().noun() == Font::ON) {
996 LYXERR(Debug::LATEX) << "font.noun: "
997 << fcit->font().noun()
999 features.require("noun");
1000 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
1001 << to_utf8(fcit->font().stateText(0))
1004 switch (fcit->font().color()) {
1006 case Color::inherit:
1008 // probably we should put here all interface colors used for
1009 // font displaying! For now I just add this ones I know of (Jug)
1014 features.require("color");
1015 LYXERR(Debug::LATEX) << "Color enabled. Font: "
1016 << to_utf8(fcit->font().stateText(0))
1020 Language const * language = fcit->font().language();
1021 if (language->babel() != doc_language->babel() &&
1022 language != ignore_language &&
1023 language != latex_language)
1025 features.useLanguage(language);
1026 LYXERR(Debug::LATEX) << "Found language "
1027 << language->lang() << endl;
1031 if (!params.leftIndent().zero())
1032 features.require("ParagraphLeftIndent");
1035 InsetList::const_iterator icit = owner_->insetlist.begin();
1036 InsetList::const_iterator iend = owner_->insetlist.end();
1037 for (; icit != iend; ++icit) {
1039 icit->inset->validate(features);
1040 if (layout.needprotect &&
1041 icit->inset->lyxCode() == Inset::FOOT_CODE)
1042 features.require("NeedLyXFootnoteCode");
1046 // then the contents
1047 for (pos_type i = 0; i < size() ; ++i) {
1048 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1049 if (!special_phrases[pnr].builtin
1050 && isTextAt(special_phrases[pnr].phrase, i)) {
1051 features.require(special_phrases[pnr].phrase);
1055 Encodings::validate(getChar(i), features);
1063 /////////////////////////////////////////////////////////////////////
1067 /////////////////////////////////////////////////////////////////////
1071 Paragraph::Paragraph()
1072 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1079 Paragraph::Paragraph(Paragraph const & par)
1080 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1081 layout_(par.layout_),
1082 text_(par.text_), begin_of_body_(par.begin_of_body_),
1083 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1085 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1086 InsetList::iterator it = insetlist.begin();
1087 InsetList::iterator end = insetlist.end();
1088 for (; it != end; ++it)
1089 it->inset = it->inset->clone().release();
1093 Paragraph & Paragraph::operator=(Paragraph const & par)
1095 // needed as we will destroy the pimpl_ before copying it
1097 itemdepth = par.itemdepth;
1099 insetlist = par.insetlist;
1100 InsetList::iterator it = insetlist.begin();
1101 InsetList::iterator end = insetlist.end();
1102 for (; it != end; ++it)
1103 it->inset = it->inset->clone().release();
1105 layout_ = par.layout();
1107 begin_of_body_ = par.begin_of_body_;
1110 pimpl_ = new Pimpl(*par.pimpl_, this);
1116 Paragraph::~Paragraph()
1120 //lyxerr << "Paragraph::paragraph_id = "
1121 // << Paragraph::paragraph_id << endl;
1125 void Paragraph::write(Buffer const & buf, ostream & os,
1126 BufferParams const & bparams,
1127 depth_type & dth) const
1129 // The beginning or end of a deeper (i.e. nested) area?
1130 if (dth != params().depth()) {
1131 if (params().depth() > dth) {
1132 while (params().depth() > dth) {
1133 os << "\n\\begin_deeper";
1137 while (params().depth() < dth) {
1138 os << "\n\\end_deeper";
1144 // First write the layout
1145 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1149 Font font1(Font::ALL_INHERIT, bparams.language);
1151 Change running_change = Change(Change::UNCHANGED);
1154 for (pos_type i = 0; i <= size(); ++i) {
1156 Change change = pimpl_->lookupChange(i);
1157 Changes::lyxMarkChange(os, column, running_change, change);
1158 running_change = change;
1163 // Write font changes
1164 Font font2 = getFontSettings(bparams, i);
1165 if (font2 != font1) {
1166 font2.lyxWriteChanges(font1, os);
1171 value_type const c = getChar(i);
1175 Inset const * inset = getInset(i);
1177 if (inset->directWrite()) {
1178 // international char, let it write
1179 // code directly so it's shorter in
1181 inset->write(buf, os);
1185 os << "\\begin_inset ";
1186 inset->write(buf, os);
1187 os << "\n\\end_inset\n\n";
1193 os << "\n\\backslash\n";
1197 if (i + 1 < size() && getChar(i + 1) == ' ') {
1204 if ((column > 70 && c == ' ')
1209 // this check is to amend a bug. LyX sometimes
1210 // inserts '\0' this could cause problems.
1212 std::vector<char> tmp = ucs4_to_utf8(c);
1213 tmp.push_back('\0');
1216 lyxerr << "ERROR (Paragraph::writeFile):"
1217 " NULL char in structure." << endl;
1223 os << "\n\\end_layout\n";
1227 void Paragraph::validate(LaTeXFeatures & features) const
1229 pimpl_->validate(features, *layout());
1233 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1235 return pimpl_->eraseChar(pos, trackChanges);
1239 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1241 return pimpl_->eraseChars(start, end, trackChanges);
1245 void Paragraph::insert(pos_type start, docstring const & str,
1246 Font const & font, Change const & change)
1248 for (size_t i = 0, n = str.size(); i != n ; ++i)
1249 insertChar(start + i, str[i], font, change);
1253 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1256 pimpl_->insertChar(pos, c, Change(trackChanges ?
1257 Change::INSERTED : Change::UNCHANGED));
1261 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1262 Font const & font, bool trackChanges)
1264 pimpl_->insertChar(pos, c, Change(trackChanges ?
1265 Change::INSERTED : Change::UNCHANGED));
1270 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1271 Font const & font, Change const & change)
1273 pimpl_->insertChar(pos, c, change);
1278 void Paragraph::insertInset(pos_type pos, Inset * inset,
1279 Change const & change)
1281 pimpl_->insertInset(pos, inset, change);
1285 void Paragraph::insertInset(pos_type pos, Inset * inset,
1286 Font const & font, Change const & change)
1288 pimpl_->insertInset(pos, inset, change);
1289 // Set the font/language of the inset...
1291 // ... as well as the font/language of the text inside the inset
1292 // FIXME: This is far from perfect. It basically overrides work being done
1293 // in the InsetText constructor. Also, it doesn't work for Tables
1294 // (precisely because each cell's font/language is set in the Table's
1295 // constructor, so by now it's too late). The long-term solution should
1296 // be moving current_font into Cursor, and getting rid of all this...
1297 // (see http://thread.gmane.org/gmane.editors.lyx.devel/88869/focus=88944)
1298 if (inset->asTextInset()) {
1299 inset->asTextInset()->text_.current_font = font;
1300 inset->asTextInset()->text_.real_current_font = font;
1305 bool Paragraph::insetAllowed(Inset_code code)
1307 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1311 // Gets uninstantiated font setting at position.
1312 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1316 lyxerr << " pos: " << pos << " size: " << size() << endl;
1317 BOOST_ASSERT(pos <= size());
1320 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1321 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1322 for (; cit != end; ++cit)
1323 if (cit->pos() >= pos)
1329 if (pos == size() && !empty())
1330 return getFontSettings(bparams, pos - 1);
1332 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1336 FontSpan Paragraph::fontSpan(pos_type pos) const
1338 BOOST_ASSERT(pos <= size());
1341 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1342 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1343 for (; cit != end; ++cit) {
1344 if (cit->pos() >= pos) {
1345 if (pos >= beginOfBody())
1346 return FontSpan(std::max(start, beginOfBody()),
1349 return FontSpan(start,
1350 std::min(beginOfBody() - 1,
1353 start = cit->pos() + 1;
1356 // This should not happen, but if so, we take no chances.
1357 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1359 return FontSpan(pos, pos);
1363 // Gets uninstantiated font setting at position 0
1364 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1366 if (!empty() && !pimpl_->fontlist.empty())
1367 return pimpl_->fontlist[0].font();
1369 return Font(Font::ALL_INHERIT, bparams.language);
1373 // Gets the fully instantiated font at a given position in a paragraph
1374 // This is basically the same function as Text::GetFont() in text2.cpp.
1375 // The difference is that this one is used for generating the LaTeX file,
1376 // and thus cosmetic "improvements" are disallowed: This has to deliver
1377 // the true picture of the buffer. (Asger)
1378 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1379 Font const & outerfont) const
1381 BOOST_ASSERT(pos >= 0);
1383 Layout_ptr const & lout = layout();
1385 pos_type const body_pos = beginOfBody();
1389 layoutfont = lout->labelfont;
1391 layoutfont = lout->font;
1393 Font font = getFontSettings(bparams, pos);
1394 font.realize(layoutfont);
1395 font.realize(outerfont);
1396 font.realize(bparams.getFont());
1402 Font const Paragraph::getLabelFont
1403 (BufferParams const & bparams, Font const & outerfont) const
1405 Font tmpfont = layout()->labelfont;
1406 tmpfont.setLanguage(getParLanguage(bparams));
1407 tmpfont.realize(outerfont);
1408 tmpfont.realize(bparams.getFont());
1413 Font const Paragraph::getLayoutFont
1414 (BufferParams const & bparams, Font const & outerfont) const
1416 Font tmpfont = layout()->font;
1417 tmpfont.setLanguage(getParLanguage(bparams));
1418 tmpfont.realize(outerfont);
1419 tmpfont.realize(bparams.getFont());
1424 /// Returns the height of the highest font in range
1425 Font_size Paragraph::highestFontInRange
1426 (pos_type startpos, pos_type endpos, Font_size def_size) const
1428 if (pimpl_->fontlist.empty())
1431 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1432 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1433 for (; end_it != end; ++end_it) {
1434 if (end_it->pos() >= endpos)
1441 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1442 for (; cit != end; ++cit) {
1443 if (cit->pos() >= startpos)
1447 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1448 for (; cit != end_it; ++cit) {
1449 Font::FONT_SIZE size = cit->font().size();
1450 if (size == Font::INHERIT_SIZE)
1452 if (size > maxsize && size <= Font::SIZE_HUGER)
1459 Paragraph::value_type
1460 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1462 value_type c = getChar(pos);
1463 if (!lyxrc.rtl_support)
1493 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1500 void Paragraph::setFont(pos_type pos, Font const & font)
1502 BOOST_ASSERT(pos <= size());
1504 // First, reduce font against layout/label font
1505 // Update: The setCharFont() routine in text2.cpp already
1506 // reduces font, so we don't need to do that here. (Asger)
1507 // No need to simplify this because it will disappear
1508 // in a new kernel. (Asger)
1509 // Next search font table
1511 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1512 Pimpl::FontList::iterator it = beg;
1513 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1514 for (; it != endit; ++it) {
1515 if (it->pos() >= pos)
1518 size_t const i = distance(beg, it);
1519 bool notfound = (it == endit);
1521 if (!notfound && pimpl_->fontlist[i].font() == font)
1524 bool begin = pos == 0 || notfound ||
1525 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1526 // Is position pos is a beginning of a font block?
1527 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1528 // Is position pos is the end of a font block?
1529 if (begin && end) { // A single char block
1530 if (i + 1 < pimpl_->fontlist.size() &&
1531 pimpl_->fontlist[i + 1].font() == font) {
1532 // Merge the singleton block with the next block
1533 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1534 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1535 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1536 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1537 // Merge the singleton block with the previous block
1538 pimpl_->fontlist[i - 1].pos(pos);
1539 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1541 pimpl_->fontlist[i].font(font);
1543 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1544 pimpl_->fontlist[i - 1].pos(pos);
1546 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1547 Pimpl::FontTable(pos, font));
1549 pimpl_->fontlist[i].pos(pos - 1);
1550 if (!(i + 1 < pimpl_->fontlist.size() &&
1551 pimpl_->fontlist[i + 1].font() == font))
1552 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1553 Pimpl::FontTable(pos, font));
1554 } else { // The general case. The block is splitted into 3 blocks
1555 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1556 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1557 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1558 Pimpl::FontTable(pos, font));
1563 void Paragraph::makeSameLayout(Paragraph const & par)
1565 layout(par.layout());
1567 params() = par.params();
1571 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1573 if (isFreeSpacing())
1579 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1580 if (eraseChar(pos, trackChanges))
1586 return count > 0 || pos > 0;
1590 bool Paragraph::hasSameLayout(Paragraph const & par) const
1592 return par.layout() == layout() && params().sameLayout(par.params());
1596 depth_type Paragraph::getDepth() const
1598 return params().depth();
1602 depth_type Paragraph::getMaxDepthAfter() const
1604 if (layout()->isEnvironment())
1605 return params().depth() + 1;
1607 return params().depth();
1611 char Paragraph::getAlign() const
1613 if (params().align() == LYX_ALIGN_LAYOUT)
1614 return layout()->align;
1616 return params().align();
1620 docstring const & Paragraph::getLabelstring() const
1622 return params().labelString();
1626 // the next two functions are for the manual labels
1627 docstring const Paragraph::getLabelWidthString() const
1629 if (!params().labelWidthString().empty())
1630 return params().labelWidthString();
1632 return _("Senseless with this layout!");
1636 void Paragraph::setLabelWidthString(docstring const & s)
1638 params().labelWidthString(s);
1642 docstring const Paragraph::translateIfPossible(docstring const & s,
1643 BufferParams const & bparams) const
1645 if (!support::isAscii(s) || s.empty()) {
1646 // This must be a user defined layout. We cannot translate
1647 // this, since gettext accepts only ascii keys.
1650 // Probably standard layout, try to translate
1651 Messages & m = getMessages(getParLanguage(bparams)->code());
1652 return m.get(to_ascii(s));
1656 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1657 BufferParams const & bparams, bool process_appendix) const
1659 TextClass const & tclass = bparams.getTextClass();
1662 if (process_appendix && params().appendix())
1663 fmt = translateIfPossible(layout->labelstring_appendix(),
1666 fmt = translateIfPossible(layout->labelstring(), bparams);
1668 // handle 'inherited level parts' in 'fmt',
1669 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1670 size_t const i = fmt.find('@', 0);
1671 if (i != docstring::npos) {
1672 size_t const j = fmt.find('@', i + 1);
1673 if (j != docstring::npos) {
1674 docstring parent(fmt, i + 1, j - i - 1);
1675 docstring label = expandLabel(tclass[parent], bparams);
1676 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1680 return tclass.counters().counterLabel(fmt);
1684 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1687 LyXAlignment const oldAlign = params().align();
1688 // FIXME The first check is due to the fact that LYX_ALIGN_LAYOUT
1689 // is not required to be possible. A fix is on the way.
1690 if ((oldAlign != LYX_ALIGN_LAYOUT) &&
1691 !(oldAlign & layout()->alignpossible)) {
1692 frontend::Alert::warning(_("Alignment not permitted"),
1693 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1694 params().align(LYX_ALIGN_LAYOUT);
1699 pos_type Paragraph::beginOfBody() const
1701 return begin_of_body_;
1705 void Paragraph::setBeginOfBody()
1707 if (layout()->labeltype != LABEL_MANUAL) {
1712 // Unroll the first two cycles of the loop
1713 // and remember the previous character to
1714 // remove unnecessary getChar() calls
1716 pos_type end = size();
1717 if (i < end && !isNewline(i)) {
1719 char_type previous_char = 0;
1722 previous_char = text_[i];
1723 if (!isNewline(i)) {
1725 while (i < end && previous_char != ' ') {
1730 previous_char = temp;
1740 // returns -1 if inset not found
1741 int Paragraph::getPositionOfInset(Inset const * inset) const
1744 InsetList::const_iterator it = insetlist.begin();
1745 InsetList::const_iterator end = insetlist.end();
1746 for (; it != end; ++it)
1747 if (it->inset == inset)
1753 InsetBibitem * Paragraph::bibitem() const
1755 if (!insetlist.empty()) {
1756 Inset * inset = insetlist.begin()->inset;
1757 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1758 return static_cast<InsetBibitem *>(inset);
1764 bool Paragraph::forceDefaultParagraphs() const
1766 return inInset() && inInset()->forceDefaultParagraphs(0);
1772 // paragraphs inside floats need different alignment tags to avoid
1775 bool noTrivlistCentering(Inset::Code code)
1777 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1781 string correction(string const & orig)
1783 if (orig == "flushleft")
1784 return "raggedright";
1785 if (orig == "flushright")
1786 return "raggedleft";
1787 if (orig == "center")
1793 string const corrected_env(string const & suffix, string const & env,
1796 string output = suffix + "{";
1797 if (noTrivlistCentering(code))
1798 output += correction(env);
1802 if (suffix == "\\begin")
1808 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1810 if (!contains(str, "\n"))
1811 column += str.size();
1815 column = rsplit(str, tmp, '\n').size();
1822 // This could go to ParagraphParameters if we want to
1823 int Paragraph::startTeXParParams(BufferParams const & bparams,
1824 odocstream & os, TexRow & texrow,
1825 bool moving_arg) const
1829 if (params().noindent()) {
1830 os << "\\noindent ";
1834 LyXAlignment const curAlign = params().align();
1836 if (curAlign == layout()->align)
1840 case LYX_ALIGN_NONE:
1841 case LYX_ALIGN_BLOCK:
1842 case LYX_ALIGN_LAYOUT:
1843 case LYX_ALIGN_SPECIAL:
1845 case LYX_ALIGN_LEFT:
1846 case LYX_ALIGN_RIGHT:
1847 case LYX_ALIGN_CENTER:
1856 case LYX_ALIGN_NONE:
1857 case LYX_ALIGN_BLOCK:
1858 case LYX_ALIGN_LAYOUT:
1859 case LYX_ALIGN_SPECIAL:
1861 case LYX_ALIGN_LEFT: {
1863 if (getParLanguage(bparams)->babel() != "hebrew")
1864 output = corrected_env("\\begin", "flushleft", ownerCode());
1866 output = corrected_env("\\begin", "flushright", ownerCode());
1867 os << from_ascii(output);
1868 adjust_row_column(output, texrow, column);
1870 } case LYX_ALIGN_RIGHT: {
1872 if (getParLanguage(bparams)->babel() != "hebrew")
1873 output = corrected_env("\\begin", "flushright", ownerCode());
1875 output = corrected_env("\\begin", "flushleft", ownerCode());
1876 os << from_ascii(output);
1877 adjust_row_column(output, texrow, column);
1879 } case LYX_ALIGN_CENTER: {
1881 output = corrected_env("\\begin", "center", ownerCode());
1882 os << from_ascii(output);
1883 adjust_row_column(output, texrow, column);
1892 // This could go to ParagraphParameters if we want to
1893 int Paragraph::endTeXParParams(BufferParams const & bparams,
1894 odocstream & os, TexRow & texrow,
1895 bool moving_arg) const
1899 switch (params().align()) {
1900 case LYX_ALIGN_NONE:
1901 case LYX_ALIGN_BLOCK:
1902 case LYX_ALIGN_LAYOUT:
1903 case LYX_ALIGN_SPECIAL:
1905 case LYX_ALIGN_LEFT:
1906 case LYX_ALIGN_RIGHT:
1907 case LYX_ALIGN_CENTER:
1915 switch (params().align()) {
1916 case LYX_ALIGN_NONE:
1917 case LYX_ALIGN_BLOCK:
1918 case LYX_ALIGN_LAYOUT:
1919 case LYX_ALIGN_SPECIAL:
1921 case LYX_ALIGN_LEFT: {
1923 if (getParLanguage(bparams)->babel() != "hebrew")
1924 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1926 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1927 os << from_ascii(output);
1928 adjust_row_column(output, texrow, column);
1930 } case LYX_ALIGN_RIGHT: {
1932 if (getParLanguage(bparams)->babel() != "hebrew")
1933 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1935 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1936 os << from_ascii(output);
1937 adjust_row_column(output, texrow, column);
1939 } case LYX_ALIGN_CENTER: {
1941 output = corrected_env("\n\\par\\end", "center", ownerCode());
1942 os << from_ascii(output);
1943 adjust_row_column(output, texrow, column);
1952 // This one spits out the text of the paragraph
1953 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1954 BufferParams const & bparams,
1955 Font const & outerfont,
1956 odocstream & os, TexRow & texrow,
1957 OutputParams const & runparams) const
1959 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1961 bool return_value = false;
1965 // well we have to check if we are in an inset with unlimited
1966 // length (all in one row) if that is true then we don't allow
1967 // any special options in the paragraph and also we don't allow
1968 // any environment other than the default layout of the text class
1970 bool asdefault = forceDefaultParagraphs();
1973 style = bparams.getTextClass().defaultLayout();
1978 // Current base font for all inherited font changes, without any
1979 // change caused by an individual character, except for the language:
1980 // It is set to the language of the first character.
1981 // As long as we are in the label, this font is the base font of the
1982 // label. Before the first body character it is set to the base font
1986 // Maybe we have to create a optional argument.
1987 pos_type body_pos = beginOfBody();
1988 unsigned int column = 0;
1991 // the optional argument is kept in curly brackets in
1992 // case it contains a ']'
1995 basefont = getLabelFont(bparams, outerfont);
1997 basefont = getLayoutFont(bparams, outerfont);
2000 // Which font is currently active?
2001 Font running_font(basefont);
2002 // Do we have an open font change?
2003 bool open_font = false;
2005 Change runningChange = Change(Change::UNCHANGED);
2007 texrow.start(id(), 0);
2009 // if the paragraph is empty, the loop will not be entered at all
2011 if (style->isCommand()) {
2016 column += startTeXParParams(bparams, os, texrow,
2017 runparams.moving_arg);
2020 for (pos_type i = 0; i < size(); ++i) {
2021 // First char in paragraph or after label?
2022 if (i == body_pos) {
2025 column += running_font.latexWriteEndChanges(
2026 os, bparams, runparams,
2027 basefont, basefont);
2030 basefont = getLayoutFont(bparams, outerfont);
2031 running_font = basefont;
2033 column += Changes::latexMarkChange(os, bparams,
2034 runningChange, Change(Change::UNCHANGED));
2035 runningChange = Change(Change::UNCHANGED);
2040 if (style->isCommand()) {
2046 column += startTeXParParams(bparams, os,
2048 runparams.moving_arg);
2051 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2052 : pimpl_->lookupChange(i);
2054 if (bparams.outputChanges && runningChange != change) {
2056 column += running_font.latexWriteEndChanges(
2057 os, bparams, runparams, basefont, basefont);
2060 basefont = getLayoutFont(bparams, outerfont);
2061 running_font = basefont;
2063 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2064 runningChange = change;
2067 // do not output text which is marked deleted
2068 // if change tracking output is disabled
2069 if (!bparams.outputChanges && change.type == Change::DELETED) {
2075 value_type const c = getChar(i);
2077 // Fully instantiated font
2078 Font const font = getFont(bparams, i, outerfont);
2080 Font const last_font = running_font;
2082 // Do we need to close the previous font?
2084 (font != running_font ||
2085 font.language() != running_font.language()))
2087 column += running_font.latexWriteEndChanges(
2088 os, bparams, runparams, basefont,
2089 (i == body_pos-1) ? basefont : font);
2090 running_font = basefont;
2094 // Switch file encoding if necessary
2095 if (runparams.encoding->package() == Encoding::inputenc &&
2096 font.language()->encoding()->package() == Encoding::inputenc) {
2097 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2098 runparams.moving_arg, *(runparams.encoding),
2099 *(font.language()->encoding()));
2100 if (enc_switch.first) {
2101 column += enc_switch.second;
2102 runparams.encoding = font.language()->encoding();
2106 // Do we need to change font?
2107 if ((font != running_font ||
2108 font.language() != running_font.language()) &&
2111 odocstringstream ods;
2112 column += font.latexWriteStartChanges(ods, bparams,
2113 runparams, basefont,
2115 running_font = font;
2117 docstring fontchange = ods.str();
2118 // check if the fontchange ends with a trailing blank
2119 // (like "\small " (see bug 3382)
2120 if (suffixIs(fontchange, ' ') && c == ' ')
2121 os << fontchange.substr(0, fontchange.size() - 1)
2122 << from_ascii("{}");
2128 // Do not print the separation of the optional argument
2129 // if style->pass_thru is false. This works because
2130 // simpleTeXSpecialChars ignores spaces if
2131 // style->pass_thru is false.
2132 if (i != body_pos - 1) {
2133 if (pimpl_->simpleTeXBlanks(
2134 *(runparams.encoding), os, texrow,
2135 i, column, font, *style))
2136 // A surrogate pair was output. We
2137 // must not call simpleTeXSpecialChars
2138 // in this iteration, since
2139 // simpleTeXBlanks incremented i, and
2140 // simpleTeXSpecialChars would output
2141 // the combining character again.
2146 OutputParams rp = runparams;
2147 rp.free_spacing = style->free_spacing;
2148 rp.local_font = &font;
2149 rp.intitle = style->intitle;
2150 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2151 texrow, rp, running_font,
2152 basefont, outerfont, open_font,
2153 runningChange, *style, i, column, c);
2155 // Set the encoding to that returned from simpleTeXSpecialChars (see
2156 // comment for encoding member in OutputParams.h)
2157 runparams.encoding = rp.encoding;
2160 // If we have an open font definition, we have to close it
2162 #ifdef FIXED_LANGUAGE_END_DETECTION
2165 .latexWriteEndChanges(os, bparams, runparams,
2167 next_->getFont(bparams, 0, outerfont));
2169 running_font.latexWriteEndChanges(os, bparams,
2170 runparams, basefont, basefont);
2173 #ifdef WITH_WARNINGS
2174 //#warning For now we ALWAYS have to close the foreign font settings if they are
2175 //#warning there as we start another \selectlanguage with the next paragraph if
2176 //#warning we are in need of this. This should be fixed sometime (Jug)
2178 running_font.latexWriteEndChanges(os, bparams, runparams,
2179 basefont, basefont);
2183 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2185 // Needed if there is an optional argument but no contents.
2186 if (body_pos > 0 && body_pos == size()) {
2188 return_value = false;
2192 column += endTeXParParams(bparams, os, texrow,
2193 runparams.moving_arg);
2196 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2197 return return_value;
2214 string tag_name(PAR_TAG const & pt) {
2216 case PAR_NONE: return "!-- --";
2217 case TT: return "tt";
2218 case SF: return "sf";
2219 case BF: return "bf";
2220 case IT: return "it";
2221 case SL: return "sl";
2222 case EM: return "em";
2229 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2231 p1 = static_cast<PAR_TAG>(p1 | p2);
2236 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2238 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2244 bool Paragraph::emptyTag() const
2246 for (pos_type i = 0; i < size(); ++i) {
2248 Inset const * inset = getInset(i);
2249 Inset::Code lyx_code = inset->lyxCode();
2250 if (lyx_code != Inset::TOC_CODE &&
2251 lyx_code != Inset::INCLUDE_CODE &&
2252 lyx_code != Inset::GRAPHICS_CODE &&
2253 lyx_code != Inset::ERT_CODE &&
2254 lyx_code != Inset::LISTINGS_CODE &&
2255 lyx_code != Inset::FLOAT_CODE &&
2256 lyx_code != Inset::TABULAR_CODE) {
2260 value_type c = getChar(i);
2261 if (c != ' ' && c != '\t')
2269 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2271 for (pos_type i = 0; i < size(); ++i) {
2273 Inset const * inset = getInset(i);
2274 Inset::Code lyx_code = inset->lyxCode();
2275 if (lyx_code == Inset::LABEL_CODE) {
2276 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2277 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2286 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2289 for (i = 0; i < size(); ++i) {
2291 Inset const * inset = getInset(i);
2292 inset->docbook(buf, os, runparams);
2294 value_type c = getChar(i);
2297 os << sgml::escapeChar(c);
2304 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2308 for (pos_type i = initial; i < size(); ++i) {
2309 Font font = getFont(buf.params(), i, outerfont);
2312 if (i != initial && font != font_old)
2321 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2323 OutputParams const & runparams,
2324 Font const & outerfont,
2325 pos_type initial) const
2327 bool emph_flag = false;
2329 Layout_ptr const & style = layout();
2331 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2333 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2336 // parsing main loop
2337 for (pos_type i = initial; i < size(); ++i) {
2338 Font font = getFont(buf.params(), i, outerfont);
2340 // handle <emphasis> tag
2341 if (font_old.emph() != font.emph()) {
2342 if (font.emph() == Font::ON) {
2345 } else if (i != initial) {
2346 os << "</emphasis>";
2352 Inset const * inset = getInset(i);
2353 inset->docbook(buf, os, runparams);
2355 value_type c = getChar(i);
2357 if (style->pass_thru)
2360 os << sgml::escapeChar(c);
2366 os << "</emphasis>";
2369 if (style->free_spacing)
2371 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2376 bool Paragraph::isNewline(pos_type pos) const
2379 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2383 bool Paragraph::isLineSeparator(pos_type pos) const
2385 value_type const c = getChar(pos);
2386 return isLineSeparatorChar(c)
2387 || (c == Paragraph::META_INSET && getInset(pos) &&
2388 getInset(pos)->isLineSeparator());
2392 /// Used by the spellchecker
2393 bool Paragraph::isLetter(pos_type pos) const
2396 return getInset(pos)->isLetter();
2398 value_type const c = getChar(pos);
2399 return isLetterChar(c) || isDigit(c);
2405 Paragraph::getParLanguage(BufferParams const & bparams) const
2408 return getFirstFontSettings(bparams).language();
2409 #ifdef WITH_WARNINGS
2410 #warning FIXME we should check the prev par as well (Lgb)
2412 return bparams.language;
2416 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2418 return lyxrc.rtl_support
2419 && getParLanguage(bparams)->rightToLeft()
2420 && ownerCode() != Inset::ERT_CODE
2421 && ownerCode() != Inset::LISTINGS_CODE;
2425 void Paragraph::changeLanguage(BufferParams const & bparams,
2426 Language const * from, Language const * to)
2428 // change language including dummy font change at the end
2429 for (pos_type i = 0; i <= size(); ++i) {
2430 Font font = getFontSettings(bparams, i);
2431 if (font.language() == from) {
2432 font.setLanguage(to);
2439 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2441 Language const * doc_language = bparams.language;
2442 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2443 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2445 for (; cit != end; ++cit)
2446 if (cit->font().language() != ignore_language &&
2447 cit->font().language() != latex_language &&
2448 cit->font().language() != doc_language)
2454 // Convert the paragraph to a string.
2455 // Used for building the table of contents
2456 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2458 return asString(buffer, 0, size(), label);
2462 docstring const Paragraph::asString(Buffer const & buffer,
2463 pos_type beg, pos_type end, bool label) const
2466 odocstringstream os;
2468 if (beg == 0 && label && !params().labelString().empty())
2469 os << params().labelString() << ' ';
2471 for (pos_type i = beg; i < end; ++i) {
2472 value_type const c = getChar(i);
2475 else if (c == META_INSET)
2476 getInset(i)->textString(buffer, os);
2483 void Paragraph::setInsetOwner(Inset * inset)
2485 pimpl_->inset_owner = inset;
2489 Change const & Paragraph::lookupChange(pos_type pos) const
2491 BOOST_ASSERT(pos <= size());
2492 return pimpl_->lookupChange(pos);
2496 bool Paragraph::isChanged(pos_type start, pos_type end) const
2498 return pimpl_->isChanged(start, end);
2502 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2504 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2508 void Paragraph::setChange(Change const & change)
2510 pimpl_->setChange(change);
2514 void Paragraph::setChange(pos_type pos, Change const & change)
2516 pimpl_->setChange(pos, change);
2520 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2522 return pimpl_->acceptChanges(bparams, start, end);
2526 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2528 return pimpl_->rejectChanges(bparams, start, end);
2532 int Paragraph::id() const
2538 Layout_ptr const & Paragraph::layout() const
2544 void Paragraph::layout(Layout_ptr const & new_layout)
2546 layout_ = new_layout;
2550 Inset * Paragraph::inInset() const
2552 return pimpl_->inset_owner;
2556 Inset::Code Paragraph::ownerCode() const
2558 return pimpl_->inset_owner
2559 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2563 ParagraphParameters & Paragraph::params()
2565 return pimpl_->params;
2569 ParagraphParameters const & Paragraph::params() const
2571 return pimpl_->params;
2575 bool Paragraph::isFreeSpacing() const
2577 if (layout()->free_spacing)
2580 // for now we just need this, later should we need this in some
2581 // other way we can always add a function to Inset too.
2582 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2586 bool Paragraph::allowEmpty() const
2588 if (layout()->keepempty)
2590 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2594 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2596 if (!Encodings::is_arabic(c))
2599 value_type prev_char = ' ';
2600 value_type next_char = ' ';
2602 for (pos_type i = pos - 1; i >= 0; --i) {
2603 value_type const par_char = getChar(i);
2604 if (!Encodings::isComposeChar_arabic(par_char)) {
2605 prev_char = par_char;
2610 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2611 value_type const par_char = getChar(i);
2612 if (!Encodings::isComposeChar_arabic(par_char)) {
2613 next_char = par_char;
2618 if (Encodings::is_arabic(next_char)) {
2619 if (Encodings::is_arabic(prev_char) &&
2620 !Encodings::is_arabic_special(prev_char))
2621 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2623 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2625 if (Encodings::is_arabic(prev_char) &&
2626 !Encodings::is_arabic_special(prev_char))
2627 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2629 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2634 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2639 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2641 // expand at the end of a row only if there is another hfill on the same row
2642 if (pos == row.endpos() - 1) {
2643 for (pos_type i = row.pos(); i < pos; i++) {
2650 // expand at the beginning of a row only if it is the first row of a paragraph
2651 if (pos == row.pos()) {
2655 // do not expand in some labels
2656 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2659 // if there is anything between the first char of the row and
2660 // the specified position that is neither a newline nor an hfill,
2661 // the hfill will be expanded, otherwise it won't
2662 for (pos_type i = row.pos(); i < pos; i++) {
2663 if (!isNewline(i) && !isHfill(i))
2670 int Paragraph::checkBiblio(bool track_changes)
2673 //This is getting more and more a mess. ...We really should clean
2674 //up this bibitem issue for 1.6. See also bug 2743.
2676 // Add bibitem insets if necessary
2677 if (layout()->labeltype != LABEL_BIBLIO)
2680 bool hasbibitem = !insetlist.empty()
2681 // Insist on it being in pos 0
2682 && getChar(0) == Paragraph::META_INSET
2683 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2688 // remove a bibitem in pos != 0
2689 // restore it later in pos 0 if necessary
2690 // (e.g. if a user inserts contents _before_ the item)
2691 // we're assuming there's only one of these, which there
2693 int erasedInsetPosition = -1;
2694 InsetList::iterator it = insetlist.begin();
2695 InsetList::iterator end = insetlist.end();
2696 for (; it != end; ++it)
2697 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2699 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2700 oldkey = olditem->getParam("key");
2701 oldlabel = olditem->getParam("label");
2702 erasedInsetPosition = it->pos;
2703 eraseChar(erasedInsetPosition, track_changes);
2707 //There was an InsetBibitem at the beginning, and we didn't
2708 //have to erase one.
2709 if (hasbibitem && erasedInsetPosition < 0)
2712 //There was an InsetBibitem at the beginning and we did have to
2713 //erase one. So we give its properties to the beginning inset.
2715 InsetBibitem * inset =
2716 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2717 if (!oldkey.empty())
2718 inset->setParam("key", oldkey);
2719 inset->setParam("label", oldlabel);
2720 return -erasedInsetPosition;
2723 //There was no inset at the beginning, so we need to create one with
2724 //the key and label of the one we erased.
2725 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2726 // restore values of previously deleted item in this par.
2727 if (!oldkey.empty())
2728 inset->setParam("key", oldkey);
2729 inset->setParam("label", oldlabel);
2730 insertInset(0, static_cast<Inset *>(inset),
2731 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2737 void Paragraph::checkAuthors(AuthorList const & authorList)
2739 pimpl_->changes_.checkAuthors(authorList);