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 "InsetList.h"
31 #include "LaTeXFeatures.h"
38 #include "OutputParams.h"
39 #include "output_latex.h"
40 #include "paragraph_funcs.h"
41 #include "ParagraphParameters.h"
46 #include "frontends/alert.h"
47 #include "frontends/FontMetrics.h"
49 #include "insets/InsetBibitem.h"
50 #include "insets/InsetOptArg.h"
52 #include "support/lstrings.h"
53 #include "support/textutils.h"
54 #include "support/convert.h"
55 #include "support/unicode.h"
57 #include <boost/bind.hpp>
58 #include <boost/next_prior.hpp>
70 using support::contains;
71 using support::prefixIs;
72 using support::suffixIs;
73 using support::rsplit;
77 /** A font entry covers a range of positions. Notice that the
78 entries in the list are inserted in random order.
79 I don't think it's worth the effort to implement a more effective
80 datastructure, because the number of different fonts in a paragraph
82 Nevertheless, I decided to store fontlist_ using a sorted vector:
83 fontlist_ = { {pos_1,font_1} , {pos_2,font_2} , ... } where
84 pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
85 and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
86 (font_1 covers the chars 0,...,pos_1) (Dekel)
92 FontTable(pos_type p, Font const & f)
96 pos_type pos() const { return pos_; }
98 void pos(pos_type p) { pos_ = p; }
100 Font const & font() const { return font_; }
102 void font(Font const & f) { font_ = f;}
105 /// End position of paragraph this font attribute covers
107 /** Font. Interpretation of the font values:
108 If a value is Font::INHERIT_*, it means that the font
109 attribute is inherited from either the layout of this
110 paragraph or, in the case of nested paragraphs, from the
111 layout in the environment one level up until completely
113 The values Font::IGNORE_* and Font::TOGGLE are NOT
114 allowed in these font tables.
123 /// used by lower_bound and upper_bound
124 int operator()(FontTable const & a, FontTable const & b) const {
125 return a.pos() < b.pos();
130 typedef std::vector<FontTable> FontList;
134 /////////////////////////////////////////////////////////////////////
136 // Paragraph::Private
138 /////////////////////////////////////////////////////////////////////
144 class Paragraph::Private
148 Private(Paragraph * owner);
149 /// "Copy constructor"
150 Private(Private const &, Paragraph * owner);
153 value_type getChar(pos_type pos) const;
155 void insertChar(pos_type pos, value_type c, Change const & change);
157 /// Output the surrogate pair formed by \p c and \p next to \p os.
158 /// \return the number of characters written.
159 int latexSurrogatePair(odocstream & os, value_type c, value_type next,
162 /// Output a space in appropriate formatting (or a surrogate pair
163 /// if the next character is a combining character).
164 /// \return whether a surrogate pair was output.
165 bool simpleTeXBlanks(Encoding const &,
166 odocstream &, TexRow & texrow,
168 unsigned int & column,
170 Layout const & style);
172 /// Output consecutive known unicode chars, belonging to the same
173 /// language as specified by \p preamble, to \p os starting from \p c.
174 /// \return the number of characters written.
175 int knownLangChars(odocstream & os, value_type c, string & preamble,
176 Change &, Encoding const &, pos_type &);
178 void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
180 TexRow & texrow, OutputParams &,
183 Font const & outerfont,
185 Change & running_change,
186 Layout const & style,
188 unsigned int & column, value_type const c);
191 void validate(LaTeXFeatures & features,
192 Layout const & layout) const;
195 pos_type size() const { return owner_->size(); }
197 /// match a string against a particular point in the paragraph
198 bool isTextAt(std::string const & str, pos_type pos) const;
200 /// Which Paragraph owns us?
204 Inset * inset_owner_;
212 static unsigned int paragraph_id;
214 ParagraphParameters params_;
216 /// for recording and looking up changes
220 InsetList insetlist_;
227 using std::upper_bound;
228 using std::lower_bound;
232 // Initialization of the counter for the paragraph id's,
233 unsigned int Paragraph::Private::paragraph_id = 0;
237 struct special_phrase {
243 special_phrase const special_phrases[] = {
244 { "LyX", from_ascii("\\LyX{}"), false },
245 { "TeX", from_ascii("\\TeX{}"), true },
246 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
247 { "LaTeX", from_ascii("\\LaTeX{}"), true },
250 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
255 Paragraph::Private::Private(Paragraph * owner)
259 id_ = paragraph_id++;
263 Paragraph::Private::Private(Private const & p, Paragraph * owner)
264 : owner_(owner), inset_owner_(p.inset_owner_), fontlist_(p.fontlist_),
265 params_(p.params_), changes_(p.changes_), insetlist_(p.insetlist_)
267 id_ = paragraph_id++;
271 bool Paragraph::isChanged(pos_type start, pos_type end) const
273 BOOST_ASSERT(start >= 0 && start <= size());
274 BOOST_ASSERT(end > start && end <= size() + 1);
276 return d->changes_.isChanged(start, end);
280 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const {
281 // keep the logic here in sync with the logic of eraseChars()
287 Change change = d->changes_.lookup(size());
289 return change.type == Change::INSERTED && change.author == 0;
293 void Paragraph::setChange(Change const & change)
295 // beware of the imaginary end-of-par character!
296 d->changes_.set(change, 0, size() + 1);
299 * Propagate the change recursively - but not in case of DELETED!
301 * Imagine that your co-author makes changes in an existing inset. He
302 * sends your document to you and you come to the conclusion that the
303 * inset should go completely. If you erase it, LyX must not delete all
304 * text within the inset. Otherwise, the change tracked insertions of
305 * your co-author get lost and there is no way to restore them later.
307 * Conclusion: An inset's content should remain untouched if you delete it
310 if (change.type != Change::DELETED) {
311 for (pos_type pos = 0; pos < size(); ++pos) {
313 getInset(pos)->setChange(change);
319 void Paragraph::setChange(pos_type pos, Change const & change)
321 BOOST_ASSERT(pos >= 0 && pos <= size());
323 d->changes_.set(change, pos);
325 // see comment in setChange(Change const &) above
327 if (change.type != Change::DELETED &&
328 pos < size() && isInset(pos)) {
329 getInset(pos)->setChange(change);
334 Change const & Paragraph::lookupChange(pos_type pos) const
336 BOOST_ASSERT(pos >= 0 && pos <= size());
338 return d->changes_.lookup(pos);
342 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start,
345 BOOST_ASSERT(start >= 0 && start <= size());
346 BOOST_ASSERT(end > start && end <= size() + 1);
348 for (pos_type pos = start; pos < end; ++pos) {
349 switch (lookupChange(pos).type) {
350 case Change::UNCHANGED:
351 // accept changes in nested inset
352 if (pos < size() && isInset(pos))
353 getInset(pos)->acceptChanges(bparams);
357 case Change::INSERTED:
358 d->changes_.set(Change(Change::UNCHANGED), pos);
359 // also accept changes in nested inset
360 if (pos < size() && isInset(pos)) {
361 getInset(pos)->acceptChanges(bparams);
365 case Change::DELETED:
366 // Suppress access to non-existent
367 // "end-of-paragraph char"
369 eraseChar(pos, false);
380 void Paragraph::rejectChanges(BufferParams const & bparams,
381 pos_type start, pos_type end)
383 BOOST_ASSERT(start >= 0 && start <= size());
384 BOOST_ASSERT(end > start && end <= size() + 1);
386 for (pos_type pos = start; pos < end; ++pos) {
387 switch (lookupChange(pos).type) {
388 case Change::UNCHANGED:
389 // reject changes in nested inset
390 if (pos < size() && isInset(pos)) {
391 getInset(pos)->rejectChanges(bparams);
395 case Change::INSERTED:
396 // Suppress access to non-existent
397 // "end-of-paragraph char"
399 eraseChar(pos, false);
405 case Change::DELETED:
406 d->changes_.set(Change(Change::UNCHANGED), pos);
408 // Do NOT reject changes within a deleted inset!
409 // There may be insertions of a co-author inside of it!
417 Paragraph::value_type Paragraph::Private::getChar(pos_type pos) const
419 BOOST_ASSERT(pos >= 0 && pos <= size());
421 return owner_->getChar(pos);
425 void Paragraph::Private::insertChar(pos_type pos, value_type c,
426 Change const & change)
428 BOOST_ASSERT(pos >= 0 && pos <= size());
431 changes_.insert(change, pos);
433 // This is actually very common when parsing buffers (and
434 // maybe inserting ascii text)
436 // when appending characters, no need to update tables
437 owner_->text_.push_back(c);
441 owner_->text_.insert(owner_->text_.begin() + pos, c);
443 // Update the font table.
444 FontTable search_font(pos, Font());
445 for (FontList::iterator it
446 = lower_bound(fontlist_.begin(), fontlist_.end(), search_font, matchFT());
447 it != fontlist_.end(); ++it)
449 it->pos(it->pos() + 1);
453 insetlist_.increasePosAfterPos(pos);
457 void Paragraph::insertInset(pos_type pos, Inset * inset,
458 Change const & change)
461 BOOST_ASSERT(pos >= 0 && pos <= size());
463 d->insertChar(pos, META_INSET, change);
464 BOOST_ASSERT(d->owner_->text_[pos] == META_INSET);
466 // Add a new entry in the insetlist_.
467 d->insetlist_.insert(inset, pos);
471 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
473 BOOST_ASSERT(pos >= 0 && pos <= size());
475 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
478 Change change = d->changes_.lookup(pos);
480 // set the character to DELETED if
481 // a) it was previously unchanged or
482 // b) it was inserted by a co-author
484 if (change.type == Change::UNCHANGED ||
485 (change.type == Change::INSERTED && change.author != 0)) {
486 setChange(pos, Change(Change::DELETED));
490 if (change.type == Change::DELETED)
494 // Don't physically access the imaginary end-of-paragraph character.
495 // eraseChar() can only mark it as DELETED. A physical deletion of
496 // end-of-par must be handled externally.
502 d->changes_.erase(pos);
504 // if it is an inset, delete the inset entry
505 if (text_[pos] == Paragraph::META_INSET)
506 d->insetlist_.erase(pos);
508 text_.erase(text_.begin() + pos);
510 // Erase entries in the tables.
511 FontTable search_font(pos, Font());
513 FontList::iterator it =
514 lower_bound(d->fontlist_.begin(),
516 search_font, matchFT());
517 FontList::iterator begin = d->fontlist_.begin();
518 if (it != d->fontlist_.end() && it->pos() == pos &&
521 && boost::prior(it)->pos() == pos - 1))) {
522 // If it is a multi-character font
523 // entry, we just make it smaller
524 // (see update below), otherwise we
526 unsigned int const i = it - begin;
527 d->fontlist_.erase(begin + i);
529 if (i > 0 && i < d->fontlist_.size() &&
530 d->fontlist_[i - 1].font() == d->fontlist_[i].font()) {
531 d->fontlist_.erase(begin + i - 1);
536 // Update all other entries
537 FontList::iterator fend = d->fontlist_.end();
538 for (; it != fend; ++it)
539 it->pos(it->pos() - 1);
541 // Update the insetlist_
542 d->insetlist_.decreasePosAfterPos(pos);
548 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
550 BOOST_ASSERT(start >= 0 && start <= size());
551 BOOST_ASSERT(end >= start && end <= size() + 1);
554 for (pos_type count = end - start; count; --count) {
555 if (!eraseChar(i, trackChanges))
562 int Paragraph::Private::latexSurrogatePair(odocstream & os, value_type c,
563 value_type next, Encoding const & encoding)
565 // Writing next here may circumvent a possible font change between
566 // c and next. Since next is only output if it forms a surrogate pair
567 // with c we can ignore this:
568 // A font change inside a surrogate pair does not make sense and is
569 // hopefully impossible to input.
570 // FIXME: change tracking
571 // Is this correct WRT change tracking?
572 docstring const latex1 = encoding.latexChar(next);
573 docstring const latex2 = encoding.latexChar(c);
574 os << latex1 << '{' << latex2 << '}';
575 return latex1.length() + latex2.length() + 2;
579 bool Paragraph::Private::simpleTeXBlanks(Encoding const & encoding,
580 odocstream & os, TexRow & texrow,
582 unsigned int & column,
584 Layout const & style)
589 if (i + 1 < size()) {
590 char_type next = getChar(i + 1);
591 if (Encodings::isCombiningChar(next)) {
592 // This space has an accent, so we must always output it.
593 column += latexSurrogatePair(os, ' ', next, encoding) - 1;
599 if (lyxrc.plaintext_linelen > 0
600 && column > lyxrc.plaintext_linelen
602 && getChar(i - 1) != ' '
604 // same in FreeSpacing mode
605 && !owner_->isFreeSpacing()
606 // In typewriter mode, we want to avoid
607 // ! . ? : at the end of a line
608 && !(font.family() == Font::TYPEWRITER_FAMILY
609 && (getChar(i - 1) == '.'
610 || getChar(i - 1) == '?'
611 || getChar(i - 1) == ':'
612 || getChar(i - 1) == '!'))) {
615 texrow.start(owner_->id(), i + 1);
617 } else if (style.free_spacing) {
626 int Paragraph::Private::knownLangChars(odocstream & os,
629 Change & runningChange,
630 Encoding const & encoding,
633 // When the character is marked by the proper language, we simply
634 // get its code point in some encoding, otherwise we get the
635 // translation specified in the unicodesymbols file, which is
636 // something like "\textLANG{<spec>}". So, we have to retain
637 // "\textLANG{<spec>" for the first char but only "<spec>" for
638 // all subsequent chars.
639 docstring const latex1 = rtrim(encoding.latexChar(c), "}");
640 int length = latex1.length();
642 while (i + 1 < size()) {
643 char_type next = getChar(i + 1);
644 // Stop here if next character belongs to another
645 // language or there is a change tracking status.
646 if (!Encodings::isKnownLangChar(next, preamble) ||
647 runningChange != owner_->lookupChange(i + 1))
651 FontList::const_iterator cit = fontlist_.begin();
652 FontList::const_iterator end = fontlist_.end();
653 for (; cit != end; ++cit) {
654 if (cit->pos() >= i && !found) {
655 prev_font = cit->font();
658 if (cit->pos() >= i + 1)
661 // Stop here if there is a font attribute change.
662 if (found && cit != end && prev_font != cit->font())
664 docstring const latex = rtrim(encoding.latexChar(next), "}");
665 docstring::size_type const j =
666 latex.find_first_of(from_ascii("{"));
667 if (j == docstring::npos) {
669 length += latex.length();
671 os << latex.substr(j + 1);
672 length += latex.substr(j + 1).length();
676 // When the proper language is set, we are simply passed a code
677 // point, so we should not try to close the \textLANG command.
678 if (prefixIs(latex1, from_ascii("\\" + preamble))) {
686 bool Paragraph::Private::isTextAt(string const & str, pos_type pos) const
688 pos_type const len = str.length();
690 // is the paragraph large enough?
691 if (pos + len > size())
694 // does the wanted text start at point?
695 for (string::size_type i = 0; i < str.length(); ++i) {
696 // Caution: direct comparison of characters works only
697 // because str is pure ASCII.
698 if (str[i] != owner_->text_[pos + i])
702 // is there a font change in middle of the word?
703 FontList::const_iterator cit = fontlist_.begin();
704 FontList::const_iterator end = fontlist_.end();
705 for (; cit != end; ++cit) {
706 if (cit->pos() >= pos)
709 if (cit != end && pos + len - 1 > cit->pos())
716 void Paragraph::Private::simpleTeXSpecialChars(Buffer const & buf,
717 BufferParams const & bparams,
720 OutputParams & runparams,
723 Font const & outerfont,
725 Change & running_change,
726 Layout const & style,
728 unsigned int & column,
731 if (style.pass_thru) {
732 if (c != Paragraph::META_INSET) {
734 // FIXME UNICODE: This can fail if c cannot
735 // be encoded in the current encoding.
738 owner_->getInset(i)->plaintext(buf, os, runparams);
742 // Two major modes: LaTeX or plain
743 // Handle here those cases common to both modes
744 // and then split to handle the two modes separately.
746 case Paragraph::META_INSET: {
747 Inset * inset = owner_->getInset(i);
749 // FIXME: remove this check
753 // FIXME: move this to InsetNewline::latex
754 if (inset->lyxCode() == NEWLINE_CODE) {
755 // newlines are handled differently here than
756 // the default in simpleTeXSpecialChars().
757 if (!style.newline_allowed) {
761 column += running_font.latexWriteEndChanges(
762 os, bparams, runparams,
767 if (running_font.family() == Font::TYPEWRITER_FAMILY)
770 basefont = owner_->getLayoutFont(bparams, outerfont);
771 running_font = basefont;
773 if (runparams.moving_arg)
779 texrow.start(owner_->id(), i + 1);
784 if (owner_->lookupChange(i).type == Change::DELETED) {
785 if( ++runparams.inDeletedInset == 1)
786 runparams.changeOfDeletedInset = owner_->lookupChange(i);
789 if (inset->canTrackChanges()) {
790 column += Changes::latexMarkChange(os, bparams, running_change,
791 Change(Change::UNCHANGED));
792 running_change = Change(Change::UNCHANGED);
796 odocstream::pos_type const len = os.tellp();
798 if ((inset->lyxCode() == GRAPHICS_CODE
799 || inset->lyxCode() == MATH_CODE
800 || inset->lyxCode() == HYPERLINK_CODE)
801 && running_font.isRightToLeft()) {
802 if (running_font.language()->lang() == "farsi")
809 // FIXME: Bug: we can have an empty font change here!
810 // if there has just been a font change, we are going to close it
811 // right now, which means stupid latex code like \textsf{}. AFAIK,
812 // this does not harm dvi output. A minor bug, thus (JMarc)
813 // Some insets cannot be inside a font change command.
814 // However, even such insets *can* be placed in \L or \R
815 // or their equivalents (for RTL language switches), so we don't
816 // close the language in those cases.
817 // ArabTeX, though, cannot handle this special behavior, it seems.
818 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
819 running_font.language()->lang() == "arabic_arabtex";
820 if (open_font && inset->noFontChange()) {
821 bool closeLanguage = arabtex ||
822 basefont.isRightToLeft() == running_font.isRightToLeft();
823 unsigned int count = running_font.latexWriteEndChanges(
824 os, bparams, runparams,
825 basefont, basefont, closeLanguage);
827 // if any font properties were closed, update the running_font,
828 // making sure, however, to leave the language as it was
830 // FIXME: probably a better way to keep track of the old
831 // language, than copying the entire font?
832 Font const copy_font(running_font);
833 basefont = owner_->getLayoutFont(bparams, outerfont);
834 running_font = basefont;
836 running_font.setLanguage(copy_font.language());
837 // leave font open if language is still open
838 open_font = (running_font.language() == basefont.language());
840 runparams.local_font = &basefont;
844 int tmp = inset->latex(buf, os, runparams);
847 if (running_font.language()->lang() == "farsi")
854 for (int j = 0; j < tmp; ++j) {
857 texrow.start(owner_->id(), i + 1);
860 column += os.tellp() - len;
863 if (owner_->lookupChange(i).type == Change::DELETED) {
864 --runparams.inDeletedInset;
870 // And now for the special cases within each mode
874 os << "\\textbackslash{}";
878 case '|': case '<': case '>':
879 // In T1 encoding, these characters exist
880 if (lyxrc.fontenc == "T1") {
882 //... but we should avoid ligatures
883 if ((c == '>' || c == '<')
885 && getChar(i + 1) == c) {
886 //os << "\\textcompwordmark{}";
888 // Jean-Marc, have a look at
889 // this. I think this works
897 // Typewriter font also has them
898 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
902 // Otherwise, we use what LaTeX
906 os << "\\textless{}";
910 os << "\\textgreater{}";
920 case '-': // "--" in Typewriter mode -> "-{}-"
921 if (i <= size() - 2 &&
922 getChar(i + 1) == '-' &&
923 running_font.family() == Font::TYPEWRITER_FAMILY) {
932 os << "\\char`\\\"{}";
937 case '%': case '#': case '{':
945 os << "\\textasciitilde{}";
950 os << "\\textasciicircum{}";
955 // avoid being mistaken for optional arguments
963 // Blanks are printed before font switching.
964 // Sure? I am not! (try nice-latex)
965 // I am sure it's correct. LyX might be smarter
966 // in the future, but for now, nothing wrong is
972 // I assume this is hack treating typewriter as verbatim
973 // FIXME UNICODE: This can fail if c cannot be encoded
974 // in the current encoding.
975 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
984 // FIXME: if we have "LaTeX" with a font
985 // change in the middle (before the 'T', then
986 // the "TeX" part is still special cased.
987 // Really we should only operate this on
988 // "words" for some definition of word
992 for (; pnr < phrases_nr; ++pnr) {
993 if (isTextAt(special_phrases[pnr].phrase, i)) {
994 os << special_phrases[pnr].macro;
995 i += special_phrases[pnr].phrase.length() - 1;
996 column += special_phrases[pnr].macro.length() - 1;
1001 if (pnr == phrases_nr && c != '\0') {
1002 Encoding const & encoding = *(runparams.encoding);
1003 if (i + 1 < size()) {
1004 char_type next = getChar(i + 1);
1005 if (Encodings::isCombiningChar(next)) {
1006 column += latexSurrogatePair(os, c, next, encoding) - 1;
1012 if (Encodings::isKnownLangChar(c, preamble)) {
1014 knownLangChars(os, c, preamble,
1019 docstring const latex = encoding.latexChar(c);
1020 if (latex.length() > 1 &&
1021 latex[latex.length() - 1] != '}') {
1022 // Prevent eating of a following
1023 // space or command corruption by
1024 // following characters
1025 column += latex.length() + 1;
1026 os << latex << "{}";
1028 column += latex.length() - 1;
1038 void Paragraph::Private::validate(LaTeXFeatures & features,
1039 Layout const & layout) const
1041 BufferParams const & bparams = features.bufferParams();
1043 // check the params.
1044 if (!params_.spacing().isDefault())
1045 features.require("setspace");
1048 features.useLayout(layout.name());
1051 Language const * doc_language = bparams.language;
1053 FontList::const_iterator fcit = fontlist_.begin();
1054 FontList::const_iterator fend = fontlist_.end();
1055 for (; fcit != fend; ++fcit) {
1056 if (fcit->font().noun() == Font::ON) {
1057 LYXERR(Debug::LATEX) << "font.noun: "
1058 << fcit->font().noun()
1060 features.require("noun");
1061 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
1062 << to_utf8(fcit->font().stateText(0))
1065 switch (fcit->font().color()) {
1067 case Color::inherit:
1069 // probably we should put here all interface colors used for
1070 // font displaying! For now I just add this ones I know of (Jug)
1075 features.require("color");
1076 LYXERR(Debug::LATEX) << "Color enabled. Font: "
1077 << to_utf8(fcit->font().stateText(0))
1081 Language const * language = fcit->font().language();
1082 if (language->babel() != doc_language->babel() &&
1083 language != ignore_language &&
1084 language != latex_language)
1086 features.useLanguage(language);
1087 LYXERR(Debug::LATEX) << "Found language "
1088 << language->lang() << endl;
1092 if (!params_.leftIndent().zero())
1093 features.require("ParagraphLeftIndent");
1096 InsetList::const_iterator icit = insetlist_.begin();
1097 InsetList::const_iterator iend = insetlist_.end();
1098 for (; icit != iend; ++icit) {
1100 icit->inset->validate(features);
1101 if (layout.needprotect &&
1102 icit->inset->lyxCode() == FOOT_CODE)
1103 features.require("NeedLyXFootnoteCode");
1107 // then the contents
1108 for (pos_type i = 0; i < size() ; ++i) {
1109 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1110 if (!special_phrases[pnr].builtin
1111 && isTextAt(special_phrases[pnr].phrase, i)) {
1112 features.require(special_phrases[pnr].phrase);
1116 Encodings::validate(getChar(i), features);
1124 /////////////////////////////////////////////////////////////////////
1128 /////////////////////////////////////////////////////////////////////
1132 Paragraph::Paragraph()
1133 : begin_of_body_(0), d(new Paragraph::Private(this))
1140 Paragraph::Paragraph(Paragraph const & par)
1141 : itemdepth(par.itemdepth),
1142 layout_(par.layout_),
1143 text_(par.text_), begin_of_body_(par.begin_of_body_),
1144 d(new Paragraph::Private(*par.d, this))
1149 Paragraph & Paragraph::operator=(Paragraph const & par)
1151 // needed as we will destroy the private part before copying it
1153 itemdepth = par.itemdepth;
1154 layout_ = par.layout();
1156 begin_of_body_ = par.begin_of_body_;
1159 d = new Private(*par.d, this);
1165 Paragraph::~Paragraph()
1171 void Paragraph::write(Buffer const & buf, ostream & os,
1172 BufferParams const & bparams,
1173 depth_type & dth) const
1175 // The beginning or end of a deeper (i.e. nested) area?
1176 if (dth != params().depth()) {
1177 if (params().depth() > dth) {
1178 while (params().depth() > dth) {
1179 os << "\n\\begin_deeper";
1183 while (params().depth() < dth) {
1184 os << "\n\\end_deeper";
1190 // First write the layout
1191 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1195 Font font1(Font::ALL_INHERIT, bparams.language);
1197 Change running_change = Change(Change::UNCHANGED);
1200 for (pos_type i = 0; i <= size(); ++i) {
1202 Change change = lookupChange(i);
1203 Changes::lyxMarkChange(os, column, running_change, change);
1204 running_change = change;
1209 // Write font changes
1210 Font font2 = getFontSettings(bparams, i);
1211 if (font2 != font1) {
1212 font2.lyxWriteChanges(font1, os);
1217 value_type const c = getChar(i);
1221 Inset const * inset = getInset(i);
1223 if (inset->directWrite()) {
1224 // international char, let it write
1225 // code directly so it's shorter in
1227 inset->write(buf, os);
1231 os << "\\begin_inset ";
1232 inset->write(buf, os);
1233 os << "\n\\end_inset\n\n";
1239 os << "\n\\backslash\n";
1243 if (i + 1 < size() && getChar(i + 1) == ' ') {
1250 if ((column > 70 && c == ' ')
1255 // this check is to amend a bug. LyX sometimes
1256 // inserts '\0' this could cause problems.
1258 std::vector<char> tmp = ucs4_to_utf8(c);
1259 tmp.push_back('\0');
1262 lyxerr << "ERROR (Paragraph::writeFile):"
1263 " NULL char in structure." << endl;
1269 os << "\n\\end_layout\n";
1273 void Paragraph::validate(LaTeXFeatures & features) const
1275 d->validate(features, *layout());
1279 void Paragraph::insert(pos_type start, docstring const & str,
1280 Font const & font, Change const & change)
1282 for (size_t i = 0, n = str.size(); i != n ; ++i)
1283 insertChar(start + i, str[i], font, change);
1287 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1290 d->insertChar(pos, c, Change(trackChanges ?
1291 Change::INSERTED : Change::UNCHANGED));
1295 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1296 Font const & font, bool trackChanges)
1298 d->insertChar(pos, c, Change(trackChanges ?
1299 Change::INSERTED : Change::UNCHANGED));
1304 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1305 Font const & font, Change const & change)
1307 d->insertChar(pos, c, change);
1312 void Paragraph::insertInset(pos_type pos, Inset * inset,
1313 Font const & font, Change const & change)
1315 insertInset(pos, inset, change);
1316 // Set the font/language of the inset...
1321 bool Paragraph::insetAllowed(InsetCode code)
1323 return !d->inset_owner_ || d->inset_owner_->insetAllowed(code);
1327 // Gets uninstantiated font setting at position.
1328 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1332 lyxerr << " pos: " << pos << " size: " << size() << endl;
1333 BOOST_ASSERT(pos <= size());
1336 FontList::const_iterator cit = d->fontlist_.begin();
1337 FontList::const_iterator end = d->fontlist_.end();
1338 for (; cit != end; ++cit)
1339 if (cit->pos() >= pos)
1345 if (pos == size() && !empty())
1346 return getFontSettings(bparams, pos - 1);
1348 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1352 FontSpan Paragraph::fontSpan(pos_type pos) const
1354 BOOST_ASSERT(pos <= size());
1357 FontList::const_iterator cit = d->fontlist_.begin();
1358 FontList::const_iterator end = d->fontlist_.end();
1359 for (; cit != end; ++cit) {
1360 if (cit->pos() >= pos) {
1361 if (pos >= beginOfBody())
1362 return FontSpan(std::max(start, beginOfBody()),
1365 return FontSpan(start,
1366 std::min(beginOfBody() - 1,
1369 start = cit->pos() + 1;
1372 // This should not happen, but if so, we take no chances.
1373 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1375 return FontSpan(pos, pos);
1379 // Gets uninstantiated font setting at position 0
1380 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1382 if (!empty() && !d->fontlist_.empty())
1383 return d->fontlist_[0].font();
1385 return Font(Font::ALL_INHERIT, bparams.language);
1389 // Gets the fully instantiated font at a given position in a paragraph
1390 // This is basically the same function as Text::GetFont() in text2.cpp.
1391 // The difference is that this one is used for generating the LaTeX file,
1392 // and thus cosmetic "improvements" are disallowed: This has to deliver
1393 // the true picture of the buffer. (Asger)
1394 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1395 Font const & outerfont) const
1397 BOOST_ASSERT(pos >= 0);
1399 Font font = getFontSettings(bparams, pos);
1401 pos_type const body_pos = beginOfBody();
1403 font.realize(layout_->labelfont);
1405 font.realize(layout_->font);
1407 font.realize(outerfont);
1408 font.realize(bparams.getFont());
1414 Font const Paragraph::getLabelFont
1415 (BufferParams const & bparams, Font const & outerfont) const
1417 Font tmpfont = layout()->labelfont;
1418 tmpfont.setLanguage(getParLanguage(bparams));
1419 tmpfont.realize(outerfont);
1420 tmpfont.realize(bparams.getFont());
1425 Font const Paragraph::getLayoutFont
1426 (BufferParams const & bparams, Font const & outerfont) const
1428 Font tmpfont = layout()->font;
1429 tmpfont.setLanguage(getParLanguage(bparams));
1430 tmpfont.realize(outerfont);
1431 tmpfont.realize(bparams.getFont());
1436 /// Returns the height of the highest font in range
1437 Font_size Paragraph::highestFontInRange
1438 (pos_type startpos, pos_type endpos, Font_size def_size) const
1440 if (d->fontlist_.empty())
1443 FontList::const_iterator end_it = d->fontlist_.begin();
1444 FontList::const_iterator const end = d->fontlist_.end();
1445 for (; end_it != end; ++end_it) {
1446 if (end_it->pos() >= endpos)
1453 FontList::const_iterator cit = d->fontlist_.begin();
1454 for (; cit != end; ++cit) {
1455 if (cit->pos() >= startpos)
1459 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1460 for (; cit != end_it; ++cit) {
1461 Font::FONT_SIZE size = cit->font().size();
1462 if (size == Font::INHERIT_SIZE)
1464 if (size > maxsize && size <= Font::SIZE_HUGER)
1471 Paragraph::value_type
1472 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1474 value_type c = getChar(pos);
1475 if (!lyxrc.rtl_support)
1505 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1512 void Paragraph::setFont(pos_type pos, Font const & font)
1514 BOOST_ASSERT(pos <= size());
1516 // First, reduce font against layout/label font
1517 // Update: The setCharFont() routine in text2.cpp already
1518 // reduces font, so we don't need to do that here. (Asger)
1519 // No need to simplify this because it will disappear
1520 // in a new kernel. (Asger)
1521 // Next search font table
1523 FontList::iterator beg = d->fontlist_.begin();
1524 FontList::iterator it = beg;
1525 FontList::iterator endit = d->fontlist_.end();
1526 for (; it != endit; ++it) {
1527 if (it->pos() >= pos)
1530 size_t const i = distance(beg, it);
1531 bool notfound = (it == endit);
1533 if (!notfound && d->fontlist_[i].font() == font)
1536 bool begin = pos == 0 || notfound ||
1537 (i > 0 && d->fontlist_[i - 1].pos() == pos - 1);
1538 // Is position pos is a beginning of a font block?
1539 bool end = !notfound && d->fontlist_[i].pos() == pos;
1540 // Is position pos is the end of a font block?
1541 if (begin && end) { // A single char block
1542 if (i + 1 < d->fontlist_.size() &&
1543 d->fontlist_[i + 1].font() == font) {
1544 // Merge the singleton block with the next block
1545 d->fontlist_.erase(d->fontlist_.begin() + i);
1546 if (i > 0 && d->fontlist_[i - 1].font() == font)
1547 d->fontlist_.erase(d->fontlist_.begin() + i - 1);
1548 } else if (i > 0 && d->fontlist_[i - 1].font() == font) {
1549 // Merge the singleton block with the previous block
1550 d->fontlist_[i - 1].pos(pos);
1551 d->fontlist_.erase(d->fontlist_.begin() + i);
1553 d->fontlist_[i].font(font);
1555 if (i > 0 && d->fontlist_[i - 1].font() == font)
1556 d->fontlist_[i - 1].pos(pos);
1558 d->fontlist_.insert(d->fontlist_.begin() + i,
1559 FontTable(pos, font));
1561 d->fontlist_[i].pos(pos - 1);
1562 if (!(i + 1 < d->fontlist_.size() &&
1563 d->fontlist_[i + 1].font() == font))
1564 d->fontlist_.insert(d->fontlist_.begin() + i + 1,
1565 FontTable(pos, font));
1566 } else { // The general case. The block is splitted into 3 blocks
1567 d->fontlist_.insert(d->fontlist_.begin() + i,
1568 FontTable(pos - 1, d->fontlist_[i].font()));
1569 d->fontlist_.insert(d->fontlist_.begin() + i + 1,
1570 FontTable(pos, font));
1575 void Paragraph::makeSameLayout(Paragraph const & par)
1577 layout(par.layout());
1579 d->params_ = par.params();
1583 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1585 if (isFreeSpacing())
1591 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1592 if (eraseChar(pos, trackChanges))
1598 return count > 0 || pos > 0;
1602 bool Paragraph::hasSameLayout(Paragraph const & par) const
1604 return par.layout() == layout() && d->params_.sameLayout(par.params());
1608 depth_type Paragraph::getDepth() const
1610 return params().depth();
1614 depth_type Paragraph::getMaxDepthAfter() const
1616 if (layout()->isEnvironment())
1617 return params().depth() + 1;
1619 return params().depth();
1623 char Paragraph::getAlign() const
1625 if (params().align() == LYX_ALIGN_LAYOUT)
1626 return layout()->align;
1628 return params().align();
1632 docstring const & Paragraph::getLabelstring() const
1634 return params().labelString();
1638 // the next two functions are for the manual labels
1639 docstring const Paragraph::getLabelWidthString() const
1641 if (layout()->margintype == MARGIN_MANUAL)
1642 return params().labelWidthString();
1644 return _("Senseless with this layout!");
1648 void Paragraph::setLabelWidthString(docstring const & s)
1650 params().labelWidthString(s);
1654 docstring const Paragraph::translateIfPossible(docstring const & s,
1655 BufferParams const & bparams) const
1657 if (!support::isAscii(s) || s.empty()) {
1658 // This must be a user defined layout. We cannot translate
1659 // this, since gettext accepts only ascii keys.
1662 // Probably standard layout, try to translate
1663 Messages & m = getMessages(getParLanguage(bparams)->code());
1664 return m.get(to_ascii(s));
1668 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1669 BufferParams const & bparams, bool process_appendix) const
1671 TextClass const & tclass = bparams.getTextClass();
1674 if (process_appendix && params().appendix())
1675 fmt = translateIfPossible(layout->labelstring_appendix(),
1678 fmt = translateIfPossible(layout->labelstring(), bparams);
1680 if (fmt.empty() && layout->labeltype == LABEL_COUNTER
1681 && !layout->counter.empty())
1682 fmt = "\\the" + layout->counter;
1684 // handle 'inherited level parts' in 'fmt',
1685 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1686 size_t const i = fmt.find('@', 0);
1687 if (i != docstring::npos) {
1688 size_t const j = fmt.find('@', i + 1);
1689 if (j != docstring::npos) {
1690 docstring parent(fmt, i + 1, j - i - 1);
1691 docstring label = from_ascii("??");
1692 if (tclass.hasLayout(parent))
1693 docstring label = expandLabel(tclass[parent], bparams,
1695 fmt = docstring(fmt, 0, i) + label
1696 + docstring(fmt, j + 1, docstring::npos);
1700 return tclass.counters().counterLabel(fmt);
1704 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1707 LyXAlignment const oldAlign = params().align();
1709 if (!(oldAlign & layout()->alignpossible)) {
1710 frontend::Alert::warning(_("Alignment not permitted"),
1711 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1712 params().align(LYX_ALIGN_LAYOUT);
1717 pos_type Paragraph::beginOfBody() const
1719 return begin_of_body_;
1723 void Paragraph::setBeginOfBody()
1725 if (layout()->labeltype != LABEL_MANUAL) {
1730 // Unroll the first two cycles of the loop
1731 // and remember the previous character to
1732 // remove unnecessary getChar() calls
1734 pos_type end = size();
1735 if (i < end && !isNewline(i)) {
1737 char_type previous_char = 0;
1740 previous_char = text_[i];
1741 if (!isNewline(i)) {
1743 while (i < end && previous_char != ' ') {
1748 previous_char = temp;
1758 // returns -1 if inset not found
1759 int Paragraph::getPositionOfInset(Inset const * inset) const
1762 InsetList::const_iterator it = d->insetlist_.begin();
1763 InsetList::const_iterator end = d->insetlist_.end();
1764 for (; it != end; ++it)
1765 if (it->inset == inset)
1771 InsetBibitem * Paragraph::bibitem() const
1773 if (!d->insetlist_.empty()) {
1774 Inset * inset = d->insetlist_.begin()->inset;
1775 if (inset->lyxCode() == BIBITEM_CODE)
1776 return static_cast<InsetBibitem *>(inset);
1782 bool Paragraph::forceDefaultParagraphs() const
1784 return inInset() && inInset()->forceDefaultParagraphs(0);
1790 // paragraphs inside floats need different alignment tags to avoid
1793 bool noTrivlistCentering(InsetCode code)
1795 return code == FLOAT_CODE || code == WRAP_CODE;
1799 string correction(string const & orig)
1801 if (orig == "flushleft")
1802 return "raggedright";
1803 if (orig == "flushright")
1804 return "raggedleft";
1805 if (orig == "center")
1811 string const corrected_env(string const & suffix, string const & env,
1814 string output = suffix + "{";
1815 if (noTrivlistCentering(code))
1816 output += correction(env);
1820 if (suffix == "\\begin")
1826 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1828 if (!contains(str, "\n"))
1829 column += str.size();
1833 column = rsplit(str, tmp, '\n').size();
1840 // This could go to ParagraphParameters if we want to
1841 int Paragraph::startTeXParParams(BufferParams const & bparams,
1842 odocstream & os, TexRow & texrow,
1843 bool moving_arg) const
1847 if (params().noindent()) {
1848 os << "\\noindent ";
1852 LyXAlignment const curAlign = params().align();
1854 if (curAlign == layout()->align)
1858 case LYX_ALIGN_NONE:
1859 case LYX_ALIGN_BLOCK:
1860 case LYX_ALIGN_LAYOUT:
1861 case LYX_ALIGN_SPECIAL:
1863 case LYX_ALIGN_LEFT:
1864 case LYX_ALIGN_RIGHT:
1865 case LYX_ALIGN_CENTER:
1874 case LYX_ALIGN_NONE:
1875 case LYX_ALIGN_BLOCK:
1876 case LYX_ALIGN_LAYOUT:
1877 case LYX_ALIGN_SPECIAL:
1879 case LYX_ALIGN_LEFT: {
1881 if (getParLanguage(bparams)->babel() != "hebrew")
1882 output = corrected_env("\\begin", "flushleft", ownerCode());
1884 output = corrected_env("\\begin", "flushright", ownerCode());
1885 os << from_ascii(output);
1886 adjust_row_column(output, texrow, column);
1888 } case LYX_ALIGN_RIGHT: {
1890 if (getParLanguage(bparams)->babel() != "hebrew")
1891 output = corrected_env("\\begin", "flushright", ownerCode());
1893 output = corrected_env("\\begin", "flushleft", ownerCode());
1894 os << from_ascii(output);
1895 adjust_row_column(output, texrow, column);
1897 } case LYX_ALIGN_CENTER: {
1899 output = corrected_env("\\begin", "center", ownerCode());
1900 os << from_ascii(output);
1901 adjust_row_column(output, texrow, column);
1910 // This could go to ParagraphParameters if we want to
1911 int Paragraph::endTeXParParams(BufferParams const & bparams,
1912 odocstream & os, TexRow & texrow,
1913 bool moving_arg) const
1917 switch (params().align()) {
1918 case LYX_ALIGN_NONE:
1919 case LYX_ALIGN_BLOCK:
1920 case LYX_ALIGN_LAYOUT:
1921 case LYX_ALIGN_SPECIAL:
1923 case LYX_ALIGN_LEFT:
1924 case LYX_ALIGN_RIGHT:
1925 case LYX_ALIGN_CENTER:
1933 switch (params().align()) {
1934 case LYX_ALIGN_NONE:
1935 case LYX_ALIGN_BLOCK:
1936 case LYX_ALIGN_LAYOUT:
1937 case LYX_ALIGN_SPECIAL:
1939 case LYX_ALIGN_LEFT: {
1941 if (getParLanguage(bparams)->babel() != "hebrew")
1942 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1944 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1945 os << from_ascii(output);
1946 adjust_row_column(output, texrow, column);
1948 } case LYX_ALIGN_RIGHT: {
1950 if (getParLanguage(bparams)->babel() != "hebrew")
1951 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1953 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1954 os << from_ascii(output);
1955 adjust_row_column(output, texrow, column);
1957 } case LYX_ALIGN_CENTER: {
1959 output = corrected_env("\n\\par\\end", "center", ownerCode());
1960 os << from_ascii(output);
1961 adjust_row_column(output, texrow, column);
1970 // This one spits out the text of the paragraph
1971 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1972 BufferParams const & bparams,
1973 Font const & outerfont,
1974 odocstream & os, TexRow & texrow,
1975 OutputParams const & runparams) const
1977 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1979 bool return_value = false;
1983 // well we have to check if we are in an inset with unlimited
1984 // length (all in one row) if that is true then we don't allow
1985 // any special options in the paragraph and also we don't allow
1986 // any environment other than the default layout of the text class
1988 bool asdefault = forceDefaultParagraphs();
1991 style = bparams.getTextClass().defaultLayout();
1996 // Current base font for all inherited font changes, without any
1997 // change caused by an individual character, except for the language:
1998 // It is set to the language of the first character.
1999 // As long as we are in the label, this font is the base font of the
2000 // label. Before the first body character it is set to the base font
2004 // Maybe we have to create a optional argument.
2005 pos_type body_pos = beginOfBody();
2006 unsigned int column = 0;
2009 // the optional argument is kept in curly brackets in
2010 // case it contains a ']'
2013 basefont = getLabelFont(bparams, outerfont);
2015 basefont = getLayoutFont(bparams, outerfont);
2018 // Which font is currently active?
2019 Font running_font(basefont);
2020 // Do we have an open font change?
2021 bool open_font = false;
2023 Change runningChange = Change(Change::UNCHANGED);
2025 texrow.start(id(), 0);
2027 // if the paragraph is empty, the loop will not be entered at all
2029 if (style->isCommand()) {
2034 column += startTeXParParams(bparams, os, texrow,
2035 runparams.moving_arg);
2038 for (pos_type i = 0; i < size(); ++i) {
2039 // First char in paragraph or after label?
2040 if (i == body_pos) {
2043 column += running_font.latexWriteEndChanges(
2044 os, bparams, runparams,
2045 basefont, basefont);
2048 basefont = getLayoutFont(bparams, outerfont);
2049 running_font = basefont;
2051 column += Changes::latexMarkChange(os, bparams,
2052 runningChange, Change(Change::UNCHANGED));
2053 runningChange = Change(Change::UNCHANGED);
2058 if (style->isCommand()) {
2064 column += startTeXParParams(bparams, os,
2066 runparams.moving_arg);
2069 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2072 if (bparams.outputChanges && runningChange != change) {
2074 column += running_font.latexWriteEndChanges(
2075 os, bparams, runparams, basefont, basefont);
2078 basefont = getLayoutFont(bparams, outerfont);
2079 running_font = basefont;
2081 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2082 runningChange = change;
2085 // do not output text which is marked deleted
2086 // if change tracking output is disabled
2087 if (!bparams.outputChanges && change.type == Change::DELETED) {
2093 value_type const c = getChar(i);
2095 // Fully instantiated font
2096 Font const font = getFont(bparams, i, outerfont);
2098 Font const last_font = running_font;
2100 // Do we need to close the previous font?
2102 (font != running_font ||
2103 font.language() != running_font.language()))
2105 column += running_font.latexWriteEndChanges(
2106 os, bparams, runparams, basefont,
2107 (i == body_pos-1) ? basefont : font);
2108 running_font = basefont;
2112 // Switch file encoding if necessary
2113 if (runparams.encoding->package() == Encoding::inputenc &&
2114 font.language()->encoding()->package() == Encoding::inputenc) {
2115 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2116 runparams.moving_arg, *(runparams.encoding),
2117 *(font.language()->encoding()));
2118 if (enc_switch.first) {
2119 column += enc_switch.second;
2120 runparams.encoding = font.language()->encoding();
2124 // Do we need to change font?
2125 if ((font != running_font ||
2126 font.language() != running_font.language()) &&
2129 odocstringstream ods;
2130 column += font.latexWriteStartChanges(ods, bparams,
2131 runparams, basefont,
2133 running_font = font;
2135 docstring fontchange = ods.str();
2136 // check if the fontchange ends with a trailing blank
2137 // (like "\small " (see bug 3382)
2138 if (suffixIs(fontchange, ' ') && c == ' ')
2139 os << fontchange.substr(0, fontchange.size() - 1)
2140 << from_ascii("{}");
2146 // Do not print the separation of the optional argument
2147 // if style->pass_thru is false. This works because
2148 // simpleTeXSpecialChars ignores spaces if
2149 // style->pass_thru is false.
2150 if (i != body_pos - 1) {
2151 if (d->simpleTeXBlanks(
2152 *(runparams.encoding), os, texrow,
2153 i, column, font, *style))
2154 // A surrogate pair was output. We
2155 // must not call simpleTeXSpecialChars
2156 // in this iteration, since
2157 // simpleTeXBlanks incremented i, and
2158 // simpleTeXSpecialChars would output
2159 // the combining character again.
2164 OutputParams rp = runparams;
2165 rp.free_spacing = style->free_spacing;
2166 rp.local_font = &font;
2167 rp.intitle = style->intitle;
2168 d->simpleTeXSpecialChars(buf, bparams, os,
2169 texrow, rp, running_font,
2170 basefont, outerfont, open_font,
2171 runningChange, *style, i, column, c);
2173 // Set the encoding to that returned from simpleTeXSpecialChars (see
2174 // comment for encoding member in OutputParams.h)
2175 runparams.encoding = rp.encoding;
2178 // If we have an open font definition, we have to close it
2180 #ifdef FIXED_LANGUAGE_END_DETECTION
2183 .latexWriteEndChanges(os, bparams, runparams,
2185 next_->getFont(bparams, 0, outerfont));
2187 running_font.latexWriteEndChanges(os, bparams,
2188 runparams, basefont, basefont);
2191 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2192 //FIXME: there as we start another \selectlanguage with the next paragraph if
2193 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2194 running_font.latexWriteEndChanges(os, bparams, runparams,
2195 basefont, basefont);
2199 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2201 // Needed if there is an optional argument but no contents.
2202 if (body_pos > 0 && body_pos == size()) {
2204 return_value = false;
2208 column += endTeXParParams(bparams, os, texrow,
2209 runparams.moving_arg);
2212 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2213 return return_value;
2230 string tag_name(PAR_TAG const & pt) {
2232 case PAR_NONE: return "!-- --";
2233 case TT: return "tt";
2234 case SF: return "sf";
2235 case BF: return "bf";
2236 case IT: return "it";
2237 case SL: return "sl";
2238 case EM: return "em";
2245 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2247 p1 = static_cast<PAR_TAG>(p1 | p2);
2252 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2254 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2260 bool Paragraph::emptyTag() const
2262 for (pos_type i = 0; i < size(); ++i) {
2264 Inset const * inset = getInset(i);
2265 InsetCode lyx_code = inset->lyxCode();
2266 if (lyx_code != TOC_CODE &&
2267 lyx_code != INCLUDE_CODE &&
2268 lyx_code != GRAPHICS_CODE &&
2269 lyx_code != ERT_CODE &&
2270 lyx_code != LISTINGS_CODE &&
2271 lyx_code != FLOAT_CODE &&
2272 lyx_code != TABULAR_CODE) {
2276 value_type c = getChar(i);
2277 if (c != ' ' && c != '\t')
2285 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2287 for (pos_type i = 0; i < size(); ++i) {
2289 Inset const * inset = getInset(i);
2290 InsetCode lyx_code = inset->lyxCode();
2291 if (lyx_code == LABEL_CODE) {
2292 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2293 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2302 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2305 for (i = 0; i < size(); ++i) {
2307 Inset const * inset = getInset(i);
2308 inset->docbook(buf, os, runparams);
2310 value_type c = getChar(i);
2313 os << sgml::escapeChar(c);
2320 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2324 for (pos_type i = initial; i < size(); ++i) {
2325 Font font = getFont(buf.params(), i, outerfont);
2328 if (i != initial && font != font_old)
2337 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2339 OutputParams const & runparams,
2340 Font const & outerfont,
2341 pos_type initial) const
2343 bool emph_flag = false;
2345 LayoutPtr const & style = layout();
2347 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2349 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2352 // parsing main loop
2353 for (pos_type i = initial; i < size(); ++i) {
2354 Font font = getFont(buf.params(), i, outerfont);
2356 // handle <emphasis> tag
2357 if (font_old.emph() != font.emph()) {
2358 if (font.emph() == Font::ON) {
2361 } else if (i != initial) {
2362 os << "</emphasis>";
2368 Inset const * inset = getInset(i);
2369 inset->docbook(buf, os, runparams);
2371 value_type c = getChar(i);
2373 if (style->pass_thru)
2376 os << sgml::escapeChar(c);
2382 os << "</emphasis>";
2385 if (style->free_spacing)
2387 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2392 bool Paragraph::isHfill(pos_type pos) const
2395 && getInset(pos)->lyxCode() == HFILL_CODE;
2399 bool Paragraph::isNewline(pos_type pos) const
2402 && getInset(pos)->lyxCode() == NEWLINE_CODE;
2406 bool Paragraph::isLineSeparator(pos_type pos) const
2408 value_type const c = getChar(pos);
2409 return isLineSeparatorChar(c)
2410 || (c == Paragraph::META_INSET && getInset(pos) &&
2411 getInset(pos)->isLineSeparator());
2415 /// Used by the spellchecker
2416 bool Paragraph::isLetter(pos_type pos) const
2419 return getInset(pos)->isLetter();
2421 value_type const c = getChar(pos);
2422 return isLetterChar(c) || isDigit(c);
2428 Paragraph::getParLanguage(BufferParams const & bparams) const
2431 return getFirstFontSettings(bparams).language();
2432 // FIXME: we should check the prev par as well (Lgb)
2433 return bparams.language;
2437 bool Paragraph::isRTL(BufferParams const & bparams) const
2439 return lyxrc.rtl_support
2440 && getParLanguage(bparams)->rightToLeft()
2441 && ownerCode() != ERT_CODE
2442 && ownerCode() != LISTINGS_CODE;
2446 void Paragraph::changeLanguage(BufferParams const & bparams,
2447 Language const * from, Language const * to)
2449 // change language including dummy font change at the end
2450 for (pos_type i = 0; i <= size(); ++i) {
2451 Font font = getFontSettings(bparams, i);
2452 if (font.language() == from) {
2453 font.setLanguage(to);
2460 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2462 Language const * doc_language = bparams.language;
2463 FontList::const_iterator cit = d->fontlist_.begin();
2464 FontList::const_iterator end = d->fontlist_.end();
2466 for (; cit != end; ++cit)
2467 if (cit->font().language() != ignore_language &&
2468 cit->font().language() != latex_language &&
2469 cit->font().language() != doc_language)
2475 // Convert the paragraph to a string.
2476 // Used for building the table of contents
2477 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2479 return asString(buffer, 0, size(), label);
2483 docstring const Paragraph::asString(Buffer const & buffer,
2484 pos_type beg, pos_type end, bool label) const
2487 odocstringstream os;
2489 if (beg == 0 && label && !params().labelString().empty())
2490 os << params().labelString() << ' ';
2492 for (pos_type i = beg; i < end; ++i) {
2493 value_type const c = getChar(i);
2496 else if (c == META_INSET)
2497 getInset(i)->textString(buffer, os);
2504 void Paragraph::setInsetOwner(Inset * inset)
2506 d->inset_owner_ = inset;
2510 int Paragraph::id() const
2516 LayoutPtr const & Paragraph::layout() const
2522 void Paragraph::layout(LayoutPtr const & new_layout)
2524 layout_ = new_layout;
2528 Inset * Paragraph::inInset() const
2530 return d->inset_owner_;
2534 InsetCode Paragraph::ownerCode() const
2536 return d->inset_owner_ ?
2537 d->inset_owner_->lyxCode() : NO_CODE;
2541 ParagraphParameters & Paragraph::params()
2547 ParagraphParameters const & Paragraph::params() const
2553 bool Paragraph::isFreeSpacing() const
2555 if (layout()->free_spacing)
2558 // for now we just need this, later should we need this in some
2559 // other way we can always add a function to Inset too.
2560 return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE;
2564 bool Paragraph::allowEmpty() const
2566 if (layout()->keepempty)
2568 return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE;
2572 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2574 if (!Encodings::is_arabic(c))
2577 value_type prev_char = ' ';
2578 value_type next_char = ' ';
2580 for (pos_type i = pos - 1; i >= 0; --i) {
2581 value_type const par_char = getChar(i);
2582 if (!Encodings::isComposeChar_arabic(par_char)) {
2583 prev_char = par_char;
2588 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2589 value_type const par_char = getChar(i);
2590 if (!Encodings::isComposeChar_arabic(par_char)) {
2591 next_char = par_char;
2596 if (Encodings::is_arabic(next_char)) {
2597 if (Encodings::is_arabic(prev_char) &&
2598 !Encodings::is_arabic_special(prev_char))
2599 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2601 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2603 if (Encodings::is_arabic(prev_char) &&
2604 !Encodings::is_arabic_special(prev_char))
2605 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2607 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2612 int Paragraph::checkBiblio(bool track_changes)
2615 //This is getting more and more a mess. ...We really should clean
2616 //up this bibitem issue for 1.6. See also bug 2743.
2618 // Add bibitem insets if necessary
2619 if (layout()->labeltype != LABEL_BIBLIO)
2622 bool hasbibitem = !d->insetlist_.empty()
2623 // Insist on it being in pos 0
2624 && getChar(0) == Paragraph::META_INSET
2625 && d->insetlist_.begin()->inset->lyxCode() == BIBITEM_CODE;
2630 // remove a bibitem in pos != 0
2631 // restore it later in pos 0 if necessary
2632 // (e.g. if a user inserts contents _before_ the item)
2633 // we're assuming there's only one of these, which there
2635 int erasedInsetPosition = -1;
2636 InsetList::iterator it = d->insetlist_.begin();
2637 InsetList::iterator end = d->insetlist_.end();
2638 for (; it != end; ++it)
2639 if (it->inset->lyxCode() == BIBITEM_CODE
2641 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2642 oldkey = olditem->getParam("key");
2643 oldlabel = olditem->getParam("label");
2644 erasedInsetPosition = it->pos;
2645 eraseChar(erasedInsetPosition, track_changes);
2649 //There was an InsetBibitem at the beginning, and we didn't
2650 //have to erase one.
2651 if (hasbibitem && erasedInsetPosition < 0)
2654 //There was an InsetBibitem at the beginning and we did have to
2655 //erase one. So we give its properties to the beginning inset.
2657 InsetBibitem * inset =
2658 static_cast<InsetBibitem *>(d->insetlist_.begin()->inset);
2659 if (!oldkey.empty())
2660 inset->setParam("key", oldkey);
2661 inset->setParam("label", oldlabel);
2662 return -erasedInsetPosition;
2665 //There was no inset at the beginning, so we need to create one with
2666 //the key and label of the one we erased.
2667 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2668 // restore values of previously deleted item in this par.
2669 if (!oldkey.empty())
2670 inset->setParam("key", oldkey);
2671 inset->setParam("label", oldlabel);
2672 insertInset(0, static_cast<Inset *>(inset),
2673 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2679 void Paragraph::checkAuthors(AuthorList const & authorList)
2681 d->changes_.checkAuthors(authorList);
2685 bool Paragraph::isUnchanged(pos_type pos) const
2687 return lookupChange(pos).type == Change::UNCHANGED;
2691 bool Paragraph::isInserted(pos_type pos) const
2693 return lookupChange(pos).type == Change::INSERTED;
2697 bool Paragraph::isDeleted(pos_type pos) const
2699 return lookupChange(pos).type == Change::DELETED;
2703 InsetList const & Paragraph::insetList() const
2705 return d->insetlist_;
2709 Inset * Paragraph::releaseInset(pos_type pos)
2711 Inset * inset = d->insetlist_.release(pos);
2712 /// does not honour change tracking!
2713 eraseChar(pos, false);
2718 Inset * Paragraph::getInset(pos_type pos)
2720 return d->insetlist_.get(pos);
2724 Inset const * Paragraph::getInset(pos_type pos) const
2726 return d->insetlist_.get(pos);