3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Jean-Marc Lasgouttes
9 * \author Angus Leeming
11 * \author André Pönitz
13 * \author Jürgen Vigna
15 * Full author contact details are available in file CREDITS.
20 #include "Paragraph.h"
23 #include "BufferParams.h"
29 #include "LaTeXFeatures.h"
36 #include "OutputParams.h"
37 #include "output_latex.h"
38 #include "paragraph_funcs.h"
39 #include "ParagraphParameters.h"
40 #include "rowpainter.h"
45 #include "frontends/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::rsplit;
72 /////////////////////////////////////////////////////////////////////
76 /////////////////////////////////////////////////////////////////////
82 class Paragraph::Pimpl {
85 Pimpl(Paragraph * owner);
86 /// "Copy constructor"
87 Pimpl(Pimpl const &, Paragraph * owner);
92 /// look up change at given pos
93 Change const & lookupChange(pos_type pos) const;
94 /// is there a change within the given range ?
95 bool isChanged(pos_type start, pos_type end) const;
96 /// will the paragraph be physically merged with the next
97 /// one if the imaginary end-of-par character is logically deleted?
98 bool isMergedOnEndOfParDeletion(bool trackChanges) const;
99 /// set change for the entire par
100 void setChange(Change const & change);
101 /// set change at given pos
102 void setChange(pos_type pos, Change const & change);
103 /// accept changes within the given range
104 void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end);
105 /// reject changes within the given range
106 void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end);
109 value_type getChar(pos_type pos) const;
111 void insertChar(pos_type pos, value_type c, Change const & change);
113 void insertInset(pos_type pos, Inset * inset, Change const & change);
114 /// (logically) erase the char at pos; return true if it was actually erased
115 bool eraseChar(pos_type pos, bool trackChanges);
116 /// (logically) erase the given range; return the number of chars actually erased
117 int eraseChars(pos_type start, pos_type end, bool trackChanges);
121 /** A font entry covers a range of positions. Notice that the
122 entries in the list are inserted in random order.
123 I don't think it's worth the effort to implement a more effective
124 datastructure, because the number of different fonts in a paragraph
126 Nevertheless, I decided to store fontlist using a sorted vector:
127 fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where
128 pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
129 and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
130 (font_1 covers the chars 0,...,pos_1) (Dekel)
135 FontTable(pos_type p, Font const & f)
139 pos_type pos() const { return pos_; }
141 void pos(pos_type p) { pos_ = p; }
143 Font const & font() const { return font_; }
145 void font(Font const & f) { font_ = f;}
147 /// End position of paragraph this font attribute covers
149 /** Font. Interpretation of the font values:
150 If a value is Font::INHERIT_*, it means that the font
151 attribute is inherited from either the layout of this
152 paragraph or, in the case of nested paragraphs, from the
153 layout in the environment one level up until completely
155 The values Font::IGNORE_* and Font::TOGGLE are NOT
156 allowed in these font tables.
161 friend class matchFT;
165 /// used by lower_bound and upper_bound
166 int operator()(FontTable const & a, FontTable const & b) const {
167 return a.pos() < b.pos();
172 typedef std::vector<FontTable> FontList;
176 /// Output the surrogate pair formed by \p c and \p next to \p os.
177 /// \return the number of characters written.
178 int latexSurrogatePair(odocstream & os, value_type c, value_type next,
180 /// Output a space in appropriate formatting (or a surrogate pair
181 /// if the next character is a combining character).
182 /// \return whether a surrogate pair was output.
183 bool simpleTeXBlanks(Encoding const &,
184 odocstream &, TexRow & texrow,
186 unsigned int & column,
188 Layout const & style);
190 void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
192 TexRow & texrow, OutputParams const &,
195 Font const & outerfont,
197 Change & running_change,
198 Layout const & style,
200 unsigned int & column, value_type const c);
203 void validate(LaTeXFeatures & features,
204 Layout const & layout) const;
209 static unsigned int paragraph_id;
211 ParagraphParameters params;
215 pos_type size() const { return owner_->size(); }
216 /// match a string against a particular point in the paragraph
217 bool isTextAt(std::string const & str, pos_type pos) const;
219 /// for recording and looking up changes
230 using std::upper_bound;
231 using std::lower_bound;
235 // Initialization of the counter for the paragraph id's,
236 unsigned int Paragraph::Pimpl::paragraph_id = 0;
240 struct special_phrase {
246 special_phrase const special_phrases[] = {
247 { "LyX", from_ascii("\\LyX{}"), false },
248 { "TeX", from_ascii("\\TeX{}"), true },
249 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
250 { "LaTeX", from_ascii("\\LaTeX{}"), true },
253 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
258 Paragraph::Pimpl::Pimpl(Paragraph * owner)
262 id_ = paragraph_id++;
266 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
267 : params(p.params), changes_(p.changes_), owner_(owner)
269 inset_owner = p.inset_owner;
270 fontlist = p.fontlist;
271 id_ = paragraph_id++;
275 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
277 BOOST_ASSERT(start >= 0 && start <= size());
278 BOOST_ASSERT(end > start && end <= size() + 1);
280 return changes_.isChanged(start, end);
284 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
285 // keep the logic here in sync with the logic of eraseChars()
291 Change change = changes_.lookup(size());
293 return change.type == Change::INSERTED && change.author == 0;
297 void Paragraph::Pimpl::setChange(Change const & change)
299 // beware of the imaginary end-of-par character!
300 changes_.set(change, 0, size() + 1);
303 * Propagate the change recursively - but not in case of DELETED!
305 * Imagine that your co-author makes changes in an existing inset. He
306 * sends your document to you and you come to the conclusion that the
307 * inset should go completely. If you erase it, LyX must not delete all
308 * text within the inset. Otherwise, the change tracked insertions of
309 * your co-author get lost and there is no way to restore them later.
311 * Conclusion: An inset's content should remain untouched if you delete it
314 if (change.type != Change::DELETED) {
315 for (pos_type pos = 0; pos < size(); ++pos) {
316 if (owner_->isInset(pos)) {
317 owner_->getInset(pos)->setChange(change);
324 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
326 BOOST_ASSERT(pos >= 0 && pos <= size());
328 changes_.set(change, pos);
330 // see comment in setChange(Change const &) above
332 if (change.type != Change::DELETED &&
333 pos < size() && owner_->isInset(pos)) {
334 owner_->getInset(pos)->setChange(change);
339 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
341 BOOST_ASSERT(pos >= 0 && pos <= size());
343 return changes_.lookup(pos);
347 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
349 BOOST_ASSERT(start >= 0 && start <= size());
350 BOOST_ASSERT(end > start && end <= size() + 1);
352 for (pos_type pos = start; pos < end; ++pos) {
353 switch (lookupChange(pos).type) {
354 case Change::UNCHANGED:
355 // accept changes in nested inset
356 if (pos < size() && owner_->isInset(pos)) {
357 owner_->getInset(pos)->acceptChanges(bparams);
362 case Change::INSERTED:
363 changes_.set(Change(Change::UNCHANGED), pos);
364 // also accept changes in nested inset
365 if (pos < size() && owner_->isInset(pos)) {
366 owner_->getInset(pos)->acceptChanges(bparams);
370 case Change::DELETED:
371 // Suppress access to non-existent
372 // "end-of-paragraph char"
374 eraseChar(pos, false);
385 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
387 BOOST_ASSERT(start >= 0 && start <= size());
388 BOOST_ASSERT(end > start && end <= size() + 1);
390 for (pos_type pos = start; pos < end; ++pos) {
391 switch (lookupChange(pos).type) {
392 case Change::UNCHANGED:
393 // reject changes in nested inset
394 if (pos < size() && owner_->isInset(pos)) {
395 owner_->getInset(pos)->rejectChanges(bparams);
399 case Change::INSERTED:
400 // Suppress access to non-existent
401 // "end-of-paragraph char"
403 eraseChar(pos, false);
409 case Change::DELETED:
410 changes_.set(Change(Change::UNCHANGED), pos);
412 // Do NOT reject changes within a deleted inset!
413 // There may be insertions of a co-author inside of it!
421 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
423 BOOST_ASSERT(pos >= 0 && pos <= size());
425 return owner_->getChar(pos);
429 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
431 BOOST_ASSERT(pos >= 0 && pos <= size());
434 changes_.insert(change, pos);
436 // This is actually very common when parsing buffers (and
437 // maybe inserting ascii text)
439 // when appending characters, no need to update tables
440 owner_->text_.push_back(c);
444 owner_->text_.insert(owner_->text_.begin() + pos, c);
446 // Update the font table.
447 FontTable search_font(pos, Font());
448 for (FontList::iterator it
449 = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
450 it != fontlist.end(); ++it)
452 it->pos(it->pos() + 1);
456 owner_->insetlist.increasePosAfterPos(pos);
460 void Paragraph::Pimpl::insertInset(pos_type pos, Inset * inset,
461 Change const & change)
464 BOOST_ASSERT(pos >= 0 && pos <= size());
466 insertChar(pos, META_INSET, change);
467 BOOST_ASSERT(owner_->text_[pos] == META_INSET);
469 // Add a new entry in the insetlist.
470 owner_->insetlist.insert(inset, pos);
474 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
476 BOOST_ASSERT(pos >= 0 && pos <= size());
478 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
481 Change change = changes_.lookup(pos);
483 // set the character to DELETED if
484 // a) it was previously unchanged or
485 // b) it was inserted by a co-author
487 if (change.type == Change::UNCHANGED ||
488 (change.type == Change::INSERTED && change.author != 0)) {
489 setChange(pos, Change(Change::DELETED));
493 if (change.type == Change::DELETED)
497 // Don't physically access the imaginary end-of-paragraph character.
498 // eraseChar() can only mark it as DELETED. A physical deletion of
499 // end-of-par must be handled externally.
507 // if it is an inset, delete the inset entry
508 if (owner_->text_[pos] == Paragraph::META_INSET) {
509 owner_->insetlist.erase(pos);
512 owner_->text_.erase(owner_->text_.begin() + pos);
514 // Erase entries in the tables.
515 FontTable search_font(pos, Font());
517 FontList::iterator it =
518 lower_bound(fontlist.begin(),
520 search_font, matchFT());
521 if (it != fontlist.end() && it->pos() == pos &&
523 (it != fontlist.begin()
524 && boost::prior(it)->pos() == pos - 1))) {
525 // If it is a multi-character font
526 // entry, we just make it smaller
527 // (see update below), otherwise we
529 unsigned int const i = it - fontlist.begin();
530 fontlist.erase(fontlist.begin() + i);
531 it = fontlist.begin() + i;
532 if (i > 0 && i < fontlist.size() &&
533 fontlist[i - 1].font() == fontlist[i].font()) {
534 fontlist.erase(fontlist.begin() + i - 1);
535 it = fontlist.begin() + i - 1;
539 // Update all other entries
540 FontList::iterator fend = fontlist.end();
541 for (; it != fend; ++it)
542 it->pos(it->pos() - 1);
544 // Update the insetlist
545 owner_->insetlist.decreasePosAfterPos(pos);
551 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
553 BOOST_ASSERT(start >= 0 && start <= size());
554 BOOST_ASSERT(end >= start && end <= size() + 1);
557 for (pos_type count = end - start; count; --count) {
558 if (!eraseChar(i, trackChanges))
565 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
566 value_type next, Encoding const & encoding)
568 // Writing next here may circumvent a possible font change between
569 // c and next. Since next is only output if it forms a surrogate pair
570 // with c we can ignore this:
571 // A font change inside a surrogate pair does not make sense and is
572 // hopefully impossible to input.
573 // FIXME: change tracking
574 // Is this correct WRT change tracking?
575 docstring const latex1 = encoding.latexChar(next);
576 docstring const latex2 = encoding.latexChar(c);
577 os << latex1 << '{' << latex2 << '}';
578 return latex1.length() + latex2.length() + 2;
582 bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding,
583 odocstream & os, TexRow & texrow,
585 unsigned int & column,
587 Layout const & style)
592 if (i < size() - 1) {
593 char_type next = getChar(i + 1);
594 if (Encodings::isCombiningChar(next)) {
595 // This space has an accent, so we must always output it.
596 column += latexSurrogatePair(os, ' ', next, encoding) - 1;
602 if (lyxrc.plaintext_linelen > 0
603 && column > lyxrc.plaintext_linelen
605 && getChar(i - 1) != ' '
607 // same in FreeSpacing mode
608 && !owner_->isFreeSpacing()
609 // In typewriter mode, we want to avoid
610 // ! . ? : at the end of a line
611 && !(font.family() == Font::TYPEWRITER_FAMILY
612 && (getChar(i - 1) == '.'
613 || getChar(i - 1) == '?'
614 || getChar(i - 1) == ':'
615 || getChar(i - 1) == '!'))) {
618 texrow.start(owner_->id(), i + 1);
620 } else if (style.free_spacing) {
629 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
631 pos_type const len = str.length();
633 // is the paragraph large enough?
634 if (pos + len > size())
637 // does the wanted text start at point?
638 for (string::size_type i = 0; i < str.length(); ++i) {
639 // Caution: direct comparison of characters works only
640 // because str is pure ASCII.
641 if (str[i] != owner_->text_[pos + i])
645 // is there a font change in middle of the word?
646 FontList::const_iterator cit = fontlist.begin();
647 FontList::const_iterator end = fontlist.end();
648 for (; cit != end; ++cit) {
649 if (cit->pos() >= pos)
652 if (cit != end && pos + len - 1 > cit->pos())
659 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
660 BufferParams const & bparams,
663 OutputParams const & runparams,
666 Font const & outerfont,
668 Change & running_change,
669 Layout const & style,
671 unsigned int & column,
674 if (style.pass_thru) {
675 if (c != Paragraph::META_INSET) {
677 // FIXME UNICODE: This can fail if c cannot
678 // be encoded in the current encoding.
681 owner_->getInset(i)->plaintext(buf, os, runparams);
685 // Two major modes: LaTeX or plain
686 // Handle here those cases common to both modes
687 // and then split to handle the two modes separately.
689 case Paragraph::META_INSET: {
690 Inset * inset = owner_->getInset(i);
692 // FIXME: remove this check
696 // FIXME: move this to InsetNewline::latex
697 if (inset->lyxCode() == Inset::NEWLINE_CODE) {
698 // newlines are handled differently here than
699 // the default in simpleTeXSpecialChars().
700 if (!style.newline_allowed) {
704 column += running_font.latexWriteEndChanges(
705 os, bparams, runparams,
710 if (running_font.family() == Font::TYPEWRITER_FAMILY)
713 basefont = owner_->getLayoutFont(bparams, outerfont);
714 running_font = basefont;
716 if (runparams.moving_arg)
722 texrow.start(owner_->id(), i + 1);
727 if (inset->canTrackChanges()) {
728 column += Changes::latexMarkChange(os, bparams, running_change,
729 Change(Change::UNCHANGED));
730 running_change = Change(Change::UNCHANGED);
734 odocstream::pos_type const len = os.tellp();
736 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
737 || inset->lyxCode() == Inset::MATH_CODE
738 || inset->lyxCode() == Inset::URL_CODE)
739 && running_font.isRightToLeft()) {
740 if (running_font.language()->lang() == "farsi")
748 #warning Bug: we can have an empty font change here!
749 // if there has just been a font change, we are going to close it
750 // right now, which means stupid latex code like \textsf{}. AFAIK,
751 // this does not harm dvi output. A minor bug, thus (JMarc)
753 // some insets cannot be inside a font change command
754 if (open_font && inset->noFontChange()) {
755 column += running_font.latexWriteEndChanges(
756 os, bparams, runparams,
759 basefont = owner_->getLayoutFont(bparams, outerfont);
760 running_font = basefont;
763 int tmp = inset->latex(buf, os, runparams);
766 if (running_font.language()->lang() == "farsi")
773 for (int j = 0; j < tmp; ++j) {
776 texrow.start(owner_->id(), i + 1);
779 column += os.tellp() - len;
785 // And now for the special cases within each mode
789 os << "\\textbackslash{}";
793 case '|': case '<': case '>':
794 // In T1 encoding, these characters exist
795 if (lyxrc.fontenc == "T1") {
797 //... but we should avoid ligatures
798 if ((c == '>' || c == '<')
800 && getChar(i + 1) == c) {
801 //os << "\\textcompwordmark{}";
803 // Jean-Marc, have a look at
804 // this. I think this works
812 // Typewriter font also has them
813 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
817 // Otherwise, we use what LaTeX
821 os << "\\textless{}";
825 os << "\\textgreater{}";
835 case '-': // "--" in Typewriter mode -> "-{}-"
836 if (i <= size() - 2 &&
837 getChar(i + 1) == '-' &&
838 running_font.family() == Font::TYPEWRITER_FAMILY) {
847 os << "\\char`\\\"{}";
852 case '%': case '#': case '{':
860 os << "\\textasciitilde{}";
865 os << "\\textasciicircum{}";
870 // avoid being mistaken for optional arguments
878 // Blanks are printed before font switching.
879 // Sure? I am not! (try nice-latex)
880 // I am sure it's correct. LyX might be smarter
881 // in the future, but for now, nothing wrong is
887 // I assume this is hack treating typewriter as verbatim
888 // FIXME UNICODE: This can fail if c cannot be encoded
889 // in the current encoding.
890 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
899 // FIXME: if we have "LaTeX" with a font
900 // change in the middle (before the 'T', then
901 // the "TeX" part is still special cased.
902 // Really we should only operate this on
903 // "words" for some definition of word
907 for (; pnr < phrases_nr; ++pnr) {
908 if (isTextAt(special_phrases[pnr].phrase, i)) {
909 os << special_phrases[pnr].macro;
910 i += special_phrases[pnr].phrase.length() - 1;
911 column += special_phrases[pnr].macro.length() - 1;
916 if (pnr == phrases_nr && c != '\0') {
917 Encoding const & encoding = *(runparams.encoding);
918 if (i < size() - 1) {
919 char_type next = getChar(i + 1);
920 if (Encodings::isCombiningChar(next)) {
921 column += latexSurrogatePair(os, c, next, encoding) - 1;
926 docstring const latex = encoding.latexChar(c);
927 if (latex.length() > 1 &&
928 latex[latex.length() - 1] != '}') {
929 // Prevent eating of a following
930 // space or command corruption by
931 // following characters
932 column += latex.length() + 1;
935 column += latex.length() - 1;
945 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
946 Layout const & layout) const
948 BufferParams const & bparams = features.bufferParams();
951 if (!params.spacing().isDefault())
952 features.require("setspace");
955 features.useLayout(layout.name());
958 Language const * doc_language = bparams.language;
960 FontList::const_iterator fcit = fontlist.begin();
961 FontList::const_iterator fend = fontlist.end();
962 for (; fcit != fend; ++fcit) {
963 if (fcit->font().noun() == Font::ON) {
964 LYXERR(Debug::LATEX) << "font.noun: "
965 << fcit->font().noun()
967 features.require("noun");
968 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
969 << to_utf8(fcit->font().stateText(0))
972 switch (fcit->font().color()) {
976 // probably we should put here all interface colors used for
977 // font displaying! For now I just add this ones I know of (Jug)
982 features.require("color");
983 LYXERR(Debug::LATEX) << "Color enabled. Font: "
984 << to_utf8(fcit->font().stateText(0))
988 Language const * language = fcit->font().language();
989 if (language->babel() != doc_language->babel() &&
990 language != ignore_language &&
991 language != latex_language)
993 features.useLanguage(language);
994 LYXERR(Debug::LATEX) << "Found language "
995 << language->lang() << endl;
999 if (!params.leftIndent().zero())
1000 features.require("ParagraphLeftIndent");
1003 InsetList::const_iterator icit = owner_->insetlist.begin();
1004 InsetList::const_iterator iend = owner_->insetlist.end();
1005 for (; icit != iend; ++icit) {
1007 icit->inset->validate(features);
1008 if (layout.needprotect &&
1009 icit->inset->lyxCode() == Inset::FOOT_CODE)
1010 features.require("NeedLyXFootnoteCode");
1014 // then the contents
1015 for (pos_type i = 0; i < size() ; ++i) {
1016 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1017 if (!special_phrases[pnr].builtin
1018 && isTextAt(special_phrases[pnr].phrase, i)) {
1019 features.require(special_phrases[pnr].phrase);
1023 Encodings::validate(getChar(i), features);
1031 /////////////////////////////////////////////////////////////////////
1035 /////////////////////////////////////////////////////////////////////
1039 Paragraph::Paragraph()
1040 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1047 Paragraph::Paragraph(Paragraph const & par)
1048 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1049 layout_(par.layout_),
1050 text_(par.text_), begin_of_body_(par.begin_of_body_),
1051 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1053 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1054 InsetList::iterator it = insetlist.begin();
1055 InsetList::iterator end = insetlist.end();
1056 for (; it != end; ++it)
1057 it->inset = it->inset->clone().release();
1061 Paragraph & Paragraph::operator=(Paragraph const & par)
1063 // needed as we will destroy the pimpl_ before copying it
1065 itemdepth = par.itemdepth;
1067 insetlist = par.insetlist;
1068 InsetList::iterator it = insetlist.begin();
1069 InsetList::iterator end = insetlist.end();
1070 for (; it != end; ++it)
1071 it->inset = it->inset->clone().release();
1073 layout_ = par.layout();
1075 begin_of_body_ = par.begin_of_body_;
1078 pimpl_ = new Pimpl(*par.pimpl_, this);
1084 Paragraph::~Paragraph()
1088 //lyxerr << "Paragraph::paragraph_id = "
1089 // << Paragraph::paragraph_id << endl;
1093 void Paragraph::write(Buffer const & buf, ostream & os,
1094 BufferParams const & bparams,
1095 depth_type & dth) const
1097 // The beginning or end of a deeper (i.e. nested) area?
1098 if (dth != params().depth()) {
1099 if (params().depth() > dth) {
1100 while (params().depth() > dth) {
1101 os << "\n\\begin_deeper";
1105 while (params().depth() < dth) {
1106 os << "\n\\end_deeper";
1112 // First write the layout
1113 os << "\n\\begin_layout " << layout()->name() << '\n';
1117 Font font1(Font::ALL_INHERIT, bparams.language);
1119 Change running_change = Change(Change::UNCHANGED);
1122 for (pos_type i = 0; i <= size(); ++i) {
1124 Change change = pimpl_->lookupChange(i);
1125 Changes::lyxMarkChange(os, column, running_change, change);
1126 running_change = change;
1131 // Write font changes
1132 Font font2 = getFontSettings(bparams, i);
1133 if (font2 != font1) {
1134 font2.lyxWriteChanges(font1, os);
1139 value_type const c = getChar(i);
1143 Inset const * inset = getInset(i);
1145 if (inset->directWrite()) {
1146 // international char, let it write
1147 // code directly so it's shorter in
1149 inset->write(buf, os);
1153 os << "\\begin_inset ";
1154 inset->write(buf, os);
1155 os << "\n\\end_inset\n\n";
1161 os << "\n\\backslash\n";
1165 if (i + 1 < size() && getChar(i + 1) == ' ') {
1172 if ((column > 70 && c == ' ')
1177 // this check is to amend a bug. LyX sometimes
1178 // inserts '\0' this could cause problems.
1180 std::vector<char> tmp = ucs4_to_utf8(c);
1181 tmp.push_back('\0');
1184 lyxerr << "ERROR (Paragraph::writeFile):"
1185 " NULL char in structure." << endl;
1191 os << "\n\\end_layout\n";
1195 void Paragraph::validate(LaTeXFeatures & features) const
1197 pimpl_->validate(features, *layout());
1201 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1203 return pimpl_->eraseChar(pos, trackChanges);
1207 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1209 return pimpl_->eraseChars(start, end, trackChanges);
1213 void Paragraph::insert(pos_type start, docstring const & str,
1214 Font const & font, Change const & change)
1216 for (size_t i = 0, n = str.size(); i != n ; ++i)
1217 insertChar(start + i, str[i], font, change);
1221 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1224 pimpl_->insertChar(pos, c, Change(trackChanges ?
1225 Change::INSERTED : Change::UNCHANGED));
1229 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1230 Font const & font, bool trackChanges)
1232 pimpl_->insertChar(pos, c, Change(trackChanges ?
1233 Change::INSERTED : Change::UNCHANGED));
1238 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1239 Font const & font, Change const & change)
1241 pimpl_->insertChar(pos, c, change);
1246 void Paragraph::insertInset(pos_type pos, Inset * inset,
1247 Change const & change)
1249 pimpl_->insertInset(pos, inset, change);
1253 void Paragraph::insertInset(pos_type pos, Inset * inset,
1254 Font const & font, Change const & change)
1256 pimpl_->insertInset(pos, inset, change);
1261 bool Paragraph::insetAllowed(Inset_code code)
1263 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1267 // Gets uninstantiated font setting at position.
1268 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1272 lyxerr << " pos: " << pos << " size: " << size() << endl;
1273 BOOST_ASSERT(pos <= size());
1276 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1277 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1278 for (; cit != end; ++cit)
1279 if (cit->pos() >= pos)
1285 if (pos == size() && !empty())
1286 return getFontSettings(bparams, pos - 1);
1288 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1292 FontSpan Paragraph::fontSpan(pos_type pos) const
1294 BOOST_ASSERT(pos <= size());
1297 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1298 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1299 for (; cit != end; ++cit) {
1300 if (cit->pos() >= pos) {
1301 if (pos >= beginOfBody())
1302 return FontSpan(std::max(start, beginOfBody()),
1305 return FontSpan(start,
1306 std::min(beginOfBody() - 1,
1309 start = cit->pos() + 1;
1312 // This should not happen, but if so, we take no chances.
1313 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1315 return FontSpan(pos, pos);
1319 // Gets uninstantiated font setting at position 0
1320 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1322 if (!empty() && !pimpl_->fontlist.empty())
1323 return pimpl_->fontlist[0].font();
1325 return Font(Font::ALL_INHERIT, bparams.language);
1329 // Gets the fully instantiated font at a given position in a paragraph
1330 // This is basically the same function as Text::GetFont() in text2.cpp.
1331 // The difference is that this one is used for generating the LaTeX file,
1332 // and thus cosmetic "improvements" are disallowed: This has to deliver
1333 // the true picture of the buffer. (Asger)
1334 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1335 Font const & outerfont) const
1337 BOOST_ASSERT(pos >= 0);
1339 Layout_ptr const & lout = layout();
1341 pos_type const body_pos = beginOfBody();
1345 layoutfont = lout->labelfont;
1347 layoutfont = lout->font;
1349 Font font = getFontSettings(bparams, pos);
1350 font.realize(layoutfont);
1351 font.realize(outerfont);
1352 font.realize(bparams.getFont());
1358 Font const Paragraph::getLabelFont
1359 (BufferParams const & bparams, Font const & outerfont) const
1361 Font tmpfont = layout()->labelfont;
1362 tmpfont.setLanguage(getParLanguage(bparams));
1363 tmpfont.realize(outerfont);
1364 tmpfont.realize(bparams.getFont());
1369 Font const Paragraph::getLayoutFont
1370 (BufferParams const & bparams, Font const & outerfont) const
1372 Font tmpfont = layout()->font;
1373 tmpfont.setLanguage(getParLanguage(bparams));
1374 tmpfont.realize(outerfont);
1375 tmpfont.realize(bparams.getFont());
1380 /// Returns the height of the highest font in range
1381 Font_size Paragraph::highestFontInRange
1382 (pos_type startpos, pos_type endpos, Font_size def_size) const
1384 if (pimpl_->fontlist.empty())
1387 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1388 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1389 for (; end_it != end; ++end_it) {
1390 if (end_it->pos() >= endpos)
1397 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1398 for (; cit != end; ++cit) {
1399 if (cit->pos() >= startpos)
1403 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1404 for (; cit != end_it; ++cit) {
1405 Font::FONT_SIZE size = cit->font().size();
1406 if (size == Font::INHERIT_SIZE)
1408 if (size > maxsize && size <= Font::SIZE_HUGER)
1415 Paragraph::value_type
1416 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1418 value_type c = getChar(pos);
1419 if (!lyxrc.rtl_support)
1449 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1456 void Paragraph::setFont(pos_type pos, Font const & font)
1458 BOOST_ASSERT(pos <= size());
1460 // First, reduce font against layout/label font
1461 // Update: The setCharFont() routine in text2.cpp already
1462 // reduces font, so we don't need to do that here. (Asger)
1463 // No need to simplify this because it will disappear
1464 // in a new kernel. (Asger)
1465 // Next search font table
1467 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1468 Pimpl::FontList::iterator it = beg;
1469 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1470 for (; it != endit; ++it) {
1471 if (it->pos() >= pos)
1474 size_t const i = distance(beg, it);
1475 bool notfound = (it == endit);
1477 if (!notfound && pimpl_->fontlist[i].font() == font)
1480 bool begin = pos == 0 || notfound ||
1481 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1482 // Is position pos is a beginning of a font block?
1483 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1484 // Is position pos is the end of a font block?
1485 if (begin && end) { // A single char block
1486 if (i + 1 < pimpl_->fontlist.size() &&
1487 pimpl_->fontlist[i + 1].font() == font) {
1488 // Merge the singleton block with the next block
1489 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1490 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1491 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1492 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1493 // Merge the singleton block with the previous block
1494 pimpl_->fontlist[i - 1].pos(pos);
1495 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1497 pimpl_->fontlist[i].font(font);
1499 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1500 pimpl_->fontlist[i - 1].pos(pos);
1502 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1503 Pimpl::FontTable(pos, font));
1505 pimpl_->fontlist[i].pos(pos - 1);
1506 if (!(i + 1 < pimpl_->fontlist.size() &&
1507 pimpl_->fontlist[i + 1].font() == font))
1508 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1509 Pimpl::FontTable(pos, font));
1510 } else { // The general case. The block is splitted into 3 blocks
1511 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1512 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1513 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1514 Pimpl::FontTable(pos, font));
1519 void Paragraph::makeSameLayout(Paragraph const & par)
1521 layout(par.layout());
1523 params() = par.params();
1527 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1529 if (isFreeSpacing())
1535 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1536 if (eraseChar(pos, trackChanges))
1542 return count > 0 || pos > 0;
1546 bool Paragraph::hasSameLayout(Paragraph const & par) const
1548 return par.layout() == layout() && params().sameLayout(par.params());
1552 depth_type Paragraph::getDepth() const
1554 return params().depth();
1558 depth_type Paragraph::getMaxDepthAfter() const
1560 if (layout()->isEnvironment())
1561 return params().depth() + 1;
1563 return params().depth();
1567 char Paragraph::getAlign() const
1569 if (params().align() == LYX_ALIGN_LAYOUT)
1570 return layout()->align;
1572 return params().align();
1576 docstring const & Paragraph::getLabelstring() const
1578 return params().labelString();
1582 // the next two functions are for the manual labels
1583 docstring const Paragraph::getLabelWidthString() const
1585 if (!params().labelWidthString().empty())
1586 return params().labelWidthString();
1588 return _("Senseless with this layout!");
1592 void Paragraph::setLabelWidthString(docstring const & s)
1594 params().labelWidthString(s);
1598 docstring const Paragraph::translateIfPossible(docstring const & s,
1599 BufferParams const & bparams) const
1601 if (!support::isAscii(s) || s.empty()) {
1602 // This must be a user defined layout. We cannot translate
1603 // this, since gettext accepts only ascii keys.
1606 // Probably standard layout, try to translate
1607 Messages & m = getMessages(getParLanguage(bparams)->code());
1608 return m.get(to_ascii(s));
1612 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1613 BufferParams const & bparams, bool process_appendix) const
1615 TextClass const & tclass = bparams.getTextClass();
1618 if (process_appendix && params().appendix())
1619 fmt = translateIfPossible(layout->labelstring_appendix(),
1622 fmt = translateIfPossible(layout->labelstring(), bparams);
1624 // handle 'inherited level parts' in 'fmt',
1625 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1626 size_t const i = fmt.find('@', 0);
1627 if (i != docstring::npos) {
1628 size_t const j = fmt.find('@', i + 1);
1629 if (j != docstring::npos) {
1630 docstring parent(fmt, i + 1, j - i - 1);
1632 docstring label = expandLabel(tclass[to_utf8(parent)], bparams);
1633 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1637 return tclass.counters().counterLabel(fmt);
1641 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1644 params().labelWidthString(docstring());
1645 params().align(LYX_ALIGN_LAYOUT);
1646 params().spacing(Spacing(Spacing::Default));
1650 pos_type Paragraph::beginOfBody() const
1652 return begin_of_body_;
1656 void Paragraph::setBeginOfBody()
1658 if (layout()->labeltype != LABEL_MANUAL) {
1663 // Unroll the first two cycles of the loop
1664 // and remember the previous character to
1665 // remove unnecessary getChar() calls
1667 pos_type end = size();
1668 if (i < end && !isNewline(i)) {
1670 char_type previous_char = 0;
1673 previous_char = text_[i];
1674 if (!isNewline(i)) {
1676 while (i < end && previous_char != ' ') {
1681 previous_char = temp;
1691 // returns -1 if inset not found
1692 int Paragraph::getPositionOfInset(Inset const * inset) const
1695 InsetList::const_iterator it = insetlist.begin();
1696 InsetList::const_iterator end = insetlist.end();
1697 for (; it != end; ++it)
1698 if (it->inset == inset)
1704 InsetBibitem * Paragraph::bibitem() const
1706 if (!insetlist.empty()) {
1707 Inset * inset = insetlist.begin()->inset;
1708 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1709 return static_cast<InsetBibitem *>(inset);
1715 bool Paragraph::forceDefaultParagraphs() const
1717 return inInset() && inInset()->forceDefaultParagraphs(0);
1723 // paragraphs inside floats need different alignment tags to avoid
1726 bool noTrivlistCentering(Inset::Code code)
1728 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1732 string correction(string const & orig)
1734 if (orig == "flushleft")
1735 return "raggedright";
1736 if (orig == "flushright")
1737 return "raggedleft";
1738 if (orig == "center")
1744 string const corrected_env(string const & suffix, string const & env,
1747 string output = suffix + "{";
1748 if (noTrivlistCentering(code))
1749 output += correction(env);
1753 if (suffix == "\\begin")
1759 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1761 if (!contains(str, "\n"))
1762 column += str.size();
1766 column = rsplit(str, tmp, '\n').size();
1773 // This could go to ParagraphParameters if we want to
1774 int Paragraph::startTeXParParams(BufferParams const & bparams,
1775 odocstream & os, TexRow & texrow,
1776 bool moving_arg) const
1780 if (params().noindent()) {
1781 os << "\\noindent ";
1785 switch (params().align()) {
1786 case LYX_ALIGN_NONE:
1787 case LYX_ALIGN_BLOCK:
1788 case LYX_ALIGN_LAYOUT:
1789 case LYX_ALIGN_SPECIAL:
1791 case LYX_ALIGN_LEFT:
1792 case LYX_ALIGN_RIGHT:
1793 case LYX_ALIGN_CENTER:
1801 switch (params().align()) {
1802 case LYX_ALIGN_NONE:
1803 case LYX_ALIGN_BLOCK:
1804 case LYX_ALIGN_LAYOUT:
1805 case LYX_ALIGN_SPECIAL:
1807 case LYX_ALIGN_LEFT: {
1809 if (getParLanguage(bparams)->babel() != "hebrew")
1810 output = corrected_env("\\begin", "flushleft", ownerCode());
1812 output = corrected_env("\\begin", "flushright", ownerCode());
1813 os << from_ascii(output);
1814 adjust_row_column(output, texrow, column);
1816 } case LYX_ALIGN_RIGHT: {
1818 if (getParLanguage(bparams)->babel() != "hebrew")
1819 output = corrected_env("\\begin", "flushright", ownerCode());
1821 output = corrected_env("\\begin", "flushleft", ownerCode());
1822 os << from_ascii(output);
1823 adjust_row_column(output, texrow, column);
1825 } case LYX_ALIGN_CENTER: {
1827 output = corrected_env("\\begin", "center", ownerCode());
1828 os << from_ascii(output);
1829 adjust_row_column(output, texrow, column);
1838 // This could go to ParagraphParameters if we want to
1839 int Paragraph::endTeXParParams(BufferParams const & bparams,
1840 odocstream & os, TexRow & texrow,
1841 bool moving_arg) const
1845 switch (params().align()) {
1846 case LYX_ALIGN_NONE:
1847 case LYX_ALIGN_BLOCK:
1848 case LYX_ALIGN_LAYOUT:
1849 case LYX_ALIGN_SPECIAL:
1851 case LYX_ALIGN_LEFT:
1852 case LYX_ALIGN_RIGHT:
1853 case LYX_ALIGN_CENTER:
1861 switch (params().align()) {
1862 case LYX_ALIGN_NONE:
1863 case LYX_ALIGN_BLOCK:
1864 case LYX_ALIGN_LAYOUT:
1865 case LYX_ALIGN_SPECIAL:
1867 case LYX_ALIGN_LEFT: {
1869 if (getParLanguage(bparams)->babel() != "hebrew")
1870 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1872 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1873 os << from_ascii(output);
1874 adjust_row_column(output, texrow, column);
1876 } case LYX_ALIGN_RIGHT: {
1878 if (getParLanguage(bparams)->babel() != "hebrew")
1879 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1881 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1882 os << from_ascii(output);
1883 adjust_row_column(output, texrow, column);
1885 } case LYX_ALIGN_CENTER: {
1887 output = corrected_env("\n\\par\\end", "center", ownerCode());
1888 os << from_ascii(output);
1889 adjust_row_column(output, texrow, column);
1898 // This one spits out the text of the paragraph
1899 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1900 BufferParams const & bparams,
1901 Font const & outerfont,
1902 odocstream & os, TexRow & texrow,
1903 OutputParams const & runparams) const
1905 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1907 bool return_value = false;
1911 // well we have to check if we are in an inset with unlimited
1912 // length (all in one row) if that is true then we don't allow
1913 // any special options in the paragraph and also we don't allow
1914 // any environment other than the default layout of the text class
1916 bool asdefault = forceDefaultParagraphs();
1919 style = bparams.getTextClass().defaultLayout();
1924 // Current base font for all inherited font changes, without any
1925 // change caused by an individual character, except for the language:
1926 // It is set to the language of the first character.
1927 // As long as we are in the label, this font is the base font of the
1928 // label. Before the first body character it is set to the base font
1932 // Maybe we have to create a optional argument.
1933 pos_type body_pos = beginOfBody();
1934 unsigned int column = 0;
1937 // the optional argument is kept in curly brackets in
1938 // case it contains a ']'
1941 basefont = getLabelFont(bparams, outerfont);
1943 basefont = getLayoutFont(bparams, outerfont);
1946 // Which font is currently active?
1947 Font running_font(basefont);
1948 // Do we have an open font change?
1949 bool open_font = false;
1951 Change runningChange = Change(Change::UNCHANGED);
1953 texrow.start(id(), 0);
1955 // if the paragraph is empty, the loop will not be entered at all
1957 if (style->isCommand()) {
1962 column += startTeXParParams(bparams, os, texrow,
1963 runparams.moving_arg);
1966 for (pos_type i = 0; i < size(); ++i) {
1967 // First char in paragraph or after label?
1968 if (i == body_pos) {
1971 column += running_font.latexWriteEndChanges(
1972 os, bparams, runparams,
1973 basefont, basefont);
1976 basefont = getLayoutFont(bparams, outerfont);
1977 running_font = basefont;
1979 column += Changes::latexMarkChange(os, bparams,
1980 runningChange, Change(Change::UNCHANGED));
1981 runningChange = Change(Change::UNCHANGED);
1986 if (style->isCommand()) {
1992 column += startTeXParParams(bparams, os,
1994 runparams.moving_arg);
1997 Change const & change = pimpl_->lookupChange(i);
1999 if (bparams.outputChanges && runningChange != change) {
2001 column += running_font.latexWriteEndChanges(
2002 os, bparams, runparams, basefont, basefont);
2005 basefont = getLayoutFont(bparams, outerfont);
2006 running_font = basefont;
2008 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2009 runningChange = change;
2012 // do not output text which is marked deleted
2013 // if change tracking output is disabled
2014 if (!bparams.outputChanges && change.type == Change::DELETED) {
2020 value_type const c = getChar(i);
2022 // Fully instantiated font
2023 Font const font = getFont(bparams, i, outerfont);
2025 Font const last_font = running_font;
2027 // Do we need to close the previous font?
2029 (font != running_font ||
2030 font.language() != running_font.language()))
2032 column += running_font.latexWriteEndChanges(
2033 os, bparams, runparams, basefont,
2034 (i == body_pos-1) ? basefont : font);
2035 running_font = basefont;
2039 // Switch file encoding if necessary
2040 if (runparams.encoding->package() == Encoding::inputenc &&
2041 font.language()->encoding()->package() == Encoding::inputenc) {
2042 int const count = switchEncoding(os, bparams,
2043 runparams.moving_arg, *(runparams.encoding),
2044 *(font.language()->encoding()));
2047 runparams.encoding = font.language()->encoding();
2051 // Do we need to change font?
2052 if ((font != running_font ||
2053 font.language() != running_font.language()) &&
2056 column += font.latexWriteStartChanges(os, bparams,
2057 runparams, basefont,
2059 running_font = font;
2064 // Do not print the separation of the optional argument
2065 // if style->pass_thru is false. This works because
2066 // simpleTeXSpecialChars ignores spaces if
2067 // style->pass_thru is false.
2068 if (i != body_pos - 1) {
2069 if (pimpl_->simpleTeXBlanks(
2070 *(runparams.encoding), os, texrow,
2071 i, column, font, *style))
2072 // A surrogate pair was output. We
2073 // must not call simpleTeXSpecialChars
2074 // in this iteration, since
2075 // simpleTeXBlanks incremented i, and
2076 // simpleTeXSpecialChars would output
2077 // the combining character again.
2082 OutputParams rp = runparams;
2083 rp.free_spacing = style->free_spacing;
2084 rp.local_font = &font;
2085 rp.intitle = style->intitle;
2086 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2087 texrow, rp, running_font,
2088 basefont, outerfont, open_font,
2089 runningChange, *style, i, column, c);
2092 // If we have an open font definition, we have to close it
2094 #ifdef FIXED_LANGUAGE_END_DETECTION
2097 .latexWriteEndChanges(os, bparams, runparams,
2099 next_->getFont(bparams, 0, outerfont));
2101 running_font.latexWriteEndChanges(os, bparams,
2102 runparams, basefont, basefont);
2105 #ifdef WITH_WARNINGS
2106 //#warning For now we ALWAYS have to close the foreign font settings if they are
2107 //#warning there as we start another \selectlanguage with the next paragraph if
2108 //#warning we are in need of this. This should be fixed sometime (Jug)
2110 running_font.latexWriteEndChanges(os, bparams, runparams,
2111 basefont, basefont);
2115 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2117 // Needed if there is an optional argument but no contents.
2118 if (body_pos > 0 && body_pos == size()) {
2120 return_value = false;
2124 column += endTeXParParams(bparams, os, texrow,
2125 runparams.moving_arg);
2128 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2129 return return_value;
2146 string tag_name(PAR_TAG const & pt) {
2148 case PAR_NONE: return "!-- --";
2149 case TT: return "tt";
2150 case SF: return "sf";
2151 case BF: return "bf";
2152 case IT: return "it";
2153 case SL: return "sl";
2154 case EM: return "em";
2161 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2163 p1 = static_cast<PAR_TAG>(p1 | p2);
2168 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2170 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2176 bool Paragraph::emptyTag() const
2178 for (pos_type i = 0; i < size(); ++i) {
2180 Inset const * inset = getInset(i);
2181 Inset::Code lyx_code = inset->lyxCode();
2182 if (lyx_code != Inset::TOC_CODE &&
2183 lyx_code != Inset::INCLUDE_CODE &&
2184 lyx_code != Inset::GRAPHICS_CODE &&
2185 lyx_code != Inset::ERT_CODE &&
2186 lyx_code != Inset::LISTINGS_CODE &&
2187 lyx_code != Inset::FLOAT_CODE &&
2188 lyx_code != Inset::TABULAR_CODE) {
2192 value_type c = getChar(i);
2193 if (c != ' ' && c != '\t')
2201 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2203 for (pos_type i = 0; i < size(); ++i) {
2205 Inset const * inset = getInset(i);
2206 Inset::Code lyx_code = inset->lyxCode();
2207 if (lyx_code == Inset::LABEL_CODE) {
2208 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2209 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2218 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2221 for (i = 0; i < size(); ++i) {
2223 Inset const * inset = getInset(i);
2224 inset->docbook(buf, os, runparams);
2226 value_type c = getChar(i);
2229 os << sgml::escapeChar(c);
2236 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2240 for (pos_type i = initial; i < size(); ++i) {
2241 Font font = getFont(buf.params(), i, outerfont);
2244 if (i != initial && font != font_old)
2253 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2255 OutputParams const & runparams,
2256 Font const & outerfont,
2257 pos_type initial) const
2259 bool emph_flag = false;
2261 Layout_ptr const & style = layout();
2263 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2265 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2268 // parsing main loop
2269 for (pos_type i = initial; i < size(); ++i) {
2270 Font font = getFont(buf.params(), i, outerfont);
2272 // handle <emphasis> tag
2273 if (font_old.emph() != font.emph()) {
2274 if (font.emph() == Font::ON) {
2277 } else if (i != initial) {
2278 os << "</emphasis>";
2284 Inset const * inset = getInset(i);
2285 inset->docbook(buf, os, runparams);
2287 value_type c = getChar(i);
2289 if (style->pass_thru)
2292 os << sgml::escapeChar(c);
2298 os << "</emphasis>";
2301 if (style->free_spacing)
2303 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2308 bool Paragraph::isNewline(pos_type pos) const
2311 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2315 bool Paragraph::isLineSeparator(pos_type pos) const
2317 value_type const c = getChar(pos);
2318 return isLineSeparatorChar(c)
2319 || (c == Paragraph::META_INSET && getInset(pos) &&
2320 getInset(pos)->isLineSeparator());
2324 /// Used by the spellchecker
2325 bool Paragraph::isLetter(pos_type pos) const
2328 return getInset(pos)->isLetter();
2330 value_type const c = getChar(pos);
2331 return isLetterChar(c) || isDigit(c);
2337 Paragraph::getParLanguage(BufferParams const & bparams) const
2340 return getFirstFontSettings(bparams).language();
2341 #ifdef WITH_WARNINGS
2342 #warning FIXME we should check the prev par as well (Lgb)
2344 return bparams.language;
2348 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2350 return lyxrc.rtl_support
2351 && getParLanguage(bparams)->rightToLeft()
2352 && ownerCode() != Inset::ERT_CODE
2353 && ownerCode() != Inset::LISTINGS_CODE;
2357 void Paragraph::changeLanguage(BufferParams const & bparams,
2358 Language const * from, Language const * to)
2360 // change language including dummy font change at the end
2361 for (pos_type i = 0; i <= size(); ++i) {
2362 Font font = getFontSettings(bparams, i);
2363 if (font.language() == from) {
2364 font.setLanguage(to);
2371 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2373 Language const * doc_language = bparams.language;
2374 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2375 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2377 for (; cit != end; ++cit)
2378 if (cit->font().language() != ignore_language &&
2379 cit->font().language() != latex_language &&
2380 cit->font().language() != doc_language)
2386 // Convert the paragraph to a string.
2387 // Used for building the table of contents
2388 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2390 return asString(buffer, 0, size(), label);
2394 docstring const Paragraph::asString(Buffer const & buffer,
2395 pos_type beg, pos_type end, bool label) const
2398 odocstringstream os;
2400 if (beg == 0 && label && !params().labelString().empty())
2401 os << params().labelString() << ' ';
2403 for (pos_type i = beg; i < end; ++i) {
2404 value_type const c = getChar(i);
2407 else if (c == META_INSET)
2408 getInset(i)->textString(buffer, os);
2415 void Paragraph::setInsetOwner(Inset * inset)
2417 pimpl_->inset_owner = inset;
2421 Change const & Paragraph::lookupChange(pos_type pos) const
2423 BOOST_ASSERT(pos <= size());
2424 return pimpl_->lookupChange(pos);
2428 bool Paragraph::isChanged(pos_type start, pos_type end) const
2430 return pimpl_->isChanged(start, end);
2434 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2436 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2440 void Paragraph::setChange(Change const & change)
2442 pimpl_->setChange(change);
2446 void Paragraph::setChange(pos_type pos, Change const & change)
2448 pimpl_->setChange(pos, change);
2452 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2454 return pimpl_->acceptChanges(bparams, start, end);
2458 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2460 return pimpl_->rejectChanges(bparams, start, end);
2464 int Paragraph::id() const
2470 Layout_ptr const & Paragraph::layout() const
2476 void Paragraph::layout(Layout_ptr const & new_layout)
2478 layout_ = new_layout;
2482 Inset * Paragraph::inInset() const
2484 return pimpl_->inset_owner;
2488 Inset::Code Paragraph::ownerCode() const
2490 return pimpl_->inset_owner
2491 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2495 ParagraphParameters & Paragraph::params()
2497 return pimpl_->params;
2501 ParagraphParameters const & Paragraph::params() const
2503 return pimpl_->params;
2507 bool Paragraph::isFreeSpacing() const
2509 if (layout()->free_spacing)
2512 // for now we just need this, later should we need this in some
2513 // other way we can always add a function to Inset too.
2514 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2518 bool Paragraph::allowEmpty() const
2520 if (layout()->keepempty)
2522 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2526 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2528 if (!Encodings::is_arabic(c))
2531 value_type prev_char = ' ';
2532 value_type next_char = ' ';
2534 for (pos_type i = pos - 1; i >= 0; --i) {
2535 value_type const par_char = getChar(i);
2536 if (!Encodings::isComposeChar_arabic(par_char)) {
2537 prev_char = par_char;
2542 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2543 value_type const par_char = getChar(i);
2544 if (!Encodings::isComposeChar_arabic(par_char)) {
2545 next_char = par_char;
2550 if (Encodings::is_arabic(next_char)) {
2551 if (Encodings::is_arabic(prev_char) &&
2552 !Encodings::is_arabic_special(prev_char))
2553 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2555 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2557 if (Encodings::is_arabic(prev_char) &&
2558 !Encodings::is_arabic_special(prev_char))
2559 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2561 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2566 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2571 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2573 // expand at the end of a row only if there is another hfill on the same row
2574 if (pos == row.endpos() - 1) {
2575 for (pos_type i = row.pos(); i < pos; i++) {
2582 // expand at the beginning of a row only if it is the first row of a paragraph
2583 if (pos == row.pos()) {
2587 // do not expand in some labels
2588 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2591 // if there is anything between the first char of the row and
2592 // the specified position that is neither a newline nor an hfill,
2593 // the hfill will be expanded, otherwise it won't
2594 for (pos_type i = row.pos(); i < pos; i++) {
2595 if (!isNewline(i) && !isHfill(i))
2602 int Paragraph::checkBiblio(bool track_changes)
2605 //This is getting more and more a mess. ...We really should clean
2606 //up this bibitem issue for 1.6. See also bug 2743.
2608 // Add bibitem insets if necessary
2609 if (layout()->labeltype != LABEL_BIBLIO)
2612 bool hasbibitem = !insetlist.empty()
2613 // Insist on it being in pos 0
2614 && getChar(0) == Paragraph::META_INSET
2615 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2620 // remove a bibitem in pos != 0
2621 // restore it later in pos 0 if necessary
2622 // (e.g. if a user inserts contents _before_ the item)
2623 // we're assuming there's only one of these, which there
2625 int erasedInsetPosition = -1;
2626 InsetList::iterator it = insetlist.begin();
2627 InsetList::iterator end = insetlist.end();
2628 for (; it != end; ++it)
2629 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2631 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2632 oldkey = olditem->getParam("key");
2633 oldlabel = olditem->getParam("label");
2634 erasedInsetPosition = it->pos;
2635 eraseChar(erasedInsetPosition, track_changes);
2639 //There was an InsetBibitem at the beginning, and we didn't
2640 //have to erase one.
2641 if (hasbibitem && erasedInsetPosition < 0)
2644 //There was an InsetBibitem at the beginning and we did have to
2645 //erase one. So we give its properties to the beginning inset.
2647 InsetBibitem * inset =
2648 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2649 if (!oldkey.empty())
2650 inset->setParam("key", oldkey);
2651 inset->setParam("label", oldlabel);
2652 return -erasedInsetPosition;
2655 //There was no inset at the beginning, so we need to create one with
2656 //the key and label of the one we erased.
2657 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2658 // restore values of previously deleted item in this par.
2659 if (!oldkey.empty())
2660 inset->setParam("key", oldkey);
2661 inset->setParam("label", oldlabel);
2662 insertInset(0, static_cast<Inset *>(inset),
2663 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));