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::Type & 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::Type & 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 // output change tracking marks only if desired,
728 // if dvipost is installed,
729 // and with dvi/ps (other formats don't work)
730 LaTeXFeatures features(buf, bparams, runparams);
731 bool const output = bparams.outputChanges
732 && runparams.flavor == OutputParams::LATEX
733 && features.isAvailable("dvipost");
735 if (inset->canTrackChanges()) {
736 column += Changes::latexMarkChange(os, running_change,
737 Change::UNCHANGED, output);
738 running_change = Change::UNCHANGED;
742 odocstream::pos_type const len = os.tellp();
744 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
745 || inset->lyxCode() == Inset::MATH_CODE
746 || inset->lyxCode() == Inset::URL_CODE)
747 && running_font.isRightToLeft()) {
753 #warning Bug: we can have an empty font change here!
754 // if there has just been a font change, we are going to close it
755 // right now, which means stupid latex code like \textsf{}. AFAIK,
756 // this does not harm dvi output. A minor bug, thus (JMarc)
758 // some insets cannot be inside a font change command
759 if (open_font && inset->noFontChange()) {
760 column += running_font.latexWriteEndChanges(
761 os, bparams, runparams,
764 basefont = owner_->getLayoutFont(bparams, outerfont);
765 running_font = basefont;
768 int tmp = inset->latex(buf, os, runparams);
774 for (int j = 0; j < tmp; ++j) {
777 texrow.start(owner_->id(), i + 1);
780 column += os.tellp() - len;
786 // And now for the special cases within each mode
790 os << "\\textbackslash{}";
794 case '|': case '<': case '>':
795 // In T1 encoding, these characters exist
796 if (lyxrc.fontenc == "T1") {
798 //... but we should avoid ligatures
799 if ((c == '>' || c == '<')
801 && getChar(i + 1) == c) {
802 //os << "\\textcompwordmark{}";
804 // Jean-Marc, have a look at
805 // this. I think this works
813 // Typewriter font also has them
814 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
818 // Otherwise, we use what LaTeX
822 os << "\\textless{}";
826 os << "\\textgreater{}";
836 case '-': // "--" in Typewriter mode -> "-{}-"
837 if (i <= size() - 2 &&
838 getChar(i + 1) == '-' &&
839 running_font.family() == Font::TYPEWRITER_FAMILY) {
848 os << "\\char`\\\"{}";
853 case '%': case '#': case '{':
861 os << "\\textasciitilde{}";
866 os << "\\textasciicircum{}";
871 // avoid being mistaken for optional arguments
879 // Blanks are printed before font switching.
880 // Sure? I am not! (try nice-latex)
881 // I am sure it's correct. LyX might be smarter
882 // in the future, but for now, nothing wrong is
888 // I assume this is hack treating typewriter as verbatim
889 // FIXME UNICODE: This can fail if c cannot be encoded
890 // in the current encoding.
891 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
900 // FIXME: if we have "LaTeX" with a font
901 // change in the middle (before the 'T', then
902 // the "TeX" part is still special cased.
903 // Really we should only operate this on
904 // "words" for some definition of word
908 for (; pnr < phrases_nr; ++pnr) {
909 if (isTextAt(special_phrases[pnr].phrase, i)) {
910 os << special_phrases[pnr].macro;
911 i += special_phrases[pnr].phrase.length() - 1;
912 column += special_phrases[pnr].macro.length() - 1;
917 if (pnr == phrases_nr && c != '\0') {
918 Encoding const & encoding = *(runparams.encoding);
919 if (i < size() - 1) {
920 char_type next = getChar(i + 1);
921 if (Encodings::isCombiningChar(next)) {
922 column += latexSurrogatePair(os, c, next, encoding) - 1;
927 docstring const latex = encoding.latexChar(c);
928 if (latex.length() > 1 &&
929 latex[latex.length() - 1] != '}') {
930 // Prevent eating of a following
931 // space or command corruption by
932 // following characters
933 column += latex.length() + 1;
936 column += latex.length() - 1;
946 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
947 Layout const & layout) const
949 BufferParams const & bparams = features.bufferParams();
952 if (!params.spacing().isDefault())
953 features.require("setspace");
956 features.useLayout(layout.name());
959 Language const * doc_language = bparams.language;
961 FontList::const_iterator fcit = fontlist.begin();
962 FontList::const_iterator fend = fontlist.end();
963 for (; fcit != fend; ++fcit) {
964 if (fcit->font().noun() == Font::ON) {
965 LYXERR(Debug::LATEX) << "font.noun: "
966 << fcit->font().noun()
968 features.require("noun");
969 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
970 << to_utf8(fcit->font().stateText(0))
973 switch (fcit->font().color()) {
977 // probably we should put here all interface colors used for
978 // font displaying! For now I just add this ones I know of (Jug)
983 features.require("color");
984 LYXERR(Debug::LATEX) << "Color enabled. Font: "
985 << to_utf8(fcit->font().stateText(0))
989 Language const * language = fcit->font().language();
990 if (language->babel() != doc_language->babel() &&
991 language != ignore_language &&
992 language != latex_language)
994 features.useLanguage(language);
995 LYXERR(Debug::LATEX) << "Found language "
996 << language->lang() << endl;
1000 if (!params.leftIndent().zero())
1001 features.require("ParagraphLeftIndent");
1004 InsetList::const_iterator icit = owner_->insetlist.begin();
1005 InsetList::const_iterator iend = owner_->insetlist.end();
1006 for (; icit != iend; ++icit) {
1008 icit->inset->validate(features);
1009 if (layout.needprotect &&
1010 icit->inset->lyxCode() == Inset::FOOT_CODE)
1011 features.require("NeedLyXFootnoteCode");
1015 // then the contents
1016 for (pos_type i = 0; i < size() ; ++i) {
1017 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1018 if (!special_phrases[pnr].builtin
1019 && isTextAt(special_phrases[pnr].phrase, i)) {
1020 features.require(special_phrases[pnr].phrase);
1024 Encodings::validate(getChar(i), features);
1032 /////////////////////////////////////////////////////////////////////
1036 /////////////////////////////////////////////////////////////////////
1040 Paragraph::Paragraph()
1041 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1048 Paragraph::Paragraph(Paragraph const & par)
1049 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1050 layout_(par.layout_),
1051 text_(par.text_), begin_of_body_(par.begin_of_body_),
1052 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1054 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1055 InsetList::iterator it = insetlist.begin();
1056 InsetList::iterator end = insetlist.end();
1057 for (; it != end; ++it)
1058 it->inset = it->inset->clone().release();
1062 Paragraph & Paragraph::operator=(Paragraph const & par)
1064 // needed as we will destroy the pimpl_ before copying it
1066 itemdepth = par.itemdepth;
1068 insetlist = par.insetlist;
1069 InsetList::iterator it = insetlist.begin();
1070 InsetList::iterator end = insetlist.end();
1071 for (; it != end; ++it)
1072 it->inset = it->inset->clone().release();
1074 layout_ = par.layout();
1076 begin_of_body_ = par.begin_of_body_;
1079 pimpl_ = new Pimpl(*par.pimpl_, this);
1085 Paragraph::~Paragraph()
1089 //lyxerr << "Paragraph::paragraph_id = "
1090 // << Paragraph::paragraph_id << endl;
1094 void Paragraph::write(Buffer const & buf, ostream & os,
1095 BufferParams const & bparams,
1096 depth_type & dth) const
1098 // The beginning or end of a deeper (i.e. nested) area?
1099 if (dth != params().depth()) {
1100 if (params().depth() > dth) {
1101 while (params().depth() > dth) {
1102 os << "\n\\begin_deeper";
1106 while (params().depth() < dth) {
1107 os << "\n\\end_deeper";
1113 // First write the layout
1114 os << "\n\\begin_layout " << layout()->name() << '\n';
1118 Font font1(Font::ALL_INHERIT, bparams.language);
1120 Change running_change = Change(Change::UNCHANGED);
1123 for (pos_type i = 0; i <= size(); ++i) {
1125 Change change = pimpl_->lookupChange(i);
1126 Changes::lyxMarkChange(os, column, running_change, change);
1127 running_change = change;
1132 // Write font changes
1133 Font font2 = getFontSettings(bparams, i);
1134 if (font2 != font1) {
1135 font2.lyxWriteChanges(font1, os);
1140 value_type const c = getChar(i);
1144 Inset const * inset = getInset(i);
1146 if (inset->directWrite()) {
1147 // international char, let it write
1148 // code directly so it's shorter in
1150 inset->write(buf, os);
1154 os << "\\begin_inset ";
1155 inset->write(buf, os);
1156 os << "\n\\end_inset\n\n";
1162 os << "\n\\backslash\n";
1166 if (i + 1 < size() && getChar(i + 1) == ' ') {
1173 if ((column > 70 && c == ' ')
1178 // this check is to amend a bug. LyX sometimes
1179 // inserts '\0' this could cause problems.
1181 std::vector<char> tmp = ucs4_to_utf8(c);
1182 tmp.push_back('\0');
1185 lyxerr << "ERROR (Paragraph::writeFile):"
1186 " NULL char in structure." << endl;
1192 os << "\n\\end_layout\n";
1196 void Paragraph::validate(LaTeXFeatures & features) const
1198 pimpl_->validate(features, *layout());
1202 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1204 return pimpl_->eraseChar(pos, trackChanges);
1208 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1210 return pimpl_->eraseChars(start, end, trackChanges);
1214 void Paragraph::insert(pos_type start, docstring const & str,
1215 Font const & font, Change const & change)
1217 for (size_t i = 0, n = str.size(); i != n ; ++i)
1218 insertChar(start + i, str[i], font, change);
1222 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1225 pimpl_->insertChar(pos, c, Change(trackChanges ?
1226 Change::INSERTED : Change::UNCHANGED));
1230 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1231 Font const & font, bool trackChanges)
1233 pimpl_->insertChar(pos, c, Change(trackChanges ?
1234 Change::INSERTED : Change::UNCHANGED));
1239 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1240 Font const & font, Change const & change)
1242 pimpl_->insertChar(pos, c, change);
1247 void Paragraph::insertInset(pos_type pos, Inset * inset,
1248 Change const & change)
1250 pimpl_->insertInset(pos, inset, change);
1254 void Paragraph::insertInset(pos_type pos, Inset * inset,
1255 Font const & font, Change const & change)
1257 pimpl_->insertInset(pos, inset, change);
1262 bool Paragraph::insetAllowed(Inset_code code)
1264 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1268 // Gets uninstantiated font setting at position.
1269 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1273 lyxerr << " pos: " << pos << " size: " << size() << endl;
1274 BOOST_ASSERT(pos <= size());
1277 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1278 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1279 for (; cit != end; ++cit)
1280 if (cit->pos() >= pos)
1286 if (pos == size() && !empty())
1287 return getFontSettings(bparams, pos - 1);
1289 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1293 FontSpan Paragraph::fontSpan(pos_type pos) const
1295 BOOST_ASSERT(pos <= size());
1298 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1299 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1300 for (; cit != end; ++cit) {
1301 if (cit->pos() >= pos) {
1302 if (pos >= beginOfBody())
1303 return FontSpan(std::max(start, beginOfBody()),
1306 return FontSpan(start,
1307 std::min(beginOfBody() - 1,
1310 start = cit->pos() + 1;
1313 // This should not happen, but if so, we take no chances.
1314 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1316 return FontSpan(pos, pos);
1320 // Gets uninstantiated font setting at position 0
1321 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1323 if (!empty() && !pimpl_->fontlist.empty())
1324 return pimpl_->fontlist[0].font();
1326 return Font(Font::ALL_INHERIT, bparams.language);
1330 // Gets the fully instantiated font at a given position in a paragraph
1331 // This is basically the same function as Text::GetFont() in text2.cpp.
1332 // The difference is that this one is used for generating the LaTeX file,
1333 // and thus cosmetic "improvements" are disallowed: This has to deliver
1334 // the true picture of the buffer. (Asger)
1335 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1336 Font const & outerfont) const
1338 BOOST_ASSERT(pos >= 0);
1340 Layout_ptr const & lout = layout();
1342 pos_type const body_pos = beginOfBody();
1346 layoutfont = lout->labelfont;
1348 layoutfont = lout->font;
1350 Font font = getFontSettings(bparams, pos);
1351 font.realize(layoutfont);
1352 font.realize(outerfont);
1353 font.realize(bparams.getFont());
1359 Font const Paragraph::getLabelFont
1360 (BufferParams const & bparams, Font const & outerfont) const
1362 Font tmpfont = layout()->labelfont;
1363 tmpfont.setLanguage(getParLanguage(bparams));
1364 tmpfont.realize(outerfont);
1365 tmpfont.realize(bparams.getFont());
1370 Font const Paragraph::getLayoutFont
1371 (BufferParams const & bparams, Font const & outerfont) const
1373 Font tmpfont = layout()->font;
1374 tmpfont.setLanguage(getParLanguage(bparams));
1375 tmpfont.realize(outerfont);
1376 tmpfont.realize(bparams.getFont());
1381 /// Returns the height of the highest font in range
1382 Font_size Paragraph::highestFontInRange
1383 (pos_type startpos, pos_type endpos, Font_size def_size) const
1385 if (pimpl_->fontlist.empty())
1388 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1389 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1390 for (; end_it != end; ++end_it) {
1391 if (end_it->pos() >= endpos)
1398 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1399 for (; cit != end; ++cit) {
1400 if (cit->pos() >= startpos)
1404 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1405 for (; cit != end_it; ++cit) {
1406 Font::FONT_SIZE size = cit->font().size();
1407 if (size == Font::INHERIT_SIZE)
1409 if (size > maxsize && size <= Font::SIZE_HUGER)
1416 Paragraph::value_type
1417 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1419 value_type c = getChar(pos);
1420 if (!lyxrc.rtl_support)
1450 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1457 void Paragraph::setFont(pos_type pos, Font const & font)
1459 BOOST_ASSERT(pos <= size());
1461 // First, reduce font against layout/label font
1462 // Update: The setCharFont() routine in text2.cpp already
1463 // reduces font, so we don't need to do that here. (Asger)
1464 // No need to simplify this because it will disappear
1465 // in a new kernel. (Asger)
1466 // Next search font table
1468 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1469 Pimpl::FontList::iterator it = beg;
1470 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1471 for (; it != endit; ++it) {
1472 if (it->pos() >= pos)
1475 size_t const i = distance(beg, it);
1476 bool notfound = (it == endit);
1478 if (!notfound && pimpl_->fontlist[i].font() == font)
1481 bool begin = pos == 0 || notfound ||
1482 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1483 // Is position pos is a beginning of a font block?
1484 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1485 // Is position pos is the end of a font block?
1486 if (begin && end) { // A single char block
1487 if (i + 1 < pimpl_->fontlist.size() &&
1488 pimpl_->fontlist[i + 1].font() == font) {
1489 // Merge the singleton block with the next block
1490 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1491 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1492 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1493 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1494 // Merge the singleton block with the previous block
1495 pimpl_->fontlist[i - 1].pos(pos);
1496 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1498 pimpl_->fontlist[i].font(font);
1500 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1501 pimpl_->fontlist[i - 1].pos(pos);
1503 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1504 Pimpl::FontTable(pos, font));
1506 pimpl_->fontlist[i].pos(pos - 1);
1507 if (!(i + 1 < pimpl_->fontlist.size() &&
1508 pimpl_->fontlist[i + 1].font() == font))
1509 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1510 Pimpl::FontTable(pos, font));
1511 } else { // The general case. The block is splitted into 3 blocks
1512 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1513 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1514 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1515 Pimpl::FontTable(pos, font));
1520 void Paragraph::makeSameLayout(Paragraph const & par)
1522 layout(par.layout());
1524 params() = par.params();
1528 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1530 if (isFreeSpacing())
1536 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1537 if (eraseChar(pos, trackChanges))
1543 return count > 0 || pos > 0;
1547 bool Paragraph::hasSameLayout(Paragraph const & par) const
1549 return par.layout() == layout() && params().sameLayout(par.params());
1553 depth_type Paragraph::getDepth() const
1555 return params().depth();
1559 depth_type Paragraph::getMaxDepthAfter() const
1561 if (layout()->isEnvironment())
1562 return params().depth() + 1;
1564 return params().depth();
1568 char Paragraph::getAlign() const
1570 if (params().align() == LYX_ALIGN_LAYOUT)
1571 return layout()->align;
1573 return params().align();
1577 docstring const & Paragraph::getLabelstring() const
1579 return params().labelString();
1583 // the next two functions are for the manual labels
1584 docstring const Paragraph::getLabelWidthString() const
1586 if (!params().labelWidthString().empty())
1587 return params().labelWidthString();
1589 return _("Senseless with this layout!");
1593 void Paragraph::setLabelWidthString(docstring const & s)
1595 params().labelWidthString(s);
1599 docstring const Paragraph::translateIfPossible(docstring const & s,
1600 BufferParams const & bparams) const
1602 if (!support::isAscii(s) || s.empty()) {
1603 // This must be a user defined layout. We cannot translate
1604 // this, since gettext accepts only ascii keys.
1607 // Probably standard layout, try to translate
1608 Messages & m = getMessages(getParLanguage(bparams)->code());
1609 return m.get(to_ascii(s));
1613 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1614 BufferParams const & bparams, bool process_appendix) const
1616 TextClass const & tclass = bparams.getTextClass();
1619 if (process_appendix && params().appendix())
1620 fmt = translateIfPossible(layout->labelstring_appendix(),
1623 fmt = translateIfPossible(layout->labelstring(), bparams);
1625 // handle 'inherited level parts' in 'fmt',
1626 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1627 size_t const i = fmt.find('@', 0);
1628 if (i != docstring::npos) {
1629 size_t const j = fmt.find('@', i + 1);
1630 if (j != docstring::npos) {
1631 docstring parent(fmt, i + 1, j - i - 1);
1633 docstring label = expandLabel(tclass[to_utf8(parent)], bparams);
1634 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1638 return tclass.counters().counterLabel(fmt);
1642 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1645 params().labelWidthString(docstring());
1646 params().align(LYX_ALIGN_LAYOUT);
1647 params().spacing(Spacing(Spacing::Default));
1651 pos_type Paragraph::beginOfBody() const
1653 return begin_of_body_;
1657 void Paragraph::setBeginOfBody()
1659 if (layout()->labeltype != LABEL_MANUAL) {
1664 // Unroll the first two cycles of the loop
1665 // and remember the previous character to
1666 // remove unnecessary getChar() calls
1668 pos_type end = size();
1669 if (i < end && !isNewline(i)) {
1671 char_type previous_char = 0;
1674 previous_char = text_[i];
1675 if (!isNewline(i)) {
1677 while (i < end && previous_char != ' ') {
1682 previous_char = temp;
1692 // returns -1 if inset not found
1693 int Paragraph::getPositionOfInset(Inset const * inset) const
1696 InsetList::const_iterator it = insetlist.begin();
1697 InsetList::const_iterator end = insetlist.end();
1698 for (; it != end; ++it)
1699 if (it->inset == inset)
1705 InsetBibitem * Paragraph::bibitem() const
1707 if (!insetlist.empty()) {
1708 Inset * inset = insetlist.begin()->inset;
1709 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1710 return static_cast<InsetBibitem *>(inset);
1716 bool Paragraph::forceDefaultParagraphs() const
1718 return inInset() && inInset()->forceDefaultParagraphs(0);
1724 // paragraphs inside floats need different alignment tags to avoid
1727 bool noTrivlistCentering(Inset::Code code)
1729 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1733 string correction(string const & orig)
1735 if (orig == "flushleft")
1736 return "raggedright";
1737 if (orig == "flushright")
1738 return "raggedleft";
1739 if (orig == "center")
1745 string const corrected_env(string const & suffix, string const & env,
1748 string output = suffix + "{";
1749 if (noTrivlistCentering(code))
1750 output += correction(env);
1754 if (suffix == "\\begin")
1760 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1762 if (!contains(str, "\n"))
1763 column += str.size();
1767 column = rsplit(str, tmp, '\n').size();
1774 // This could go to ParagraphParameters if we want to
1775 int Paragraph::startTeXParParams(BufferParams const & bparams,
1776 odocstream & os, TexRow & texrow,
1777 bool moving_arg) const
1781 if (params().noindent()) {
1782 os << "\\noindent ";
1786 switch (params().align()) {
1787 case LYX_ALIGN_NONE:
1788 case LYX_ALIGN_BLOCK:
1789 case LYX_ALIGN_LAYOUT:
1790 case LYX_ALIGN_SPECIAL:
1792 case LYX_ALIGN_LEFT:
1793 case LYX_ALIGN_RIGHT:
1794 case LYX_ALIGN_CENTER:
1802 switch (params().align()) {
1803 case LYX_ALIGN_NONE:
1804 case LYX_ALIGN_BLOCK:
1805 case LYX_ALIGN_LAYOUT:
1806 case LYX_ALIGN_SPECIAL:
1808 case LYX_ALIGN_LEFT: {
1810 if (getParLanguage(bparams)->babel() != "hebrew")
1811 output = corrected_env("\\begin", "flushleft", ownerCode());
1813 output = corrected_env("\\begin", "flushright", ownerCode());
1814 os << from_ascii(output);
1815 adjust_row_column(output, texrow, column);
1817 } case LYX_ALIGN_RIGHT: {
1819 if (getParLanguage(bparams)->babel() != "hebrew")
1820 output = corrected_env("\\begin", "flushright", ownerCode());
1822 output = corrected_env("\\begin", "flushleft", ownerCode());
1823 os << from_ascii(output);
1824 adjust_row_column(output, texrow, column);
1826 } case LYX_ALIGN_CENTER: {
1828 output = corrected_env("\\begin", "center", ownerCode());
1829 os << from_ascii(output);
1830 adjust_row_column(output, texrow, column);
1839 // This could go to ParagraphParameters if we want to
1840 int Paragraph::endTeXParParams(BufferParams const & bparams,
1841 odocstream & os, TexRow & texrow,
1842 bool moving_arg) const
1846 switch (params().align()) {
1847 case LYX_ALIGN_NONE:
1848 case LYX_ALIGN_BLOCK:
1849 case LYX_ALIGN_LAYOUT:
1850 case LYX_ALIGN_SPECIAL:
1852 case LYX_ALIGN_LEFT:
1853 case LYX_ALIGN_RIGHT:
1854 case LYX_ALIGN_CENTER:
1862 switch (params().align()) {
1863 case LYX_ALIGN_NONE:
1864 case LYX_ALIGN_BLOCK:
1865 case LYX_ALIGN_LAYOUT:
1866 case LYX_ALIGN_SPECIAL:
1868 case LYX_ALIGN_LEFT: {
1870 if (getParLanguage(bparams)->babel() != "hebrew")
1871 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1873 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1874 os << from_ascii(output);
1875 adjust_row_column(output, texrow, column);
1877 } case LYX_ALIGN_RIGHT: {
1879 if (getParLanguage(bparams)->babel() != "hebrew")
1880 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1882 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1883 os << from_ascii(output);
1884 adjust_row_column(output, texrow, column);
1886 } case LYX_ALIGN_CENTER: {
1888 output = corrected_env("\n\\par\\end", "center", ownerCode());
1889 os << from_ascii(output);
1890 adjust_row_column(output, texrow, column);
1899 // This one spits out the text of the paragraph
1900 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1901 BufferParams const & bparams,
1902 Font const & outerfont,
1903 odocstream & os, TexRow & texrow,
1904 OutputParams const & runparams) const
1906 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1908 bool return_value = false;
1912 // well we have to check if we are in an inset with unlimited
1913 // length (all in one row) if that is true then we don't allow
1914 // any special options in the paragraph and also we don't allow
1915 // any environment other than the default layout of the text class
1917 bool asdefault = forceDefaultParagraphs();
1920 style = bparams.getTextClass().defaultLayout();
1925 // Current base font for all inherited font changes, without any
1926 // change caused by an individual character, except for the language:
1927 // It is set to the language of the first character.
1928 // As long as we are in the label, this font is the base font of the
1929 // label. Before the first body character it is set to the base font
1933 // output change tracking marks only if desired,
1934 // if dvipost is installed,
1935 // and with dvi/ps (other formats don't work)
1936 bool const output = bparams.outputChanges
1937 && runparams.flavor == OutputParams::LATEX
1938 && LaTeXFeatures::isAvailable("dvipost");
1940 // Maybe we have to create a optional argument.
1941 pos_type body_pos = beginOfBody();
1942 unsigned int column = 0;
1945 // the optional argument is kept in curly brackets in
1946 // case it contains a ']'
1949 basefont = getLabelFont(bparams, outerfont);
1951 basefont = getLayoutFont(bparams, outerfont);
1954 // Which font is currently active?
1955 Font running_font(basefont);
1956 // Do we have an open font change?
1957 bool open_font = false;
1959 Change::Type runningChangeType = Change::UNCHANGED;
1961 texrow.start(id(), 0);
1963 // if the paragraph is empty, the loop will not be entered at all
1965 if (style->isCommand()) {
1970 column += startTeXParParams(bparams, os, texrow,
1971 runparams.moving_arg);
1974 for (pos_type i = 0; i < size(); ++i) {
1975 // First char in paragraph or after label?
1976 if (i == body_pos) {
1979 column += running_font.latexWriteEndChanges(
1980 os, bparams, runparams,
1981 basefont, basefont);
1984 basefont = getLayoutFont(bparams, outerfont);
1985 running_font = basefont;
1987 column += Changes::latexMarkChange(os,
1988 runningChangeType, Change::UNCHANGED, output);
1989 runningChangeType = Change::UNCHANGED;
1994 if (style->isCommand()) {
2000 column += startTeXParParams(bparams, os,
2002 runparams.moving_arg);
2005 Change::Type changeType = pimpl_->lookupChange(i).type;
2007 // do not output text which is marked deleted
2008 // if change tracking output is disabled
2009 if (!output && changeType == Change::DELETED) {
2010 runningChangeType = changeType;
2016 column += Changes::latexMarkChange(os, runningChangeType,
2017 changeType, output);
2018 runningChangeType = changeType;
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 runningChangeType, *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,
2116 runningChangeType, Change::UNCHANGED, output);
2118 // Needed if there is an optional argument but no contents.
2119 if (body_pos > 0 && body_pos == size()) {
2121 return_value = false;
2125 column += endTeXParParams(bparams, os, texrow,
2126 runparams.moving_arg);
2129 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2130 return return_value;
2147 string tag_name(PAR_TAG const & pt) {
2149 case PAR_NONE: return "!-- --";
2150 case TT: return "tt";
2151 case SF: return "sf";
2152 case BF: return "bf";
2153 case IT: return "it";
2154 case SL: return "sl";
2155 case EM: return "em";
2162 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2164 p1 = static_cast<PAR_TAG>(p1 | p2);
2169 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2171 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2177 bool Paragraph::emptyTag() const
2179 for (pos_type i = 0; i < size(); ++i) {
2181 Inset const * inset = getInset(i);
2182 Inset::Code lyx_code = inset->lyxCode();
2183 if (lyx_code != Inset::TOC_CODE &&
2184 lyx_code != Inset::INCLUDE_CODE &&
2185 lyx_code != Inset::GRAPHICS_CODE &&
2186 lyx_code != Inset::ERT_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;
2356 void Paragraph::changeLanguage(BufferParams const & bparams,
2357 Language const * from, Language const * to)
2359 // change language including dummy font change at the end
2360 for (pos_type i = 0; i <= size(); ++i) {
2361 Font font = getFontSettings(bparams, i);
2362 if (font.language() == from) {
2363 font.setLanguage(to);
2370 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2372 Language const * doc_language = bparams.language;
2373 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2374 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2376 for (; cit != end; ++cit)
2377 if (cit->font().language() != ignore_language &&
2378 cit->font().language() != latex_language &&
2379 cit->font().language() != doc_language)
2385 // Convert the paragraph to a string.
2386 // Used for building the table of contents
2387 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2389 return asString(buffer, 0, size(), label);
2393 docstring const Paragraph::asString(Buffer const & buffer,
2394 pos_type beg, pos_type end, bool label) const
2397 odocstringstream os;
2399 if (beg == 0 && label && !params().labelString().empty())
2400 os << params().labelString() << ' ';
2402 for (pos_type i = beg; i < end; ++i) {
2403 value_type const c = getUChar(buffer.params(), i);
2406 else if (c == META_INSET)
2407 getInset(i)->textString(buffer, os);
2414 void Paragraph::setInsetOwner(Inset * inset)
2416 pimpl_->inset_owner = inset;
2420 Change const & Paragraph::lookupChange(pos_type pos) const
2422 BOOST_ASSERT(pos <= size());
2423 return pimpl_->lookupChange(pos);
2427 bool Paragraph::isChanged(pos_type start, pos_type end) const
2429 return pimpl_->isChanged(start, end);
2433 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2435 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2439 void Paragraph::setChange(Change const & change)
2441 pimpl_->setChange(change);
2445 void Paragraph::setChange(pos_type pos, Change const & change)
2447 pimpl_->setChange(pos, change);
2451 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2453 return pimpl_->acceptChanges(bparams, start, end);
2457 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2459 return pimpl_->rejectChanges(bparams, start, end);
2463 int Paragraph::id() const
2469 Layout_ptr const & Paragraph::layout() const
2475 void Paragraph::layout(Layout_ptr const & new_layout)
2477 layout_ = new_layout;
2481 Inset * Paragraph::inInset() const
2483 return pimpl_->inset_owner;
2487 Inset::Code Paragraph::ownerCode() const
2489 return pimpl_->inset_owner
2490 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2494 ParagraphParameters & Paragraph::params()
2496 return pimpl_->params;
2500 ParagraphParameters const & Paragraph::params() const
2502 return pimpl_->params;
2506 bool Paragraph::isFreeSpacing() const
2508 if (layout()->free_spacing)
2511 // for now we just need this, later should we need this in some
2512 // other way we can always add a function to Inset too.
2513 return ownerCode() == Inset::ERT_CODE;
2517 bool Paragraph::allowEmpty() const
2519 if (layout()->keepempty)
2521 return ownerCode() == Inset::ERT_CODE;
2525 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2527 if (!Encodings::is_arabic(c))
2530 value_type prev_char = ' ';
2531 value_type next_char = ' ';
2533 for (pos_type i = pos - 1; i >= 0; --i) {
2534 value_type const par_char = getChar(i);
2535 if (!Encodings::isComposeChar_arabic(par_char)) {
2536 prev_char = par_char;
2541 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2542 value_type const par_char = getChar(i);
2543 if (!Encodings::isComposeChar_arabic(par_char)) {
2544 next_char = par_char;
2549 if (Encodings::is_arabic(next_char)) {
2550 if (Encodings::is_arabic(prev_char) &&
2551 !Encodings::is_arabic_special(prev_char))
2552 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2554 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2556 if (Encodings::is_arabic(prev_char) &&
2557 !Encodings::is_arabic_special(prev_char))
2558 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2560 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2565 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2570 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2572 // expand at the end of a row only if there is another hfill on the same row
2573 if (pos == row.endpos() - 1) {
2574 for (pos_type i = row.pos(); i < pos; i++) {
2581 // expand at the beginning of a row only if it is the first row of a paragraph
2582 if (pos == row.pos()) {
2586 // do not expand in some labels
2587 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2590 // if there is anything between the first char of the row and
2591 // the specified position that is neither a newline nor an hfill,
2592 // the hfill will be expanded, otherwise it won't
2593 for (pos_type i = row.pos(); i < pos; i++) {
2594 if (!isNewline(i) && !isHfill(i))
2601 bool Paragraph::checkBiblio(bool track_changes)
2603 // Add bibitem insets if necessary
2604 if (layout()->labeltype != LABEL_BIBLIO)
2607 bool hasbibitem = !insetlist.empty()
2608 // Insist on it being in pos 0
2609 && getChar(0) == Paragraph::META_INSET
2610 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2615 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2616 insertInset(0, static_cast<Inset *>(inset),
2617 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));