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()) {
745 #warning Bug: we can have an empty font change here!
746 // if there has just been a font change, we are going to close it
747 // right now, which means stupid latex code like \textsf{}. AFAIK,
748 // this does not harm dvi output. A minor bug, thus (JMarc)
750 // some insets cannot be inside a font change command
751 if (open_font && inset->noFontChange()) {
752 column += running_font.latexWriteEndChanges(
753 os, bparams, runparams,
756 basefont = owner_->getLayoutFont(bparams, outerfont);
757 running_font = basefont;
760 int tmp = inset->latex(buf, os, runparams);
766 for (int j = 0; j < tmp; ++j) {
769 texrow.start(owner_->id(), i + 1);
772 column += os.tellp() - len;
778 // And now for the special cases within each mode
782 os << "\\textbackslash{}";
786 case '|': case '<': case '>':
787 // In T1 encoding, these characters exist
788 if (lyxrc.fontenc == "T1") {
790 //... but we should avoid ligatures
791 if ((c == '>' || c == '<')
793 && getChar(i + 1) == c) {
794 //os << "\\textcompwordmark{}";
796 // Jean-Marc, have a look at
797 // this. I think this works
805 // Typewriter font also has them
806 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
810 // Otherwise, we use what LaTeX
814 os << "\\textless{}";
818 os << "\\textgreater{}";
828 case '-': // "--" in Typewriter mode -> "-{}-"
829 if (i <= size() - 2 &&
830 getChar(i + 1) == '-' &&
831 running_font.family() == Font::TYPEWRITER_FAMILY) {
840 os << "\\char`\\\"{}";
845 case '%': case '#': case '{':
853 os << "\\textasciitilde{}";
858 os << "\\textasciicircum{}";
863 // avoid being mistaken for optional arguments
871 // Blanks are printed before font switching.
872 // Sure? I am not! (try nice-latex)
873 // I am sure it's correct. LyX might be smarter
874 // in the future, but for now, nothing wrong is
880 // I assume this is hack treating typewriter as verbatim
881 // FIXME UNICODE: This can fail if c cannot be encoded
882 // in the current encoding.
883 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
892 // FIXME: if we have "LaTeX" with a font
893 // change in the middle (before the 'T', then
894 // the "TeX" part is still special cased.
895 // Really we should only operate this on
896 // "words" for some definition of word
900 for (; pnr < phrases_nr; ++pnr) {
901 if (isTextAt(special_phrases[pnr].phrase, i)) {
902 os << special_phrases[pnr].macro;
903 i += special_phrases[pnr].phrase.length() - 1;
904 column += special_phrases[pnr].macro.length() - 1;
909 if (pnr == phrases_nr && c != '\0') {
910 Encoding const & encoding = *(runparams.encoding);
911 if (i < size() - 1) {
912 char_type next = getChar(i + 1);
913 if (Encodings::isCombiningChar(next)) {
914 column += latexSurrogatePair(os, c, next, encoding) - 1;
919 docstring const latex = encoding.latexChar(c);
920 if (latex.length() > 1 &&
921 latex[latex.length() - 1] != '}') {
922 // Prevent eating of a following
923 // space or command corruption by
924 // following characters
925 column += latex.length() + 1;
928 column += latex.length() - 1;
938 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
939 Layout const & layout) const
941 BufferParams const & bparams = features.bufferParams();
944 if (!params.spacing().isDefault())
945 features.require("setspace");
948 features.useLayout(layout.name());
951 Language const * doc_language = bparams.language;
953 FontList::const_iterator fcit = fontlist.begin();
954 FontList::const_iterator fend = fontlist.end();
955 for (; fcit != fend; ++fcit) {
956 if (fcit->font().noun() == Font::ON) {
957 LYXERR(Debug::LATEX) << "font.noun: "
958 << fcit->font().noun()
960 features.require("noun");
961 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
962 << to_utf8(fcit->font().stateText(0))
965 switch (fcit->font().color()) {
969 // probably we should put here all interface colors used for
970 // font displaying! For now I just add this ones I know of (Jug)
975 features.require("color");
976 LYXERR(Debug::LATEX) << "Color enabled. Font: "
977 << to_utf8(fcit->font().stateText(0))
981 Language const * language = fcit->font().language();
982 if (language->babel() != doc_language->babel() &&
983 language != ignore_language &&
984 language != latex_language)
986 features.useLanguage(language);
987 LYXERR(Debug::LATEX) << "Found language "
988 << language->lang() << endl;
992 if (!params.leftIndent().zero())
993 features.require("ParagraphLeftIndent");
996 InsetList::const_iterator icit = owner_->insetlist.begin();
997 InsetList::const_iterator iend = owner_->insetlist.end();
998 for (; icit != iend; ++icit) {
1000 icit->inset->validate(features);
1001 if (layout.needprotect &&
1002 icit->inset->lyxCode() == Inset::FOOT_CODE)
1003 features.require("NeedLyXFootnoteCode");
1007 // then the contents
1008 for (pos_type i = 0; i < size() ; ++i) {
1009 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1010 if (!special_phrases[pnr].builtin
1011 && isTextAt(special_phrases[pnr].phrase, i)) {
1012 features.require(special_phrases[pnr].phrase);
1016 Encodings::validate(getChar(i), features);
1024 /////////////////////////////////////////////////////////////////////
1028 /////////////////////////////////////////////////////////////////////
1032 Paragraph::Paragraph()
1033 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1040 Paragraph::Paragraph(Paragraph const & par)
1041 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1042 layout_(par.layout_),
1043 text_(par.text_), begin_of_body_(par.begin_of_body_),
1044 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1046 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1047 InsetList::iterator it = insetlist.begin();
1048 InsetList::iterator end = insetlist.end();
1049 for (; it != end; ++it)
1050 it->inset = it->inset->clone().release();
1054 Paragraph & Paragraph::operator=(Paragraph const & par)
1056 // needed as we will destroy the pimpl_ before copying it
1058 itemdepth = par.itemdepth;
1060 insetlist = par.insetlist;
1061 InsetList::iterator it = insetlist.begin();
1062 InsetList::iterator end = insetlist.end();
1063 for (; it != end; ++it)
1064 it->inset = it->inset->clone().release();
1066 layout_ = par.layout();
1068 begin_of_body_ = par.begin_of_body_;
1071 pimpl_ = new Pimpl(*par.pimpl_, this);
1077 Paragraph::~Paragraph()
1081 //lyxerr << "Paragraph::paragraph_id = "
1082 // << Paragraph::paragraph_id << endl;
1086 void Paragraph::write(Buffer const & buf, ostream & os,
1087 BufferParams const & bparams,
1088 depth_type & dth) const
1090 // The beginning or end of a deeper (i.e. nested) area?
1091 if (dth != params().depth()) {
1092 if (params().depth() > dth) {
1093 while (params().depth() > dth) {
1094 os << "\n\\begin_deeper";
1098 while (params().depth() < dth) {
1099 os << "\n\\end_deeper";
1105 // First write the layout
1106 os << "\n\\begin_layout " << layout()->name() << '\n';
1110 Font font1(Font::ALL_INHERIT, bparams.language);
1112 Change running_change = Change(Change::UNCHANGED);
1115 for (pos_type i = 0; i <= size(); ++i) {
1117 Change change = pimpl_->lookupChange(i);
1118 Changes::lyxMarkChange(os, column, running_change, change);
1119 running_change = change;
1124 // Write font changes
1125 Font font2 = getFontSettings(bparams, i);
1126 if (font2 != font1) {
1127 font2.lyxWriteChanges(font1, os);
1132 value_type const c = getChar(i);
1136 Inset const * inset = getInset(i);
1138 if (inset->directWrite()) {
1139 // international char, let it write
1140 // code directly so it's shorter in
1142 inset->write(buf, os);
1146 os << "\\begin_inset ";
1147 inset->write(buf, os);
1148 os << "\n\\end_inset\n\n";
1154 os << "\n\\backslash\n";
1158 if (i + 1 < size() && getChar(i + 1) == ' ') {
1165 if ((column > 70 && c == ' ')
1170 // this check is to amend a bug. LyX sometimes
1171 // inserts '\0' this could cause problems.
1173 std::vector<char> tmp = ucs4_to_utf8(c);
1174 tmp.push_back('\0');
1177 lyxerr << "ERROR (Paragraph::writeFile):"
1178 " NULL char in structure." << endl;
1184 os << "\n\\end_layout\n";
1188 void Paragraph::validate(LaTeXFeatures & features) const
1190 pimpl_->validate(features, *layout());
1194 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1196 return pimpl_->eraseChar(pos, trackChanges);
1200 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1202 return pimpl_->eraseChars(start, end, trackChanges);
1206 void Paragraph::insert(pos_type start, docstring const & str,
1207 Font const & font, Change const & change)
1209 for (size_t i = 0, n = str.size(); i != n ; ++i)
1210 insertChar(start + i, str[i], font, change);
1214 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1217 pimpl_->insertChar(pos, c, Change(trackChanges ?
1218 Change::INSERTED : Change::UNCHANGED));
1222 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1223 Font const & font, bool trackChanges)
1225 pimpl_->insertChar(pos, c, Change(trackChanges ?
1226 Change::INSERTED : Change::UNCHANGED));
1231 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1232 Font const & font, Change const & change)
1234 pimpl_->insertChar(pos, c, change);
1239 void Paragraph::insertInset(pos_type pos, Inset * inset,
1240 Change const & change)
1242 pimpl_->insertInset(pos, inset, change);
1246 void Paragraph::insertInset(pos_type pos, Inset * inset,
1247 Font const & font, Change const & change)
1249 pimpl_->insertInset(pos, inset, change);
1254 bool Paragraph::insetAllowed(Inset_code code)
1256 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1260 // Gets uninstantiated font setting at position.
1261 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1265 lyxerr << " pos: " << pos << " size: " << size() << endl;
1266 BOOST_ASSERT(pos <= size());
1269 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1270 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1271 for (; cit != end; ++cit)
1272 if (cit->pos() >= pos)
1278 if (pos == size() && !empty())
1279 return getFontSettings(bparams, pos - 1);
1281 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1285 FontSpan Paragraph::fontSpan(pos_type pos) const
1287 BOOST_ASSERT(pos <= size());
1290 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1291 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1292 for (; cit != end; ++cit) {
1293 if (cit->pos() >= pos) {
1294 if (pos >= beginOfBody())
1295 return FontSpan(std::max(start, beginOfBody()),
1298 return FontSpan(start,
1299 std::min(beginOfBody() - 1,
1302 start = cit->pos() + 1;
1305 // This should not happen, but if so, we take no chances.
1306 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1308 return FontSpan(pos, pos);
1312 // Gets uninstantiated font setting at position 0
1313 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1315 if (!empty() && !pimpl_->fontlist.empty())
1316 return pimpl_->fontlist[0].font();
1318 return Font(Font::ALL_INHERIT, bparams.language);
1322 // Gets the fully instantiated font at a given position in a paragraph
1323 // This is basically the same function as Text::GetFont() in text2.cpp.
1324 // The difference is that this one is used for generating the LaTeX file,
1325 // and thus cosmetic "improvements" are disallowed: This has to deliver
1326 // the true picture of the buffer. (Asger)
1327 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1328 Font const & outerfont) const
1330 BOOST_ASSERT(pos >= 0);
1332 Layout_ptr const & lout = layout();
1334 pos_type const body_pos = beginOfBody();
1338 layoutfont = lout->labelfont;
1340 layoutfont = lout->font;
1342 Font font = getFontSettings(bparams, pos);
1343 font.realize(layoutfont);
1344 font.realize(outerfont);
1345 font.realize(bparams.getFont());
1351 Font const Paragraph::getLabelFont
1352 (BufferParams const & bparams, Font const & outerfont) const
1354 Font tmpfont = layout()->labelfont;
1355 tmpfont.setLanguage(getParLanguage(bparams));
1356 tmpfont.realize(outerfont);
1357 tmpfont.realize(bparams.getFont());
1362 Font const Paragraph::getLayoutFont
1363 (BufferParams const & bparams, Font const & outerfont) const
1365 Font tmpfont = layout()->font;
1366 tmpfont.setLanguage(getParLanguage(bparams));
1367 tmpfont.realize(outerfont);
1368 tmpfont.realize(bparams.getFont());
1373 /// Returns the height of the highest font in range
1374 Font_size Paragraph::highestFontInRange
1375 (pos_type startpos, pos_type endpos, Font_size def_size) const
1377 if (pimpl_->fontlist.empty())
1380 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1381 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1382 for (; end_it != end; ++end_it) {
1383 if (end_it->pos() >= endpos)
1390 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1391 for (; cit != end; ++cit) {
1392 if (cit->pos() >= startpos)
1396 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1397 for (; cit != end_it; ++cit) {
1398 Font::FONT_SIZE size = cit->font().size();
1399 if (size == Font::INHERIT_SIZE)
1401 if (size > maxsize && size <= Font::SIZE_HUGER)
1408 Paragraph::value_type
1409 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1411 value_type c = getChar(pos);
1412 if (!lyxrc.rtl_support)
1442 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1449 void Paragraph::setFont(pos_type pos, Font const & font)
1451 BOOST_ASSERT(pos <= size());
1453 // First, reduce font against layout/label font
1454 // Update: The setCharFont() routine in text2.cpp already
1455 // reduces font, so we don't need to do that here. (Asger)
1456 // No need to simplify this because it will disappear
1457 // in a new kernel. (Asger)
1458 // Next search font table
1460 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1461 Pimpl::FontList::iterator it = beg;
1462 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1463 for (; it != endit; ++it) {
1464 if (it->pos() >= pos)
1467 size_t const i = distance(beg, it);
1468 bool notfound = (it == endit);
1470 if (!notfound && pimpl_->fontlist[i].font() == font)
1473 bool begin = pos == 0 || notfound ||
1474 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1475 // Is position pos is a beginning of a font block?
1476 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1477 // Is position pos is the end of a font block?
1478 if (begin && end) { // A single char block
1479 if (i + 1 < pimpl_->fontlist.size() &&
1480 pimpl_->fontlist[i + 1].font() == font) {
1481 // Merge the singleton block with the next block
1482 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1483 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1484 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1485 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1486 // Merge the singleton block with the previous block
1487 pimpl_->fontlist[i - 1].pos(pos);
1488 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1490 pimpl_->fontlist[i].font(font);
1492 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1493 pimpl_->fontlist[i - 1].pos(pos);
1495 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1496 Pimpl::FontTable(pos, font));
1498 pimpl_->fontlist[i].pos(pos - 1);
1499 if (!(i + 1 < pimpl_->fontlist.size() &&
1500 pimpl_->fontlist[i + 1].font() == font))
1501 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1502 Pimpl::FontTable(pos, font));
1503 } else { // The general case. The block is splitted into 3 blocks
1504 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1505 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1506 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1507 Pimpl::FontTable(pos, font));
1512 void Paragraph::makeSameLayout(Paragraph const & par)
1514 layout(par.layout());
1516 params() = par.params();
1520 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1522 if (isFreeSpacing())
1528 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1529 if (eraseChar(pos, trackChanges))
1535 return count > 0 || pos > 0;
1539 bool Paragraph::hasSameLayout(Paragraph const & par) const
1541 return par.layout() == layout() && params().sameLayout(par.params());
1545 depth_type Paragraph::getDepth() const
1547 return params().depth();
1551 depth_type Paragraph::getMaxDepthAfter() const
1553 if (layout()->isEnvironment())
1554 return params().depth() + 1;
1556 return params().depth();
1560 char Paragraph::getAlign() const
1562 if (params().align() == LYX_ALIGN_LAYOUT)
1563 return layout()->align;
1565 return params().align();
1569 docstring const & Paragraph::getLabelstring() const
1571 return params().labelString();
1575 // the next two functions are for the manual labels
1576 docstring const Paragraph::getLabelWidthString() const
1578 if (!params().labelWidthString().empty())
1579 return params().labelWidthString();
1581 return _("Senseless with this layout!");
1585 void Paragraph::setLabelWidthString(docstring const & s)
1587 params().labelWidthString(s);
1591 docstring const Paragraph::translateIfPossible(docstring const & s,
1592 BufferParams const & bparams) const
1594 if (!support::isAscii(s) || s.empty()) {
1595 // This must be a user defined layout. We cannot translate
1596 // this, since gettext accepts only ascii keys.
1599 // Probably standard layout, try to translate
1600 Messages & m = getMessages(getParLanguage(bparams)->code());
1601 return m.get(to_ascii(s));
1605 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1606 BufferParams const & bparams, bool process_appendix) const
1608 TextClass const & tclass = bparams.getTextClass();
1611 if (process_appendix && params().appendix())
1612 fmt = translateIfPossible(layout->labelstring_appendix(),
1615 fmt = translateIfPossible(layout->labelstring(), bparams);
1617 // handle 'inherited level parts' in 'fmt',
1618 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1619 size_t const i = fmt.find('@', 0);
1620 if (i != docstring::npos) {
1621 size_t const j = fmt.find('@', i + 1);
1622 if (j != docstring::npos) {
1623 docstring parent(fmt, i + 1, j - i - 1);
1625 docstring label = expandLabel(tclass[to_utf8(parent)], bparams);
1626 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1630 return tclass.counters().counterLabel(fmt);
1634 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1637 params().labelWidthString(docstring());
1638 params().align(LYX_ALIGN_LAYOUT);
1639 params().spacing(Spacing(Spacing::Default));
1643 pos_type Paragraph::beginOfBody() const
1645 return begin_of_body_;
1649 void Paragraph::setBeginOfBody()
1651 if (layout()->labeltype != LABEL_MANUAL) {
1656 // Unroll the first two cycles of the loop
1657 // and remember the previous character to
1658 // remove unnecessary getChar() calls
1660 pos_type end = size();
1661 if (i < end && !isNewline(i)) {
1663 char_type previous_char = 0;
1666 previous_char = text_[i];
1667 if (!isNewline(i)) {
1669 while (i < end && previous_char != ' ') {
1674 previous_char = temp;
1684 // returns -1 if inset not found
1685 int Paragraph::getPositionOfInset(Inset const * inset) const
1688 InsetList::const_iterator it = insetlist.begin();
1689 InsetList::const_iterator end = insetlist.end();
1690 for (; it != end; ++it)
1691 if (it->inset == inset)
1697 InsetBibitem * Paragraph::bibitem() const
1699 if (!insetlist.empty()) {
1700 Inset * inset = insetlist.begin()->inset;
1701 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1702 return static_cast<InsetBibitem *>(inset);
1708 bool Paragraph::forceDefaultParagraphs() const
1710 return inInset() && inInset()->forceDefaultParagraphs(0);
1716 // paragraphs inside floats need different alignment tags to avoid
1719 bool noTrivlistCentering(Inset::Code code)
1721 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1725 string correction(string const & orig)
1727 if (orig == "flushleft")
1728 return "raggedright";
1729 if (orig == "flushright")
1730 return "raggedleft";
1731 if (orig == "center")
1737 string const corrected_env(string const & suffix, string const & env,
1740 string output = suffix + "{";
1741 if (noTrivlistCentering(code))
1742 output += correction(env);
1746 if (suffix == "\\begin")
1752 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1754 if (!contains(str, "\n"))
1755 column += str.size();
1759 column = rsplit(str, tmp, '\n').size();
1766 // This could go to ParagraphParameters if we want to
1767 int Paragraph::startTeXParParams(BufferParams const & bparams,
1768 odocstream & os, TexRow & texrow,
1769 bool moving_arg) const
1773 if (params().noindent()) {
1774 os << "\\noindent ";
1778 switch (params().align()) {
1779 case LYX_ALIGN_NONE:
1780 case LYX_ALIGN_BLOCK:
1781 case LYX_ALIGN_LAYOUT:
1782 case LYX_ALIGN_SPECIAL:
1784 case LYX_ALIGN_LEFT:
1785 case LYX_ALIGN_RIGHT:
1786 case LYX_ALIGN_CENTER:
1794 switch (params().align()) {
1795 case LYX_ALIGN_NONE:
1796 case LYX_ALIGN_BLOCK:
1797 case LYX_ALIGN_LAYOUT:
1798 case LYX_ALIGN_SPECIAL:
1800 case LYX_ALIGN_LEFT: {
1802 if (getParLanguage(bparams)->babel() != "hebrew")
1803 output = corrected_env("\\begin", "flushleft", ownerCode());
1805 output = corrected_env("\\begin", "flushright", ownerCode());
1806 os << from_ascii(output);
1807 adjust_row_column(output, texrow, column);
1809 } case LYX_ALIGN_RIGHT: {
1811 if (getParLanguage(bparams)->babel() != "hebrew")
1812 output = corrected_env("\\begin", "flushright", ownerCode());
1814 output = corrected_env("\\begin", "flushleft", ownerCode());
1815 os << from_ascii(output);
1816 adjust_row_column(output, texrow, column);
1818 } case LYX_ALIGN_CENTER: {
1820 output = corrected_env("\\begin", "center", ownerCode());
1821 os << from_ascii(output);
1822 adjust_row_column(output, texrow, column);
1831 // This could go to ParagraphParameters if we want to
1832 int Paragraph::endTeXParParams(BufferParams const & bparams,
1833 odocstream & os, TexRow & texrow,
1834 bool moving_arg) const
1838 switch (params().align()) {
1839 case LYX_ALIGN_NONE:
1840 case LYX_ALIGN_BLOCK:
1841 case LYX_ALIGN_LAYOUT:
1842 case LYX_ALIGN_SPECIAL:
1844 case LYX_ALIGN_LEFT:
1845 case LYX_ALIGN_RIGHT:
1846 case LYX_ALIGN_CENTER:
1854 switch (params().align()) {
1855 case LYX_ALIGN_NONE:
1856 case LYX_ALIGN_BLOCK:
1857 case LYX_ALIGN_LAYOUT:
1858 case LYX_ALIGN_SPECIAL:
1860 case LYX_ALIGN_LEFT: {
1862 if (getParLanguage(bparams)->babel() != "hebrew")
1863 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1865 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1866 os << from_ascii(output);
1867 adjust_row_column(output, texrow, column);
1869 } case LYX_ALIGN_RIGHT: {
1871 if (getParLanguage(bparams)->babel() != "hebrew")
1872 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1874 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1875 os << from_ascii(output);
1876 adjust_row_column(output, texrow, column);
1878 } case LYX_ALIGN_CENTER: {
1880 output = corrected_env("\n\\par\\end", "center", ownerCode());
1881 os << from_ascii(output);
1882 adjust_row_column(output, texrow, column);
1891 // This one spits out the text of the paragraph
1892 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1893 BufferParams const & bparams,
1894 Font const & outerfont,
1895 odocstream & os, TexRow & texrow,
1896 OutputParams const & runparams) const
1898 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1900 bool return_value = false;
1904 // well we have to check if we are in an inset with unlimited
1905 // length (all in one row) if that is true then we don't allow
1906 // any special options in the paragraph and also we don't allow
1907 // any environment other than the default layout of the text class
1909 bool asdefault = forceDefaultParagraphs();
1912 style = bparams.getTextClass().defaultLayout();
1917 // Current base font for all inherited font changes, without any
1918 // change caused by an individual character, except for the language:
1919 // It is set to the language of the first character.
1920 // As long as we are in the label, this font is the base font of the
1921 // label. Before the first body character it is set to the base font
1925 // Maybe we have to create a optional argument.
1926 pos_type body_pos = beginOfBody();
1927 unsigned int column = 0;
1930 // the optional argument is kept in curly brackets in
1931 // case it contains a ']'
1934 basefont = getLabelFont(bparams, outerfont);
1936 basefont = getLayoutFont(bparams, outerfont);
1939 // Which font is currently active?
1940 Font running_font(basefont);
1941 // Do we have an open font change?
1942 bool open_font = false;
1944 Change runningChange = Change(Change::UNCHANGED);
1946 texrow.start(id(), 0);
1948 // if the paragraph is empty, the loop will not be entered at all
1950 if (style->isCommand()) {
1955 column += startTeXParParams(bparams, os, texrow,
1956 runparams.moving_arg);
1959 for (pos_type i = 0; i < size(); ++i) {
1960 // First char in paragraph or after label?
1961 if (i == body_pos) {
1964 column += running_font.latexWriteEndChanges(
1965 os, bparams, runparams,
1966 basefont, basefont);
1969 basefont = getLayoutFont(bparams, outerfont);
1970 running_font = basefont;
1972 column += Changes::latexMarkChange(os, bparams,
1973 runningChange, Change(Change::UNCHANGED));
1974 runningChange = Change(Change::UNCHANGED);
1979 if (style->isCommand()) {
1985 column += startTeXParParams(bparams, os,
1987 runparams.moving_arg);
1990 Change const & change = pimpl_->lookupChange(i);
1992 if (bparams.outputChanges && runningChange != change) {
1994 column += running_font.latexWriteEndChanges(
1995 os, bparams, runparams, basefont, basefont);
1998 basefont = getLayoutFont(bparams, outerfont);
1999 running_font = basefont;
2001 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2002 runningChange = change;
2005 // do not output text which is marked deleted
2006 // if change tracking output is disabled
2007 if (!bparams.outputChanges && change.type == Change::DELETED) {
2013 value_type const c = getChar(i);
2015 // Fully instantiated font
2016 Font const font = getFont(bparams, i, outerfont);
2018 Font const last_font = running_font;
2020 // Do we need to close the previous font?
2022 (font != running_font ||
2023 font.language() != running_font.language()))
2025 column += running_font.latexWriteEndChanges(
2026 os, bparams, runparams, basefont,
2027 (i == body_pos-1) ? basefont : font);
2028 running_font = basefont;
2032 // Switch file encoding if necessary
2033 if (runparams.encoding->package() == Encoding::inputenc &&
2034 font.language()->encoding()->package() == Encoding::inputenc) {
2035 int const count = switchEncoding(os, bparams,
2036 runparams.moving_arg, *(runparams.encoding),
2037 *(font.language()->encoding()));
2040 runparams.encoding = font.language()->encoding();
2044 // Do we need to change font?
2045 if ((font != running_font ||
2046 font.language() != running_font.language()) &&
2049 column += font.latexWriteStartChanges(os, bparams,
2050 runparams, basefont,
2052 running_font = font;
2057 // Do not print the separation of the optional argument
2058 // if style->pass_thru is false. This works because
2059 // simpleTeXSpecialChars ignores spaces if
2060 // style->pass_thru is false.
2061 if (i != body_pos - 1) {
2062 if (pimpl_->simpleTeXBlanks(
2063 *(runparams.encoding), os, texrow,
2064 i, column, font, *style))
2065 // A surrogate pair was output. We
2066 // must not call simpleTeXSpecialChars
2067 // in this iteration, since
2068 // simpleTeXBlanks incremented i, and
2069 // simpleTeXSpecialChars would output
2070 // the combining character again.
2075 OutputParams rp = runparams;
2076 rp.free_spacing = style->free_spacing;
2077 rp.local_font = &font;
2078 rp.intitle = style->intitle;
2079 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2080 texrow, rp, running_font,
2081 basefont, outerfont, open_font,
2082 runningChange, *style, i, column, c);
2085 // If we have an open font definition, we have to close it
2087 #ifdef FIXED_LANGUAGE_END_DETECTION
2090 .latexWriteEndChanges(os, bparams, runparams,
2092 next_->getFont(bparams, 0, outerfont));
2094 running_font.latexWriteEndChanges(os, bparams,
2095 runparams, basefont, basefont);
2098 #ifdef WITH_WARNINGS
2099 //#warning For now we ALWAYS have to close the foreign font settings if they are
2100 //#warning there as we start another \selectlanguage with the next paragraph if
2101 //#warning we are in need of this. This should be fixed sometime (Jug)
2103 running_font.latexWriteEndChanges(os, bparams, runparams,
2104 basefont, basefont);
2108 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2110 // Needed if there is an optional argument but no contents.
2111 if (body_pos > 0 && body_pos == size()) {
2113 return_value = false;
2117 column += endTeXParParams(bparams, os, texrow,
2118 runparams.moving_arg);
2121 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2122 return return_value;
2139 string tag_name(PAR_TAG const & pt) {
2141 case PAR_NONE: return "!-- --";
2142 case TT: return "tt";
2143 case SF: return "sf";
2144 case BF: return "bf";
2145 case IT: return "it";
2146 case SL: return "sl";
2147 case EM: return "em";
2154 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2156 p1 = static_cast<PAR_TAG>(p1 | p2);
2161 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2163 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2169 bool Paragraph::emptyTag() const
2171 for (pos_type i = 0; i < size(); ++i) {
2173 Inset const * inset = getInset(i);
2174 Inset::Code lyx_code = inset->lyxCode();
2175 if (lyx_code != Inset::TOC_CODE &&
2176 lyx_code != Inset::INCLUDE_CODE &&
2177 lyx_code != Inset::GRAPHICS_CODE &&
2178 lyx_code != Inset::ERT_CODE &&
2179 lyx_code != Inset::LISTINGS_CODE &&
2180 lyx_code != Inset::FLOAT_CODE &&
2181 lyx_code != Inset::TABULAR_CODE) {
2185 value_type c = getChar(i);
2186 if (c != ' ' && c != '\t')
2194 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2196 for (pos_type i = 0; i < size(); ++i) {
2198 Inset const * inset = getInset(i);
2199 Inset::Code lyx_code = inset->lyxCode();
2200 if (lyx_code == Inset::LABEL_CODE) {
2201 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2202 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2211 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2214 for (i = 0; i < size(); ++i) {
2216 Inset const * inset = getInset(i);
2217 inset->docbook(buf, os, runparams);
2219 value_type c = getChar(i);
2222 os << sgml::escapeChar(c);
2229 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2233 for (pos_type i = initial; i < size(); ++i) {
2234 Font font = getFont(buf.params(), i, outerfont);
2237 if (i != initial && font != font_old)
2246 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2248 OutputParams const & runparams,
2249 Font const & outerfont,
2250 pos_type initial) const
2252 bool emph_flag = false;
2254 Layout_ptr const & style = layout();
2256 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2258 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2261 // parsing main loop
2262 for (pos_type i = initial; i < size(); ++i) {
2263 Font font = getFont(buf.params(), i, outerfont);
2265 // handle <emphasis> tag
2266 if (font_old.emph() != font.emph()) {
2267 if (font.emph() == Font::ON) {
2270 } else if (i != initial) {
2271 os << "</emphasis>";
2277 Inset const * inset = getInset(i);
2278 inset->docbook(buf, os, runparams);
2280 value_type c = getChar(i);
2282 if (style->pass_thru)
2285 os << sgml::escapeChar(c);
2291 os << "</emphasis>";
2294 if (style->free_spacing)
2296 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2301 bool Paragraph::isNewline(pos_type pos) const
2304 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2308 bool Paragraph::isLineSeparator(pos_type pos) const
2310 value_type const c = getChar(pos);
2311 return isLineSeparatorChar(c)
2312 || (c == Paragraph::META_INSET && getInset(pos) &&
2313 getInset(pos)->isLineSeparator());
2317 /// Used by the spellchecker
2318 bool Paragraph::isLetter(pos_type pos) const
2321 return getInset(pos)->isLetter();
2323 value_type const c = getChar(pos);
2324 return isLetterChar(c) || isDigit(c);
2330 Paragraph::getParLanguage(BufferParams const & bparams) const
2333 return getFirstFontSettings(bparams).language();
2334 #ifdef WITH_WARNINGS
2335 #warning FIXME we should check the prev par as well (Lgb)
2337 return bparams.language;
2341 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2343 return lyxrc.rtl_support
2344 && getParLanguage(bparams)->rightToLeft()
2345 && ownerCode() != Inset::ERT_CODE
2346 && ownerCode() != Inset::LISTINGS_CODE;
2350 void Paragraph::changeLanguage(BufferParams const & bparams,
2351 Language const * from, Language const * to)
2353 // change language including dummy font change at the end
2354 for (pos_type i = 0; i <= size(); ++i) {
2355 Font font = getFontSettings(bparams, i);
2356 if (font.language() == from) {
2357 font.setLanguage(to);
2364 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2366 Language const * doc_language = bparams.language;
2367 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2368 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2370 for (; cit != end; ++cit)
2371 if (cit->font().language() != ignore_language &&
2372 cit->font().language() != latex_language &&
2373 cit->font().language() != doc_language)
2379 // Convert the paragraph to a string.
2380 // Used for building the table of contents
2381 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2383 return asString(buffer, 0, size(), label);
2387 docstring const Paragraph::asString(Buffer const & buffer,
2388 pos_type beg, pos_type end, bool label) const
2391 odocstringstream os;
2393 if (beg == 0 && label && !params().labelString().empty())
2394 os << params().labelString() << ' ';
2396 for (pos_type i = beg; i < end; ++i) {
2397 value_type const c = getUChar(buffer.params(), i);
2400 else if (c == META_INSET)
2401 getInset(i)->textString(buffer, os);
2408 void Paragraph::setInsetOwner(Inset * inset)
2410 pimpl_->inset_owner = inset;
2414 Change const & Paragraph::lookupChange(pos_type pos) const
2416 BOOST_ASSERT(pos <= size());
2417 return pimpl_->lookupChange(pos);
2421 bool Paragraph::isChanged(pos_type start, pos_type end) const
2423 return pimpl_->isChanged(start, end);
2427 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2429 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2433 void Paragraph::setChange(Change const & change)
2435 pimpl_->setChange(change);
2439 void Paragraph::setChange(pos_type pos, Change const & change)
2441 pimpl_->setChange(pos, change);
2445 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2447 return pimpl_->acceptChanges(bparams, start, end);
2451 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2453 return pimpl_->rejectChanges(bparams, start, end);
2457 int Paragraph::id() const
2463 Layout_ptr const & Paragraph::layout() const
2469 void Paragraph::layout(Layout_ptr const & new_layout)
2471 layout_ = new_layout;
2475 Inset * Paragraph::inInset() const
2477 return pimpl_->inset_owner;
2481 Inset::Code Paragraph::ownerCode() const
2483 return pimpl_->inset_owner
2484 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2488 ParagraphParameters & Paragraph::params()
2490 return pimpl_->params;
2494 ParagraphParameters const & Paragraph::params() const
2496 return pimpl_->params;
2500 bool Paragraph::isFreeSpacing() const
2502 if (layout()->free_spacing)
2505 // for now we just need this, later should we need this in some
2506 // other way we can always add a function to Inset too.
2507 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2511 bool Paragraph::allowEmpty() const
2513 if (layout()->keepempty)
2515 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2519 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2521 if (!Encodings::is_arabic(c))
2524 value_type prev_char = ' ';
2525 value_type next_char = ' ';
2527 for (pos_type i = pos - 1; i >= 0; --i) {
2528 value_type const par_char = getChar(i);
2529 if (!Encodings::isComposeChar_arabic(par_char)) {
2530 prev_char = par_char;
2535 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2536 value_type const par_char = getChar(i);
2537 if (!Encodings::isComposeChar_arabic(par_char)) {
2538 next_char = par_char;
2543 if (Encodings::is_arabic(next_char)) {
2544 if (Encodings::is_arabic(prev_char) &&
2545 !Encodings::is_arabic_special(prev_char))
2546 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2548 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2550 if (Encodings::is_arabic(prev_char) &&
2551 !Encodings::is_arabic_special(prev_char))
2552 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2554 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2559 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2564 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2566 // expand at the end of a row only if there is another hfill on the same row
2567 if (pos == row.endpos() - 1) {
2568 for (pos_type i = row.pos(); i < pos; i++) {
2575 // expand at the beginning of a row only if it is the first row of a paragraph
2576 if (pos == row.pos()) {
2580 // do not expand in some labels
2581 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2584 // if there is anything between the first char of the row and
2585 // the specified position that is neither a newline nor an hfill,
2586 // the hfill will be expanded, otherwise it won't
2587 for (pos_type i = row.pos(); i < pos; i++) {
2588 if (!isNewline(i) && !isHfill(i))
2595 bool Paragraph::checkBiblio(bool track_changes)
2597 // Add bibitem insets if necessary
2598 if (layout()->labeltype != LABEL_BIBLIO)
2601 bool hasbibitem = !insetlist.empty()
2602 // Insist on it being in pos 0
2603 && getChar(0) == Paragraph::META_INSET
2604 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2609 // remove bibitems in pos != 0
2610 // restore them later in pos 0 if necessary
2611 // (e.g. if a user inserts contents _before_ the item)
2612 InsetList::const_iterator it = insetlist.begin();
2613 InsetList::const_iterator end = insetlist.end();
2614 for (; it != end; ++it)
2615 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2617 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2618 oldkey = olditem->getParam("key");
2619 oldlabel = olditem->getParam("label");
2620 eraseChar(it->pos, track_changes);
2626 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2627 // restore values of previously deleted item in this par.
2628 if (!oldkey.empty())
2629 inset->setParam("key", oldkey);
2630 if (!oldlabel.empty())
2631 inset->setParam("label", oldlabel);
2632 insertInset(0, static_cast<Inset *>(inset),
2633 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));