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 (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 // However, even such insets *can* be placed in \L or \R
757 // or their equivalents (for RTL language switches), so we don't
758 // close the language in those cases.
759 // ArabTeX, though, cannot handle this special behavior, it seems.
760 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
761 running_font.language()->lang() == "arabic_arabtex";
762 if (open_font && inset->noFontChange()) {
763 bool closeLanguage = arabtex ||
764 basefont.isRightToLeft() == running_font.isRightToLeft();
765 unsigned int count = running_font.latexWriteEndChanges(
766 os, bparams, runparams,
767 basefont, basefont, closeLanguage);
769 // if any font properties were closed, update the running_font,
770 // making sure, however, to leave the language as it was
772 // FIXME: probably a better way to keep track of the old
773 // language, than copying the entire font?
774 Font const copy_font(running_font);
775 basefont = owner_->getLayoutFont(bparams, outerfont);
776 running_font = basefont;
778 running_font.setLanguage(copy_font.language());
779 // leave font open if language is still open
780 open_font = (running_font.language() == basefont.language());
782 runparams.local_font = &basefont;
786 int tmp = inset->latex(buf, os, runparams);
789 if (running_font.language()->lang() == "farsi")
796 for (int j = 0; j < tmp; ++j) {
799 texrow.start(owner_->id(), i + 1);
802 column += os.tellp() - len;
808 // And now for the special cases within each mode
812 os << "\\textbackslash{}";
816 case '|': case '<': case '>':
817 // In T1 encoding, these characters exist
818 if (lyxrc.fontenc == "T1") {
820 //... but we should avoid ligatures
821 if ((c == '>' || c == '<')
823 && getChar(i + 1) == c) {
824 //os << "\\textcompwordmark{}";
826 // Jean-Marc, have a look at
827 // this. I think this works
835 // Typewriter font also has them
836 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
840 // Otherwise, we use what LaTeX
844 os << "\\textless{}";
848 os << "\\textgreater{}";
858 case '-': // "--" in Typewriter mode -> "-{}-"
859 if (i <= size() - 2 &&
860 getChar(i + 1) == '-' &&
861 running_font.family() == Font::TYPEWRITER_FAMILY) {
870 os << "\\char`\\\"{}";
875 case '%': case '#': case '{':
883 os << "\\textasciitilde{}";
888 os << "\\textasciicircum{}";
893 // avoid being mistaken for optional arguments
901 // Blanks are printed before font switching.
902 // Sure? I am not! (try nice-latex)
903 // I am sure it's correct. LyX might be smarter
904 // in the future, but for now, nothing wrong is
910 // I assume this is hack treating typewriter as verbatim
911 // FIXME UNICODE: This can fail if c cannot be encoded
912 // in the current encoding.
913 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
922 // FIXME: if we have "LaTeX" with a font
923 // change in the middle (before the 'T', then
924 // the "TeX" part is still special cased.
925 // Really we should only operate this on
926 // "words" for some definition of word
930 for (; pnr < phrases_nr; ++pnr) {
931 if (isTextAt(special_phrases[pnr].phrase, i)) {
932 os << special_phrases[pnr].macro;
933 i += special_phrases[pnr].phrase.length() - 1;
934 column += special_phrases[pnr].macro.length() - 1;
939 if (pnr == phrases_nr && c != '\0') {
940 Encoding const & encoding = *(runparams.encoding);
941 if (i < size() - 1) {
942 char_type next = getChar(i + 1);
943 if (Encodings::isCombiningChar(next)) {
944 column += latexSurrogatePair(os, c, next, encoding) - 1;
949 docstring const latex = encoding.latexChar(c);
950 if (latex.length() > 1 &&
951 latex[latex.length() - 1] != '}') {
952 // Prevent eating of a following
953 // space or command corruption by
954 // following characters
955 column += latex.length() + 1;
958 column += latex.length() - 1;
968 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
969 Layout const & layout) const
971 BufferParams const & bparams = features.bufferParams();
974 if (!params.spacing().isDefault())
975 features.require("setspace");
978 features.useLayout(layout.name());
981 Language const * doc_language = bparams.language;
983 FontList::const_iterator fcit = fontlist.begin();
984 FontList::const_iterator fend = fontlist.end();
985 for (; fcit != fend; ++fcit) {
986 if (fcit->font().noun() == Font::ON) {
987 LYXERR(Debug::LATEX) << "font.noun: "
988 << fcit->font().noun()
990 features.require("noun");
991 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
992 << to_utf8(fcit->font().stateText(0))
995 switch (fcit->font().color()) {
999 // probably we should put here all interface colors used for
1000 // font displaying! For now I just add this ones I know of (Jug)
1005 features.require("color");
1006 LYXERR(Debug::LATEX) << "Color enabled. Font: "
1007 << to_utf8(fcit->font().stateText(0))
1011 Language const * language = fcit->font().language();
1012 if (language->babel() != doc_language->babel() &&
1013 language != ignore_language &&
1014 language != latex_language)
1016 features.useLanguage(language);
1017 LYXERR(Debug::LATEX) << "Found language "
1018 << language->lang() << endl;
1022 if (!params.leftIndent().zero())
1023 features.require("ParagraphLeftIndent");
1026 InsetList::const_iterator icit = owner_->insetlist.begin();
1027 InsetList::const_iterator iend = owner_->insetlist.end();
1028 for (; icit != iend; ++icit) {
1030 icit->inset->validate(features);
1031 if (layout.needprotect &&
1032 icit->inset->lyxCode() == Inset::FOOT_CODE)
1033 features.require("NeedLyXFootnoteCode");
1037 // then the contents
1038 for (pos_type i = 0; i < size() ; ++i) {
1039 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1040 if (!special_phrases[pnr].builtin
1041 && isTextAt(special_phrases[pnr].phrase, i)) {
1042 features.require(special_phrases[pnr].phrase);
1046 Encodings::validate(getChar(i), features);
1054 /////////////////////////////////////////////////////////////////////
1058 /////////////////////////////////////////////////////////////////////
1062 Paragraph::Paragraph()
1063 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1070 Paragraph::Paragraph(Paragraph const & par)
1071 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1072 layout_(par.layout_),
1073 text_(par.text_), begin_of_body_(par.begin_of_body_),
1074 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1076 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1077 InsetList::iterator it = insetlist.begin();
1078 InsetList::iterator end = insetlist.end();
1079 for (; it != end; ++it)
1080 it->inset = it->inset->clone().release();
1084 Paragraph & Paragraph::operator=(Paragraph const & par)
1086 // needed as we will destroy the pimpl_ before copying it
1088 itemdepth = par.itemdepth;
1090 insetlist = par.insetlist;
1091 InsetList::iterator it = insetlist.begin();
1092 InsetList::iterator end = insetlist.end();
1093 for (; it != end; ++it)
1094 it->inset = it->inset->clone().release();
1096 layout_ = par.layout();
1098 begin_of_body_ = par.begin_of_body_;
1101 pimpl_ = new Pimpl(*par.pimpl_, this);
1107 Paragraph::~Paragraph()
1111 //lyxerr << "Paragraph::paragraph_id = "
1112 // << Paragraph::paragraph_id << endl;
1116 void Paragraph::write(Buffer const & buf, ostream & os,
1117 BufferParams const & bparams,
1118 depth_type & dth) const
1120 // The beginning or end of a deeper (i.e. nested) area?
1121 if (dth != params().depth()) {
1122 if (params().depth() > dth) {
1123 while (params().depth() > dth) {
1124 os << "\n\\begin_deeper";
1128 while (params().depth() < dth) {
1129 os << "\n\\end_deeper";
1135 // First write the layout
1136 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1140 Font font1(Font::ALL_INHERIT, bparams.language);
1142 Change running_change = Change(Change::UNCHANGED);
1145 for (pos_type i = 0; i <= size(); ++i) {
1147 Change change = pimpl_->lookupChange(i);
1148 Changes::lyxMarkChange(os, column, running_change, change);
1149 running_change = change;
1154 // Write font changes
1155 Font font2 = getFontSettings(bparams, i);
1156 if (font2 != font1) {
1157 font2.lyxWriteChanges(font1, os);
1162 value_type const c = getChar(i);
1166 Inset const * inset = getInset(i);
1168 if (inset->directWrite()) {
1169 // international char, let it write
1170 // code directly so it's shorter in
1172 inset->write(buf, os);
1176 os << "\\begin_inset ";
1177 inset->write(buf, os);
1178 os << "\n\\end_inset\n\n";
1184 os << "\n\\backslash\n";
1188 if (i + 1 < size() && getChar(i + 1) == ' ') {
1195 if ((column > 70 && c == ' ')
1200 // this check is to amend a bug. LyX sometimes
1201 // inserts '\0' this could cause problems.
1203 std::vector<char> tmp = ucs4_to_utf8(c);
1204 tmp.push_back('\0');
1207 lyxerr << "ERROR (Paragraph::writeFile):"
1208 " NULL char in structure." << endl;
1214 os << "\n\\end_layout\n";
1218 void Paragraph::validate(LaTeXFeatures & features) const
1220 pimpl_->validate(features, *layout());
1224 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1226 return pimpl_->eraseChar(pos, trackChanges);
1230 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1232 return pimpl_->eraseChars(start, end, trackChanges);
1236 void Paragraph::insert(pos_type start, docstring const & str,
1237 Font const & font, Change const & change)
1239 for (size_t i = 0, n = str.size(); i != n ; ++i)
1240 insertChar(start + i, str[i], font, change);
1244 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1247 pimpl_->insertChar(pos, c, Change(trackChanges ?
1248 Change::INSERTED : Change::UNCHANGED));
1252 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1253 Font const & font, bool trackChanges)
1255 pimpl_->insertChar(pos, c, Change(trackChanges ?
1256 Change::INSERTED : Change::UNCHANGED));
1261 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1262 Font const & font, Change const & change)
1264 pimpl_->insertChar(pos, c, change);
1269 void Paragraph::insertInset(pos_type pos, Inset * inset,
1270 Change const & change)
1272 pimpl_->insertInset(pos, inset, change);
1276 void Paragraph::insertInset(pos_type pos, Inset * inset,
1277 Font const & font, Change const & change)
1279 pimpl_->insertInset(pos, inset, change);
1280 // Set the font/language of the inset...
1282 // ... as well as the font/language of the text inside the inset
1283 // FIXME: This is far from perfect. It basically overrides work being done
1284 // in the InsetText constructor. Also, it doesn't work for Tables
1285 // (precisely because each cell's font/language is set in the Table's
1286 // constructor, so by now it's too late). The long-term solution should
1287 // be moving current_font into Cursor, and getting rid of all this...
1288 // (see http://thread.gmane.org/gmane.editors.lyx.devel/88869/focus=88944)
1289 if (inset->asTextInset()) {
1290 inset->asTextInset()->text_.current_font = font;
1291 inset->asTextInset()->text_.real_current_font = font;
1296 bool Paragraph::insetAllowed(Inset_code code)
1298 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1302 // Gets uninstantiated font setting at position.
1303 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1307 lyxerr << " pos: " << pos << " size: " << size() << endl;
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)
1320 if (pos == size() && !empty())
1321 return getFontSettings(bparams, pos - 1);
1323 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1327 FontSpan Paragraph::fontSpan(pos_type pos) const
1329 BOOST_ASSERT(pos <= size());
1332 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1333 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1334 for (; cit != end; ++cit) {
1335 if (cit->pos() >= pos) {
1336 if (pos >= beginOfBody())
1337 return FontSpan(std::max(start, beginOfBody()),
1340 return FontSpan(start,
1341 std::min(beginOfBody() - 1,
1344 start = cit->pos() + 1;
1347 // This should not happen, but if so, we take no chances.
1348 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1350 return FontSpan(pos, pos);
1354 // Gets uninstantiated font setting at position 0
1355 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1357 if (!empty() && !pimpl_->fontlist.empty())
1358 return pimpl_->fontlist[0].font();
1360 return Font(Font::ALL_INHERIT, bparams.language);
1364 // Gets the fully instantiated font at a given position in a paragraph
1365 // This is basically the same function as Text::GetFont() in text2.cpp.
1366 // The difference is that this one is used for generating the LaTeX file,
1367 // and thus cosmetic "improvements" are disallowed: This has to deliver
1368 // the true picture of the buffer. (Asger)
1369 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1370 Font const & outerfont) const
1372 BOOST_ASSERT(pos >= 0);
1374 Layout_ptr const & lout = layout();
1376 pos_type const body_pos = beginOfBody();
1380 layoutfont = lout->labelfont;
1382 layoutfont = lout->font;
1384 Font font = getFontSettings(bparams, pos);
1385 font.realize(layoutfont);
1386 font.realize(outerfont);
1387 font.realize(bparams.getFont());
1393 Font const Paragraph::getLabelFont
1394 (BufferParams const & bparams, Font const & outerfont) const
1396 Font tmpfont = layout()->labelfont;
1397 tmpfont.setLanguage(getParLanguage(bparams));
1398 tmpfont.realize(outerfont);
1399 tmpfont.realize(bparams.getFont());
1404 Font const Paragraph::getLayoutFont
1405 (BufferParams const & bparams, Font const & outerfont) const
1407 Font tmpfont = layout()->font;
1408 tmpfont.setLanguage(getParLanguage(bparams));
1409 tmpfont.realize(outerfont);
1410 tmpfont.realize(bparams.getFont());
1415 /// Returns the height of the highest font in range
1416 Font_size Paragraph::highestFontInRange
1417 (pos_type startpos, pos_type endpos, Font_size def_size) const
1419 if (pimpl_->fontlist.empty())
1422 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1423 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1424 for (; end_it != end; ++end_it) {
1425 if (end_it->pos() >= endpos)
1432 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1433 for (; cit != end; ++cit) {
1434 if (cit->pos() >= startpos)
1438 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1439 for (; cit != end_it; ++cit) {
1440 Font::FONT_SIZE size = cit->font().size();
1441 if (size == Font::INHERIT_SIZE)
1443 if (size > maxsize && size <= Font::SIZE_HUGER)
1450 Paragraph::value_type
1451 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1453 value_type c = getChar(pos);
1454 if (!lyxrc.rtl_support)
1484 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1491 void Paragraph::setFont(pos_type pos, Font const & font)
1493 BOOST_ASSERT(pos <= size());
1495 // First, reduce font against layout/label font
1496 // Update: The setCharFont() routine in text2.cpp already
1497 // reduces font, so we don't need to do that here. (Asger)
1498 // No need to simplify this because it will disappear
1499 // in a new kernel. (Asger)
1500 // Next search font table
1502 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1503 Pimpl::FontList::iterator it = beg;
1504 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1505 for (; it != endit; ++it) {
1506 if (it->pos() >= pos)
1509 size_t const i = distance(beg, it);
1510 bool notfound = (it == endit);
1512 if (!notfound && pimpl_->fontlist[i].font() == font)
1515 bool begin = pos == 0 || notfound ||
1516 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1517 // Is position pos is a beginning of a font block?
1518 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1519 // Is position pos is the end of a font block?
1520 if (begin && end) { // A single char block
1521 if (i + 1 < pimpl_->fontlist.size() &&
1522 pimpl_->fontlist[i + 1].font() == font) {
1523 // Merge the singleton block with the next block
1524 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1525 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1526 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1527 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1528 // Merge the singleton block with the previous block
1529 pimpl_->fontlist[i - 1].pos(pos);
1530 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1532 pimpl_->fontlist[i].font(font);
1534 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1535 pimpl_->fontlist[i - 1].pos(pos);
1537 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1538 Pimpl::FontTable(pos, font));
1540 pimpl_->fontlist[i].pos(pos - 1);
1541 if (!(i + 1 < pimpl_->fontlist.size() &&
1542 pimpl_->fontlist[i + 1].font() == font))
1543 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1544 Pimpl::FontTable(pos, font));
1545 } else { // The general case. The block is splitted into 3 blocks
1546 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1547 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1548 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1549 Pimpl::FontTable(pos, font));
1554 void Paragraph::makeSameLayout(Paragraph const & par)
1556 layout(par.layout());
1558 params() = par.params();
1562 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1564 if (isFreeSpacing())
1570 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1571 if (eraseChar(pos, trackChanges))
1577 return count > 0 || pos > 0;
1581 bool Paragraph::hasSameLayout(Paragraph const & par) const
1583 return par.layout() == layout() && params().sameLayout(par.params());
1587 depth_type Paragraph::getDepth() const
1589 return params().depth();
1593 depth_type Paragraph::getMaxDepthAfter() const
1595 if (layout()->isEnvironment())
1596 return params().depth() + 1;
1598 return params().depth();
1602 char Paragraph::getAlign() const
1604 if (params().align() == LYX_ALIGN_LAYOUT)
1605 return layout()->align;
1607 return params().align();
1611 docstring const & Paragraph::getLabelstring() const
1613 return params().labelString();
1617 // the next two functions are for the manual labels
1618 docstring const Paragraph::getLabelWidthString() const
1620 if (!params().labelWidthString().empty())
1621 return params().labelWidthString();
1623 return _("Senseless with this layout!");
1627 void Paragraph::setLabelWidthString(docstring const & s)
1629 params().labelWidthString(s);
1633 docstring const Paragraph::translateIfPossible(docstring const & s,
1634 BufferParams const & bparams) const
1636 if (!support::isAscii(s) || s.empty()) {
1637 // This must be a user defined layout. We cannot translate
1638 // this, since gettext accepts only ascii keys.
1641 // Probably standard layout, try to translate
1642 Messages & m = getMessages(getParLanguage(bparams)->code());
1643 return m.get(to_ascii(s));
1647 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1648 BufferParams const & bparams, bool process_appendix) const
1650 TextClass const & tclass = bparams.getTextClass();
1653 if (process_appendix && params().appendix())
1654 fmt = translateIfPossible(layout->labelstring_appendix(),
1657 fmt = translateIfPossible(layout->labelstring(), bparams);
1659 // handle 'inherited level parts' in 'fmt',
1660 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1661 size_t const i = fmt.find('@', 0);
1662 if (i != docstring::npos) {
1663 size_t const j = fmt.find('@', i + 1);
1664 if (j != docstring::npos) {
1665 docstring parent(fmt, i + 1, j - i - 1);
1666 docstring label = expandLabel(tclass[parent], bparams);
1667 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1671 return tclass.counters().counterLabel(fmt);
1675 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1678 LyXAlignment const oldAlign = params().align();
1679 // FIXME The first check is due to the fact that LYX_ALIGN_LAYOUT
1680 // is not required to be possible. A fix is on the way.
1681 if ((oldAlign != LYX_ALIGN_LAYOUT) &&
1682 !(oldAlign & layout()->alignpossible)) {
1683 frontend::Alert::warning(_("Alignment not permitted"),
1684 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1685 params().align(LYX_ALIGN_LAYOUT);
1690 pos_type Paragraph::beginOfBody() const
1692 return begin_of_body_;
1696 void Paragraph::setBeginOfBody()
1698 if (layout()->labeltype != LABEL_MANUAL) {
1703 // Unroll the first two cycles of the loop
1704 // and remember the previous character to
1705 // remove unnecessary getChar() calls
1707 pos_type end = size();
1708 if (i < end && !isNewline(i)) {
1710 char_type previous_char = 0;
1713 previous_char = text_[i];
1714 if (!isNewline(i)) {
1716 while (i < end && previous_char != ' ') {
1721 previous_char = temp;
1731 // returns -1 if inset not found
1732 int Paragraph::getPositionOfInset(Inset const * inset) const
1735 InsetList::const_iterator it = insetlist.begin();
1736 InsetList::const_iterator end = insetlist.end();
1737 for (; it != end; ++it)
1738 if (it->inset == inset)
1744 InsetBibitem * Paragraph::bibitem() const
1746 if (!insetlist.empty()) {
1747 Inset * inset = insetlist.begin()->inset;
1748 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1749 return static_cast<InsetBibitem *>(inset);
1755 bool Paragraph::forceDefaultParagraphs() const
1757 return inInset() && inInset()->forceDefaultParagraphs(0);
1763 // paragraphs inside floats need different alignment tags to avoid
1766 bool noTrivlistCentering(Inset::Code code)
1768 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1772 string correction(string const & orig)
1774 if (orig == "flushleft")
1775 return "raggedright";
1776 if (orig == "flushright")
1777 return "raggedleft";
1778 if (orig == "center")
1784 string const corrected_env(string const & suffix, string const & env,
1787 string output = suffix + "{";
1788 if (noTrivlistCentering(code))
1789 output += correction(env);
1793 if (suffix == "\\begin")
1799 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1801 if (!contains(str, "\n"))
1802 column += str.size();
1806 column = rsplit(str, tmp, '\n').size();
1813 // This could go to ParagraphParameters if we want to
1814 int Paragraph::startTeXParParams(BufferParams const & bparams,
1815 odocstream & os, TexRow & texrow,
1816 bool moving_arg) const
1820 if (params().noindent()) {
1821 os << "\\noindent ";
1825 LyXAlignment const curAlign = params().align();
1827 if (curAlign == layout()->align)
1831 case LYX_ALIGN_NONE:
1832 case LYX_ALIGN_BLOCK:
1833 case LYX_ALIGN_LAYOUT:
1834 case LYX_ALIGN_SPECIAL:
1836 case LYX_ALIGN_LEFT:
1837 case LYX_ALIGN_RIGHT:
1838 case LYX_ALIGN_CENTER:
1847 case LYX_ALIGN_NONE:
1848 case LYX_ALIGN_BLOCK:
1849 case LYX_ALIGN_LAYOUT:
1850 case LYX_ALIGN_SPECIAL:
1852 case LYX_ALIGN_LEFT: {
1854 if (getParLanguage(bparams)->babel() != "hebrew")
1855 output = corrected_env("\\begin", "flushleft", ownerCode());
1857 output = corrected_env("\\begin", "flushright", ownerCode());
1858 os << from_ascii(output);
1859 adjust_row_column(output, texrow, column);
1861 } case LYX_ALIGN_RIGHT: {
1863 if (getParLanguage(bparams)->babel() != "hebrew")
1864 output = corrected_env("\\begin", "flushright", ownerCode());
1866 output = corrected_env("\\begin", "flushleft", ownerCode());
1867 os << from_ascii(output);
1868 adjust_row_column(output, texrow, column);
1870 } case LYX_ALIGN_CENTER: {
1872 output = corrected_env("\\begin", "center", ownerCode());
1873 os << from_ascii(output);
1874 adjust_row_column(output, texrow, column);
1883 // This could go to ParagraphParameters if we want to
1884 int Paragraph::endTeXParParams(BufferParams const & bparams,
1885 odocstream & os, TexRow & texrow,
1886 bool moving_arg) const
1890 switch (params().align()) {
1891 case LYX_ALIGN_NONE:
1892 case LYX_ALIGN_BLOCK:
1893 case LYX_ALIGN_LAYOUT:
1894 case LYX_ALIGN_SPECIAL:
1896 case LYX_ALIGN_LEFT:
1897 case LYX_ALIGN_RIGHT:
1898 case LYX_ALIGN_CENTER:
1906 switch (params().align()) {
1907 case LYX_ALIGN_NONE:
1908 case LYX_ALIGN_BLOCK:
1909 case LYX_ALIGN_LAYOUT:
1910 case LYX_ALIGN_SPECIAL:
1912 case LYX_ALIGN_LEFT: {
1914 if (getParLanguage(bparams)->babel() != "hebrew")
1915 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1917 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1918 os << from_ascii(output);
1919 adjust_row_column(output, texrow, column);
1921 } case LYX_ALIGN_RIGHT: {
1923 if (getParLanguage(bparams)->babel() != "hebrew")
1924 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1926 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1927 os << from_ascii(output);
1928 adjust_row_column(output, texrow, column);
1930 } case LYX_ALIGN_CENTER: {
1932 output = corrected_env("\n\\par\\end", "center", ownerCode());
1933 os << from_ascii(output);
1934 adjust_row_column(output, texrow, column);
1943 // This one spits out the text of the paragraph
1944 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1945 BufferParams const & bparams,
1946 Font const & outerfont,
1947 odocstream & os, TexRow & texrow,
1948 OutputParams const & runparams) const
1950 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1952 bool return_value = false;
1956 // well we have to check if we are in an inset with unlimited
1957 // length (all in one row) if that is true then we don't allow
1958 // any special options in the paragraph and also we don't allow
1959 // any environment other than the default layout of the text class
1961 bool asdefault = forceDefaultParagraphs();
1964 style = bparams.getTextClass().defaultLayout();
1969 // Current base font for all inherited font changes, without any
1970 // change caused by an individual character, except for the language:
1971 // It is set to the language of the first character.
1972 // As long as we are in the label, this font is the base font of the
1973 // label. Before the first body character it is set to the base font
1977 // Maybe we have to create a optional argument.
1978 pos_type body_pos = beginOfBody();
1979 unsigned int column = 0;
1982 // the optional argument is kept in curly brackets in
1983 // case it contains a ']'
1986 basefont = getLabelFont(bparams, outerfont);
1988 basefont = getLayoutFont(bparams, outerfont);
1991 // Which font is currently active?
1992 Font running_font(basefont);
1993 // Do we have an open font change?
1994 bool open_font = false;
1996 Change runningChange = Change(Change::UNCHANGED);
1998 texrow.start(id(), 0);
2000 // if the paragraph is empty, the loop will not be entered at all
2002 if (style->isCommand()) {
2007 column += startTeXParParams(bparams, os, texrow,
2008 runparams.moving_arg);
2011 for (pos_type i = 0; i < size(); ++i) {
2012 // First char in paragraph or after label?
2013 if (i == body_pos) {
2016 column += running_font.latexWriteEndChanges(
2017 os, bparams, runparams,
2018 basefont, basefont);
2021 basefont = getLayoutFont(bparams, outerfont);
2022 running_font = basefont;
2024 column += Changes::latexMarkChange(os, bparams,
2025 runningChange, Change(Change::UNCHANGED));
2026 runningChange = Change(Change::UNCHANGED);
2031 if (style->isCommand()) {
2037 column += startTeXParParams(bparams, os,
2039 runparams.moving_arg);
2042 Change const & change = pimpl_->lookupChange(i);
2044 if (bparams.outputChanges && runningChange != change) {
2046 column += running_font.latexWriteEndChanges(
2047 os, bparams, runparams, basefont, basefont);
2050 basefont = getLayoutFont(bparams, outerfont);
2051 running_font = basefont;
2053 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2054 runningChange = change;
2057 // do not output text which is marked deleted
2058 // if change tracking output is disabled
2059 if (!bparams.outputChanges && change.type == Change::DELETED) {
2065 value_type const c = getChar(i);
2067 // Fully instantiated font
2068 Font const font = getFont(bparams, i, outerfont);
2070 Font const last_font = running_font;
2072 // Do we need to close the previous font?
2074 (font != running_font ||
2075 font.language() != running_font.language()))
2077 column += running_font.latexWriteEndChanges(
2078 os, bparams, runparams, basefont,
2079 (i == body_pos-1) ? basefont : font);
2080 running_font = basefont;
2084 // Switch file encoding if necessary
2085 if (runparams.encoding->package() == Encoding::inputenc &&
2086 font.language()->encoding()->package() == Encoding::inputenc) {
2087 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2088 runparams.moving_arg, *(runparams.encoding),
2089 *(font.language()->encoding()));
2090 if (enc_switch.first) {
2091 column += enc_switch.second;
2092 runparams.encoding = font.language()->encoding();
2096 // Do we need to change font?
2097 if ((font != running_font ||
2098 font.language() != running_font.language()) &&
2101 odocstringstream ods;
2102 column += font.latexWriteStartChanges(ods, bparams,
2103 runparams, basefont,
2105 running_font = font;
2107 docstring fontchange = ods.str();
2108 // check if the fontchange ends with a trailing blank
2109 // (like "\small " (see bug 3382)
2110 if (suffixIs(fontchange, ' ') && c == ' ')
2111 os << fontchange.substr(0, fontchange.size() - 1)
2112 << from_ascii("{}");
2118 // Do not print the separation of the optional argument
2119 // if style->pass_thru is false. This works because
2120 // simpleTeXSpecialChars ignores spaces if
2121 // style->pass_thru is false.
2122 if (i != body_pos - 1) {
2123 if (pimpl_->simpleTeXBlanks(
2124 *(runparams.encoding), os, texrow,
2125 i, column, font, *style))
2126 // A surrogate pair was output. We
2127 // must not call simpleTeXSpecialChars
2128 // in this iteration, since
2129 // simpleTeXBlanks incremented i, and
2130 // simpleTeXSpecialChars would output
2131 // the combining character again.
2136 OutputParams rp = runparams;
2137 rp.free_spacing = style->free_spacing;
2138 rp.local_font = &font;
2139 rp.intitle = style->intitle;
2140 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2141 texrow, rp, running_font,
2142 basefont, outerfont, open_font,
2143 runningChange, *style, i, column, c);
2145 // Set the encoding to that returned from simpleTeXSpecialChars (see
2146 // comment for encoding member in OutputParams.h)
2147 runparams.encoding = rp.encoding;
2150 // If we have an open font definition, we have to close it
2152 #ifdef FIXED_LANGUAGE_END_DETECTION
2155 .latexWriteEndChanges(os, bparams, runparams,
2157 next_->getFont(bparams, 0, outerfont));
2159 running_font.latexWriteEndChanges(os, bparams,
2160 runparams, basefont, basefont);
2163 #ifdef WITH_WARNINGS
2164 //#warning For now we ALWAYS have to close the foreign font settings if they are
2165 //#warning there as we start another \selectlanguage with the next paragraph if
2166 //#warning we are in need of this. This should be fixed sometime (Jug)
2168 running_font.latexWriteEndChanges(os, bparams, runparams,
2169 basefont, basefont);
2173 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2175 // Needed if there is an optional argument but no contents.
2176 if (body_pos > 0 && body_pos == size()) {
2178 return_value = false;
2182 column += endTeXParParams(bparams, os, texrow,
2183 runparams.moving_arg);
2186 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2187 return return_value;
2204 string tag_name(PAR_TAG const & pt) {
2206 case PAR_NONE: return "!-- --";
2207 case TT: return "tt";
2208 case SF: return "sf";
2209 case BF: return "bf";
2210 case IT: return "it";
2211 case SL: return "sl";
2212 case EM: return "em";
2219 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2221 p1 = static_cast<PAR_TAG>(p1 | p2);
2226 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2228 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2234 bool Paragraph::emptyTag() 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::TOC_CODE &&
2241 lyx_code != Inset::INCLUDE_CODE &&
2242 lyx_code != Inset::GRAPHICS_CODE &&
2243 lyx_code != Inset::ERT_CODE &&
2244 lyx_code != Inset::LISTINGS_CODE &&
2245 lyx_code != Inset::FLOAT_CODE &&
2246 lyx_code != Inset::TABULAR_CODE) {
2250 value_type c = getChar(i);
2251 if (c != ' ' && c != '\t')
2259 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2261 for (pos_type i = 0; i < size(); ++i) {
2263 Inset const * inset = getInset(i);
2264 Inset::Code lyx_code = inset->lyxCode();
2265 if (lyx_code == Inset::LABEL_CODE) {
2266 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2267 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2276 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2279 for (i = 0; i < size(); ++i) {
2281 Inset const * inset = getInset(i);
2282 inset->docbook(buf, os, runparams);
2284 value_type c = getChar(i);
2287 os << sgml::escapeChar(c);
2294 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2298 for (pos_type i = initial; i < size(); ++i) {
2299 Font font = getFont(buf.params(), i, outerfont);
2302 if (i != initial && font != font_old)
2311 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2313 OutputParams const & runparams,
2314 Font const & outerfont,
2315 pos_type initial) const
2317 bool emph_flag = false;
2319 Layout_ptr const & style = layout();
2321 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2323 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2326 // parsing main loop
2327 for (pos_type i = initial; i < size(); ++i) {
2328 Font font = getFont(buf.params(), i, outerfont);
2330 // handle <emphasis> tag
2331 if (font_old.emph() != font.emph()) {
2332 if (font.emph() == Font::ON) {
2335 } else if (i != initial) {
2336 os << "</emphasis>";
2342 Inset const * inset = getInset(i);
2343 inset->docbook(buf, os, runparams);
2345 value_type c = getChar(i);
2347 if (style->pass_thru)
2350 os << sgml::escapeChar(c);
2356 os << "</emphasis>";
2359 if (style->free_spacing)
2361 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2366 bool Paragraph::isNewline(pos_type pos) const
2369 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2373 bool Paragraph::isLineSeparator(pos_type pos) const
2375 value_type const c = getChar(pos);
2376 return isLineSeparatorChar(c)
2377 || (c == Paragraph::META_INSET && getInset(pos) &&
2378 getInset(pos)->isLineSeparator());
2382 /// Used by the spellchecker
2383 bool Paragraph::isLetter(pos_type pos) const
2386 return getInset(pos)->isLetter();
2388 value_type const c = getChar(pos);
2389 return isLetterChar(c) || isDigit(c);
2395 Paragraph::getParLanguage(BufferParams const & bparams) const
2398 return getFirstFontSettings(bparams).language();
2399 #ifdef WITH_WARNINGS
2400 #warning FIXME we should check the prev par as well (Lgb)
2402 return bparams.language;
2406 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2408 return lyxrc.rtl_support
2409 && getParLanguage(bparams)->rightToLeft()
2410 && ownerCode() != Inset::ERT_CODE
2411 && ownerCode() != Inset::LISTINGS_CODE;
2415 void Paragraph::changeLanguage(BufferParams const & bparams,
2416 Language const * from, Language const * to)
2418 // change language including dummy font change at the end
2419 for (pos_type i = 0; i <= size(); ++i) {
2420 Font font = getFontSettings(bparams, i);
2421 if (font.language() == from) {
2422 font.setLanguage(to);
2429 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2431 Language const * doc_language = bparams.language;
2432 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2433 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2435 for (; cit != end; ++cit)
2436 if (cit->font().language() != ignore_language &&
2437 cit->font().language() != latex_language &&
2438 cit->font().language() != doc_language)
2444 // Convert the paragraph to a string.
2445 // Used for building the table of contents
2446 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2448 return asString(buffer, 0, size(), label);
2452 docstring const Paragraph::asString(Buffer const & buffer,
2453 pos_type beg, pos_type end, bool label) const
2456 odocstringstream os;
2458 if (beg == 0 && label && !params().labelString().empty())
2459 os << params().labelString() << ' ';
2461 for (pos_type i = beg; i < end; ++i) {
2462 value_type const c = getChar(i);
2465 else if (c == META_INSET)
2466 getInset(i)->textString(buffer, os);
2473 void Paragraph::setInsetOwner(Inset * inset)
2475 pimpl_->inset_owner = inset;
2479 Change const & Paragraph::lookupChange(pos_type pos) const
2481 BOOST_ASSERT(pos <= size());
2482 return pimpl_->lookupChange(pos);
2486 bool Paragraph::isChanged(pos_type start, pos_type end) const
2488 return pimpl_->isChanged(start, end);
2492 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2494 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2498 void Paragraph::setChange(Change const & change)
2500 pimpl_->setChange(change);
2504 void Paragraph::setChange(pos_type pos, Change const & change)
2506 pimpl_->setChange(pos, change);
2510 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2512 return pimpl_->acceptChanges(bparams, start, end);
2516 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2518 return pimpl_->rejectChanges(bparams, start, end);
2522 int Paragraph::id() const
2528 Layout_ptr const & Paragraph::layout() const
2534 void Paragraph::layout(Layout_ptr const & new_layout)
2536 layout_ = new_layout;
2540 Inset * Paragraph::inInset() const
2542 return pimpl_->inset_owner;
2546 Inset::Code Paragraph::ownerCode() const
2548 return pimpl_->inset_owner
2549 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2553 ParagraphParameters & Paragraph::params()
2555 return pimpl_->params;
2559 ParagraphParameters const & Paragraph::params() const
2561 return pimpl_->params;
2565 bool Paragraph::isFreeSpacing() const
2567 if (layout()->free_spacing)
2570 // for now we just need this, later should we need this in some
2571 // other way we can always add a function to Inset too.
2572 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2576 bool Paragraph::allowEmpty() const
2578 if (layout()->keepempty)
2580 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2584 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2586 if (!Encodings::is_arabic(c))
2589 value_type prev_char = ' ';
2590 value_type next_char = ' ';
2592 for (pos_type i = pos - 1; i >= 0; --i) {
2593 value_type const par_char = getChar(i);
2594 if (!Encodings::isComposeChar_arabic(par_char)) {
2595 prev_char = par_char;
2600 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2601 value_type const par_char = getChar(i);
2602 if (!Encodings::isComposeChar_arabic(par_char)) {
2603 next_char = par_char;
2608 if (Encodings::is_arabic(next_char)) {
2609 if (Encodings::is_arabic(prev_char) &&
2610 !Encodings::is_arabic_special(prev_char))
2611 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2613 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2615 if (Encodings::is_arabic(prev_char) &&
2616 !Encodings::is_arabic_special(prev_char))
2617 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2619 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2624 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2629 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2631 // expand at the end of a row only if there is another hfill on the same row
2632 if (pos == row.endpos() - 1) {
2633 for (pos_type i = row.pos(); i < pos; i++) {
2640 // expand at the beginning of a row only if it is the first row of a paragraph
2641 if (pos == row.pos()) {
2645 // do not expand in some labels
2646 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2649 // if there is anything between the first char of the row and
2650 // the specified position that is neither a newline nor an hfill,
2651 // the hfill will be expanded, otherwise it won't
2652 for (pos_type i = row.pos(); i < pos; i++) {
2653 if (!isNewline(i) && !isHfill(i))
2660 int Paragraph::checkBiblio(bool track_changes)
2663 //This is getting more and more a mess. ...We really should clean
2664 //up this bibitem issue for 1.6. See also bug 2743.
2666 // Add bibitem insets if necessary
2667 if (layout()->labeltype != LABEL_BIBLIO)
2670 bool hasbibitem = !insetlist.empty()
2671 // Insist on it being in pos 0
2672 && getChar(0) == Paragraph::META_INSET
2673 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2678 // remove a bibitem in pos != 0
2679 // restore it later in pos 0 if necessary
2680 // (e.g. if a user inserts contents _before_ the item)
2681 // we're assuming there's only one of these, which there
2683 int erasedInsetPosition = -1;
2684 InsetList::iterator it = insetlist.begin();
2685 InsetList::iterator end = insetlist.end();
2686 for (; it != end; ++it)
2687 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2689 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2690 oldkey = olditem->getParam("key");
2691 oldlabel = olditem->getParam("label");
2692 erasedInsetPosition = it->pos;
2693 eraseChar(erasedInsetPosition, track_changes);
2697 //There was an InsetBibitem at the beginning, and we didn't
2698 //have to erase one.
2699 if (hasbibitem && erasedInsetPosition < 0)
2702 //There was an InsetBibitem at the beginning and we did have to
2703 //erase one. So we give its properties to the beginning inset.
2705 InsetBibitem * inset =
2706 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2707 if (!oldkey.empty())
2708 inset->setParam("key", oldkey);
2709 inset->setParam("label", oldlabel);
2710 return -erasedInsetPosition;
2713 //There was no inset at the beginning, so we need to create one with
2714 //the key and label of the one we erased.
2715 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2716 // restore values of previously deleted item in this par.
2717 if (!oldkey.empty())
2718 inset->setParam("key", oldkey);
2719 inset->setParam("label", oldlabel);
2720 insertInset(0, static_cast<Inset *>(inset),
2721 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2727 void Paragraph::checkAuthors(AuthorList const & authorList)
2729 pimpl_->changes_.checkAuthors(authorList);