3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Jean-Marc Lasgouttes
9 * \author Angus Leeming
11 * \author André Pönitz
13 * \author Jürgen Vigna
15 * Full author contact details are available in file CREDITS.
20 #include "Paragraph.h"
23 #include "BufferParams.h"
29 #include "InsetList.h"
31 #include "LaTeXFeatures.h"
38 #include "OutputParams.h"
39 #include "output_latex.h"
40 #include "paragraph_funcs.h"
41 #include "ParagraphParameters.h"
46 #include "frontends/alert.h"
47 #include "frontends/FontMetrics.h"
49 #include "insets/InsetBibitem.h"
50 #include "insets/InsetOptArg.h"
52 #include "support/lstrings.h"
53 #include "support/textutils.h"
54 #include "support/convert.h"
55 #include "support/unicode.h"
57 #include <boost/bind.hpp>
58 #include <boost/next_prior.hpp>
70 using support::contains;
71 using support::prefixIs;
72 using support::suffixIs;
73 using support::rsplit;
77 /////////////////////////////////////////////////////////////////////
81 /////////////////////////////////////////////////////////////////////
87 class Paragraph::Pimpl {
90 Pimpl(Paragraph * owner);
91 /// "Copy constructor"
92 Pimpl(Pimpl const &, Paragraph * owner);
97 /// look up change at given pos
98 Change const & lookupChange(pos_type pos) const;
99 /// is there a change within the given range ?
100 bool isChanged(pos_type start, pos_type end) const;
101 /// will the paragraph be physically merged with the next
102 /// one if the imaginary end-of-par character is logically deleted?
103 bool isMergedOnEndOfParDeletion(bool trackChanges) const;
104 /// set change for the entire par
105 void setChange(Change const & change);
106 /// set change at given pos
107 void setChange(pos_type pos, Change const & change);
108 /// accept changes within the given range
109 void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end);
110 /// reject changes within the given range
111 void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end);
114 value_type getChar(pos_type pos) const;
116 void insertChar(pos_type pos, value_type c, Change const & change);
118 void insertInset(pos_type pos, Inset * inset, Change const & change);
119 /// (logically) erase the char at pos; return true if it was actually erased
120 bool eraseChar(pos_type pos, bool trackChanges);
121 /// (logically) erase the given range; return the number of chars actually erased
122 int eraseChars(pos_type start, pos_type end, bool trackChanges);
126 /** A font entry covers a range of positions. Notice that the
127 entries in the list are inserted in random order.
128 I don't think it's worth the effort to implement a more effective
129 datastructure, because the number of different fonts in a paragraph
131 Nevertheless, I decided to store fontlist using a sorted vector:
132 fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where
133 pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
134 and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
135 (font_1 covers the chars 0,...,pos_1) (Dekel)
140 FontTable(pos_type p, Font const & f)
144 pos_type pos() const { return pos_; }
146 void pos(pos_type p) { pos_ = p; }
148 Font const & font() const { return font_; }
150 void font(Font const & f) { font_ = f;}
152 /// End position of paragraph this font attribute covers
154 /** Font. Interpretation of the font values:
155 If a value is Font::INHERIT_*, it means that the font
156 attribute is inherited from either the layout of this
157 paragraph or, in the case of nested paragraphs, from the
158 layout in the environment one level up until completely
160 The values Font::IGNORE_* and Font::TOGGLE are NOT
161 allowed in these font tables.
166 friend class matchFT;
170 /// used by lower_bound and upper_bound
171 int operator()(FontTable const & a, FontTable const & b) const {
172 return a.pos() < b.pos();
177 typedef std::vector<FontTable> FontList;
181 /// Output the surrogate pair formed by \p c and \p next to \p os.
182 /// \return the number of characters written.
183 int latexSurrogatePair(odocstream & os, value_type c, value_type next,
185 /// Output a space in appropriate formatting (or a surrogate pair
186 /// if the next character is a combining character).
187 /// \return whether a surrogate pair was output.
188 bool simpleTeXBlanks(Encoding const &,
189 odocstream &, TexRow & texrow,
191 unsigned int & column,
193 Layout const & style);
194 /// Output consecutive known unicode chars, belonging to the same
195 /// language as specified by \p preamble, to \p os starting from \p c.
196 /// \return the number of characters written.
197 int knownLangChars(odocstream & os, value_type c, string & preamble,
198 Change &, Encoding const &, pos_type &);
200 void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
202 TexRow & texrow, OutputParams &,
205 Font const & outerfont,
207 Change & running_change,
208 Layout const & style,
210 unsigned int & column, value_type const c);
213 void validate(LaTeXFeatures & features,
214 Layout const & layout) const;
219 static unsigned int paragraph_id;
221 ParagraphParameters params;
225 pos_type size() const { return owner_->size(); }
226 /// match a string against a particular point in the paragraph
227 bool isTextAt(std::string const & str, pos_type pos) const;
229 /// for recording and looking up changes
236 InsetList insetlist_;
243 using std::upper_bound;
244 using std::lower_bound;
248 // Initialization of the counter for the paragraph id's,
249 unsigned int Paragraph::Pimpl::paragraph_id = 0;
253 struct special_phrase {
259 special_phrase const special_phrases[] = {
260 { "LyX", from_ascii("\\LyX{}"), false },
261 { "TeX", from_ascii("\\TeX{}"), true },
262 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
263 { "LaTeX", from_ascii("\\LaTeX{}"), true },
266 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
271 Paragraph::Pimpl::Pimpl(Paragraph * owner)
275 id_ = paragraph_id++;
279 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
280 : params(p.params), changes_(p.changes_), owner_(owner)
282 inset_owner = p.inset_owner;
283 fontlist = p.fontlist;
284 id_ = paragraph_id++;
285 insetlist_ = p.insetlist_;
290 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
292 BOOST_ASSERT(start >= 0 && start <= size());
293 BOOST_ASSERT(end > start && end <= size() + 1);
295 return changes_.isChanged(start, end);
299 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
300 // keep the logic here in sync with the logic of eraseChars()
306 Change change = changes_.lookup(size());
308 return change.type == Change::INSERTED && change.author == 0;
312 void Paragraph::Pimpl::setChange(Change const & change)
314 // beware of the imaginary end-of-par character!
315 changes_.set(change, 0, size() + 1);
318 * Propagate the change recursively - but not in case of DELETED!
320 * Imagine that your co-author makes changes in an existing inset. He
321 * sends your document to you and you come to the conclusion that the
322 * inset should go completely. If you erase it, LyX must not delete all
323 * text within the inset. Otherwise, the change tracked insertions of
324 * your co-author get lost and there is no way to restore them later.
326 * Conclusion: An inset's content should remain untouched if you delete it
329 if (change.type != Change::DELETED) {
330 for (pos_type pos = 0; pos < size(); ++pos) {
331 if (owner_->isInset(pos)) {
332 owner_->getInset(pos)->setChange(change);
339 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
341 BOOST_ASSERT(pos >= 0 && pos <= size());
343 changes_.set(change, pos);
345 // see comment in setChange(Change const &) above
347 if (change.type != Change::DELETED &&
348 pos < size() && owner_->isInset(pos)) {
349 owner_->getInset(pos)->setChange(change);
354 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
356 BOOST_ASSERT(pos >= 0 && pos <= size());
358 return changes_.lookup(pos);
362 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
364 BOOST_ASSERT(start >= 0 && start <= size());
365 BOOST_ASSERT(end > start && end <= size() + 1);
367 for (pos_type pos = start; pos < end; ++pos) {
368 switch (lookupChange(pos).type) {
369 case Change::UNCHANGED:
370 // accept changes in nested inset
371 if (pos < size() && owner_->isInset(pos)) {
372 owner_->getInset(pos)->acceptChanges(bparams);
377 case Change::INSERTED:
378 changes_.set(Change(Change::UNCHANGED), pos);
379 // also accept changes in nested inset
380 if (pos < size() && owner_->isInset(pos)) {
381 owner_->getInset(pos)->acceptChanges(bparams);
385 case Change::DELETED:
386 // Suppress access to non-existent
387 // "end-of-paragraph char"
389 eraseChar(pos, false);
400 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
402 BOOST_ASSERT(start >= 0 && start <= size());
403 BOOST_ASSERT(end > start && end <= size() + 1);
405 for (pos_type pos = start; pos < end; ++pos) {
406 switch (lookupChange(pos).type) {
407 case Change::UNCHANGED:
408 // reject changes in nested inset
409 if (pos < size() && owner_->isInset(pos)) {
410 owner_->getInset(pos)->rejectChanges(bparams);
414 case Change::INSERTED:
415 // Suppress access to non-existent
416 // "end-of-paragraph char"
418 eraseChar(pos, false);
424 case Change::DELETED:
425 changes_.set(Change(Change::UNCHANGED), pos);
427 // Do NOT reject changes within a deleted inset!
428 // There may be insertions of a co-author inside of it!
436 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
438 BOOST_ASSERT(pos >= 0 && pos <= size());
440 return owner_->getChar(pos);
444 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
446 BOOST_ASSERT(pos >= 0 && pos <= size());
449 changes_.insert(change, pos);
451 // This is actually very common when parsing buffers (and
452 // maybe inserting ascii text)
454 // when appending characters, no need to update tables
455 owner_->text_.push_back(c);
459 owner_->text_.insert(owner_->text_.begin() + pos, c);
461 // Update the font table.
462 FontTable search_font(pos, Font());
463 for (FontList::iterator it
464 = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
465 it != fontlist.end(); ++it)
467 it->pos(it->pos() + 1);
471 insetlist_.increasePosAfterPos(pos);
475 void Paragraph::Pimpl::insertInset(pos_type pos, Inset * inset,
476 Change const & change)
479 BOOST_ASSERT(pos >= 0 && pos <= size());
481 insertChar(pos, META_INSET, change);
482 BOOST_ASSERT(owner_->text_[pos] == META_INSET);
484 // Add a new entry in the insetlist_.
485 insetlist_.insert(inset, pos);
489 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
491 BOOST_ASSERT(pos >= 0 && pos <= size());
493 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
496 Change change = changes_.lookup(pos);
498 // set the character to DELETED if
499 // a) it was previously unchanged or
500 // b) it was inserted by a co-author
502 if (change.type == Change::UNCHANGED ||
503 (change.type == Change::INSERTED && change.author != 0)) {
504 setChange(pos, Change(Change::DELETED));
508 if (change.type == Change::DELETED)
512 // Don't physically access the imaginary end-of-paragraph character.
513 // eraseChar() can only mark it as DELETED. A physical deletion of
514 // end-of-par must be handled externally.
522 // if it is an inset, delete the inset entry
523 if (owner_->text_[pos] == Paragraph::META_INSET) {
524 insetlist_.erase(pos);
527 owner_->text_.erase(owner_->text_.begin() + pos);
529 // Erase entries in the tables.
530 FontTable search_font(pos, Font());
532 FontList::iterator it =
533 lower_bound(fontlist.begin(),
535 search_font, matchFT());
536 if (it != fontlist.end() && it->pos() == pos &&
538 (it != fontlist.begin()
539 && boost::prior(it)->pos() == pos - 1))) {
540 // If it is a multi-character font
541 // entry, we just make it smaller
542 // (see update below), otherwise we
544 unsigned int const i = it - fontlist.begin();
545 fontlist.erase(fontlist.begin() + i);
546 it = fontlist.begin() + i;
547 if (i > 0 && i < fontlist.size() &&
548 fontlist[i - 1].font() == fontlist[i].font()) {
549 fontlist.erase(fontlist.begin() + i - 1);
550 it = fontlist.begin() + i - 1;
554 // Update all other entries
555 FontList::iterator fend = fontlist.end();
556 for (; it != fend; ++it)
557 it->pos(it->pos() - 1);
559 // Update the insetlist_
560 insetlist_.decreasePosAfterPos(pos);
566 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
568 BOOST_ASSERT(start >= 0 && start <= size());
569 BOOST_ASSERT(end >= start && end <= size() + 1);
572 for (pos_type count = end - start; count; --count) {
573 if (!eraseChar(i, trackChanges))
580 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
581 value_type next, Encoding const & encoding)
583 // Writing next here may circumvent a possible font change between
584 // c and next. Since next is only output if it forms a surrogate pair
585 // with c we can ignore this:
586 // A font change inside a surrogate pair does not make sense and is
587 // hopefully impossible to input.
588 // FIXME: change tracking
589 // Is this correct WRT change tracking?
590 docstring const latex1 = encoding.latexChar(next);
591 docstring const latex2 = encoding.latexChar(c);
592 os << latex1 << '{' << latex2 << '}';
593 return latex1.length() + latex2.length() + 2;
597 bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding,
598 odocstream & os, TexRow & texrow,
600 unsigned int & column,
602 Layout const & style)
607 if (i + 1 < size()) {
608 char_type next = getChar(i + 1);
609 if (Encodings::isCombiningChar(next)) {
610 // This space has an accent, so we must always output it.
611 column += latexSurrogatePair(os, ' ', next, encoding) - 1;
617 if (lyxrc.plaintext_linelen > 0
618 && column > lyxrc.plaintext_linelen
620 && getChar(i - 1) != ' '
622 // same in FreeSpacing mode
623 && !owner_->isFreeSpacing()
624 // In typewriter mode, we want to avoid
625 // ! . ? : at the end of a line
626 && !(font.family() == Font::TYPEWRITER_FAMILY
627 && (getChar(i - 1) == '.'
628 || getChar(i - 1) == '?'
629 || getChar(i - 1) == ':'
630 || getChar(i - 1) == '!'))) {
633 texrow.start(owner_->id(), i + 1);
635 } else if (style.free_spacing) {
644 int Paragraph::Pimpl::knownLangChars(odocstream & os,
647 Change & runningChange,
648 Encoding const & encoding,
651 // When the character is marked by the proper language, we simply
652 // get its code point in some encoding, otherwise we get the
653 // translation specified in the unicodesymbols file, which is
654 // something like "\textLANG{<spec>}". So, we have to retain
655 // "\textLANG{<spec>" for the first char but only "<spec>" for
656 // all subsequent chars.
657 docstring const latex1 = rtrim(encoding.latexChar(c), "}");
658 int length = latex1.length();
660 while (i + 1 < size()) {
661 char_type next = getChar(i + 1);
662 // Stop here if next character belongs to another
663 // language or there is a change tracking status.
664 if (!Encodings::isKnownLangChar(next, preamble) ||
665 runningChange != lookupChange(i + 1))
669 FontList::const_iterator cit = fontlist.begin();
670 FontList::const_iterator end = fontlist.end();
671 for (; cit != end; ++cit) {
672 if (cit->pos() >= i && !found) {
673 prev_font = cit->font();
676 if (cit->pos() >= i + 1)
679 // Stop here if there is a font attribute change.
680 if (found && cit != end && prev_font != cit->font())
682 docstring const latex = rtrim(encoding.latexChar(next), "}");
683 docstring::size_type const j =
684 latex.find_first_of(from_ascii("{"));
685 if (j == docstring::npos) {
687 length += latex.length();
689 os << latex.substr(j + 1);
690 length += latex.substr(j + 1).length();
694 // When the proper language is set, we are simply passed a code
695 // point, so we should not try to close the \textLANG command.
696 if (prefixIs(latex1, from_ascii("\\" + preamble))) {
704 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
706 pos_type const len = str.length();
708 // is the paragraph large enough?
709 if (pos + len > size())
712 // does the wanted text start at point?
713 for (string::size_type i = 0; i < str.length(); ++i) {
714 // Caution: direct comparison of characters works only
715 // because str is pure ASCII.
716 if (str[i] != owner_->text_[pos + i])
720 // is there a font change in middle of the word?
721 FontList::const_iterator cit = fontlist.begin();
722 FontList::const_iterator end = fontlist.end();
723 for (; cit != end; ++cit) {
724 if (cit->pos() >= pos)
727 if (cit != end && pos + len - 1 > cit->pos())
734 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
735 BufferParams const & bparams,
738 OutputParams & runparams,
741 Font const & outerfont,
743 Change & running_change,
744 Layout const & style,
746 unsigned int & column,
749 if (style.pass_thru) {
750 if (c != Paragraph::META_INSET) {
752 // FIXME UNICODE: This can fail if c cannot
753 // be encoded in the current encoding.
756 owner_->getInset(i)->plaintext(buf, os, runparams);
760 // Two major modes: LaTeX or plain
761 // Handle here those cases common to both modes
762 // and then split to handle the two modes separately.
764 case Paragraph::META_INSET: {
765 Inset * inset = owner_->getInset(i);
767 // FIXME: remove this check
771 // FIXME: move this to InsetNewline::latex
772 if (inset->lyxCode() == NEWLINE_CODE) {
773 // newlines are handled differently here than
774 // the default in simpleTeXSpecialChars().
775 if (!style.newline_allowed) {
779 column += running_font.latexWriteEndChanges(
780 os, bparams, runparams,
785 if (running_font.family() == Font::TYPEWRITER_FAMILY)
788 basefont = owner_->getLayoutFont(bparams, outerfont);
789 running_font = basefont;
791 if (runparams.moving_arg)
797 texrow.start(owner_->id(), i + 1);
802 if (lookupChange(i).type == Change::DELETED) {
803 if( ++runparams.inDeletedInset == 1)
804 runparams.changeOfDeletedInset = lookupChange(i);
807 if (inset->canTrackChanges()) {
808 column += Changes::latexMarkChange(os, bparams, running_change,
809 Change(Change::UNCHANGED));
810 running_change = Change(Change::UNCHANGED);
814 odocstream::pos_type const len = os.tellp();
816 if ((inset->lyxCode() == GRAPHICS_CODE
817 || inset->lyxCode() == MATH_CODE
818 || inset->lyxCode() == HYPERLINK_CODE)
819 && running_font.isRightToLeft()) {
820 if (running_font.language()->lang() == "farsi")
827 // FIXME: Bug: we can have an empty font change here!
828 // if there has just been a font change, we are going to close it
829 // right now, which means stupid latex code like \textsf{}. AFAIK,
830 // this does not harm dvi output. A minor bug, thus (JMarc)
831 // Some insets cannot be inside a font change command.
832 // However, even such insets *can* be placed in \L or \R
833 // or their equivalents (for RTL language switches), so we don't
834 // close the language in those cases.
835 // ArabTeX, though, cannot handle this special behavior, it seems.
836 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
837 running_font.language()->lang() == "arabic_arabtex";
838 if (open_font && inset->noFontChange()) {
839 bool closeLanguage = arabtex ||
840 basefont.isRightToLeft() == running_font.isRightToLeft();
841 unsigned int count = running_font.latexWriteEndChanges(
842 os, bparams, runparams,
843 basefont, basefont, closeLanguage);
845 // if any font properties were closed, update the running_font,
846 // making sure, however, to leave the language as it was
848 // FIXME: probably a better way to keep track of the old
849 // language, than copying the entire font?
850 Font const copy_font(running_font);
851 basefont = owner_->getLayoutFont(bparams, outerfont);
852 running_font = basefont;
854 running_font.setLanguage(copy_font.language());
855 // leave font open if language is still open
856 open_font = (running_font.language() == basefont.language());
858 runparams.local_font = &basefont;
862 int tmp = inset->latex(buf, os, runparams);
865 if (running_font.language()->lang() == "farsi")
872 for (int j = 0; j < tmp; ++j) {
875 texrow.start(owner_->id(), i + 1);
878 column += os.tellp() - len;
881 if (lookupChange(i).type == Change::DELETED) {
882 --runparams.inDeletedInset;
888 // And now for the special cases within each mode
892 os << "\\textbackslash{}";
896 case '|': case '<': case '>':
897 // In T1 encoding, these characters exist
898 if (lyxrc.fontenc == "T1") {
900 //... but we should avoid ligatures
901 if ((c == '>' || c == '<')
903 && getChar(i + 1) == c) {
904 //os << "\\textcompwordmark{}";
906 // Jean-Marc, have a look at
907 // this. I think this works
915 // Typewriter font also has them
916 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
920 // Otherwise, we use what LaTeX
924 os << "\\textless{}";
928 os << "\\textgreater{}";
938 case '-': // "--" in Typewriter mode -> "-{}-"
939 if (i <= size() - 2 &&
940 getChar(i + 1) == '-' &&
941 running_font.family() == Font::TYPEWRITER_FAMILY) {
950 os << "\\char`\\\"{}";
955 case '%': case '#': case '{':
963 os << "\\textasciitilde{}";
968 os << "\\textasciicircum{}";
973 // avoid being mistaken for optional arguments
981 // Blanks are printed before font switching.
982 // Sure? I am not! (try nice-latex)
983 // I am sure it's correct. LyX might be smarter
984 // in the future, but for now, nothing wrong is
990 // I assume this is hack treating typewriter as verbatim
991 // FIXME UNICODE: This can fail if c cannot be encoded
992 // in the current encoding.
993 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
1002 // FIXME: if we have "LaTeX" with a font
1003 // change in the middle (before the 'T', then
1004 // the "TeX" part is still special cased.
1005 // Really we should only operate this on
1006 // "words" for some definition of word
1010 for (; pnr < phrases_nr; ++pnr) {
1011 if (isTextAt(special_phrases[pnr].phrase, i)) {
1012 os << special_phrases[pnr].macro;
1013 i += special_phrases[pnr].phrase.length() - 1;
1014 column += special_phrases[pnr].macro.length() - 1;
1019 if (pnr == phrases_nr && c != '\0') {
1020 Encoding const & encoding = *(runparams.encoding);
1021 if (i + 1 < size()) {
1022 char_type next = getChar(i + 1);
1023 if (Encodings::isCombiningChar(next)) {
1024 column += latexSurrogatePair(os, c, next, encoding) - 1;
1030 if (Encodings::isKnownLangChar(c, preamble)) {
1032 knownLangChars(os, c, preamble,
1037 docstring const latex = encoding.latexChar(c);
1038 if (latex.length() > 1 &&
1039 latex[latex.length() - 1] != '}') {
1040 // Prevent eating of a following
1041 // space or command corruption by
1042 // following characters
1043 column += latex.length() + 1;
1044 os << latex << "{}";
1046 column += latex.length() - 1;
1056 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
1057 Layout const & layout) const
1059 BufferParams const & bparams = features.bufferParams();
1061 // check the params.
1062 if (!params.spacing().isDefault())
1063 features.require("setspace");
1066 features.useLayout(layout.name());
1069 Language const * doc_language = bparams.language;
1071 FontList::const_iterator fcit = fontlist.begin();
1072 FontList::const_iterator fend = fontlist.end();
1073 for (; fcit != fend; ++fcit) {
1074 if (fcit->font().noun() == Font::ON) {
1075 LYXERR(Debug::LATEX) << "font.noun: "
1076 << fcit->font().noun()
1078 features.require("noun");
1079 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
1080 << to_utf8(fcit->font().stateText(0))
1083 switch (fcit->font().color()) {
1085 case Color::inherit:
1087 // probably we should put here all interface colors used for
1088 // font displaying! For now I just add this ones I know of (Jug)
1093 features.require("color");
1094 LYXERR(Debug::LATEX) << "Color enabled. Font: "
1095 << to_utf8(fcit->font().stateText(0))
1099 Language const * language = fcit->font().language();
1100 if (language->babel() != doc_language->babel() &&
1101 language != ignore_language &&
1102 language != latex_language)
1104 features.useLanguage(language);
1105 LYXERR(Debug::LATEX) << "Found language "
1106 << language->lang() << endl;
1110 if (!params.leftIndent().zero())
1111 features.require("ParagraphLeftIndent");
1114 InsetList::const_iterator icit = insetlist_.begin();
1115 InsetList::const_iterator iend = insetlist_.end();
1116 for (; icit != iend; ++icit) {
1118 icit->inset->validate(features);
1119 if (layout.needprotect &&
1120 icit->inset->lyxCode() == FOOT_CODE)
1121 features.require("NeedLyXFootnoteCode");
1125 // then the contents
1126 for (pos_type i = 0; i < size() ; ++i) {
1127 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1128 if (!special_phrases[pnr].builtin
1129 && isTextAt(special_phrases[pnr].phrase, i)) {
1130 features.require(special_phrases[pnr].phrase);
1134 Encodings::validate(getChar(i), features);
1142 /////////////////////////////////////////////////////////////////////
1146 /////////////////////////////////////////////////////////////////////
1150 Paragraph::Paragraph()
1151 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1158 Paragraph::Paragraph(Paragraph const & par)
1159 : itemdepth(par.itemdepth),
1160 layout_(par.layout_),
1161 text_(par.text_), begin_of_body_(par.begin_of_body_),
1162 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1167 Paragraph & Paragraph::operator=(Paragraph const & par)
1169 // needed as we will destroy the pimpl_ before copying it
1171 itemdepth = par.itemdepth;
1172 layout_ = par.layout();
1174 begin_of_body_ = par.begin_of_body_;
1177 pimpl_ = new Pimpl(*par.pimpl_, this);
1183 Paragraph::~Paragraph()
1187 //lyxerr << "Paragraph::paragraph_id = "
1188 // << Paragraph::paragraph_id << endl;
1192 void Paragraph::write(Buffer const & buf, ostream & os,
1193 BufferParams const & bparams,
1194 depth_type & dth) const
1196 // The beginning or end of a deeper (i.e. nested) area?
1197 if (dth != params().depth()) {
1198 if (params().depth() > dth) {
1199 while (params().depth() > dth) {
1200 os << "\n\\begin_deeper";
1204 while (params().depth() < dth) {
1205 os << "\n\\end_deeper";
1211 // First write the layout
1212 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1216 Font font1(Font::ALL_INHERIT, bparams.language);
1218 Change running_change = Change(Change::UNCHANGED);
1221 for (pos_type i = 0; i <= size(); ++i) {
1223 Change change = pimpl_->lookupChange(i);
1224 Changes::lyxMarkChange(os, column, running_change, change);
1225 running_change = change;
1230 // Write font changes
1231 Font font2 = getFontSettings(bparams, i);
1232 if (font2 != font1) {
1233 font2.lyxWriteChanges(font1, os);
1238 value_type const c = getChar(i);
1242 Inset const * inset = getInset(i);
1244 if (inset->directWrite()) {
1245 // international char, let it write
1246 // code directly so it's shorter in
1248 inset->write(buf, os);
1252 os << "\\begin_inset ";
1253 inset->write(buf, os);
1254 os << "\n\\end_inset\n\n";
1260 os << "\n\\backslash\n";
1264 if (i + 1 < size() && getChar(i + 1) == ' ') {
1271 if ((column > 70 && c == ' ')
1276 // this check is to amend a bug. LyX sometimes
1277 // inserts '\0' this could cause problems.
1279 std::vector<char> tmp = ucs4_to_utf8(c);
1280 tmp.push_back('\0');
1283 lyxerr << "ERROR (Paragraph::writeFile):"
1284 " NULL char in structure." << endl;
1290 os << "\n\\end_layout\n";
1294 void Paragraph::validate(LaTeXFeatures & features) const
1296 pimpl_->validate(features, *layout());
1300 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1302 return pimpl_->eraseChar(pos, trackChanges);
1306 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1308 return pimpl_->eraseChars(start, end, trackChanges);
1312 void Paragraph::insert(pos_type start, docstring const & str,
1313 Font const & font, Change const & change)
1315 for (size_t i = 0, n = str.size(); i != n ; ++i)
1316 insertChar(start + i, str[i], font, change);
1320 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1323 pimpl_->insertChar(pos, c, Change(trackChanges ?
1324 Change::INSERTED : Change::UNCHANGED));
1328 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1329 Font const & font, bool trackChanges)
1331 pimpl_->insertChar(pos, c, Change(trackChanges ?
1332 Change::INSERTED : Change::UNCHANGED));
1337 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1338 Font const & font, Change const & change)
1340 pimpl_->insertChar(pos, c, change);
1345 void Paragraph::insertInset(pos_type pos, Inset * inset,
1346 Change const & change)
1348 pimpl_->insertInset(pos, inset, change);
1352 void Paragraph::insertInset(pos_type pos, Inset * inset,
1353 Font const & font, Change const & change)
1355 pimpl_->insertInset(pos, inset, change);
1356 // Set the font/language of the inset...
1361 bool Paragraph::insetAllowed(InsetCode code)
1363 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1367 // Gets uninstantiated font setting at position.
1368 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1372 lyxerr << " pos: " << pos << " size: " << size() << endl;
1373 BOOST_ASSERT(pos <= size());
1376 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1377 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1378 for (; cit != end; ++cit)
1379 if (cit->pos() >= pos)
1385 if (pos == size() && !empty())
1386 return getFontSettings(bparams, pos - 1);
1388 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1392 FontSpan Paragraph::fontSpan(pos_type pos) const
1394 BOOST_ASSERT(pos <= size());
1397 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1398 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1399 for (; cit != end; ++cit) {
1400 if (cit->pos() >= pos) {
1401 if (pos >= beginOfBody())
1402 return FontSpan(std::max(start, beginOfBody()),
1405 return FontSpan(start,
1406 std::min(beginOfBody() - 1,
1409 start = cit->pos() + 1;
1412 // This should not happen, but if so, we take no chances.
1413 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1415 return FontSpan(pos, pos);
1419 // Gets uninstantiated font setting at position 0
1420 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1422 if (!empty() && !pimpl_->fontlist.empty())
1423 return pimpl_->fontlist[0].font();
1425 return Font(Font::ALL_INHERIT, bparams.language);
1429 // Gets the fully instantiated font at a given position in a paragraph
1430 // This is basically the same function as Text::GetFont() in text2.cpp.
1431 // The difference is that this one is used for generating the LaTeX file,
1432 // and thus cosmetic "improvements" are disallowed: This has to deliver
1433 // the true picture of the buffer. (Asger)
1434 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1435 Font const & outerfont) const
1437 BOOST_ASSERT(pos >= 0);
1439 Font font = getFontSettings(bparams, pos);
1441 pos_type const body_pos = beginOfBody();
1443 font.realize(layout_->labelfont);
1445 font.realize(layout_->font);
1447 font.realize(outerfont);
1448 font.realize(bparams.getFont());
1454 Font const Paragraph::getLabelFont
1455 (BufferParams const & bparams, Font const & outerfont) const
1457 Font tmpfont = layout()->labelfont;
1458 tmpfont.setLanguage(getParLanguage(bparams));
1459 tmpfont.realize(outerfont);
1460 tmpfont.realize(bparams.getFont());
1465 Font const Paragraph::getLayoutFont
1466 (BufferParams const & bparams, Font const & outerfont) const
1468 Font tmpfont = layout()->font;
1469 tmpfont.setLanguage(getParLanguage(bparams));
1470 tmpfont.realize(outerfont);
1471 tmpfont.realize(bparams.getFont());
1476 /// Returns the height of the highest font in range
1477 Font_size Paragraph::highestFontInRange
1478 (pos_type startpos, pos_type endpos, Font_size def_size) const
1480 if (pimpl_->fontlist.empty())
1483 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1484 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1485 for (; end_it != end; ++end_it) {
1486 if (end_it->pos() >= endpos)
1493 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1494 for (; cit != end; ++cit) {
1495 if (cit->pos() >= startpos)
1499 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1500 for (; cit != end_it; ++cit) {
1501 Font::FONT_SIZE size = cit->font().size();
1502 if (size == Font::INHERIT_SIZE)
1504 if (size > maxsize && size <= Font::SIZE_HUGER)
1511 Paragraph::value_type
1512 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1514 value_type c = getChar(pos);
1515 if (!lyxrc.rtl_support)
1545 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1552 void Paragraph::setFont(pos_type pos, Font const & font)
1554 BOOST_ASSERT(pos <= size());
1556 // First, reduce font against layout/label font
1557 // Update: The setCharFont() routine in text2.cpp already
1558 // reduces font, so we don't need to do that here. (Asger)
1559 // No need to simplify this because it will disappear
1560 // in a new kernel. (Asger)
1561 // Next search font table
1563 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1564 Pimpl::FontList::iterator it = beg;
1565 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1566 for (; it != endit; ++it) {
1567 if (it->pos() >= pos)
1570 size_t const i = distance(beg, it);
1571 bool notfound = (it == endit);
1573 if (!notfound && pimpl_->fontlist[i].font() == font)
1576 bool begin = pos == 0 || notfound ||
1577 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1578 // Is position pos is a beginning of a font block?
1579 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1580 // Is position pos is the end of a font block?
1581 if (begin && end) { // A single char block
1582 if (i + 1 < pimpl_->fontlist.size() &&
1583 pimpl_->fontlist[i + 1].font() == font) {
1584 // Merge the singleton block with the next block
1585 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1586 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1587 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1588 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1589 // Merge the singleton block with the previous block
1590 pimpl_->fontlist[i - 1].pos(pos);
1591 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1593 pimpl_->fontlist[i].font(font);
1595 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1596 pimpl_->fontlist[i - 1].pos(pos);
1598 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1599 Pimpl::FontTable(pos, font));
1601 pimpl_->fontlist[i].pos(pos - 1);
1602 if (!(i + 1 < pimpl_->fontlist.size() &&
1603 pimpl_->fontlist[i + 1].font() == font))
1604 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1605 Pimpl::FontTable(pos, font));
1606 } else { // The general case. The block is splitted into 3 blocks
1607 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1608 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1609 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1610 Pimpl::FontTable(pos, font));
1615 void Paragraph::makeSameLayout(Paragraph const & par)
1617 layout(par.layout());
1619 params() = par.params();
1623 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1625 if (isFreeSpacing())
1631 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1632 if (eraseChar(pos, trackChanges))
1638 return count > 0 || pos > 0;
1642 bool Paragraph::hasSameLayout(Paragraph const & par) const
1644 return par.layout() == layout() && params().sameLayout(par.params());
1648 depth_type Paragraph::getDepth() const
1650 return params().depth();
1654 depth_type Paragraph::getMaxDepthAfter() const
1656 if (layout()->isEnvironment())
1657 return params().depth() + 1;
1659 return params().depth();
1663 char Paragraph::getAlign() const
1665 if (params().align() == LYX_ALIGN_LAYOUT)
1666 return layout()->align;
1668 return params().align();
1672 docstring const & Paragraph::getLabelstring() const
1674 return params().labelString();
1678 // the next two functions are for the manual labels
1679 docstring const Paragraph::getLabelWidthString() const
1681 if (layout()->margintype == MARGIN_MANUAL)
1682 return params().labelWidthString();
1684 return _("Senseless with this layout!");
1688 void Paragraph::setLabelWidthString(docstring const & s)
1690 params().labelWidthString(s);
1694 docstring const Paragraph::translateIfPossible(docstring const & s,
1695 BufferParams const & bparams) const
1697 if (!support::isAscii(s) || s.empty()) {
1698 // This must be a user defined layout. We cannot translate
1699 // this, since gettext accepts only ascii keys.
1702 // Probably standard layout, try to translate
1703 Messages & m = getMessages(getParLanguage(bparams)->code());
1704 return m.get(to_ascii(s));
1708 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1709 BufferParams const & bparams, bool process_appendix) const
1711 TextClass const & tclass = bparams.getTextClass();
1714 if (process_appendix && params().appendix())
1715 fmt = translateIfPossible(layout->labelstring_appendix(),
1718 fmt = translateIfPossible(layout->labelstring(), bparams);
1720 if (fmt.empty() && layout->labeltype == LABEL_COUNTER
1721 && !layout->counter.empty())
1722 fmt = "\\the" + layout->counter;
1724 // handle 'inherited level parts' in 'fmt',
1725 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1726 size_t const i = fmt.find('@', 0);
1727 if (i != docstring::npos) {
1728 size_t const j = fmt.find('@', i + 1);
1729 if (j != docstring::npos) {
1730 docstring parent(fmt, i + 1, j - i - 1);
1731 docstring label = from_ascii("??");
1732 if (tclass.hasLayout(parent))
1733 docstring label = expandLabel(tclass[parent], bparams,
1735 fmt = docstring(fmt, 0, i) + label
1736 + docstring(fmt, j + 1, docstring::npos);
1740 return tclass.counters().counterLabel(fmt);
1744 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1747 LyXAlignment const oldAlign = params().align();
1749 if (!(oldAlign & layout()->alignpossible)) {
1750 frontend::Alert::warning(_("Alignment not permitted"),
1751 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1752 params().align(LYX_ALIGN_LAYOUT);
1757 pos_type Paragraph::beginOfBody() const
1759 return begin_of_body_;
1763 void Paragraph::setBeginOfBody()
1765 if (layout()->labeltype != LABEL_MANUAL) {
1770 // Unroll the first two cycles of the loop
1771 // and remember the previous character to
1772 // remove unnecessary getChar() calls
1774 pos_type end = size();
1775 if (i < end && !isNewline(i)) {
1777 char_type previous_char = 0;
1780 previous_char = text_[i];
1781 if (!isNewline(i)) {
1783 while (i < end && previous_char != ' ') {
1788 previous_char = temp;
1798 // returns -1 if inset not found
1799 int Paragraph::getPositionOfInset(Inset const * inset) const
1802 InsetList::const_iterator it = pimpl_->insetlist_.begin();
1803 InsetList::const_iterator end = pimpl_->insetlist_.end();
1804 for (; it != end; ++it)
1805 if (it->inset == inset)
1811 InsetBibitem * Paragraph::bibitem() const
1813 if (!pimpl_->insetlist_.empty()) {
1814 Inset * inset = pimpl_->insetlist_.begin()->inset;
1815 if (inset->lyxCode() == BIBITEM_CODE)
1816 return static_cast<InsetBibitem *>(inset);
1822 bool Paragraph::forceDefaultParagraphs() const
1824 return inInset() && inInset()->forceDefaultParagraphs(0);
1830 // paragraphs inside floats need different alignment tags to avoid
1833 bool noTrivlistCentering(InsetCode code)
1835 return code == FLOAT_CODE || code == WRAP_CODE;
1839 string correction(string const & orig)
1841 if (orig == "flushleft")
1842 return "raggedright";
1843 if (orig == "flushright")
1844 return "raggedleft";
1845 if (orig == "center")
1851 string const corrected_env(string const & suffix, string const & env,
1854 string output = suffix + "{";
1855 if (noTrivlistCentering(code))
1856 output += correction(env);
1860 if (suffix == "\\begin")
1866 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1868 if (!contains(str, "\n"))
1869 column += str.size();
1873 column = rsplit(str, tmp, '\n').size();
1880 // This could go to ParagraphParameters if we want to
1881 int Paragraph::startTeXParParams(BufferParams const & bparams,
1882 odocstream & os, TexRow & texrow,
1883 bool moving_arg) const
1887 if (params().noindent()) {
1888 os << "\\noindent ";
1892 LyXAlignment const curAlign = params().align();
1894 if (curAlign == layout()->align)
1898 case LYX_ALIGN_NONE:
1899 case LYX_ALIGN_BLOCK:
1900 case LYX_ALIGN_LAYOUT:
1901 case LYX_ALIGN_SPECIAL:
1903 case LYX_ALIGN_LEFT:
1904 case LYX_ALIGN_RIGHT:
1905 case LYX_ALIGN_CENTER:
1914 case LYX_ALIGN_NONE:
1915 case LYX_ALIGN_BLOCK:
1916 case LYX_ALIGN_LAYOUT:
1917 case LYX_ALIGN_SPECIAL:
1919 case LYX_ALIGN_LEFT: {
1921 if (getParLanguage(bparams)->babel() != "hebrew")
1922 output = corrected_env("\\begin", "flushleft", ownerCode());
1924 output = corrected_env("\\begin", "flushright", ownerCode());
1925 os << from_ascii(output);
1926 adjust_row_column(output, texrow, column);
1928 } case LYX_ALIGN_RIGHT: {
1930 if (getParLanguage(bparams)->babel() != "hebrew")
1931 output = corrected_env("\\begin", "flushright", ownerCode());
1933 output = corrected_env("\\begin", "flushleft", ownerCode());
1934 os << from_ascii(output);
1935 adjust_row_column(output, texrow, column);
1937 } case LYX_ALIGN_CENTER: {
1939 output = corrected_env("\\begin", "center", ownerCode());
1940 os << from_ascii(output);
1941 adjust_row_column(output, texrow, column);
1950 // This could go to ParagraphParameters if we want to
1951 int Paragraph::endTeXParParams(BufferParams const & bparams,
1952 odocstream & os, TexRow & texrow,
1953 bool moving_arg) const
1957 switch (params().align()) {
1958 case LYX_ALIGN_NONE:
1959 case LYX_ALIGN_BLOCK:
1960 case LYX_ALIGN_LAYOUT:
1961 case LYX_ALIGN_SPECIAL:
1963 case LYX_ALIGN_LEFT:
1964 case LYX_ALIGN_RIGHT:
1965 case LYX_ALIGN_CENTER:
1973 switch (params().align()) {
1974 case LYX_ALIGN_NONE:
1975 case LYX_ALIGN_BLOCK:
1976 case LYX_ALIGN_LAYOUT:
1977 case LYX_ALIGN_SPECIAL:
1979 case LYX_ALIGN_LEFT: {
1981 if (getParLanguage(bparams)->babel() != "hebrew")
1982 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1984 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1985 os << from_ascii(output);
1986 adjust_row_column(output, texrow, column);
1988 } case LYX_ALIGN_RIGHT: {
1990 if (getParLanguage(bparams)->babel() != "hebrew")
1991 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1993 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1994 os << from_ascii(output);
1995 adjust_row_column(output, texrow, column);
1997 } case LYX_ALIGN_CENTER: {
1999 output = corrected_env("\n\\par\\end", "center", ownerCode());
2000 os << from_ascii(output);
2001 adjust_row_column(output, texrow, column);
2010 // This one spits out the text of the paragraph
2011 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
2012 BufferParams const & bparams,
2013 Font const & outerfont,
2014 odocstream & os, TexRow & texrow,
2015 OutputParams const & runparams) const
2017 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
2019 bool return_value = false;
2023 // well we have to check if we are in an inset with unlimited
2024 // length (all in one row) if that is true then we don't allow
2025 // any special options in the paragraph and also we don't allow
2026 // any environment other than the default layout of the text class
2028 bool asdefault = forceDefaultParagraphs();
2031 style = bparams.getTextClass().defaultLayout();
2036 // Current base font for all inherited font changes, without any
2037 // change caused by an individual character, except for the language:
2038 // It is set to the language of the first character.
2039 // As long as we are in the label, this font is the base font of the
2040 // label. Before the first body character it is set to the base font
2044 // Maybe we have to create a optional argument.
2045 pos_type body_pos = beginOfBody();
2046 unsigned int column = 0;
2049 // the optional argument is kept in curly brackets in
2050 // case it contains a ']'
2053 basefont = getLabelFont(bparams, outerfont);
2055 basefont = getLayoutFont(bparams, outerfont);
2058 // Which font is currently active?
2059 Font running_font(basefont);
2060 // Do we have an open font change?
2061 bool open_font = false;
2063 Change runningChange = Change(Change::UNCHANGED);
2065 texrow.start(id(), 0);
2067 // if the paragraph is empty, the loop will not be entered at all
2069 if (style->isCommand()) {
2074 column += startTeXParParams(bparams, os, texrow,
2075 runparams.moving_arg);
2078 for (pos_type i = 0; i < size(); ++i) {
2079 // First char in paragraph or after label?
2080 if (i == body_pos) {
2083 column += running_font.latexWriteEndChanges(
2084 os, bparams, runparams,
2085 basefont, basefont);
2088 basefont = getLayoutFont(bparams, outerfont);
2089 running_font = basefont;
2091 column += Changes::latexMarkChange(os, bparams,
2092 runningChange, Change(Change::UNCHANGED));
2093 runningChange = Change(Change::UNCHANGED);
2098 if (style->isCommand()) {
2104 column += startTeXParParams(bparams, os,
2106 runparams.moving_arg);
2109 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2110 : pimpl_->lookupChange(i);
2112 if (bparams.outputChanges && runningChange != change) {
2114 column += running_font.latexWriteEndChanges(
2115 os, bparams, runparams, basefont, basefont);
2118 basefont = getLayoutFont(bparams, outerfont);
2119 running_font = basefont;
2121 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2122 runningChange = change;
2125 // do not output text which is marked deleted
2126 // if change tracking output is disabled
2127 if (!bparams.outputChanges && change.type == Change::DELETED) {
2133 value_type const c = getChar(i);
2135 // Fully instantiated font
2136 Font const font = getFont(bparams, i, outerfont);
2138 Font const last_font = running_font;
2140 // Do we need to close the previous font?
2142 (font != running_font ||
2143 font.language() != running_font.language()))
2145 column += running_font.latexWriteEndChanges(
2146 os, bparams, runparams, basefont,
2147 (i == body_pos-1) ? basefont : font);
2148 running_font = basefont;
2152 // Switch file encoding if necessary
2153 if (runparams.encoding->package() == Encoding::inputenc &&
2154 font.language()->encoding()->package() == Encoding::inputenc) {
2155 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2156 runparams.moving_arg, *(runparams.encoding),
2157 *(font.language()->encoding()));
2158 if (enc_switch.first) {
2159 column += enc_switch.second;
2160 runparams.encoding = font.language()->encoding();
2164 // Do we need to change font?
2165 if ((font != running_font ||
2166 font.language() != running_font.language()) &&
2169 odocstringstream ods;
2170 column += font.latexWriteStartChanges(ods, bparams,
2171 runparams, basefont,
2173 running_font = font;
2175 docstring fontchange = ods.str();
2176 // check if the fontchange ends with a trailing blank
2177 // (like "\small " (see bug 3382)
2178 if (suffixIs(fontchange, ' ') && c == ' ')
2179 os << fontchange.substr(0, fontchange.size() - 1)
2180 << from_ascii("{}");
2186 // Do not print the separation of the optional argument
2187 // if style->pass_thru is false. This works because
2188 // simpleTeXSpecialChars ignores spaces if
2189 // style->pass_thru is false.
2190 if (i != body_pos - 1) {
2191 if (pimpl_->simpleTeXBlanks(
2192 *(runparams.encoding), os, texrow,
2193 i, column, font, *style))
2194 // A surrogate pair was output. We
2195 // must not call simpleTeXSpecialChars
2196 // in this iteration, since
2197 // simpleTeXBlanks incremented i, and
2198 // simpleTeXSpecialChars would output
2199 // the combining character again.
2204 OutputParams rp = runparams;
2205 rp.free_spacing = style->free_spacing;
2206 rp.local_font = &font;
2207 rp.intitle = style->intitle;
2208 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2209 texrow, rp, running_font,
2210 basefont, outerfont, open_font,
2211 runningChange, *style, i, column, c);
2213 // Set the encoding to that returned from simpleTeXSpecialChars (see
2214 // comment for encoding member in OutputParams.h)
2215 runparams.encoding = rp.encoding;
2218 // If we have an open font definition, we have to close it
2220 #ifdef FIXED_LANGUAGE_END_DETECTION
2223 .latexWriteEndChanges(os, bparams, runparams,
2225 next_->getFont(bparams, 0, outerfont));
2227 running_font.latexWriteEndChanges(os, bparams,
2228 runparams, basefont, basefont);
2231 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2232 //FIXME: there as we start another \selectlanguage with the next paragraph if
2233 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2234 running_font.latexWriteEndChanges(os, bparams, runparams,
2235 basefont, basefont);
2239 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2241 // Needed if there is an optional argument but no contents.
2242 if (body_pos > 0 && body_pos == size()) {
2244 return_value = false;
2248 column += endTeXParParams(bparams, os, texrow,
2249 runparams.moving_arg);
2252 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2253 return return_value;
2270 string tag_name(PAR_TAG const & pt) {
2272 case PAR_NONE: return "!-- --";
2273 case TT: return "tt";
2274 case SF: return "sf";
2275 case BF: return "bf";
2276 case IT: return "it";
2277 case SL: return "sl";
2278 case EM: return "em";
2285 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2287 p1 = static_cast<PAR_TAG>(p1 | p2);
2292 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2294 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2300 bool Paragraph::emptyTag() const
2302 for (pos_type i = 0; i < size(); ++i) {
2304 Inset const * inset = getInset(i);
2305 InsetCode lyx_code = inset->lyxCode();
2306 if (lyx_code != TOC_CODE &&
2307 lyx_code != INCLUDE_CODE &&
2308 lyx_code != GRAPHICS_CODE &&
2309 lyx_code != ERT_CODE &&
2310 lyx_code != LISTINGS_CODE &&
2311 lyx_code != FLOAT_CODE &&
2312 lyx_code != TABULAR_CODE) {
2316 value_type c = getChar(i);
2317 if (c != ' ' && c != '\t')
2325 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2327 for (pos_type i = 0; i < size(); ++i) {
2329 Inset const * inset = getInset(i);
2330 InsetCode lyx_code = inset->lyxCode();
2331 if (lyx_code == LABEL_CODE) {
2332 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2333 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2342 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2345 for (i = 0; i < size(); ++i) {
2347 Inset const * inset = getInset(i);
2348 inset->docbook(buf, os, runparams);
2350 value_type c = getChar(i);
2353 os << sgml::escapeChar(c);
2360 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2364 for (pos_type i = initial; i < size(); ++i) {
2365 Font font = getFont(buf.params(), i, outerfont);
2368 if (i != initial && font != font_old)
2377 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2379 OutputParams const & runparams,
2380 Font const & outerfont,
2381 pos_type initial) const
2383 bool emph_flag = false;
2385 LayoutPtr const & style = layout();
2387 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2389 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2392 // parsing main loop
2393 for (pos_type i = initial; i < size(); ++i) {
2394 Font font = getFont(buf.params(), i, outerfont);
2396 // handle <emphasis> tag
2397 if (font_old.emph() != font.emph()) {
2398 if (font.emph() == Font::ON) {
2401 } else if (i != initial) {
2402 os << "</emphasis>";
2408 Inset const * inset = getInset(i);
2409 inset->docbook(buf, os, runparams);
2411 value_type c = getChar(i);
2413 if (style->pass_thru)
2416 os << sgml::escapeChar(c);
2422 os << "</emphasis>";
2425 if (style->free_spacing)
2427 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2432 bool Paragraph::isHfill(pos_type pos) const
2435 && getInset(pos)->lyxCode() == HFILL_CODE;
2439 bool Paragraph::isNewline(pos_type pos) const
2442 && getInset(pos)->lyxCode() == NEWLINE_CODE;
2446 bool Paragraph::isLineSeparator(pos_type pos) const
2448 value_type const c = getChar(pos);
2449 return isLineSeparatorChar(c)
2450 || (c == Paragraph::META_INSET && getInset(pos) &&
2451 getInset(pos)->isLineSeparator());
2455 /// Used by the spellchecker
2456 bool Paragraph::isLetter(pos_type pos) const
2459 return getInset(pos)->isLetter();
2461 value_type const c = getChar(pos);
2462 return isLetterChar(c) || isDigit(c);
2468 Paragraph::getParLanguage(BufferParams const & bparams) const
2471 return getFirstFontSettings(bparams).language();
2472 // FIXME: we should check the prev par as well (Lgb)
2473 return bparams.language;
2477 bool Paragraph::isRTL(BufferParams const & bparams) const
2479 return lyxrc.rtl_support
2480 && getParLanguage(bparams)->rightToLeft()
2481 && ownerCode() != ERT_CODE
2482 && ownerCode() != LISTINGS_CODE;
2486 void Paragraph::changeLanguage(BufferParams const & bparams,
2487 Language const * from, Language const * to)
2489 // change language including dummy font change at the end
2490 for (pos_type i = 0; i <= size(); ++i) {
2491 Font font = getFontSettings(bparams, i);
2492 if (font.language() == from) {
2493 font.setLanguage(to);
2500 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2502 Language const * doc_language = bparams.language;
2503 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2504 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2506 for (; cit != end; ++cit)
2507 if (cit->font().language() != ignore_language &&
2508 cit->font().language() != latex_language &&
2509 cit->font().language() != doc_language)
2515 // Convert the paragraph to a string.
2516 // Used for building the table of contents
2517 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2519 return asString(buffer, 0, size(), label);
2523 docstring const Paragraph::asString(Buffer const & buffer,
2524 pos_type beg, pos_type end, bool label) const
2527 odocstringstream os;
2529 if (beg == 0 && label && !params().labelString().empty())
2530 os << params().labelString() << ' ';
2532 for (pos_type i = beg; i < end; ++i) {
2533 value_type const c = getChar(i);
2536 else if (c == META_INSET)
2537 getInset(i)->textString(buffer, os);
2544 void Paragraph::setInsetOwner(Inset * inset)
2546 pimpl_->inset_owner = inset;
2550 Change const & Paragraph::lookupChange(pos_type pos) const
2552 BOOST_ASSERT(pos <= size());
2553 return pimpl_->lookupChange(pos);
2557 bool Paragraph::isChanged(pos_type start, pos_type end) const
2559 return pimpl_->isChanged(start, end);
2563 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2565 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2569 void Paragraph::setChange(Change const & change)
2571 pimpl_->setChange(change);
2575 void Paragraph::setChange(pos_type pos, Change const & change)
2577 pimpl_->setChange(pos, change);
2581 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2583 return pimpl_->acceptChanges(bparams, start, end);
2587 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2589 return pimpl_->rejectChanges(bparams, start, end);
2593 int Paragraph::id() const
2599 LayoutPtr const & Paragraph::layout() const
2605 void Paragraph::layout(LayoutPtr const & new_layout)
2607 layout_ = new_layout;
2611 Inset * Paragraph::inInset() const
2613 return pimpl_->inset_owner;
2617 InsetCode Paragraph::ownerCode() const
2619 return pimpl_->inset_owner
2620 ? pimpl_->inset_owner->lyxCode() : NO_CODE;
2624 ParagraphParameters & Paragraph::params()
2626 return pimpl_->params;
2630 ParagraphParameters const & Paragraph::params() const
2632 return pimpl_->params;
2636 bool Paragraph::isFreeSpacing() const
2638 if (layout()->free_spacing)
2641 // for now we just need this, later should we need this in some
2642 // other way we can always add a function to Inset too.
2643 return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE;
2647 bool Paragraph::allowEmpty() const
2649 if (layout()->keepempty)
2651 return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE;
2655 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2657 if (!Encodings::is_arabic(c))
2660 value_type prev_char = ' ';
2661 value_type next_char = ' ';
2663 for (pos_type i = pos - 1; i >= 0; --i) {
2664 value_type const par_char = getChar(i);
2665 if (!Encodings::isComposeChar_arabic(par_char)) {
2666 prev_char = par_char;
2671 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2672 value_type const par_char = getChar(i);
2673 if (!Encodings::isComposeChar_arabic(par_char)) {
2674 next_char = par_char;
2679 if (Encodings::is_arabic(next_char)) {
2680 if (Encodings::is_arabic(prev_char) &&
2681 !Encodings::is_arabic_special(prev_char))
2682 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2684 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2686 if (Encodings::is_arabic(prev_char) &&
2687 !Encodings::is_arabic_special(prev_char))
2688 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2690 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2695 int Paragraph::checkBiblio(bool track_changes)
2698 //This is getting more and more a mess. ...We really should clean
2699 //up this bibitem issue for 1.6. See also bug 2743.
2701 // Add bibitem insets if necessary
2702 if (layout()->labeltype != LABEL_BIBLIO)
2705 bool hasbibitem = !pimpl_->insetlist_.empty()
2706 // Insist on it being in pos 0
2707 && getChar(0) == Paragraph::META_INSET
2708 && pimpl_->insetlist_.begin()->inset->lyxCode() == BIBITEM_CODE;
2713 // remove a bibitem in pos != 0
2714 // restore it later in pos 0 if necessary
2715 // (e.g. if a user inserts contents _before_ the item)
2716 // we're assuming there's only one of these, which there
2718 int erasedInsetPosition = -1;
2719 InsetList::iterator it = pimpl_->insetlist_.begin();
2720 InsetList::iterator end = pimpl_->insetlist_.end();
2721 for (; it != end; ++it)
2722 if (it->inset->lyxCode() == BIBITEM_CODE
2724 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2725 oldkey = olditem->getParam("key");
2726 oldlabel = olditem->getParam("label");
2727 erasedInsetPosition = it->pos;
2728 eraseChar(erasedInsetPosition, track_changes);
2732 //There was an InsetBibitem at the beginning, and we didn't
2733 //have to erase one.
2734 if (hasbibitem && erasedInsetPosition < 0)
2737 //There was an InsetBibitem at the beginning and we did have to
2738 //erase one. So we give its properties to the beginning inset.
2740 InsetBibitem * inset =
2741 static_cast<InsetBibitem *>(pimpl_->insetlist_.begin()->inset);
2742 if (!oldkey.empty())
2743 inset->setParam("key", oldkey);
2744 inset->setParam("label", oldlabel);
2745 return -erasedInsetPosition;
2748 //There was no inset at the beginning, so we need to create one with
2749 //the key and label of the one we erased.
2750 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2751 // restore values of previously deleted item in this par.
2752 if (!oldkey.empty())
2753 inset->setParam("key", oldkey);
2754 inset->setParam("label", oldlabel);
2755 insertInset(0, static_cast<Inset *>(inset),
2756 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2762 void Paragraph::checkAuthors(AuthorList const & authorList)
2764 pimpl_->changes_.checkAuthors(authorList);
2768 bool Paragraph::isUnchanged(pos_type pos) const
2770 return lookupChange(pos).type == Change::UNCHANGED;
2774 bool Paragraph::isInserted(pos_type pos) const
2776 return lookupChange(pos).type == Change::INSERTED;
2780 bool Paragraph::isDeleted(pos_type pos) const
2782 return lookupChange(pos).type == Change::DELETED;
2786 InsetList const & Paragraph::insetList() const
2788 return pimpl_->insetlist_;
2792 Inset * Paragraph::releaseInset(pos_type pos)
2794 Inset * inset = pimpl_->insetlist_.release(pos);
2795 /// does not honour change tracking!
2796 eraseChar(pos, false);
2801 Inset * Paragraph::getInset(pos_type pos)
2803 return pimpl_->insetlist_.get(pos);
2807 Inset const * Paragraph::getInset(pos_type pos) const
2809 return pimpl_->insetlist_.get(pos);