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 // When the character is marked by the proper language, we simply
645 // get its code point in some encoding, otherwise we get the
646 // translation specified in the unicodesymbols file, which is
647 // something like "\textLANG{<spec>}". So, we have to retain
648 // "\textLANG{<spec>" for the first char but only "<spec>" for
649 // all subsequent chars.
650 docstring const latex1 = rtrim(encoding.latexChar(c), "}");
651 int length = latex1.length();
653 while (i < size() - 1) {
654 char_type next = getChar(i + 1);
655 // Stop here if next character belongs to another
656 // language or there is a change tracking status.
657 if (!Encodings::isKnownLangChar(next, preamble) ||
658 runningChange != lookupChange(i + 1))
662 FontList::const_iterator cit = fontlist.begin();
663 FontList::const_iterator end = fontlist.end();
664 for (; cit != end; ++cit) {
665 if (cit->pos() >= i && !found) {
666 prev_font = cit->font();
669 if (cit->pos() >= i + 1)
672 // Stop here if there is a font attribute change.
673 if (found && cit != end && prev_font != cit->font())
675 docstring const latex = rtrim(encoding.latexChar(next), "}");
676 docstring::size_type const j =
677 latex.find_first_of(from_ascii("{"));
678 if (j == docstring::npos) {
680 length += latex.length();
682 os << latex.substr(j + 1);
683 length += latex.substr(j + 1).length();
687 // When the proper language is set, we are simply passed a code
688 // point, so we should not try to close the \textLANG command.
689 if (prefixIs(latex1, from_ascii("\\" + preamble))) {
697 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
699 pos_type const len = str.length();
701 // is the paragraph large enough?
702 if (pos + len > size())
705 // does the wanted text start at point?
706 for (string::size_type i = 0; i < str.length(); ++i) {
707 // Caution: direct comparison of characters works only
708 // because str is pure ASCII.
709 if (str[i] != owner_->text_[pos + i])
713 // is there a font change in middle of the word?
714 FontList::const_iterator cit = fontlist.begin();
715 FontList::const_iterator end = fontlist.end();
716 for (; cit != end; ++cit) {
717 if (cit->pos() >= pos)
720 if (cit != end && pos + len - 1 > cit->pos())
727 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
728 BufferParams const & bparams,
731 OutputParams & runparams,
734 Font const & outerfont,
736 Change & running_change,
737 Layout const & style,
739 unsigned int & column,
742 if (style.pass_thru) {
743 if (c != Paragraph::META_INSET) {
745 // FIXME UNICODE: This can fail if c cannot
746 // be encoded in the current encoding.
749 owner_->getInset(i)->plaintext(buf, os, runparams);
753 // Two major modes: LaTeX or plain
754 // Handle here those cases common to both modes
755 // and then split to handle the two modes separately.
757 case Paragraph::META_INSET: {
758 Inset * inset = owner_->getInset(i);
760 // FIXME: remove this check
764 // FIXME: move this to InsetNewline::latex
765 if (inset->lyxCode() == NEWLINE_CODE) {
766 // newlines are handled differently here than
767 // the default in simpleTeXSpecialChars().
768 if (!style.newline_allowed) {
772 column += running_font.latexWriteEndChanges(
773 os, bparams, runparams,
778 if (running_font.family() == Font::TYPEWRITER_FAMILY)
781 basefont = owner_->getLayoutFont(bparams, outerfont);
782 running_font = basefont;
784 if (runparams.moving_arg)
790 texrow.start(owner_->id(), i + 1);
795 if (lookupChange(i).type == Change::DELETED) {
796 if( ++runparams.inDeletedInset == 1)
797 runparams.changeOfDeletedInset = lookupChange(i);
800 if (inset->canTrackChanges()) {
801 column += Changes::latexMarkChange(os, bparams, running_change,
802 Change(Change::UNCHANGED));
803 running_change = Change(Change::UNCHANGED);
807 odocstream::pos_type const len = os.tellp();
809 if ((inset->lyxCode() == GRAPHICS_CODE
810 || inset->lyxCode() == MATH_CODE
811 || inset->lyxCode() == HYPERLINK_CODE)
812 && running_font.isRightToLeft()) {
813 if (running_font.language()->lang() == "farsi")
820 // FIXME: Bug: we can have an empty font change here!
821 // if there has just been a font change, we are going to close it
822 // right now, which means stupid latex code like \textsf{}. AFAIK,
823 // this does not harm dvi output. A minor bug, thus (JMarc)
824 // Some insets cannot be inside a font change command.
825 // However, even such insets *can* be placed in \L or \R
826 // or their equivalents (for RTL language switches), so we don't
827 // close the language in those cases.
828 // ArabTeX, though, cannot handle this special behavior, it seems.
829 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
830 running_font.language()->lang() == "arabic_arabtex";
831 if (open_font && inset->noFontChange()) {
832 bool closeLanguage = arabtex ||
833 basefont.isRightToLeft() == running_font.isRightToLeft();
834 unsigned int count = running_font.latexWriteEndChanges(
835 os, bparams, runparams,
836 basefont, basefont, closeLanguage);
838 // if any font properties were closed, update the running_font,
839 // making sure, however, to leave the language as it was
841 // FIXME: probably a better way to keep track of the old
842 // language, than copying the entire font?
843 Font const copy_font(running_font);
844 basefont = owner_->getLayoutFont(bparams, outerfont);
845 running_font = basefont;
847 running_font.setLanguage(copy_font.language());
848 // leave font open if language is still open
849 open_font = (running_font.language() == basefont.language());
851 runparams.local_font = &basefont;
855 int tmp = inset->latex(buf, os, runparams);
858 if (running_font.language()->lang() == "farsi")
865 for (int j = 0; j < tmp; ++j) {
868 texrow.start(owner_->id(), i + 1);
871 column += os.tellp() - len;
874 if (lookupChange(i).type == Change::DELETED) {
875 --runparams.inDeletedInset;
881 // And now for the special cases within each mode
885 os << "\\textbackslash{}";
889 case '|': case '<': case '>':
890 // In T1 encoding, these characters exist
891 if (lyxrc.fontenc == "T1") {
893 //... but we should avoid ligatures
894 if ((c == '>' || c == '<')
896 && getChar(i + 1) == c) {
897 //os << "\\textcompwordmark{}";
899 // Jean-Marc, have a look at
900 // this. I think this works
908 // Typewriter font also has them
909 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
913 // Otherwise, we use what LaTeX
917 os << "\\textless{}";
921 os << "\\textgreater{}";
931 case '-': // "--" in Typewriter mode -> "-{}-"
932 if (i <= size() - 2 &&
933 getChar(i + 1) == '-' &&
934 running_font.family() == Font::TYPEWRITER_FAMILY) {
943 os << "\\char`\\\"{}";
948 case '%': case '#': case '{':
956 os << "\\textasciitilde{}";
961 os << "\\textasciicircum{}";
966 // avoid being mistaken for optional arguments
974 // Blanks are printed before font switching.
975 // Sure? I am not! (try nice-latex)
976 // I am sure it's correct. LyX might be smarter
977 // in the future, but for now, nothing wrong is
983 // I assume this is hack treating typewriter as verbatim
984 // FIXME UNICODE: This can fail if c cannot be encoded
985 // in the current encoding.
986 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
995 // FIXME: if we have "LaTeX" with a font
996 // change in the middle (before the 'T', then
997 // the "TeX" part is still special cased.
998 // Really we should only operate this on
999 // "words" for some definition of word
1003 for (; pnr < phrases_nr; ++pnr) {
1004 if (isTextAt(special_phrases[pnr].phrase, i)) {
1005 os << special_phrases[pnr].macro;
1006 i += special_phrases[pnr].phrase.length() - 1;
1007 column += special_phrases[pnr].macro.length() - 1;
1012 if (pnr == phrases_nr && c != '\0') {
1013 Encoding const & encoding = *(runparams.encoding);
1014 if (i < size() - 1) {
1015 char_type next = getChar(i + 1);
1016 if (Encodings::isCombiningChar(next)) {
1017 column += latexSurrogatePair(os, c, next, encoding) - 1;
1023 if (Encodings::isKnownLangChar(c, preamble)) {
1025 knownLangChars(os, c, preamble,
1030 docstring const latex = encoding.latexChar(c);
1031 if (latex.length() > 1 &&
1032 latex[latex.length() - 1] != '}') {
1033 // Prevent eating of a following
1034 // space or command corruption by
1035 // following characters
1036 column += latex.length() + 1;
1037 os << latex << "{}";
1039 column += latex.length() - 1;
1049 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
1050 Layout const & layout) const
1052 BufferParams const & bparams = features.bufferParams();
1054 // check the params.
1055 if (!params.spacing().isDefault())
1056 features.require("setspace");
1059 features.useLayout(layout.name());
1062 Language const * doc_language = bparams.language;
1064 FontList::const_iterator fcit = fontlist.begin();
1065 FontList::const_iterator fend = fontlist.end();
1066 for (; fcit != fend; ++fcit) {
1067 if (fcit->font().noun() == Font::ON) {
1068 LYXERR(Debug::LATEX) << "font.noun: "
1069 << fcit->font().noun()
1071 features.require("noun");
1072 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
1073 << to_utf8(fcit->font().stateText(0))
1076 switch (fcit->font().color()) {
1078 case Color::inherit:
1080 // probably we should put here all interface colors used for
1081 // font displaying! For now I just add this ones I know of (Jug)
1086 features.require("color");
1087 LYXERR(Debug::LATEX) << "Color enabled. Font: "
1088 << to_utf8(fcit->font().stateText(0))
1092 Language const * language = fcit->font().language();
1093 if (language->babel() != doc_language->babel() &&
1094 language != ignore_language &&
1095 language != latex_language)
1097 features.useLanguage(language);
1098 LYXERR(Debug::LATEX) << "Found language "
1099 << language->lang() << endl;
1103 if (!params.leftIndent().zero())
1104 features.require("ParagraphLeftIndent");
1107 InsetList::const_iterator icit = owner_->insetlist.begin();
1108 InsetList::const_iterator iend = owner_->insetlist.end();
1109 for (; icit != iend; ++icit) {
1111 icit->inset->validate(features);
1112 if (layout.needprotect &&
1113 icit->inset->lyxCode() == FOOT_CODE)
1114 features.require("NeedLyXFootnoteCode");
1118 // then the contents
1119 for (pos_type i = 0; i < size() ; ++i) {
1120 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1121 if (!special_phrases[pnr].builtin
1122 && isTextAt(special_phrases[pnr].phrase, i)) {
1123 features.require(special_phrases[pnr].phrase);
1127 Encodings::validate(getChar(i), features);
1135 /////////////////////////////////////////////////////////////////////
1139 /////////////////////////////////////////////////////////////////////
1143 Paragraph::Paragraph()
1144 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1151 Paragraph::Paragraph(Paragraph const & par)
1152 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1153 layout_(par.layout_),
1154 text_(par.text_), begin_of_body_(par.begin_of_body_),
1155 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1157 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1158 InsetList::iterator it = insetlist.begin();
1159 InsetList::iterator end = insetlist.end();
1160 for (; it != end; ++it)
1161 it->inset = it->inset->clone();
1165 Paragraph & Paragraph::operator=(Paragraph const & par)
1167 // needed as we will destroy the pimpl_ before copying it
1169 itemdepth = par.itemdepth;
1171 insetlist = par.insetlist;
1172 InsetList::iterator it = insetlist.begin();
1173 InsetList::iterator end = insetlist.end();
1174 for (; it != end; ++it)
1175 it->inset = it->inset->clone();
1177 layout_ = par.layout();
1179 begin_of_body_ = par.begin_of_body_;
1182 pimpl_ = new Pimpl(*par.pimpl_, this);
1188 Paragraph::~Paragraph()
1192 //lyxerr << "Paragraph::paragraph_id = "
1193 // << Paragraph::paragraph_id << endl;
1197 void Paragraph::write(Buffer const & buf, ostream & os,
1198 BufferParams const & bparams,
1199 depth_type & dth) const
1201 // The beginning or end of a deeper (i.e. nested) area?
1202 if (dth != params().depth()) {
1203 if (params().depth() > dth) {
1204 while (params().depth() > dth) {
1205 os << "\n\\begin_deeper";
1209 while (params().depth() < dth) {
1210 os << "\n\\end_deeper";
1216 // First write the layout
1217 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1221 Font font1(Font::ALL_INHERIT, bparams.language);
1223 Change running_change = Change(Change::UNCHANGED);
1226 for (pos_type i = 0; i <= size(); ++i) {
1228 Change change = pimpl_->lookupChange(i);
1229 Changes::lyxMarkChange(os, column, running_change, change);
1230 running_change = change;
1235 // Write font changes
1236 Font font2 = getFontSettings(bparams, i);
1237 if (font2 != font1) {
1238 font2.lyxWriteChanges(font1, os);
1243 value_type const c = getChar(i);
1247 Inset const * inset = getInset(i);
1249 if (inset->directWrite()) {
1250 // international char, let it write
1251 // code directly so it's shorter in
1253 inset->write(buf, os);
1257 os << "\\begin_inset ";
1258 inset->write(buf, os);
1259 os << "\n\\end_inset\n\n";
1265 os << "\n\\backslash\n";
1269 if (i + 1 < size() && getChar(i + 1) == ' ') {
1276 if ((column > 70 && c == ' ')
1281 // this check is to amend a bug. LyX sometimes
1282 // inserts '\0' this could cause problems.
1284 std::vector<char> tmp = ucs4_to_utf8(c);
1285 tmp.push_back('\0');
1288 lyxerr << "ERROR (Paragraph::writeFile):"
1289 " NULL char in structure." << endl;
1295 os << "\n\\end_layout\n";
1299 void Paragraph::validate(LaTeXFeatures & features) const
1301 pimpl_->validate(features, *layout());
1305 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1307 return pimpl_->eraseChar(pos, trackChanges);
1311 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1313 return pimpl_->eraseChars(start, end, trackChanges);
1317 void Paragraph::insert(pos_type start, docstring const & str,
1318 Font const & font, Change const & change)
1320 for (size_t i = 0, n = str.size(); i != n ; ++i)
1321 insertChar(start + i, str[i], font, change);
1325 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1328 pimpl_->insertChar(pos, c, Change(trackChanges ?
1329 Change::INSERTED : Change::UNCHANGED));
1333 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1334 Font const & font, bool trackChanges)
1336 pimpl_->insertChar(pos, c, Change(trackChanges ?
1337 Change::INSERTED : Change::UNCHANGED));
1342 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1343 Font const & font, Change const & change)
1345 pimpl_->insertChar(pos, c, change);
1350 void Paragraph::insertInset(pos_type pos, Inset * inset,
1351 Change const & change)
1353 pimpl_->insertInset(pos, inset, change);
1357 void Paragraph::insertInset(pos_type pos, Inset * inset,
1358 Font const & font, Change const & change)
1360 pimpl_->insertInset(pos, inset, change);
1361 // Set the font/language of the inset...
1366 bool Paragraph::insetAllowed(InsetCode code)
1368 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1372 // Gets uninstantiated font setting at position.
1373 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1377 lyxerr << " pos: " << pos << " size: " << size() << endl;
1378 BOOST_ASSERT(pos <= size());
1381 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1382 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1383 for (; cit != end; ++cit)
1384 if (cit->pos() >= pos)
1390 if (pos == size() && !empty())
1391 return getFontSettings(bparams, pos - 1);
1393 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1397 FontSpan Paragraph::fontSpan(pos_type pos) const
1399 BOOST_ASSERT(pos <= size());
1402 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1403 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1404 for (; cit != end; ++cit) {
1405 if (cit->pos() >= pos) {
1406 if (pos >= beginOfBody())
1407 return FontSpan(std::max(start, beginOfBody()),
1410 return FontSpan(start,
1411 std::min(beginOfBody() - 1,
1414 start = cit->pos() + 1;
1417 // This should not happen, but if so, we take no chances.
1418 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1420 return FontSpan(pos, pos);
1424 // Gets uninstantiated font setting at position 0
1425 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1427 if (!empty() && !pimpl_->fontlist.empty())
1428 return pimpl_->fontlist[0].font();
1430 return Font(Font::ALL_INHERIT, bparams.language);
1434 // Gets the fully instantiated font at a given position in a paragraph
1435 // This is basically the same function as Text::GetFont() in text2.cpp.
1436 // The difference is that this one is used for generating the LaTeX file,
1437 // and thus cosmetic "improvements" are disallowed: This has to deliver
1438 // the true picture of the buffer. (Asger)
1439 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1440 Font const & outerfont) const
1442 BOOST_ASSERT(pos >= 0);
1444 Font font = getFontSettings(bparams, pos);
1446 pos_type const body_pos = beginOfBody();
1448 font.realize(layout_->labelfont);
1450 font.realize(layout_->font);
1452 font.realize(outerfont);
1453 font.realize(bparams.getFont());
1459 Font const Paragraph::getLabelFont
1460 (BufferParams const & bparams, Font const & outerfont) const
1462 Font tmpfont = layout()->labelfont;
1463 tmpfont.setLanguage(getParLanguage(bparams));
1464 tmpfont.realize(outerfont);
1465 tmpfont.realize(bparams.getFont());
1470 Font const Paragraph::getLayoutFont
1471 (BufferParams const & bparams, Font const & outerfont) const
1473 Font tmpfont = layout()->font;
1474 tmpfont.setLanguage(getParLanguage(bparams));
1475 tmpfont.realize(outerfont);
1476 tmpfont.realize(bparams.getFont());
1481 /// Returns the height of the highest font in range
1482 Font_size Paragraph::highestFontInRange
1483 (pos_type startpos, pos_type endpos, Font_size def_size) const
1485 if (pimpl_->fontlist.empty())
1488 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1489 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1490 for (; end_it != end; ++end_it) {
1491 if (end_it->pos() >= endpos)
1498 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1499 for (; cit != end; ++cit) {
1500 if (cit->pos() >= startpos)
1504 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1505 for (; cit != end_it; ++cit) {
1506 Font::FONT_SIZE size = cit->font().size();
1507 if (size == Font::INHERIT_SIZE)
1509 if (size > maxsize && size <= Font::SIZE_HUGER)
1516 Paragraph::value_type
1517 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1519 value_type c = getChar(pos);
1520 if (!lyxrc.rtl_support)
1550 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1557 void Paragraph::setFont(pos_type pos, Font const & font)
1559 BOOST_ASSERT(pos <= size());
1561 // First, reduce font against layout/label font
1562 // Update: The setCharFont() routine in text2.cpp already
1563 // reduces font, so we don't need to do that here. (Asger)
1564 // No need to simplify this because it will disappear
1565 // in a new kernel. (Asger)
1566 // Next search font table
1568 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1569 Pimpl::FontList::iterator it = beg;
1570 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1571 for (; it != endit; ++it) {
1572 if (it->pos() >= pos)
1575 size_t const i = distance(beg, it);
1576 bool notfound = (it == endit);
1578 if (!notfound && pimpl_->fontlist[i].font() == font)
1581 bool begin = pos == 0 || notfound ||
1582 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1583 // Is position pos is a beginning of a font block?
1584 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1585 // Is position pos is the end of a font block?
1586 if (begin && end) { // A single char block
1587 if (i + 1 < pimpl_->fontlist.size() &&
1588 pimpl_->fontlist[i + 1].font() == font) {
1589 // Merge the singleton block with the next block
1590 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1591 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1592 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1593 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1594 // Merge the singleton block with the previous block
1595 pimpl_->fontlist[i - 1].pos(pos);
1596 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1598 pimpl_->fontlist[i].font(font);
1600 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1601 pimpl_->fontlist[i - 1].pos(pos);
1603 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1604 Pimpl::FontTable(pos, font));
1606 pimpl_->fontlist[i].pos(pos - 1);
1607 if (!(i + 1 < pimpl_->fontlist.size() &&
1608 pimpl_->fontlist[i + 1].font() == font))
1609 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1610 Pimpl::FontTable(pos, font));
1611 } else { // The general case. The block is splitted into 3 blocks
1612 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1613 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1614 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1615 Pimpl::FontTable(pos, font));
1620 void Paragraph::makeSameLayout(Paragraph const & par)
1622 layout(par.layout());
1624 params() = par.params();
1628 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1630 if (isFreeSpacing())
1636 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1637 if (eraseChar(pos, trackChanges))
1643 return count > 0 || pos > 0;
1647 bool Paragraph::hasSameLayout(Paragraph const & par) const
1649 return par.layout() == layout() && params().sameLayout(par.params());
1653 depth_type Paragraph::getDepth() const
1655 return params().depth();
1659 depth_type Paragraph::getMaxDepthAfter() const
1661 if (layout()->isEnvironment())
1662 return params().depth() + 1;
1664 return params().depth();
1668 char Paragraph::getAlign() const
1670 if (params().align() == LYX_ALIGN_LAYOUT)
1671 return layout()->align;
1673 return params().align();
1677 docstring const & Paragraph::getLabelstring() const
1679 return params().labelString();
1683 // the next two functions are for the manual labels
1684 docstring const Paragraph::getLabelWidthString() const
1686 if (layout()->margintype == MARGIN_MANUAL)
1687 return params().labelWidthString();
1689 return _("Senseless with this layout!");
1693 void Paragraph::setLabelWidthString(docstring const & s)
1695 params().labelWidthString(s);
1699 docstring const Paragraph::translateIfPossible(docstring const & s,
1700 BufferParams const & bparams) const
1702 if (!support::isAscii(s) || s.empty()) {
1703 // This must be a user defined layout. We cannot translate
1704 // this, since gettext accepts only ascii keys.
1707 // Probably standard layout, try to translate
1708 Messages & m = getMessages(getParLanguage(bparams)->code());
1709 return m.get(to_ascii(s));
1713 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1714 BufferParams const & bparams, bool process_appendix) const
1716 TextClass const & tclass = bparams.getTextClass();
1719 if (process_appendix && params().appendix())
1720 fmt = translateIfPossible(layout->labelstring_appendix(),
1723 fmt = translateIfPossible(layout->labelstring(), bparams);
1725 if (fmt.empty() && layout->labeltype == LABEL_COUNTER
1726 && !layout->counter.empty())
1727 fmt = "\\the" + layout->counter;
1729 // handle 'inherited level parts' in 'fmt',
1730 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1731 size_t const i = fmt.find('@', 0);
1732 if (i != docstring::npos) {
1733 size_t const j = fmt.find('@', i + 1);
1734 if (j != docstring::npos) {
1735 docstring parent(fmt, i + 1, j - i - 1);
1736 docstring label = from_ascii("??");
1737 if (tclass.hasLayout(parent))
1738 docstring label = expandLabel(tclass[parent], bparams,
1740 fmt = docstring(fmt, 0, i) + label
1741 + docstring(fmt, j + 1, docstring::npos);
1745 return tclass.counters().counterLabel(fmt);
1749 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1752 LyXAlignment const oldAlign = params().align();
1754 if (!(oldAlign & layout()->alignpossible)) {
1755 frontend::Alert::warning(_("Alignment not permitted"),
1756 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1757 params().align(LYX_ALIGN_LAYOUT);
1762 pos_type Paragraph::beginOfBody() const
1764 return begin_of_body_;
1768 void Paragraph::setBeginOfBody()
1770 if (layout()->labeltype != LABEL_MANUAL) {
1775 // Unroll the first two cycles of the loop
1776 // and remember the previous character to
1777 // remove unnecessary getChar() calls
1779 pos_type end = size();
1780 if (i < end && !isNewline(i)) {
1782 char_type previous_char = 0;
1785 previous_char = text_[i];
1786 if (!isNewline(i)) {
1788 while (i < end && previous_char != ' ') {
1793 previous_char = temp;
1803 // returns -1 if inset not found
1804 int Paragraph::getPositionOfInset(Inset const * inset) const
1807 InsetList::const_iterator it = insetlist.begin();
1808 InsetList::const_iterator end = insetlist.end();
1809 for (; it != end; ++it)
1810 if (it->inset == inset)
1816 InsetBibitem * Paragraph::bibitem() const
1818 if (!insetlist.empty()) {
1819 Inset * inset = insetlist.begin()->inset;
1820 if (inset->lyxCode() == BIBITEM_CODE)
1821 return static_cast<InsetBibitem *>(inset);
1827 bool Paragraph::forceDefaultParagraphs() const
1829 return inInset() && inInset()->forceDefaultParagraphs(0);
1835 // paragraphs inside floats need different alignment tags to avoid
1838 bool noTrivlistCentering(InsetCode code)
1840 return code == FLOAT_CODE || code == WRAP_CODE;
1844 string correction(string const & orig)
1846 if (orig == "flushleft")
1847 return "raggedright";
1848 if (orig == "flushright")
1849 return "raggedleft";
1850 if (orig == "center")
1856 string const corrected_env(string const & suffix, string const & env,
1859 string output = suffix + "{";
1860 if (noTrivlistCentering(code))
1861 output += correction(env);
1865 if (suffix == "\\begin")
1871 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1873 if (!contains(str, "\n"))
1874 column += str.size();
1878 column = rsplit(str, tmp, '\n').size();
1885 // This could go to ParagraphParameters if we want to
1886 int Paragraph::startTeXParParams(BufferParams const & bparams,
1887 odocstream & os, TexRow & texrow,
1888 bool moving_arg) const
1892 if (params().noindent()) {
1893 os << "\\noindent ";
1897 LyXAlignment const curAlign = params().align();
1899 if (curAlign == layout()->align)
1903 case LYX_ALIGN_NONE:
1904 case LYX_ALIGN_BLOCK:
1905 case LYX_ALIGN_LAYOUT:
1906 case LYX_ALIGN_SPECIAL:
1908 case LYX_ALIGN_LEFT:
1909 case LYX_ALIGN_RIGHT:
1910 case LYX_ALIGN_CENTER:
1919 case LYX_ALIGN_NONE:
1920 case LYX_ALIGN_BLOCK:
1921 case LYX_ALIGN_LAYOUT:
1922 case LYX_ALIGN_SPECIAL:
1924 case LYX_ALIGN_LEFT: {
1926 if (getParLanguage(bparams)->babel() != "hebrew")
1927 output = corrected_env("\\begin", "flushleft", ownerCode());
1929 output = corrected_env("\\begin", "flushright", ownerCode());
1930 os << from_ascii(output);
1931 adjust_row_column(output, texrow, column);
1933 } case LYX_ALIGN_RIGHT: {
1935 if (getParLanguage(bparams)->babel() != "hebrew")
1936 output = corrected_env("\\begin", "flushright", ownerCode());
1938 output = corrected_env("\\begin", "flushleft", ownerCode());
1939 os << from_ascii(output);
1940 adjust_row_column(output, texrow, column);
1942 } case LYX_ALIGN_CENTER: {
1944 output = corrected_env("\\begin", "center", ownerCode());
1945 os << from_ascii(output);
1946 adjust_row_column(output, texrow, column);
1955 // This could go to ParagraphParameters if we want to
1956 int Paragraph::endTeXParParams(BufferParams const & bparams,
1957 odocstream & os, TexRow & texrow,
1958 bool moving_arg) const
1962 switch (params().align()) {
1963 case LYX_ALIGN_NONE:
1964 case LYX_ALIGN_BLOCK:
1965 case LYX_ALIGN_LAYOUT:
1966 case LYX_ALIGN_SPECIAL:
1968 case LYX_ALIGN_LEFT:
1969 case LYX_ALIGN_RIGHT:
1970 case LYX_ALIGN_CENTER:
1978 switch (params().align()) {
1979 case LYX_ALIGN_NONE:
1980 case LYX_ALIGN_BLOCK:
1981 case LYX_ALIGN_LAYOUT:
1982 case LYX_ALIGN_SPECIAL:
1984 case LYX_ALIGN_LEFT: {
1986 if (getParLanguage(bparams)->babel() != "hebrew")
1987 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1989 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1990 os << from_ascii(output);
1991 adjust_row_column(output, texrow, column);
1993 } case LYX_ALIGN_RIGHT: {
1995 if (getParLanguage(bparams)->babel() != "hebrew")
1996 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1998 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1999 os << from_ascii(output);
2000 adjust_row_column(output, texrow, column);
2002 } case LYX_ALIGN_CENTER: {
2004 output = corrected_env("\n\\par\\end", "center", ownerCode());
2005 os << from_ascii(output);
2006 adjust_row_column(output, texrow, column);
2015 // This one spits out the text of the paragraph
2016 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
2017 BufferParams const & bparams,
2018 Font const & outerfont,
2019 odocstream & os, TexRow & texrow,
2020 OutputParams const & runparams) const
2022 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
2024 bool return_value = false;
2028 // well we have to check if we are in an inset with unlimited
2029 // length (all in one row) if that is true then we don't allow
2030 // any special options in the paragraph and also we don't allow
2031 // any environment other than the default layout of the text class
2033 bool asdefault = forceDefaultParagraphs();
2036 style = bparams.getTextClass().defaultLayout();
2041 // Current base font for all inherited font changes, without any
2042 // change caused by an individual character, except for the language:
2043 // It is set to the language of the first character.
2044 // As long as we are in the label, this font is the base font of the
2045 // label. Before the first body character it is set to the base font
2049 // Maybe we have to create a optional argument.
2050 pos_type body_pos = beginOfBody();
2051 unsigned int column = 0;
2054 // the optional argument is kept in curly brackets in
2055 // case it contains a ']'
2058 basefont = getLabelFont(bparams, outerfont);
2060 basefont = getLayoutFont(bparams, outerfont);
2063 // Which font is currently active?
2064 Font running_font(basefont);
2065 // Do we have an open font change?
2066 bool open_font = false;
2068 Change runningChange = Change(Change::UNCHANGED);
2070 texrow.start(id(), 0);
2072 // if the paragraph is empty, the loop will not be entered at all
2074 if (style->isCommand()) {
2079 column += startTeXParParams(bparams, os, texrow,
2080 runparams.moving_arg);
2083 for (pos_type i = 0; i < size(); ++i) {
2084 // First char in paragraph or after label?
2085 if (i == body_pos) {
2088 column += running_font.latexWriteEndChanges(
2089 os, bparams, runparams,
2090 basefont, basefont);
2093 basefont = getLayoutFont(bparams, outerfont);
2094 running_font = basefont;
2096 column += Changes::latexMarkChange(os, bparams,
2097 runningChange, Change(Change::UNCHANGED));
2098 runningChange = Change(Change::UNCHANGED);
2103 if (style->isCommand()) {
2109 column += startTeXParParams(bparams, os,
2111 runparams.moving_arg);
2114 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2115 : pimpl_->lookupChange(i);
2117 if (bparams.outputChanges && runningChange != change) {
2119 column += running_font.latexWriteEndChanges(
2120 os, bparams, runparams, basefont, basefont);
2123 basefont = getLayoutFont(bparams, outerfont);
2124 running_font = basefont;
2126 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2127 runningChange = change;
2130 // do not output text which is marked deleted
2131 // if change tracking output is disabled
2132 if (!bparams.outputChanges && change.type == Change::DELETED) {
2138 value_type const c = getChar(i);
2140 // Fully instantiated font
2141 Font const font = getFont(bparams, i, outerfont);
2143 Font const last_font = running_font;
2145 // Do we need to close the previous font?
2147 (font != running_font ||
2148 font.language() != running_font.language()))
2150 column += running_font.latexWriteEndChanges(
2151 os, bparams, runparams, basefont,
2152 (i == body_pos-1) ? basefont : font);
2153 running_font = basefont;
2157 // Switch file encoding if necessary
2158 if (runparams.encoding->package() == Encoding::inputenc &&
2159 font.language()->encoding()->package() == Encoding::inputenc) {
2160 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2161 runparams.moving_arg, *(runparams.encoding),
2162 *(font.language()->encoding()));
2163 if (enc_switch.first) {
2164 column += enc_switch.second;
2165 runparams.encoding = font.language()->encoding();
2169 // Do we need to change font?
2170 if ((font != running_font ||
2171 font.language() != running_font.language()) &&
2174 odocstringstream ods;
2175 column += font.latexWriteStartChanges(ods, bparams,
2176 runparams, basefont,
2178 running_font = font;
2180 docstring fontchange = ods.str();
2181 // check if the fontchange ends with a trailing blank
2182 // (like "\small " (see bug 3382)
2183 if (suffixIs(fontchange, ' ') && c == ' ')
2184 os << fontchange.substr(0, fontchange.size() - 1)
2185 << from_ascii("{}");
2191 // Do not print the separation of the optional argument
2192 // if style->pass_thru is false. This works because
2193 // simpleTeXSpecialChars ignores spaces if
2194 // style->pass_thru is false.
2195 if (i != body_pos - 1) {
2196 if (pimpl_->simpleTeXBlanks(
2197 *(runparams.encoding), os, texrow,
2198 i, column, font, *style))
2199 // A surrogate pair was output. We
2200 // must not call simpleTeXSpecialChars
2201 // in this iteration, since
2202 // simpleTeXBlanks incremented i, and
2203 // simpleTeXSpecialChars would output
2204 // the combining character again.
2209 OutputParams rp = runparams;
2210 rp.free_spacing = style->free_spacing;
2211 rp.local_font = &font;
2212 rp.intitle = style->intitle;
2213 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2214 texrow, rp, running_font,
2215 basefont, outerfont, open_font,
2216 runningChange, *style, i, column, c);
2218 // Set the encoding to that returned from simpleTeXSpecialChars (see
2219 // comment for encoding member in OutputParams.h)
2220 runparams.encoding = rp.encoding;
2223 // If we have an open font definition, we have to close it
2225 #ifdef FIXED_LANGUAGE_END_DETECTION
2228 .latexWriteEndChanges(os, bparams, runparams,
2230 next_->getFont(bparams, 0, outerfont));
2232 running_font.latexWriteEndChanges(os, bparams,
2233 runparams, basefont, basefont);
2236 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2237 //FIXME: there as we start another \selectlanguage with the next paragraph if
2238 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2239 running_font.latexWriteEndChanges(os, bparams, runparams,
2240 basefont, basefont);
2244 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2246 // Needed if there is an optional argument but no contents.
2247 if (body_pos > 0 && body_pos == size()) {
2249 return_value = false;
2253 column += endTeXParParams(bparams, os, texrow,
2254 runparams.moving_arg);
2257 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2258 return return_value;
2275 string tag_name(PAR_TAG const & pt) {
2277 case PAR_NONE: return "!-- --";
2278 case TT: return "tt";
2279 case SF: return "sf";
2280 case BF: return "bf";
2281 case IT: return "it";
2282 case SL: return "sl";
2283 case EM: return "em";
2290 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2292 p1 = static_cast<PAR_TAG>(p1 | p2);
2297 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2299 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2305 bool Paragraph::emptyTag() const
2307 for (pos_type i = 0; i < size(); ++i) {
2309 Inset const * inset = getInset(i);
2310 InsetCode lyx_code = inset->lyxCode();
2311 if (lyx_code != TOC_CODE &&
2312 lyx_code != INCLUDE_CODE &&
2313 lyx_code != GRAPHICS_CODE &&
2314 lyx_code != ERT_CODE &&
2315 lyx_code != LISTINGS_CODE &&
2316 lyx_code != FLOAT_CODE &&
2317 lyx_code != TABULAR_CODE) {
2321 value_type c = getChar(i);
2322 if (c != ' ' && c != '\t')
2330 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2332 for (pos_type i = 0; i < size(); ++i) {
2334 Inset const * inset = getInset(i);
2335 InsetCode lyx_code = inset->lyxCode();
2336 if (lyx_code == LABEL_CODE) {
2337 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2338 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2347 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2350 for (i = 0; i < size(); ++i) {
2352 Inset const * inset = getInset(i);
2353 inset->docbook(buf, os, runparams);
2355 value_type c = getChar(i);
2358 os << sgml::escapeChar(c);
2365 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2369 for (pos_type i = initial; i < size(); ++i) {
2370 Font font = getFont(buf.params(), i, outerfont);
2373 if (i != initial && font != font_old)
2382 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2384 OutputParams const & runparams,
2385 Font const & outerfont,
2386 pos_type initial) const
2388 bool emph_flag = false;
2390 LayoutPtr const & style = layout();
2392 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2394 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2397 // parsing main loop
2398 for (pos_type i = initial; i < size(); ++i) {
2399 Font font = getFont(buf.params(), i, outerfont);
2401 // handle <emphasis> tag
2402 if (font_old.emph() != font.emph()) {
2403 if (font.emph() == Font::ON) {
2406 } else if (i != initial) {
2407 os << "</emphasis>";
2413 Inset const * inset = getInset(i);
2414 inset->docbook(buf, os, runparams);
2416 value_type c = getChar(i);
2418 if (style->pass_thru)
2421 os << sgml::escapeChar(c);
2427 os << "</emphasis>";
2430 if (style->free_spacing)
2432 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2437 bool Paragraph::isHfill(pos_type pos) const
2440 && getInset(pos)->lyxCode() == HFILL_CODE;
2444 bool Paragraph::isNewline(pos_type pos) const
2447 && getInset(pos)->lyxCode() == NEWLINE_CODE;
2451 bool Paragraph::isLineSeparator(pos_type pos) const
2453 value_type const c = getChar(pos);
2454 return isLineSeparatorChar(c)
2455 || (c == Paragraph::META_INSET && getInset(pos) &&
2456 getInset(pos)->isLineSeparator());
2460 /// Used by the spellchecker
2461 bool Paragraph::isLetter(pos_type pos) const
2464 return getInset(pos)->isLetter();
2466 value_type const c = getChar(pos);
2467 return isLetterChar(c) || isDigit(c);
2473 Paragraph::getParLanguage(BufferParams const & bparams) const
2476 return getFirstFontSettings(bparams).language();
2477 // FIXME: we should check the prev par as well (Lgb)
2478 return bparams.language;
2482 bool Paragraph::isRTL(BufferParams const & bparams) const
2484 return lyxrc.rtl_support
2485 && getParLanguage(bparams)->rightToLeft()
2486 && ownerCode() != ERT_CODE
2487 && ownerCode() != LISTINGS_CODE;
2491 void Paragraph::changeLanguage(BufferParams const & bparams,
2492 Language const * from, Language const * to)
2494 // change language including dummy font change at the end
2495 for (pos_type i = 0; i <= size(); ++i) {
2496 Font font = getFontSettings(bparams, i);
2497 if (font.language() == from) {
2498 font.setLanguage(to);
2505 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2507 Language const * doc_language = bparams.language;
2508 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2509 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2511 for (; cit != end; ++cit)
2512 if (cit->font().language() != ignore_language &&
2513 cit->font().language() != latex_language &&
2514 cit->font().language() != doc_language)
2520 // Convert the paragraph to a string.
2521 // Used for building the table of contents
2522 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2524 return asString(buffer, 0, size(), label);
2528 docstring const Paragraph::asString(Buffer const & buffer,
2529 pos_type beg, pos_type end, bool label) const
2532 odocstringstream os;
2534 if (beg == 0 && label && !params().labelString().empty())
2535 os << params().labelString() << ' ';
2537 for (pos_type i = beg; i < end; ++i) {
2538 value_type const c = getChar(i);
2541 else if (c == META_INSET)
2542 getInset(i)->textString(buffer, os);
2549 void Paragraph::setInsetOwner(Inset * inset)
2551 pimpl_->inset_owner = inset;
2555 Change const & Paragraph::lookupChange(pos_type pos) const
2557 BOOST_ASSERT(pos <= size());
2558 return pimpl_->lookupChange(pos);
2562 bool Paragraph::isChanged(pos_type start, pos_type end) const
2564 return pimpl_->isChanged(start, end);
2568 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2570 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2574 void Paragraph::setChange(Change const & change)
2576 pimpl_->setChange(change);
2580 void Paragraph::setChange(pos_type pos, Change const & change)
2582 pimpl_->setChange(pos, change);
2586 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2588 return pimpl_->acceptChanges(bparams, start, end);
2592 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2594 return pimpl_->rejectChanges(bparams, start, end);
2598 int Paragraph::id() const
2604 LayoutPtr const & Paragraph::layout() const
2610 void Paragraph::layout(LayoutPtr const & new_layout)
2612 layout_ = new_layout;
2616 Inset * Paragraph::inInset() const
2618 return pimpl_->inset_owner;
2622 InsetCode Paragraph::ownerCode() const
2624 return pimpl_->inset_owner
2625 ? pimpl_->inset_owner->lyxCode() : NO_CODE;
2629 ParagraphParameters & Paragraph::params()
2631 return pimpl_->params;
2635 ParagraphParameters const & Paragraph::params() const
2637 return pimpl_->params;
2641 bool Paragraph::isFreeSpacing() const
2643 if (layout()->free_spacing)
2646 // for now we just need this, later should we need this in some
2647 // other way we can always add a function to Inset too.
2648 return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE;
2652 bool Paragraph::allowEmpty() const
2654 if (layout()->keepempty)
2656 return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE;
2660 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2662 if (!Encodings::is_arabic(c))
2665 value_type prev_char = ' ';
2666 value_type next_char = ' ';
2668 for (pos_type i = pos - 1; i >= 0; --i) {
2669 value_type const par_char = getChar(i);
2670 if (!Encodings::isComposeChar_arabic(par_char)) {
2671 prev_char = par_char;
2676 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2677 value_type const par_char = getChar(i);
2678 if (!Encodings::isComposeChar_arabic(par_char)) {
2679 next_char = par_char;
2684 if (Encodings::is_arabic(next_char)) {
2685 if (Encodings::is_arabic(prev_char) &&
2686 !Encodings::is_arabic_special(prev_char))
2687 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2689 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2691 if (Encodings::is_arabic(prev_char) &&
2692 !Encodings::is_arabic_special(prev_char))
2693 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2695 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2700 int Paragraph::checkBiblio(bool track_changes)
2703 //This is getting more and more a mess. ...We really should clean
2704 //up this bibitem issue for 1.6. See also bug 2743.
2706 // Add bibitem insets if necessary
2707 if (layout()->labeltype != LABEL_BIBLIO)
2710 bool hasbibitem = !insetlist.empty()
2711 // Insist on it being in pos 0
2712 && getChar(0) == Paragraph::META_INSET
2713 && insetlist.begin()->inset->lyxCode() == BIBITEM_CODE;
2718 // remove a bibitem in pos != 0
2719 // restore it later in pos 0 if necessary
2720 // (e.g. if a user inserts contents _before_ the item)
2721 // we're assuming there's only one of these, which there
2723 int erasedInsetPosition = -1;
2724 InsetList::iterator it = insetlist.begin();
2725 InsetList::iterator end = insetlist.end();
2726 for (; it != end; ++it)
2727 if (it->inset->lyxCode() == BIBITEM_CODE
2729 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2730 oldkey = olditem->getParam("key");
2731 oldlabel = olditem->getParam("label");
2732 erasedInsetPosition = it->pos;
2733 eraseChar(erasedInsetPosition, track_changes);
2737 //There was an InsetBibitem at the beginning, and we didn't
2738 //have to erase one.
2739 if (hasbibitem && erasedInsetPosition < 0)
2742 //There was an InsetBibitem at the beginning and we did have to
2743 //erase one. So we give its properties to the beginning inset.
2745 InsetBibitem * inset =
2746 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2747 if (!oldkey.empty())
2748 inset->setParam("key", oldkey);
2749 inset->setParam("label", oldlabel);
2750 return -erasedInsetPosition;
2753 //There was no inset at the beginning, so we need to create one with
2754 //the key and label of the one we erased.
2755 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2756 // restore values of previously deleted item in this par.
2757 if (!oldkey.empty())
2758 inset->setParam("key", oldkey);
2759 inset->setParam("label", oldlabel);
2760 insertInset(0, static_cast<Inset *>(inset),
2761 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2767 void Paragraph::checkAuthors(AuthorList const & authorList)
2769 pimpl_->changes_.checkAuthors(authorList);