3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Jean-Marc Lasgouttes
9 * \author Angus Leeming
11 * \author André Pönitz
13 * \author Jürgen Vigna
15 * Full author contact details are available in file CREDITS.
20 #include "Paragraph.h"
23 #include "BufferParams.h"
29 #include "LaTeXFeatures.h"
36 #include "OutputParams.h"
37 #include "output_latex.h"
38 #include "paragraph_funcs.h"
39 #include "ParagraphParameters.h"
44 #include "frontends/alert.h"
45 #include "frontends/FontMetrics.h"
47 #include "insets/InsetBibitem.h"
48 #include "insets/InsetOptArg.h"
50 #include "support/lstrings.h"
51 #include "support/textutils.h"
52 #include "support/convert.h"
53 #include "support/unicode.h"
55 #include <boost/bind.hpp>
56 #include <boost/next_prior.hpp>
68 using support::contains;
69 using support::prefixIs;
70 using support::suffixIs;
71 using support::rsplit;
75 /////////////////////////////////////////////////////////////////////
79 /////////////////////////////////////////////////////////////////////
85 class Paragraph::Pimpl {
88 Pimpl(Paragraph * owner);
89 /// "Copy constructor"
90 Pimpl(Pimpl const &, Paragraph * owner);
95 /// look up change at given pos
96 Change const & lookupChange(pos_type pos) const;
97 /// is there a change within the given range ?
98 bool isChanged(pos_type start, pos_type end) const;
99 /// will the paragraph be physically merged with the next
100 /// one if the imaginary end-of-par character is logically deleted?
101 bool isMergedOnEndOfParDeletion(bool trackChanges) const;
102 /// set change for the entire par
103 void setChange(Change const & change);
104 /// set change at given pos
105 void setChange(pos_type pos, Change const & change);
106 /// accept changes within the given range
107 void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end);
108 /// reject changes within the given range
109 void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end);
112 value_type getChar(pos_type pos) const;
114 void insertChar(pos_type pos, value_type c, Change const & change);
116 void insertInset(pos_type pos, Inset * inset, Change const & change);
117 /// (logically) erase the char at pos; return true if it was actually erased
118 bool eraseChar(pos_type pos, bool trackChanges);
119 /// (logically) erase the given range; return the number of chars actually erased
120 int eraseChars(pos_type start, pos_type end, bool trackChanges);
124 /** A font entry covers a range of positions. Notice that the
125 entries in the list are inserted in random order.
126 I don't think it's worth the effort to implement a more effective
127 datastructure, because the number of different fonts in a paragraph
129 Nevertheless, I decided to store fontlist using a sorted vector:
130 fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where
131 pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
132 and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
133 (font_1 covers the chars 0,...,pos_1) (Dekel)
138 FontTable(pos_type p, Font const & f)
142 pos_type pos() const { return pos_; }
144 void pos(pos_type p) { pos_ = p; }
146 Font const & font() const { return font_; }
148 void font(Font const & f) { font_ = f;}
150 /// End position of paragraph this font attribute covers
152 /** Font. Interpretation of the font values:
153 If a value is Font::INHERIT_*, it means that the font
154 attribute is inherited from either the layout of this
155 paragraph or, in the case of nested paragraphs, from the
156 layout in the environment one level up until completely
158 The values Font::IGNORE_* and Font::TOGGLE are NOT
159 allowed in these font tables.
164 friend class matchFT;
168 /// used by lower_bound and upper_bound
169 int operator()(FontTable const & a, FontTable const & b) const {
170 return a.pos() < b.pos();
175 typedef std::vector<FontTable> FontList;
179 /// Output the surrogate pair formed by \p c and \p next to \p os.
180 /// \return the number of characters written.
181 int latexSurrogatePair(odocstream & os, value_type c, value_type next,
183 /// Output a space in appropriate formatting (or a surrogate pair
184 /// if the next character is a combining character).
185 /// \return whether a surrogate pair was output.
186 bool simpleTeXBlanks(Encoding const &,
187 odocstream &, TexRow & texrow,
189 unsigned int & column,
191 Layout const & style);
192 /// Output consecutive known unicode chars, belonging to the same
193 /// language as specified by \p preamble, to \p os starting from \p c.
194 /// \return the number of characters written.
195 int knownLangChars(odocstream & os, value_type c, string & preamble,
196 Change &, Encoding const &, pos_type &);
198 void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
200 TexRow & texrow, OutputParams &,
203 Font const & outerfont,
205 Change & running_change,
206 Layout const & style,
208 unsigned int & column, value_type const c);
211 void validate(LaTeXFeatures & features,
212 Layout const & layout) const;
217 static unsigned int paragraph_id;
219 ParagraphParameters params;
223 pos_type size() const { return owner_->size(); }
224 /// match a string against a particular point in the paragraph
225 bool isTextAt(std::string const & str, pos_type pos) const;
227 /// for recording and looking up changes
238 using std::upper_bound;
239 using std::lower_bound;
243 // Initialization of the counter for the paragraph id's,
244 unsigned int Paragraph::Pimpl::paragraph_id = 0;
248 struct special_phrase {
254 special_phrase const special_phrases[] = {
255 { "LyX", from_ascii("\\LyX{}"), false },
256 { "TeX", from_ascii("\\TeX{}"), true },
257 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
258 { "LaTeX", from_ascii("\\LaTeX{}"), true },
261 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
266 Paragraph::Pimpl::Pimpl(Paragraph * owner)
270 id_ = paragraph_id++;
274 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
275 : params(p.params), changes_(p.changes_), owner_(owner)
277 inset_owner = p.inset_owner;
278 fontlist = p.fontlist;
279 id_ = paragraph_id++;
283 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
285 BOOST_ASSERT(start >= 0 && start <= size());
286 BOOST_ASSERT(end > start && end <= size() + 1);
288 return changes_.isChanged(start, end);
292 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
293 // keep the logic here in sync with the logic of eraseChars()
299 Change change = changes_.lookup(size());
301 return change.type == Change::INSERTED && change.author == 0;
305 void Paragraph::Pimpl::setChange(Change const & change)
307 // beware of the imaginary end-of-par character!
308 changes_.set(change, 0, size() + 1);
311 * Propagate the change recursively - but not in case of DELETED!
313 * Imagine that your co-author makes changes in an existing inset. He
314 * sends your document to you and you come to the conclusion that the
315 * inset should go completely. If you erase it, LyX must not delete all
316 * text within the inset. Otherwise, the change tracked insertions of
317 * your co-author get lost and there is no way to restore them later.
319 * Conclusion: An inset's content should remain untouched if you delete it
322 if (change.type != Change::DELETED) {
323 for (pos_type pos = 0; pos < size(); ++pos) {
324 if (owner_->isInset(pos)) {
325 owner_->getInset(pos)->setChange(change);
332 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
334 BOOST_ASSERT(pos >= 0 && pos <= size());
336 changes_.set(change, pos);
338 // see comment in setChange(Change const &) above
340 if (change.type != Change::DELETED &&
341 pos < size() && owner_->isInset(pos)) {
342 owner_->getInset(pos)->setChange(change);
347 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
349 BOOST_ASSERT(pos >= 0 && pos <= size());
351 return changes_.lookup(pos);
355 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
357 BOOST_ASSERT(start >= 0 && start <= size());
358 BOOST_ASSERT(end > start && end <= size() + 1);
360 for (pos_type pos = start; pos < end; ++pos) {
361 switch (lookupChange(pos).type) {
362 case Change::UNCHANGED:
363 // accept changes in nested inset
364 if (pos < size() && owner_->isInset(pos)) {
365 owner_->getInset(pos)->acceptChanges(bparams);
370 case Change::INSERTED:
371 changes_.set(Change(Change::UNCHANGED), pos);
372 // also accept changes in nested inset
373 if (pos < size() && owner_->isInset(pos)) {
374 owner_->getInset(pos)->acceptChanges(bparams);
378 case Change::DELETED:
379 // Suppress access to non-existent
380 // "end-of-paragraph char"
382 eraseChar(pos, false);
393 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
395 BOOST_ASSERT(start >= 0 && start <= size());
396 BOOST_ASSERT(end > start && end <= size() + 1);
398 for (pos_type pos = start; pos < end; ++pos) {
399 switch (lookupChange(pos).type) {
400 case Change::UNCHANGED:
401 // reject changes in nested inset
402 if (pos < size() && owner_->isInset(pos)) {
403 owner_->getInset(pos)->rejectChanges(bparams);
407 case Change::INSERTED:
408 // Suppress access to non-existent
409 // "end-of-paragraph char"
411 eraseChar(pos, false);
417 case Change::DELETED:
418 changes_.set(Change(Change::UNCHANGED), pos);
420 // Do NOT reject changes within a deleted inset!
421 // There may be insertions of a co-author inside of it!
429 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
431 BOOST_ASSERT(pos >= 0 && pos <= size());
433 return owner_->getChar(pos);
437 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
439 BOOST_ASSERT(pos >= 0 && pos <= size());
442 changes_.insert(change, pos);
444 // This is actually very common when parsing buffers (and
445 // maybe inserting ascii text)
447 // when appending characters, no need to update tables
448 owner_->text_.push_back(c);
452 owner_->text_.insert(owner_->text_.begin() + pos, c);
454 // Update the font table.
455 FontTable search_font(pos, Font());
456 for (FontList::iterator it
457 = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
458 it != fontlist.end(); ++it)
460 it->pos(it->pos() + 1);
464 owner_->insetlist.increasePosAfterPos(pos);
468 void Paragraph::Pimpl::insertInset(pos_type pos, Inset * inset,
469 Change const & change)
472 BOOST_ASSERT(pos >= 0 && pos <= size());
474 insertChar(pos, META_INSET, change);
475 BOOST_ASSERT(owner_->text_[pos] == META_INSET);
477 // Add a new entry in the insetlist.
478 owner_->insetlist.insert(inset, pos);
482 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
484 BOOST_ASSERT(pos >= 0 && pos <= size());
486 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
489 Change change = changes_.lookup(pos);
491 // set the character to DELETED if
492 // a) it was previously unchanged or
493 // b) it was inserted by a co-author
495 if (change.type == Change::UNCHANGED ||
496 (change.type == Change::INSERTED && change.author != 0)) {
497 setChange(pos, Change(Change::DELETED));
501 if (change.type == Change::DELETED)
505 // Don't physically access the imaginary end-of-paragraph character.
506 // eraseChar() can only mark it as DELETED. A physical deletion of
507 // end-of-par must be handled externally.
515 // if it is an inset, delete the inset entry
516 if (owner_->text_[pos] == Paragraph::META_INSET) {
517 owner_->insetlist.erase(pos);
520 owner_->text_.erase(owner_->text_.begin() + pos);
522 // Erase entries in the tables.
523 FontTable search_font(pos, Font());
525 FontList::iterator it =
526 lower_bound(fontlist.begin(),
528 search_font, matchFT());
529 if (it != fontlist.end() && it->pos() == pos &&
531 (it != fontlist.begin()
532 && boost::prior(it)->pos() == pos - 1))) {
533 // If it is a multi-character font
534 // entry, we just make it smaller
535 // (see update below), otherwise we
537 unsigned int const i = it - fontlist.begin();
538 fontlist.erase(fontlist.begin() + i);
539 it = fontlist.begin() + i;
540 if (i > 0 && i < fontlist.size() &&
541 fontlist[i - 1].font() == fontlist[i].font()) {
542 fontlist.erase(fontlist.begin() + i - 1);
543 it = fontlist.begin() + i - 1;
547 // Update all other entries
548 FontList::iterator fend = fontlist.end();
549 for (; it != fend; ++it)
550 it->pos(it->pos() - 1);
552 // Update the insetlist
553 owner_->insetlist.decreasePosAfterPos(pos);
559 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
561 BOOST_ASSERT(start >= 0 && start <= size());
562 BOOST_ASSERT(end >= start && end <= size() + 1);
565 for (pos_type count = end - start; count; --count) {
566 if (!eraseChar(i, trackChanges))
573 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
574 value_type next, Encoding const & encoding)
576 // Writing next here may circumvent a possible font change between
577 // c and next. Since next is only output if it forms a surrogate pair
578 // with c we can ignore this:
579 // A font change inside a surrogate pair does not make sense and is
580 // hopefully impossible to input.
581 // FIXME: change tracking
582 // Is this correct WRT change tracking?
583 docstring const latex1 = encoding.latexChar(next);
584 docstring const latex2 = encoding.latexChar(c);
585 os << latex1 << '{' << latex2 << '}';
586 return latex1.length() + latex2.length() + 2;
590 bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding,
591 odocstream & os, TexRow & texrow,
593 unsigned int & column,
595 Layout const & style)
600 if (i < size() - 1) {
601 char_type next = getChar(i + 1);
602 if (Encodings::isCombiningChar(next)) {
603 // This space has an accent, so we must always output it.
604 column += latexSurrogatePair(os, ' ', next, encoding) - 1;
610 if (lyxrc.plaintext_linelen > 0
611 && column > lyxrc.plaintext_linelen
613 && getChar(i - 1) != ' '
615 // same in FreeSpacing mode
616 && !owner_->isFreeSpacing()
617 // In typewriter mode, we want to avoid
618 // ! . ? : at the end of a line
619 && !(font.family() == Font::TYPEWRITER_FAMILY
620 && (getChar(i - 1) == '.'
621 || getChar(i - 1) == '?'
622 || getChar(i - 1) == ':'
623 || getChar(i - 1) == '!'))) {
626 texrow.start(owner_->id(), i + 1);
628 } else if (style.free_spacing) {
637 int Paragraph::Pimpl::knownLangChars(odocstream & os,
640 Change & runningChange,
641 Encoding const & encoding,
644 // The latex command is "\textLANG{<spec>}" and we have to retain
645 // "\textLANG{<spec>" for the first char but only "<spec>" for all
646 // subsequent chars (this also works when we are passed untranslated
648 docstring const latex1 = rtrim(encoding.latexChar(c), "}");
649 int length = latex1.length();
651 while (i < size() - 1) {
652 char_type next = getChar(i + 1);
653 if (!Encodings::isKnownLangChar(next, preamble) ||
654 runningChange != lookupChange(i + 1))
658 FontList::const_iterator cit = fontlist.begin();
659 FontList::const_iterator end = fontlist.end();
660 for (; cit != end; ++cit) {
661 if (cit->pos() >= i && !found) {
662 prev_font = cit->font();
665 if (cit->pos() >= i + 1)
668 if (found && cit != end && prev_font != cit->font())
670 docstring const latex = rtrim(encoding.latexChar(next), "}");
671 docstring::size_type const j =
672 latex.find_first_of(from_ascii("{"));
673 if (j == docstring::npos) {
675 length += latex.length();
677 os << latex.substr(j + 1);
678 length += latex.substr(j + 1).length();
682 // When the proper language is set, we are passed the straight unicode,
683 // so we should not try to close the \textLANG command.
684 if (prefixIs(latex1, from_ascii("\\" + preamble))) {
692 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
694 pos_type const len = str.length();
696 // is the paragraph large enough?
697 if (pos + len > size())
700 // does the wanted text start at point?
701 for (string::size_type i = 0; i < str.length(); ++i) {
702 // Caution: direct comparison of characters works only
703 // because str is pure ASCII.
704 if (str[i] != owner_->text_[pos + i])
708 // is there a font change in middle of the word?
709 FontList::const_iterator cit = fontlist.begin();
710 FontList::const_iterator end = fontlist.end();
711 for (; cit != end; ++cit) {
712 if (cit->pos() >= pos)
715 if (cit != end && pos + len - 1 > cit->pos())
722 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
723 BufferParams const & bparams,
726 OutputParams & runparams,
729 Font const & outerfont,
731 Change & running_change,
732 Layout const & style,
734 unsigned int & column,
737 if (style.pass_thru) {
738 if (c != Paragraph::META_INSET) {
740 // FIXME UNICODE: This can fail if c cannot
741 // be encoded in the current encoding.
744 owner_->getInset(i)->plaintext(buf, os, runparams);
748 // Two major modes: LaTeX or plain
749 // Handle here those cases common to both modes
750 // and then split to handle the two modes separately.
752 case Paragraph::META_INSET: {
753 Inset * inset = owner_->getInset(i);
755 // FIXME: remove this check
759 // FIXME: move this to InsetNewline::latex
760 if (inset->lyxCode() == Inset::NEWLINE_CODE) {
761 // newlines are handled differently here than
762 // the default in simpleTeXSpecialChars().
763 if (!style.newline_allowed) {
767 column += running_font.latexWriteEndChanges(
768 os, bparams, runparams,
773 if (running_font.family() == Font::TYPEWRITER_FAMILY)
776 basefont = owner_->getLayoutFont(bparams, outerfont);
777 running_font = basefont;
779 if (runparams.moving_arg)
785 texrow.start(owner_->id(), i + 1);
790 if (lookupChange(i).type == Change::DELETED) {
791 if( ++runparams.inDeletedInset == 1)
792 runparams.changeOfDeletedInset = lookupChange(i);
795 if (inset->canTrackChanges()) {
796 column += Changes::latexMarkChange(os, bparams, running_change,
797 Change(Change::UNCHANGED));
798 running_change = Change(Change::UNCHANGED);
802 odocstream::pos_type const len = os.tellp();
804 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
805 || inset->lyxCode() == Inset::MATH_CODE
806 || inset->lyxCode() == Inset::URL_CODE)
807 && running_font.isRightToLeft()) {
808 if (running_font.language()->lang() == "farsi")
815 // FIXME: Bug: we can have an empty font change here!
816 // if there has just been a font change, we are going to close it
817 // right now, which means stupid latex code like \textsf{}. AFAIK,
818 // this does not harm dvi output. A minor bug, thus (JMarc)
819 // Some insets cannot be inside a font change command.
820 // However, even such insets *can* be placed in \L or \R
821 // or their equivalents (for RTL language switches), so we don't
822 // close the language in those cases.
823 // ArabTeX, though, cannot handle this special behavior, it seems.
824 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
825 running_font.language()->lang() == "arabic_arabtex";
826 if (open_font && inset->noFontChange()) {
827 bool closeLanguage = arabtex ||
828 basefont.isRightToLeft() == running_font.isRightToLeft();
829 unsigned int count = running_font.latexWriteEndChanges(
830 os, bparams, runparams,
831 basefont, basefont, closeLanguage);
833 // if any font properties were closed, update the running_font,
834 // making sure, however, to leave the language as it was
836 // FIXME: probably a better way to keep track of the old
837 // language, than copying the entire font?
838 Font const copy_font(running_font);
839 basefont = owner_->getLayoutFont(bparams, outerfont);
840 running_font = basefont;
842 running_font.setLanguage(copy_font.language());
843 // leave font open if language is still open
844 open_font = (running_font.language() == basefont.language());
846 runparams.local_font = &basefont;
850 int tmp = inset->latex(buf, os, runparams);
853 if (running_font.language()->lang() == "farsi")
860 for (int j = 0; j < tmp; ++j) {
863 texrow.start(owner_->id(), i + 1);
866 column += os.tellp() - len;
869 if (lookupChange(i).type == Change::DELETED) {
870 --runparams.inDeletedInset;
876 // And now for the special cases within each mode
880 os << "\\textbackslash{}";
884 case '|': case '<': case '>':
885 // In T1 encoding, these characters exist
886 if (lyxrc.fontenc == "T1") {
888 //... but we should avoid ligatures
889 if ((c == '>' || c == '<')
891 && getChar(i + 1) == c) {
892 //os << "\\textcompwordmark{}";
894 // Jean-Marc, have a look at
895 // this. I think this works
903 // Typewriter font also has them
904 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
908 // Otherwise, we use what LaTeX
912 os << "\\textless{}";
916 os << "\\textgreater{}";
926 case '-': // "--" in Typewriter mode -> "-{}-"
927 if (i <= size() - 2 &&
928 getChar(i + 1) == '-' &&
929 running_font.family() == Font::TYPEWRITER_FAMILY) {
938 os << "\\char`\\\"{}";
943 case '%': case '#': case '{':
951 os << "\\textasciitilde{}";
956 os << "\\textasciicircum{}";
961 // avoid being mistaken for optional arguments
969 // Blanks are printed before font switching.
970 // Sure? I am not! (try nice-latex)
971 // I am sure it's correct. LyX might be smarter
972 // in the future, but for now, nothing wrong is
978 // I assume this is hack treating typewriter as verbatim
979 // FIXME UNICODE: This can fail if c cannot be encoded
980 // in the current encoding.
981 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
990 // FIXME: if we have "LaTeX" with a font
991 // change in the middle (before the 'T', then
992 // the "TeX" part is still special cased.
993 // Really we should only operate this on
994 // "words" for some definition of word
998 for (; pnr < phrases_nr; ++pnr) {
999 if (isTextAt(special_phrases[pnr].phrase, i)) {
1000 os << special_phrases[pnr].macro;
1001 i += special_phrases[pnr].phrase.length() - 1;
1002 column += special_phrases[pnr].macro.length() - 1;
1007 if (pnr == phrases_nr && c != '\0') {
1008 Encoding const & encoding = *(runparams.encoding);
1009 if (i < size() - 1) {
1010 char_type next = getChar(i + 1);
1011 if (Encodings::isCombiningChar(next)) {
1012 column += latexSurrogatePair(os, c, next, encoding) - 1;
1018 if (Encodings::isKnownLangChar(c, preamble)) {
1020 knownLangChars(os, c, preamble,
1025 docstring const latex = encoding.latexChar(c);
1026 if (latex.length() > 1 &&
1027 latex[latex.length() - 1] != '}') {
1028 // Prevent eating of a following
1029 // space or command corruption by
1030 // following characters
1031 column += latex.length() + 1;
1032 os << latex << "{}";
1034 column += latex.length() - 1;
1044 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
1045 Layout const & layout) const
1047 BufferParams const & bparams = features.bufferParams();
1049 // check the params.
1050 if (!params.spacing().isDefault())
1051 features.require("setspace");
1054 features.useLayout(layout.name());
1057 Language const * doc_language = bparams.language;
1059 FontList::const_iterator fcit = fontlist.begin();
1060 FontList::const_iterator fend = fontlist.end();
1061 for (; fcit != fend; ++fcit) {
1062 if (fcit->font().noun() == Font::ON) {
1063 LYXERR(Debug::LATEX) << "font.noun: "
1064 << fcit->font().noun()
1066 features.require("noun");
1067 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
1068 << to_utf8(fcit->font().stateText(0))
1071 switch (fcit->font().color()) {
1073 case Color::inherit:
1075 // probably we should put here all interface colors used for
1076 // font displaying! For now I just add this ones I know of (Jug)
1081 features.require("color");
1082 LYXERR(Debug::LATEX) << "Color enabled. Font: "
1083 << to_utf8(fcit->font().stateText(0))
1087 Language const * language = fcit->font().language();
1088 if (language->babel() != doc_language->babel() &&
1089 language != ignore_language &&
1090 language != latex_language)
1092 features.useLanguage(language);
1093 LYXERR(Debug::LATEX) << "Found language "
1094 << language->lang() << endl;
1098 if (!params.leftIndent().zero())
1099 features.require("ParagraphLeftIndent");
1102 InsetList::const_iterator icit = owner_->insetlist.begin();
1103 InsetList::const_iterator iend = owner_->insetlist.end();
1104 for (; icit != iend; ++icit) {
1106 icit->inset->validate(features);
1107 if (layout.needprotect &&
1108 icit->inset->lyxCode() == Inset::FOOT_CODE)
1109 features.require("NeedLyXFootnoteCode");
1113 // then the contents
1114 for (pos_type i = 0; i < size() ; ++i) {
1115 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1116 if (!special_phrases[pnr].builtin
1117 && isTextAt(special_phrases[pnr].phrase, i)) {
1118 features.require(special_phrases[pnr].phrase);
1122 Encodings::validate(getChar(i), features);
1130 /////////////////////////////////////////////////////////////////////
1134 /////////////////////////////////////////////////////////////////////
1138 Paragraph::Paragraph()
1139 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1146 Paragraph::Paragraph(Paragraph const & par)
1147 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1148 layout_(par.layout_),
1149 text_(par.text_), begin_of_body_(par.begin_of_body_),
1150 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1152 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1153 InsetList::iterator it = insetlist.begin();
1154 InsetList::iterator end = insetlist.end();
1155 for (; it != end; ++it)
1156 it->inset = it->inset->clone();
1160 Paragraph & Paragraph::operator=(Paragraph const & par)
1162 // needed as we will destroy the pimpl_ before copying it
1164 itemdepth = par.itemdepth;
1166 insetlist = par.insetlist;
1167 InsetList::iterator it = insetlist.begin();
1168 InsetList::iterator end = insetlist.end();
1169 for (; it != end; ++it)
1170 it->inset = it->inset->clone();
1172 layout_ = par.layout();
1174 begin_of_body_ = par.begin_of_body_;
1177 pimpl_ = new Pimpl(*par.pimpl_, this);
1183 Paragraph::~Paragraph()
1187 //lyxerr << "Paragraph::paragraph_id = "
1188 // << Paragraph::paragraph_id << endl;
1192 void Paragraph::write(Buffer const & buf, ostream & os,
1193 BufferParams const & bparams,
1194 depth_type & dth) const
1196 // The beginning or end of a deeper (i.e. nested) area?
1197 if (dth != params().depth()) {
1198 if (params().depth() > dth) {
1199 while (params().depth() > dth) {
1200 os << "\n\\begin_deeper";
1204 while (params().depth() < dth) {
1205 os << "\n\\end_deeper";
1211 // First write the layout
1212 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1216 Font font1(Font::ALL_INHERIT, bparams.language);
1218 Change running_change = Change(Change::UNCHANGED);
1221 for (pos_type i = 0; i <= size(); ++i) {
1223 Change change = pimpl_->lookupChange(i);
1224 Changes::lyxMarkChange(os, column, running_change, change);
1225 running_change = change;
1230 // Write font changes
1231 Font font2 = getFontSettings(bparams, i);
1232 if (font2 != font1) {
1233 font2.lyxWriteChanges(font1, os);
1238 value_type const c = getChar(i);
1242 Inset const * inset = getInset(i);
1244 if (inset->directWrite()) {
1245 // international char, let it write
1246 // code directly so it's shorter in
1248 inset->write(buf, os);
1252 os << "\\begin_inset ";
1253 inset->write(buf, os);
1254 os << "\n\\end_inset\n\n";
1260 os << "\n\\backslash\n";
1264 if (i + 1 < size() && getChar(i + 1) == ' ') {
1271 if ((column > 70 && c == ' ')
1276 // this check is to amend a bug. LyX sometimes
1277 // inserts '\0' this could cause problems.
1279 std::vector<char> tmp = ucs4_to_utf8(c);
1280 tmp.push_back('\0');
1283 lyxerr << "ERROR (Paragraph::writeFile):"
1284 " NULL char in structure." << endl;
1290 os << "\n\\end_layout\n";
1294 void Paragraph::validate(LaTeXFeatures & features) const
1296 pimpl_->validate(features, *layout());
1300 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1302 return pimpl_->eraseChar(pos, trackChanges);
1306 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1308 return pimpl_->eraseChars(start, end, trackChanges);
1312 void Paragraph::insert(pos_type start, docstring const & str,
1313 Font const & font, Change const & change)
1315 for (size_t i = 0, n = str.size(); i != n ; ++i)
1316 insertChar(start + i, str[i], font, change);
1320 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1323 pimpl_->insertChar(pos, c, Change(trackChanges ?
1324 Change::INSERTED : Change::UNCHANGED));
1328 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1329 Font const & font, bool trackChanges)
1331 pimpl_->insertChar(pos, c, Change(trackChanges ?
1332 Change::INSERTED : Change::UNCHANGED));
1337 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1338 Font const & font, Change const & change)
1340 pimpl_->insertChar(pos, c, change);
1345 void Paragraph::insertInset(pos_type pos, Inset * inset,
1346 Change const & change)
1348 pimpl_->insertInset(pos, inset, change);
1352 void Paragraph::insertInset(pos_type pos, Inset * inset,
1353 Font const & font, Change const & change)
1355 pimpl_->insertInset(pos, inset, change);
1356 // Set the font/language of the inset...
1361 bool Paragraph::insetAllowed(Inset_code code)
1363 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1367 // Gets uninstantiated font setting at position.
1368 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1372 lyxerr << " pos: " << pos << " size: " << size() << endl;
1373 BOOST_ASSERT(pos <= size());
1376 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1377 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1378 for (; cit != end; ++cit)
1379 if (cit->pos() >= pos)
1385 if (pos == size() && !empty())
1386 return getFontSettings(bparams, pos - 1);
1388 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1392 FontSpan Paragraph::fontSpan(pos_type pos) const
1394 BOOST_ASSERT(pos <= size());
1397 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1398 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1399 for (; cit != end; ++cit) {
1400 if (cit->pos() >= pos) {
1401 if (pos >= beginOfBody())
1402 return FontSpan(std::max(start, beginOfBody()),
1405 return FontSpan(start,
1406 std::min(beginOfBody() - 1,
1409 start = cit->pos() + 1;
1412 // This should not happen, but if so, we take no chances.
1413 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1415 return FontSpan(pos, pos);
1419 // Gets uninstantiated font setting at position 0
1420 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1422 if (!empty() && !pimpl_->fontlist.empty())
1423 return pimpl_->fontlist[0].font();
1425 return Font(Font::ALL_INHERIT, bparams.language);
1429 // Gets the fully instantiated font at a given position in a paragraph
1430 // This is basically the same function as Text::GetFont() in text2.cpp.
1431 // The difference is that this one is used for generating the LaTeX file,
1432 // and thus cosmetic "improvements" are disallowed: This has to deliver
1433 // the true picture of the buffer. (Asger)
1434 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1435 Font const & outerfont) const
1437 BOOST_ASSERT(pos >= 0);
1439 Font font = getFontSettings(bparams, pos);
1441 pos_type const body_pos = beginOfBody();
1443 font.realize(layout_->labelfont);
1445 font.realize(layout_->font);
1447 font.realize(outerfont);
1448 font.realize(bparams.getFont());
1454 Font const Paragraph::getLabelFont
1455 (BufferParams const & bparams, Font const & outerfont) const
1457 Font tmpfont = layout()->labelfont;
1458 tmpfont.setLanguage(getParLanguage(bparams));
1459 tmpfont.realize(outerfont);
1460 tmpfont.realize(bparams.getFont());
1465 Font const Paragraph::getLayoutFont
1466 (BufferParams const & bparams, Font const & outerfont) const
1468 Font tmpfont = layout()->font;
1469 tmpfont.setLanguage(getParLanguage(bparams));
1470 tmpfont.realize(outerfont);
1471 tmpfont.realize(bparams.getFont());
1476 /// Returns the height of the highest font in range
1477 Font_size Paragraph::highestFontInRange
1478 (pos_type startpos, pos_type endpos, Font_size def_size) const
1480 if (pimpl_->fontlist.empty())
1483 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1484 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1485 for (; end_it != end; ++end_it) {
1486 if (end_it->pos() >= endpos)
1493 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1494 for (; cit != end; ++cit) {
1495 if (cit->pos() >= startpos)
1499 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1500 for (; cit != end_it; ++cit) {
1501 Font::FONT_SIZE size = cit->font().size();
1502 if (size == Font::INHERIT_SIZE)
1504 if (size > maxsize && size <= Font::SIZE_HUGER)
1511 Paragraph::value_type
1512 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1514 value_type c = getChar(pos);
1515 if (!lyxrc.rtl_support)
1545 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1552 void Paragraph::setFont(pos_type pos, Font const & font)
1554 BOOST_ASSERT(pos <= size());
1556 // First, reduce font against layout/label font
1557 // Update: The setCharFont() routine in text2.cpp already
1558 // reduces font, so we don't need to do that here. (Asger)
1559 // No need to simplify this because it will disappear
1560 // in a new kernel. (Asger)
1561 // Next search font table
1563 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1564 Pimpl::FontList::iterator it = beg;
1565 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1566 for (; it != endit; ++it) {
1567 if (it->pos() >= pos)
1570 size_t const i = distance(beg, it);
1571 bool notfound = (it == endit);
1573 if (!notfound && pimpl_->fontlist[i].font() == font)
1576 bool begin = pos == 0 || notfound ||
1577 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1578 // Is position pos is a beginning of a font block?
1579 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1580 // Is position pos is the end of a font block?
1581 if (begin && end) { // A single char block
1582 if (i + 1 < pimpl_->fontlist.size() &&
1583 pimpl_->fontlist[i + 1].font() == font) {
1584 // Merge the singleton block with the next block
1585 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1586 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1587 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1588 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1589 // Merge the singleton block with the previous block
1590 pimpl_->fontlist[i - 1].pos(pos);
1591 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1593 pimpl_->fontlist[i].font(font);
1595 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1596 pimpl_->fontlist[i - 1].pos(pos);
1598 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1599 Pimpl::FontTable(pos, font));
1601 pimpl_->fontlist[i].pos(pos - 1);
1602 if (!(i + 1 < pimpl_->fontlist.size() &&
1603 pimpl_->fontlist[i + 1].font() == font))
1604 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1605 Pimpl::FontTable(pos, font));
1606 } else { // The general case. The block is splitted into 3 blocks
1607 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1608 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1609 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1610 Pimpl::FontTable(pos, font));
1615 void Paragraph::makeSameLayout(Paragraph const & par)
1617 layout(par.layout());
1619 params() = par.params();
1623 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1625 if (isFreeSpacing())
1631 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1632 if (eraseChar(pos, trackChanges))
1638 return count > 0 || pos > 0;
1642 bool Paragraph::hasSameLayout(Paragraph const & par) const
1644 return par.layout() == layout() && params().sameLayout(par.params());
1648 depth_type Paragraph::getDepth() const
1650 return params().depth();
1654 depth_type Paragraph::getMaxDepthAfter() const
1656 if (layout()->isEnvironment())
1657 return params().depth() + 1;
1659 return params().depth();
1663 char Paragraph::getAlign() const
1665 if (params().align() == LYX_ALIGN_LAYOUT)
1666 return layout()->align;
1668 return params().align();
1672 docstring const & Paragraph::getLabelstring() const
1674 return params().labelString();
1678 // the next two functions are for the manual labels
1679 docstring const Paragraph::getLabelWidthString() const
1681 if (layout()->margintype == MARGIN_MANUAL)
1682 return params().labelWidthString();
1684 return _("Senseless with this layout!");
1688 void Paragraph::setLabelWidthString(docstring const & s)
1690 params().labelWidthString(s);
1694 docstring const Paragraph::translateIfPossible(docstring const & s,
1695 BufferParams const & bparams) const
1697 if (!support::isAscii(s) || s.empty()) {
1698 // This must be a user defined layout. We cannot translate
1699 // this, since gettext accepts only ascii keys.
1702 // Probably standard layout, try to translate
1703 Messages & m = getMessages(getParLanguage(bparams)->code());
1704 return m.get(to_ascii(s));
1708 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1709 BufferParams const & bparams, bool process_appendix) const
1711 TextClass const & tclass = bparams.getTextClass();
1714 if (process_appendix && params().appendix())
1715 fmt = translateIfPossible(layout->labelstring_appendix(),
1718 fmt = translateIfPossible(layout->labelstring(), bparams);
1720 if (fmt.empty() && layout->labeltype == LABEL_COUNTER
1721 && !layout->counter.empty())
1722 fmt = "\\the" + layout->counter;
1724 // handle 'inherited level parts' in 'fmt',
1725 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1726 size_t const i = fmt.find('@', 0);
1727 if (i != docstring::npos) {
1728 size_t const j = fmt.find('@', i + 1);
1729 if (j != docstring::npos) {
1730 docstring parent(fmt, i + 1, j - i - 1);
1731 docstring label = from_ascii("??");
1732 if (tclass.hasLayout(parent))
1733 docstring label = expandLabel(tclass[parent], bparams,
1735 fmt = docstring(fmt, 0, i) + label
1736 + docstring(fmt, j + 1, docstring::npos);
1740 return tclass.counters().counterLabel(fmt);
1744 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1747 LyXAlignment const oldAlign = params().align();
1749 if (!(oldAlign & layout()->alignpossible)) {
1750 frontend::Alert::warning(_("Alignment not permitted"),
1751 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1752 params().align(LYX_ALIGN_LAYOUT);
1757 pos_type Paragraph::beginOfBody() const
1759 return begin_of_body_;
1763 void Paragraph::setBeginOfBody()
1765 if (layout()->labeltype != LABEL_MANUAL) {
1770 // Unroll the first two cycles of the loop
1771 // and remember the previous character to
1772 // remove unnecessary getChar() calls
1774 pos_type end = size();
1775 if (i < end && !isNewline(i)) {
1777 char_type previous_char = 0;
1780 previous_char = text_[i];
1781 if (!isNewline(i)) {
1783 while (i < end && previous_char != ' ') {
1788 previous_char = temp;
1798 // returns -1 if inset not found
1799 int Paragraph::getPositionOfInset(Inset const * inset) const
1802 InsetList::const_iterator it = insetlist.begin();
1803 InsetList::const_iterator end = insetlist.end();
1804 for (; it != end; ++it)
1805 if (it->inset == inset)
1811 InsetBibitem * Paragraph::bibitem() const
1813 if (!insetlist.empty()) {
1814 Inset * inset = insetlist.begin()->inset;
1815 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1816 return static_cast<InsetBibitem *>(inset);
1822 bool Paragraph::forceDefaultParagraphs() const
1824 return inInset() && inInset()->forceDefaultParagraphs(0);
1830 // paragraphs inside floats need different alignment tags to avoid
1833 bool noTrivlistCentering(Inset::Code code)
1835 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1839 string correction(string const & orig)
1841 if (orig == "flushleft")
1842 return "raggedright";
1843 if (orig == "flushright")
1844 return "raggedleft";
1845 if (orig == "center")
1851 string const corrected_env(string const & suffix, string const & env,
1854 string output = suffix + "{";
1855 if (noTrivlistCentering(code))
1856 output += correction(env);
1860 if (suffix == "\\begin")
1866 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1868 if (!contains(str, "\n"))
1869 column += str.size();
1873 column = rsplit(str, tmp, '\n').size();
1880 // This could go to ParagraphParameters if we want to
1881 int Paragraph::startTeXParParams(BufferParams const & bparams,
1882 odocstream & os, TexRow & texrow,
1883 bool moving_arg) const
1887 if (params().noindent()) {
1888 os << "\\noindent ";
1892 LyXAlignment const curAlign = params().align();
1894 if (curAlign == layout()->align)
1898 case LYX_ALIGN_NONE:
1899 case LYX_ALIGN_BLOCK:
1900 case LYX_ALIGN_LAYOUT:
1901 case LYX_ALIGN_SPECIAL:
1903 case LYX_ALIGN_LEFT:
1904 case LYX_ALIGN_RIGHT:
1905 case LYX_ALIGN_CENTER:
1914 case LYX_ALIGN_NONE:
1915 case LYX_ALIGN_BLOCK:
1916 case LYX_ALIGN_LAYOUT:
1917 case LYX_ALIGN_SPECIAL:
1919 case LYX_ALIGN_LEFT: {
1921 if (getParLanguage(bparams)->babel() != "hebrew")
1922 output = corrected_env("\\begin", "flushleft", ownerCode());
1924 output = corrected_env("\\begin", "flushright", ownerCode());
1925 os << from_ascii(output);
1926 adjust_row_column(output, texrow, column);
1928 } case LYX_ALIGN_RIGHT: {
1930 if (getParLanguage(bparams)->babel() != "hebrew")
1931 output = corrected_env("\\begin", "flushright", ownerCode());
1933 output = corrected_env("\\begin", "flushleft", ownerCode());
1934 os << from_ascii(output);
1935 adjust_row_column(output, texrow, column);
1937 } case LYX_ALIGN_CENTER: {
1939 output = corrected_env("\\begin", "center", ownerCode());
1940 os << from_ascii(output);
1941 adjust_row_column(output, texrow, column);
1950 // This could go to ParagraphParameters if we want to
1951 int Paragraph::endTeXParParams(BufferParams const & bparams,
1952 odocstream & os, TexRow & texrow,
1953 bool moving_arg) const
1957 switch (params().align()) {
1958 case LYX_ALIGN_NONE:
1959 case LYX_ALIGN_BLOCK:
1960 case LYX_ALIGN_LAYOUT:
1961 case LYX_ALIGN_SPECIAL:
1963 case LYX_ALIGN_LEFT:
1964 case LYX_ALIGN_RIGHT:
1965 case LYX_ALIGN_CENTER:
1973 switch (params().align()) {
1974 case LYX_ALIGN_NONE:
1975 case LYX_ALIGN_BLOCK:
1976 case LYX_ALIGN_LAYOUT:
1977 case LYX_ALIGN_SPECIAL:
1979 case LYX_ALIGN_LEFT: {
1981 if (getParLanguage(bparams)->babel() != "hebrew")
1982 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1984 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1985 os << from_ascii(output);
1986 adjust_row_column(output, texrow, column);
1988 } case LYX_ALIGN_RIGHT: {
1990 if (getParLanguage(bparams)->babel() != "hebrew")
1991 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1993 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1994 os << from_ascii(output);
1995 adjust_row_column(output, texrow, column);
1997 } case LYX_ALIGN_CENTER: {
1999 output = corrected_env("\n\\par\\end", "center", ownerCode());
2000 os << from_ascii(output);
2001 adjust_row_column(output, texrow, column);
2010 // This one spits out the text of the paragraph
2011 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
2012 BufferParams const & bparams,
2013 Font const & outerfont,
2014 odocstream & os, TexRow & texrow,
2015 OutputParams const & runparams) const
2017 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
2019 bool return_value = false;
2023 // well we have to check if we are in an inset with unlimited
2024 // length (all in one row) if that is true then we don't allow
2025 // any special options in the paragraph and also we don't allow
2026 // any environment other than the default layout of the text class
2028 bool asdefault = forceDefaultParagraphs();
2031 style = bparams.getTextClass().defaultLayout();
2036 // Current base font for all inherited font changes, without any
2037 // change caused by an individual character, except for the language:
2038 // It is set to the language of the first character.
2039 // As long as we are in the label, this font is the base font of the
2040 // label. Before the first body character it is set to the base font
2044 // Maybe we have to create a optional argument.
2045 pos_type body_pos = beginOfBody();
2046 unsigned int column = 0;
2049 // the optional argument is kept in curly brackets in
2050 // case it contains a ']'
2053 basefont = getLabelFont(bparams, outerfont);
2055 basefont = getLayoutFont(bparams, outerfont);
2058 // Which font is currently active?
2059 Font running_font(basefont);
2060 // Do we have an open font change?
2061 bool open_font = false;
2063 Change runningChange = Change(Change::UNCHANGED);
2065 texrow.start(id(), 0);
2067 // if the paragraph is empty, the loop will not be entered at all
2069 if (style->isCommand()) {
2074 column += startTeXParParams(bparams, os, texrow,
2075 runparams.moving_arg);
2078 for (pos_type i = 0; i < size(); ++i) {
2079 // First char in paragraph or after label?
2080 if (i == body_pos) {
2083 column += running_font.latexWriteEndChanges(
2084 os, bparams, runparams,
2085 basefont, basefont);
2088 basefont = getLayoutFont(bparams, outerfont);
2089 running_font = basefont;
2091 column += Changes::latexMarkChange(os, bparams,
2092 runningChange, Change(Change::UNCHANGED));
2093 runningChange = Change(Change::UNCHANGED);
2098 if (style->isCommand()) {
2104 column += startTeXParParams(bparams, os,
2106 runparams.moving_arg);
2109 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2110 : pimpl_->lookupChange(i);
2112 if (bparams.outputChanges && runningChange != change) {
2114 column += running_font.latexWriteEndChanges(
2115 os, bparams, runparams, basefont, basefont);
2118 basefont = getLayoutFont(bparams, outerfont);
2119 running_font = basefont;
2121 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2122 runningChange = change;
2125 // do not output text which is marked deleted
2126 // if change tracking output is disabled
2127 if (!bparams.outputChanges && change.type == Change::DELETED) {
2133 value_type const c = getChar(i);
2135 // Fully instantiated font
2136 Font const font = getFont(bparams, i, outerfont);
2138 Font const last_font = running_font;
2140 // Do we need to close the previous font?
2142 (font != running_font ||
2143 font.language() != running_font.language()))
2145 column += running_font.latexWriteEndChanges(
2146 os, bparams, runparams, basefont,
2147 (i == body_pos-1) ? basefont : font);
2148 running_font = basefont;
2152 // Switch file encoding if necessary
2153 if (runparams.encoding->package() == Encoding::inputenc &&
2154 font.language()->encoding()->package() == Encoding::inputenc) {
2155 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2156 runparams.moving_arg, *(runparams.encoding),
2157 *(font.language()->encoding()));
2158 if (enc_switch.first) {
2159 column += enc_switch.second;
2160 runparams.encoding = font.language()->encoding();
2164 // Do we need to change font?
2165 if ((font != running_font ||
2166 font.language() != running_font.language()) &&
2169 odocstringstream ods;
2170 column += font.latexWriteStartChanges(ods, bparams,
2171 runparams, basefont,
2173 running_font = font;
2175 docstring fontchange = ods.str();
2176 // check if the fontchange ends with a trailing blank
2177 // (like "\small " (see bug 3382)
2178 if (suffixIs(fontchange, ' ') && c == ' ')
2179 os << fontchange.substr(0, fontchange.size() - 1)
2180 << from_ascii("{}");
2186 // Do not print the separation of the optional argument
2187 // if style->pass_thru is false. This works because
2188 // simpleTeXSpecialChars ignores spaces if
2189 // style->pass_thru is false.
2190 if (i != body_pos - 1) {
2191 if (pimpl_->simpleTeXBlanks(
2192 *(runparams.encoding), os, texrow,
2193 i, column, font, *style))
2194 // A surrogate pair was output. We
2195 // must not call simpleTeXSpecialChars
2196 // in this iteration, since
2197 // simpleTeXBlanks incremented i, and
2198 // simpleTeXSpecialChars would output
2199 // the combining character again.
2204 OutputParams rp = runparams;
2205 rp.free_spacing = style->free_spacing;
2206 rp.local_font = &font;
2207 rp.intitle = style->intitle;
2208 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2209 texrow, rp, running_font,
2210 basefont, outerfont, open_font,
2211 runningChange, *style, i, column, c);
2213 // Set the encoding to that returned from simpleTeXSpecialChars (see
2214 // comment for encoding member in OutputParams.h)
2215 runparams.encoding = rp.encoding;
2218 // If we have an open font definition, we have to close it
2220 #ifdef FIXED_LANGUAGE_END_DETECTION
2223 .latexWriteEndChanges(os, bparams, runparams,
2225 next_->getFont(bparams, 0, outerfont));
2227 running_font.latexWriteEndChanges(os, bparams,
2228 runparams, basefont, basefont);
2231 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2232 //FIXME: there as we start another \selectlanguage with the next paragraph if
2233 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2234 running_font.latexWriteEndChanges(os, bparams, runparams,
2235 basefont, basefont);
2239 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2241 // Needed if there is an optional argument but no contents.
2242 if (body_pos > 0 && body_pos == size()) {
2244 return_value = false;
2248 column += endTeXParParams(bparams, os, texrow,
2249 runparams.moving_arg);
2252 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2253 return return_value;
2270 string tag_name(PAR_TAG const & pt) {
2272 case PAR_NONE: return "!-- --";
2273 case TT: return "tt";
2274 case SF: return "sf";
2275 case BF: return "bf";
2276 case IT: return "it";
2277 case SL: return "sl";
2278 case EM: return "em";
2285 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2287 p1 = static_cast<PAR_TAG>(p1 | p2);
2292 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2294 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2300 bool Paragraph::emptyTag() const
2302 for (pos_type i = 0; i < size(); ++i) {
2304 Inset const * inset = getInset(i);
2305 Inset::Code lyx_code = inset->lyxCode();
2306 if (lyx_code != Inset::TOC_CODE &&
2307 lyx_code != Inset::INCLUDE_CODE &&
2308 lyx_code != Inset::GRAPHICS_CODE &&
2309 lyx_code != Inset::ERT_CODE &&
2310 lyx_code != Inset::LISTINGS_CODE &&
2311 lyx_code != Inset::FLOAT_CODE &&
2312 lyx_code != Inset::TABULAR_CODE) {
2316 value_type c = getChar(i);
2317 if (c != ' ' && c != '\t')
2325 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2327 for (pos_type i = 0; i < size(); ++i) {
2329 Inset const * inset = getInset(i);
2330 Inset::Code lyx_code = inset->lyxCode();
2331 if (lyx_code == Inset::LABEL_CODE) {
2332 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2333 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2342 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2345 for (i = 0; i < size(); ++i) {
2347 Inset const * inset = getInset(i);
2348 inset->docbook(buf, os, runparams);
2350 value_type c = getChar(i);
2353 os << sgml::escapeChar(c);
2360 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2364 for (pos_type i = initial; i < size(); ++i) {
2365 Font font = getFont(buf.params(), i, outerfont);
2368 if (i != initial && font != font_old)
2377 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2379 OutputParams const & runparams,
2380 Font const & outerfont,
2381 pos_type initial) const
2383 bool emph_flag = false;
2385 LayoutPtr const & style = layout();
2387 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2389 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2392 // parsing main loop
2393 for (pos_type i = initial; i < size(); ++i) {
2394 Font font = getFont(buf.params(), i, outerfont);
2396 // handle <emphasis> tag
2397 if (font_old.emph() != font.emph()) {
2398 if (font.emph() == Font::ON) {
2401 } else if (i != initial) {
2402 os << "</emphasis>";
2408 Inset const * inset = getInset(i);
2409 inset->docbook(buf, os, runparams);
2411 value_type c = getChar(i);
2413 if (style->pass_thru)
2416 os << sgml::escapeChar(c);
2422 os << "</emphasis>";
2425 if (style->free_spacing)
2427 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2432 bool Paragraph::isNewline(pos_type pos) const
2435 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2439 bool Paragraph::isLineSeparator(pos_type pos) const
2441 value_type const c = getChar(pos);
2442 return isLineSeparatorChar(c)
2443 || (c == Paragraph::META_INSET && getInset(pos) &&
2444 getInset(pos)->isLineSeparator());
2448 /// Used by the spellchecker
2449 bool Paragraph::isLetter(pos_type pos) const
2452 return getInset(pos)->isLetter();
2454 value_type const c = getChar(pos);
2455 return isLetterChar(c) || isDigit(c);
2461 Paragraph::getParLanguage(BufferParams const & bparams) const
2464 return getFirstFontSettings(bparams).language();
2465 // FIXME: we should check the prev par as well (Lgb)
2466 return bparams.language;
2470 bool Paragraph::isRTL(BufferParams const & bparams) const
2472 return lyxrc.rtl_support
2473 && getParLanguage(bparams)->rightToLeft()
2474 && ownerCode() != Inset::ERT_CODE
2475 && ownerCode() != Inset::LISTINGS_CODE;
2479 void Paragraph::changeLanguage(BufferParams const & bparams,
2480 Language const * from, Language const * to)
2482 // change language including dummy font change at the end
2483 for (pos_type i = 0; i <= size(); ++i) {
2484 Font font = getFontSettings(bparams, i);
2485 if (font.language() == from) {
2486 font.setLanguage(to);
2493 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2495 Language const * doc_language = bparams.language;
2496 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2497 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2499 for (; cit != end; ++cit)
2500 if (cit->font().language() != ignore_language &&
2501 cit->font().language() != latex_language &&
2502 cit->font().language() != doc_language)
2508 // Convert the paragraph to a string.
2509 // Used for building the table of contents
2510 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2512 return asString(buffer, 0, size(), label);
2516 docstring const Paragraph::asString(Buffer const & buffer,
2517 pos_type beg, pos_type end, bool label) const
2520 odocstringstream os;
2522 if (beg == 0 && label && !params().labelString().empty())
2523 os << params().labelString() << ' ';
2525 for (pos_type i = beg; i < end; ++i) {
2526 value_type const c = getChar(i);
2529 else if (c == META_INSET)
2530 getInset(i)->textString(buffer, os);
2537 void Paragraph::setInsetOwner(Inset * inset)
2539 pimpl_->inset_owner = inset;
2543 Change const & Paragraph::lookupChange(pos_type pos) const
2545 BOOST_ASSERT(pos <= size());
2546 return pimpl_->lookupChange(pos);
2550 bool Paragraph::isChanged(pos_type start, pos_type end) const
2552 return pimpl_->isChanged(start, end);
2556 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2558 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2562 void Paragraph::setChange(Change const & change)
2564 pimpl_->setChange(change);
2568 void Paragraph::setChange(pos_type pos, Change const & change)
2570 pimpl_->setChange(pos, change);
2574 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2576 return pimpl_->acceptChanges(bparams, start, end);
2580 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2582 return pimpl_->rejectChanges(bparams, start, end);
2586 int Paragraph::id() const
2592 LayoutPtr const & Paragraph::layout() const
2598 void Paragraph::layout(LayoutPtr const & new_layout)
2600 layout_ = new_layout;
2604 Inset * Paragraph::inInset() const
2606 return pimpl_->inset_owner;
2610 Inset::Code Paragraph::ownerCode() const
2612 return pimpl_->inset_owner
2613 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2617 ParagraphParameters & Paragraph::params()
2619 return pimpl_->params;
2623 ParagraphParameters const & Paragraph::params() const
2625 return pimpl_->params;
2629 bool Paragraph::isFreeSpacing() const
2631 if (layout()->free_spacing)
2634 // for now we just need this, later should we need this in some
2635 // other way we can always add a function to Inset too.
2636 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2640 bool Paragraph::allowEmpty() const
2642 if (layout()->keepempty)
2644 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2648 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2650 if (!Encodings::is_arabic(c))
2653 value_type prev_char = ' ';
2654 value_type next_char = ' ';
2656 for (pos_type i = pos - 1; i >= 0; --i) {
2657 value_type const par_char = getChar(i);
2658 if (!Encodings::isComposeChar_arabic(par_char)) {
2659 prev_char = par_char;
2664 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2665 value_type const par_char = getChar(i);
2666 if (!Encodings::isComposeChar_arabic(par_char)) {
2667 next_char = par_char;
2672 if (Encodings::is_arabic(next_char)) {
2673 if (Encodings::is_arabic(prev_char) &&
2674 !Encodings::is_arabic_special(prev_char))
2675 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2677 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2679 if (Encodings::is_arabic(prev_char) &&
2680 !Encodings::is_arabic_special(prev_char))
2681 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2683 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2688 int Paragraph::checkBiblio(bool track_changes)
2691 //This is getting more and more a mess. ...We really should clean
2692 //up this bibitem issue for 1.6. See also bug 2743.
2694 // Add bibitem insets if necessary
2695 if (layout()->labeltype != LABEL_BIBLIO)
2698 bool hasbibitem = !insetlist.empty()
2699 // Insist on it being in pos 0
2700 && getChar(0) == Paragraph::META_INSET
2701 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2706 // remove a bibitem in pos != 0
2707 // restore it later in pos 0 if necessary
2708 // (e.g. if a user inserts contents _before_ the item)
2709 // we're assuming there's only one of these, which there
2711 int erasedInsetPosition = -1;
2712 InsetList::iterator it = insetlist.begin();
2713 InsetList::iterator end = insetlist.end();
2714 for (; it != end; ++it)
2715 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2717 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2718 oldkey = olditem->getParam("key");
2719 oldlabel = olditem->getParam("label");
2720 erasedInsetPosition = it->pos;
2721 eraseChar(erasedInsetPosition, track_changes);
2725 //There was an InsetBibitem at the beginning, and we didn't
2726 //have to erase one.
2727 if (hasbibitem && erasedInsetPosition < 0)
2730 //There was an InsetBibitem at the beginning and we did have to
2731 //erase one. So we give its properties to the beginning inset.
2733 InsetBibitem * inset =
2734 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2735 if (!oldkey.empty())
2736 inset->setParam("key", oldkey);
2737 inset->setParam("label", oldlabel);
2738 return -erasedInsetPosition;
2741 //There was no inset at the beginning, so we need to create one with
2742 //the key and label of the one we erased.
2743 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2744 // restore values of previously deleted item in this par.
2745 if (!oldkey.empty())
2746 inset->setParam("key", oldkey);
2747 inset->setParam("label", oldlabel);
2748 insertInset(0, static_cast<Inset *>(inset),
2749 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2755 void Paragraph::checkAuthors(AuthorList const & authorList)
2757 pimpl_->changes_.checkAuthors(authorList);