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"
22 #include "LayoutFile.h"
24 #include "BufferParams.h"
28 #include "InsetList.h"
30 #include "LaTeXFeatures.h"
36 #include "OutputParams.h"
37 #include "output_latex.h"
38 #include "output_xhtml.h"
39 #include "paragraph_funcs.h"
40 #include "ParagraphParameters.h"
41 #include "SpellChecker.h"
43 #include "TextClass.h"
47 #include "WordLangTuple.h"
50 #include "frontends/alert.h"
52 #include "insets/InsetBibitem.h"
53 #include "insets/InsetLabel.h"
55 #include "support/debug.h"
56 #include "support/docstring_list.h"
57 #include "support/ExceptionMessage.h"
58 #include "support/gettext.h"
59 #include "support/lassert.h"
60 #include "support/lstrings.h"
61 #include "support/textutils.h"
67 using namespace lyx::support
;
72 /// Inset identifier (above 0x10ffff, for ucs-4)
73 char_type
const META_INSET
= 0x200001;
76 /////////////////////////////////////////////////////////////////////
80 /////////////////////////////////////////////////////////////////////
82 class Paragraph::Private
86 Private(Paragraph
* owner
, Layout
const & layout
);
87 /// "Copy constructor"
88 Private(Private
const &, Paragraph
* owner
);
89 /// Copy constructor from \p beg to \p end
90 Private(Private
const &, Paragraph
* owner
, pos_type beg
, pos_type end
);
93 void insertChar(pos_type pos
, char_type c
, Change
const & change
);
95 /// Output the surrogate pair formed by \p c and \p next to \p os.
96 /// \return the number of characters written.
97 int latexSurrogatePair(odocstream
& os
, char_type c
, char_type next
,
98 OutputParams
const &);
100 /// Output a space in appropriate formatting (or a surrogate pair
101 /// if the next character is a combining character).
102 /// \return whether a surrogate pair was output.
103 bool simpleTeXBlanks(OutputParams
const &,
104 odocstream
&, TexRow
& texrow
,
106 unsigned int & column
,
108 Layout
const & style
);
110 /// Output consecutive unicode chars, belonging to the same script as
111 /// specified by the latex macro \p ltx, to \p os starting from \p i.
112 /// \return the number of characters written.
113 int writeScriptChars(odocstream
& os
, docstring
const & ltx
,
114 Change
const &, Encoding
const &, pos_type
& i
);
116 /// This could go to ParagraphParameters if we want to.
117 int startTeXParParams(BufferParams
const &, odocstream
&, TexRow
&,
118 OutputParams
const &) const;
120 /// This could go to ParagraphParameters if we want to.
121 int endTeXParParams(BufferParams
const &, odocstream
&, TexRow
&,
122 OutputParams
const &) const;
125 void latexInset(BufferParams
const &,
127 TexRow
& texrow
, OutputParams
&,
130 Font
const & outerfont
,
132 Change
& running_change
,
133 Layout
const & style
,
135 unsigned int & column
);
138 void latexSpecialChar(
140 OutputParams
const & runparams
,
141 Font
const & running_font
,
142 Change
const & running_change
,
143 Layout
const & style
,
145 unsigned int & column
);
152 unsigned int & column
);
154 bool latexSpecialTypewriter(
158 unsigned int & column
);
160 bool latexSpecialPhrase(
163 unsigned int & column
,
164 OutputParams
const & runparams
);
167 void validate(LaTeXFeatures
& features
,
168 Layout
const & layout
) const;
170 /// Checks if the paragraph contains only text and no inset or font change.
171 bool onlyText(Buffer
const & buf
, Font
const & outerfont
,
172 pos_type initial
) const;
174 /// match a string against a particular point in the paragraph
175 bool isTextAt(string
const & str
, pos_type pos
) const;
177 /// Which Paragraph owns us?
181 Inset
const * inset_owner_
;
189 static unsigned int paragraph_id
;
191 ParagraphParameters params_
;
193 /// for recording and looking up changes
197 InsetList insetlist_
;
200 pos_type begin_of_body_
;
202 typedef docstring TextContainer
;
206 typedef std::set
<docstring
> Words
;
210 Layout
const * layout_
;
214 // Initialization of the counter for the paragraph id's,
215 unsigned int Paragraph::Private::paragraph_id
= 0;
219 struct special_phrase
{
225 special_phrase
const special_phrases
[] = {
226 { "LyX", from_ascii("\\LyX{}"), false },
227 { "TeX", from_ascii("\\TeX{}"), true },
228 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
229 { "LaTeX", from_ascii("\\LaTeX{}"), true },
232 size_t const phrases_nr
= sizeof(special_phrases
)/sizeof(special_phrase
);
237 Paragraph::Private::Private(Paragraph
* owner
, Layout
const & layout
)
238 : owner_(owner
), inset_owner_(0), begin_of_body_(0), layout_(&layout
)
240 id_
= paragraph_id
++;
245 Paragraph::Private::Private(Private
const & p
, Paragraph
* owner
)
246 : owner_(owner
), inset_owner_(p
.inset_owner_
), fontlist_(p
.fontlist_
),
247 params_(p
.params_
), changes_(p
.changes_
), insetlist_(p
.insetlist_
),
248 begin_of_body_(p
.begin_of_body_
), text_(p
.text_
), words_(p
.words_
),
251 id_
= paragraph_id
++;
255 Paragraph::Private::Private(Private
const & p
, Paragraph
* owner
,
256 pos_type beg
, pos_type end
)
257 : owner_(owner
), inset_owner_(p
.inset_owner_
),
258 params_(p
.params_
), changes_(p
.changes_
),
259 insetlist_(p
.insetlist_
, beg
, end
),
260 begin_of_body_(p
.begin_of_body_
), words_(p
.words_
),
263 id_
= paragraph_id
++;
264 if (beg
>= pos_type(p
.text_
.size()))
266 text_
= p
.text_
.substr(beg
, end
- beg
);
268 FontList::const_iterator fcit
= fontlist_
.begin();
269 FontList::const_iterator fend
= fontlist_
.end();
270 for (; fcit
!= fend
; ++fcit
) {
271 if (fcit
->pos() < beg
)
273 if (fcit
->pos() >= end
) {
274 // Add last entry in the fontlist_.
275 fontlist_
.set(text_
.size() - 1, fcit
->font());
278 // Add a new entry in the fontlist_.
279 fontlist_
.set(fcit
->pos() - beg
, fcit
->font());
284 void Paragraph::addChangesToToc(DocIterator
const & cdit
,
285 Buffer
const & buf
) const
287 d
->changes_
.addToToc(cdit
, buf
);
291 bool Paragraph::isDeleted(pos_type start
, pos_type end
) const
293 LASSERT(start
>= 0 && start
<= size(), /**/);
294 LASSERT(end
> start
&& end
<= size() + 1, /**/);
296 return d
->changes_
.isDeleted(start
, end
);
300 bool Paragraph::isChanged(pos_type start
, pos_type end
) const
302 LASSERT(start
>= 0 && start
<= size(), /**/);
303 LASSERT(end
> start
&& end
<= size() + 1, /**/);
305 return d
->changes_
.isChanged(start
, end
);
309 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges
) const
311 // keep the logic here in sync with the logic of eraseChars()
315 Change
const change
= d
->changes_
.lookup(size());
316 return change
.inserted() && change
.currentAuthor();
320 void Paragraph::setChange(Change
const & change
)
322 // beware of the imaginary end-of-par character!
323 d
->changes_
.set(change
, 0, size() + 1);
326 * Propagate the change recursively - but not in case of DELETED!
328 * Imagine that your co-author makes changes in an existing inset. He
329 * sends your document to you and you come to the conclusion that the
330 * inset should go completely. If you erase it, LyX must not delete all
331 * text within the inset. Otherwise, the change tracked insertions of
332 * your co-author get lost and there is no way to restore them later.
334 * Conclusion: An inset's content should remain untouched if you delete it
337 if (!change
.deleted()) {
338 for (pos_type pos
= 0; pos
< size(); ++pos
) {
339 if (Inset
* inset
= getInset(pos
))
340 inset
->setChange(change
);
346 void Paragraph::setChange(pos_type pos
, Change
const & change
)
348 LASSERT(pos
>= 0 && pos
<= size(), /**/);
349 d
->changes_
.set(change
, pos
);
351 // see comment in setChange(Change const &) above
352 if (!change
.deleted() && pos
< size())
353 if (Inset
* inset
= getInset(pos
))
354 inset
->setChange(change
);
358 Change
const & Paragraph::lookupChange(pos_type pos
) const
360 LASSERT(pos
>= 0 && pos
<= size(), /**/);
361 return d
->changes_
.lookup(pos
);
365 void Paragraph::acceptChanges(pos_type start
, pos_type end
)
367 LASSERT(start
>= 0 && start
<= size(), /**/);
368 LASSERT(end
> start
&& end
<= size() + 1, /**/);
370 for (pos_type pos
= start
; pos
< end
; ++pos
) {
371 switch (lookupChange(pos
).type
) {
372 case Change::UNCHANGED
:
373 // accept changes in nested inset
374 if (Inset
* inset
= getInset(pos
))
375 inset
->acceptChanges();
378 case Change::INSERTED
:
379 d
->changes_
.set(Change(Change::UNCHANGED
), pos
);
380 // also accept changes in nested inset
381 if (Inset
* inset
= getInset(pos
))
382 inset
->acceptChanges();
385 case Change::DELETED
:
386 // Suppress access to non-existent
387 // "end-of-paragraph char"
389 eraseChar(pos
, false);
400 void Paragraph::rejectChanges(pos_type start
, pos_type end
)
402 LASSERT(start
>= 0 && start
<= size(), /**/);
403 LASSERT(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 (Inset
* inset
= getInset(pos
))
410 inset
->rejectChanges();
413 case Change::INSERTED
:
414 // Suppress access to non-existent
415 // "end-of-paragraph char"
417 eraseChar(pos
, false);
423 case Change::DELETED
:
424 d
->changes_
.set(Change(Change::UNCHANGED
), pos
);
426 // Do NOT reject changes within a deleted inset!
427 // There may be insertions of a co-author inside of it!
435 void Paragraph::Private::insertChar(pos_type pos
, char_type c
,
436 Change
const & change
)
438 LASSERT(pos
>= 0 && pos
<= int(text_
.size()), /**/);
441 changes_
.insert(change
, pos
);
443 // This is actually very common when parsing buffers (and
444 // maybe inserting ascii text)
445 if (pos
== pos_type(text_
.size())) {
446 // when appending characters, no need to update tables
451 text_
.insert(text_
.begin() + pos
, c
);
453 // Update the font table.
454 fontlist_
.increasePosAfterPos(pos
);
457 insetlist_
.increasePosAfterPos(pos
);
461 bool Paragraph::insertInset(pos_type pos
, Inset
* inset
,
462 Change
const & change
)
464 LASSERT(inset
, /**/);
465 LASSERT(pos
>= 0 && pos
<= size(), /**/);
467 // Paragraph::insertInset() can be used in cut/copy/paste operation where
468 // d->inset_owner_ is not set yet.
469 if (d
->inset_owner_
&& !d
->inset_owner_
->insetAllowed(inset
->lyxCode()))
472 d
->insertChar(pos
, META_INSET
, change
);
473 LASSERT(d
->text_
[pos
] == META_INSET
, /**/);
475 // Add a new entry in the insetlist_.
476 d
->insetlist_
.insert(inset
, pos
);
481 bool Paragraph::eraseChar(pos_type pos
, bool trackChanges
)
483 LASSERT(pos
>= 0 && pos
<= size(), return false);
485 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
488 Change change
= d
->changes_
.lookup(pos
);
490 // set the character to DELETED if
491 // a) it was previously unchanged or
492 // b) it was inserted by a co-author
494 if (!change
.changed() ||
495 (change
.inserted() && !change
.currentAuthor())) {
496 setChange(pos
, Change(Change::DELETED
));
500 if (change
.deleted())
504 // Don't physically access the imaginary end-of-paragraph character.
505 // eraseChar() can only mark it as DELETED. A physical deletion of
506 // end-of-par must be handled externally.
512 d
->changes_
.erase(pos
);
514 // if it is an inset, delete the inset entry
515 if (d
->text_
[pos
] == META_INSET
)
516 d
->insetlist_
.erase(pos
);
518 d
->text_
.erase(d
->text_
.begin() + pos
);
520 // Update the fontlist_
521 d
->fontlist_
.erase(pos
);
523 // Update the insetlist_
524 d
->insetlist_
.decreasePosAfterPos(pos
);
530 int Paragraph::eraseChars(pos_type start
, pos_type end
, bool trackChanges
)
532 LASSERT(start
>= 0 && start
<= size(), /**/);
533 LASSERT(end
>= start
&& end
<= size() + 1, /**/);
536 for (pos_type count
= end
- start
; count
; --count
) {
537 if (!eraseChar(i
, trackChanges
))
544 int Paragraph::Private::latexSurrogatePair(odocstream
& os
, char_type c
,
545 char_type next
, OutputParams
const & runparams
)
547 // Writing next here may circumvent a possible font change between
548 // c and next. Since next is only output if it forms a surrogate pair
549 // with c we can ignore this:
550 // A font change inside a surrogate pair does not make sense and is
551 // hopefully impossible to input.
552 // FIXME: change tracking
553 // Is this correct WRT change tracking?
554 Encoding
const & encoding
= *(runparams
.encoding
);
555 docstring
const latex1
= encoding
.latexChar(next
);
556 docstring
const latex2
= encoding
.latexChar(c
);
557 if (docstring(1, next
) == latex1
) {
558 // the encoding supports the combination
559 os
<< latex2
<< latex1
;
560 return latex1
.length() + latex2
.length();
561 } else if (runparams
.local_font
&&
562 runparams
.local_font
->language()->lang() == "polutonikogreek") {
563 // polutonikogreek only works without the brackets
564 os
<< latex1
<< latex2
;
565 return latex1
.length() + latex2
.length();
567 os
<< latex1
<< '{' << latex2
<< '}';
568 return latex1
.length() + latex2
.length() + 2;
572 bool Paragraph::Private::simpleTeXBlanks(OutputParams
const & runparams
,
573 odocstream
& os
, TexRow
& texrow
,
575 unsigned int & column
,
577 Layout
const & style
)
579 if (style
.pass_thru
|| runparams
.verbatim
)
582 if (i
+ 1 < int(text_
.size())) {
583 char_type next
= text_
[i
+ 1];
584 if (Encodings::isCombiningChar(next
)) {
585 // This space has an accent, so we must always output it.
586 column
+= latexSurrogatePair(os
, ' ', next
, runparams
) - 1;
591 if (lyxrc
.plaintext_linelen
> 0
592 && column
> lyxrc
.plaintext_linelen
594 && text_
[i
- 1] != ' '
595 && (i
+ 1 < int(text_
.size()))
596 // same in FreeSpacing mode
597 && !owner_
->isFreeSpacing()
598 // In typewriter mode, we want to avoid
599 // ! . ? : at the end of a line
600 && !(font
.fontInfo().family() == TYPEWRITER_FAMILY
601 && (text_
[i
- 1] == '.'
602 || text_
[i
- 1] == '?'
603 || text_
[i
- 1] == ':'
604 || text_
[i
- 1] == '!'))) {
607 texrow
.start(owner_
->id(), i
+ 1);
609 } else if (style
.free_spacing
) {
618 int Paragraph::Private::writeScriptChars(odocstream
& os
,
619 docstring
const & ltx
,
620 Change
const & runningChange
,
621 Encoding
const & encoding
,
624 // FIXME: modifying i here is not very nice...
626 // We only arrive here when a proper language for character text_[i] has
627 // not been specified (i.e., it could not be translated in the current
628 // latex encoding) or its latex translation has been forced, and it
629 // belongs to a known script.
630 // Parameter ltx contains the latex translation of text_[i] as specified
631 // in the unicodesymbols file and is something like "\textXXX{<spec>}".
632 // The latex macro name "textXXX" specifies the script to which text_[i]
633 // belongs and we use it in order to check whether characters from the
634 // same script immediately follow, such that we can collect them in a
635 // single "\textXXX" macro. So, we have to retain "\textXXX{<spec>"
636 // for the first char but only "<spec>" for all subsequent chars.
637 docstring::size_type
const brace1
= ltx
.find_first_of(from_ascii("{"));
638 docstring::size_type
const brace2
= ltx
.find_last_of(from_ascii("}"));
639 string script
= to_ascii(ltx
.substr(1, brace1
- 1));
642 bool closing_brace
= true;
643 if (script
== "textgreek" && encoding
.latexName() == "iso-8859-7") {
644 // Correct encoding is being used, so we can avoid \textgreek.
647 closing_brace
= false;
649 os
<< ltx
.substr(pos
, length
);
650 int size
= text_
.size();
651 while (i
+ 1 < size
) {
652 char_type
const next
= text_
[i
+ 1];
653 // Stop here if next character belongs to another script
654 // or there is a change in change tracking status.
655 if (!Encodings::isKnownScriptChar(next
, script
) ||
656 runningChange
!= owner_
->lookupChange(i
+ 1))
660 FontList::const_iterator cit
= fontlist_
.begin();
661 FontList::const_iterator end
= fontlist_
.end();
662 for (; cit
!= end
; ++cit
) {
663 if (cit
->pos() >= i
&& !found
) {
664 prev_font
= cit
->font();
667 if (cit
->pos() >= i
+ 1)
670 // Stop here if there is a font attribute or encoding change.
671 if (found
&& cit
!= end
&& prev_font
!= cit
->font())
673 docstring
const latex
= encoding
.latexChar(next
);
674 docstring::size_type
const b1
=
675 latex
.find_first_of(from_ascii("{"));
676 docstring::size_type
const b2
=
677 latex
.find_last_of(from_ascii("}"));
678 int const len
= b2
- b1
- 1;
679 os
<< latex
.substr(b1
+ 1, len
);
691 bool Paragraph::Private::isTextAt(string
const & str
, pos_type pos
) const
693 pos_type
const len
= str
.length();
695 // is the paragraph large enough?
696 if (pos
+ len
> int(text_
.size()))
699 // does the wanted text start at point?
700 for (string::size_type i
= 0; i
< str
.length(); ++i
) {
701 // Caution: direct comparison of characters works only
702 // because str is pure ASCII.
703 if (str
[i
] != text_
[pos
+ i
])
707 return fontlist_
.hasChangeInRange(pos
, len
);
711 void Paragraph::Private::latexInset(
712 BufferParams
const & bparams
,
715 OutputParams
& runparams
,
718 Font
const & outerfont
,
720 Change
& running_change
,
721 Layout
const & style
,
723 unsigned int & column
)
725 Inset
* inset
= owner_
->getInset(i
);
726 LASSERT(inset
, /**/);
728 if (style
.pass_thru
) {
729 inset
->plaintext(os
, runparams
);
733 // FIXME: move this to InsetNewline::latex
734 if (inset
->lyxCode() == NEWLINE_CODE
) {
735 // newlines are handled differently here than
736 // the default in simpleTeXSpecialChars().
737 if (!style
.newline_allowed
) {
741 column
+= running_font
.latexWriteEndChanges(
742 os
, bparams
, runparams
,
747 if (running_font
.fontInfo().family() == TYPEWRITER_FAMILY
)
750 basefont
= owner_
->getLayoutFont(bparams
, outerfont
);
751 running_font
= basefont
;
753 if (runparams
.moving_arg
)
758 texrow
.start(owner_
->id(), i
+ 1);
762 if (owner_
->isDeleted(i
)) {
763 if( ++runparams
.inDeletedInset
== 1)
764 runparams
.changeOfDeletedInset
= owner_
->lookupChange(i
);
767 if (inset
->canTrackChanges()) {
768 column
+= Changes::latexMarkChange(os
, bparams
, running_change
,
769 Change(Change::UNCHANGED
));
770 running_change
= Change(Change::UNCHANGED
);
774 odocstream::pos_type
const len
= os
.tellp();
776 if (inset
->forceLTR()
777 && running_font
.isRightToLeft()
778 // ERT is an exception, it should be output with no
779 // decorations at all
780 && inset
->lyxCode() != ERT_CODE
) {
781 if (running_font
.language()->lang() == "farsi")
788 // FIXME: Bug: we can have an empty font change here!
789 // if there has just been a font change, we are going to close it
790 // right now, which means stupid latex code like \textsf{}. AFAIK,
791 // this does not harm dvi output. A minor bug, thus (JMarc)
793 // Some insets cannot be inside a font change command.
794 // However, even such insets *can* be placed in \L or \R
795 // or their equivalents (for RTL language switches), so we don't
796 // close the language in those cases.
797 // ArabTeX, though, cannot handle this special behavior, it seems.
798 bool arabtex
= basefont
.language()->lang() == "arabic_arabtex"
799 || running_font
.language()->lang() == "arabic_arabtex";
800 if (open_font
&& inset
->noFontChange()) {
801 bool closeLanguage
= arabtex
802 || basefont
.isRightToLeft() == running_font
.isRightToLeft();
803 unsigned int count
= running_font
.latexWriteEndChanges(os
,
804 bparams
, runparams
, basefont
, basefont
, closeLanguage
);
806 // if any font properties were closed, update the running_font,
807 // making sure, however, to leave the language as it was
809 // FIXME: probably a better way to keep track of the old
810 // language, than copying the entire font?
811 Font
const copy_font(running_font
);
812 basefont
= owner_
->getLayoutFont(bparams
, outerfont
);
813 running_font
= basefont
;
815 running_font
.setLanguage(copy_font
.language());
816 // leave font open if language is still open
817 open_font
= (running_font
.language() == basefont
.language());
819 runparams
.local_font
= &basefont
;
826 tmp
= inset
->latex(os
, runparams
);
827 } catch (EncodingException
& e
) {
828 // add location information and throw again.
835 if (running_font
.language()->lang() == "farsi")
842 for (int j
= 0; j
< tmp
; ++j
)
845 texrow
.start(owner_
->id(), i
+ 1);
848 column
+= os
.tellp() - len
;
851 if (owner_
->isDeleted(i
))
852 --runparams
.inDeletedInset
;
856 void Paragraph::Private::latexSpecialChar(
858 OutputParams
const & runparams
,
859 Font
const & running_font
,
860 Change
const & running_change
,
861 Layout
const & style
,
863 unsigned int & column
)
865 char_type
const c
= text_
[i
];
867 if (style
.pass_thru
) {
869 // FIXME UNICODE: This can fail if c cannot
870 // be encoded in the current encoding.
875 if (runparams
.verbatim
) {
876 // FIXME UNICODE: This can fail if c cannot
877 // be encoded in the current encoding.
882 // If T1 font encoding is used, use the special
883 // characters it provides.
884 // NOTE: some languages reset the font encoding
886 if (!running_font
.language()->internalFontEncoding()
887 && lyxrc
.fontenc
== "T1" && latexSpecialT1(c
, os
, i
, column
))
890 // \tt font needs special treatment
891 if (running_font
.fontInfo().family() == TYPEWRITER_FAMILY
892 && latexSpecialTypewriter(c
, os
, i
, column
))
895 // Otherwise, we use what LaTeX provides us.
898 os
<< "\\textbackslash{}";
902 os
<< "\\textless{}";
906 os
<< "\\textgreater{}";
917 os
<< "\\char`\\\"{}";
922 case '%': case '#': case '{':
930 os
<< "\\textasciitilde{}";
935 os
<< "\\textasciicircum{}";
942 // avoid being mistaken for optional arguments
950 // Blanks are printed before font switching.
951 // Sure? I am not! (try nice-latex)
952 // I am sure it's correct. LyX might be smarter
953 // in the future, but for now, nothing wrong is
959 if (latexSpecialPhrase(os
, i
, column
, runparams
))
965 Encoding
const & encoding
= *(runparams
.encoding
);
966 if (i
+ 1 < int(text_
.size())) {
967 char_type next
= text_
[i
+ 1];
968 if (Encodings::isCombiningChar(next
)) {
969 column
+= latexSurrogatePair(os
, c
, next
, runparams
) - 1;
975 docstring
const latex
= encoding
.latexChar(c
);
976 if (Encodings::isKnownScriptChar(c
, script
)
977 && prefixIs(latex
, from_ascii("\\" + script
)))
978 column
+= writeScriptChars(os
, latex
,
979 running_change
, encoding
, i
) - 1;
980 else if (latex
.length() > 1 && latex
[latex
.length() - 1] != '}') {
981 // Prevent eating of a following
982 // space or command corruption by
983 // following characters
984 column
+= latex
.length() + 1;
987 column
+= latex
.length() - 1;
995 bool Paragraph::Private::latexSpecialT1(char_type
const c
, odocstream
& os
,
996 pos_type i
, unsigned int & column
)
1002 // In T1 encoding, these characters exist
1003 // but we should avoid ligatures
1004 if (i
+ 1 >= int(text_
.size()) || text_
[i
+ 1] != c
)
1006 os
<< "\\textcompwordmark{}";
1013 // soul.sty breaks with \char`\"
1014 os
<< "\\textquotedbl{}";
1023 bool Paragraph::Private::latexSpecialTypewriter(char_type
const c
, odocstream
& os
,
1024 pos_type i
, unsigned int & column
)
1028 // within \ttfamily, "--" is merged to "-" (no endash)
1029 // so we avoid this rather irritating ligature
1030 if (i
+ 1 < int(text_
.size()) && text_
[i
+ 1] == '-') {
1037 // everything else has to be checked separately
1038 // (depending on the encoding)
1045 bool Paragraph::Private::latexSpecialPhrase(odocstream
& os
, pos_type
& i
,
1046 unsigned int & column
, OutputParams
const & runparams
)
1048 // FIXME: if we have "LaTeX" with a font
1049 // change in the middle (before the 'T', then
1050 // the "TeX" part is still special cased.
1051 // Really we should only operate this on
1052 // "words" for some definition of word
1054 for (size_t pnr
= 0; pnr
< phrases_nr
; ++pnr
) {
1055 if (!isTextAt(special_phrases
[pnr
].phrase
, i
))
1057 if (runparams
.moving_arg
)
1059 os
<< special_phrases
[pnr
].macro
;
1060 i
+= special_phrases
[pnr
].phrase
.length() - 1;
1061 column
+= special_phrases
[pnr
].macro
.length() - 1;
1068 void Paragraph::Private::validate(LaTeXFeatures
& features
,
1069 Layout
const & layout
) const
1071 // check the params.
1072 if (!params_
.spacing().isDefault())
1073 features
.require("setspace");
1076 features
.useLayout(layout
.name());
1079 fontlist_
.validate(features
);
1081 // then the indentation
1082 if (!params_
.leftIndent().zero())
1083 features
.require("ParagraphLeftIndent");
1086 InsetList::const_iterator icit
= insetlist_
.begin();
1087 InsetList::const_iterator iend
= insetlist_
.end();
1088 for (; icit
!= iend
; ++icit
) {
1090 icit
->inset
->validate(features
);
1091 if (layout
.needprotect
&&
1092 icit
->inset
->lyxCode() == FOOT_CODE
)
1093 features
.require("NeedLyXFootnoteCode");
1097 // then the contents
1098 for (pos_type i
= 0; i
< int(text_
.size()) ; ++i
) {
1099 for (size_t pnr
= 0; pnr
< phrases_nr
; ++pnr
) {
1100 if (!special_phrases
[pnr
].builtin
1101 && isTextAt(special_phrases
[pnr
].phrase
, i
)) {
1102 features
.require(special_phrases
[pnr
].phrase
);
1106 Encodings::validate(text_
[i
], features
);
1110 /////////////////////////////////////////////////////////////////////
1114 /////////////////////////////////////////////////////////////////////
1117 Layout
const emptyParagraphLayout
;
1120 Paragraph::Paragraph()
1121 : d(new Paragraph::Private(this, emptyParagraphLayout
))
1128 Paragraph::Paragraph(Paragraph
const & par
)
1129 : itemdepth(par
.itemdepth
),
1130 d(new Paragraph::Private(*par
.d
, this))
1136 Paragraph::Paragraph(Paragraph
const & par
, pos_type beg
, pos_type end
)
1137 : itemdepth(par
.itemdepth
),
1138 d(new Paragraph::Private(*par
.d
, this, beg
, end
))
1144 Paragraph
& Paragraph::operator=(Paragraph
const & par
)
1146 // needed as we will destroy the private part before copying it
1148 itemdepth
= par
.itemdepth
;
1152 d
= new Private(*par
.d
, this);
1159 Paragraph::~Paragraph()
1166 void Paragraph::write(ostream
& os
, BufferParams
const & bparams
,
1167 depth_type
& dth
) const
1169 // The beginning or end of a deeper (i.e. nested) area?
1170 if (dth
!= d
->params_
.depth()) {
1171 if (d
->params_
.depth() > dth
) {
1172 while (d
->params_
.depth() > dth
) {
1173 os
<< "\n\\begin_deeper";
1177 while (d
->params_
.depth() < dth
) {
1178 os
<< "\n\\end_deeper";
1184 // First write the layout
1185 os
<< "\n\\begin_layout " << to_utf8(d
->layout_
->name()) << '\n';
1187 d
->params_
.write(os
);
1189 Font
font1(inherit_font
, bparams
.language
);
1191 Change running_change
= Change(Change::UNCHANGED
);
1194 for (pos_type i
= 0; i
<= size(); ++i
) {
1196 Change
const change
= lookupChange(i
);
1197 Changes::lyxMarkChange(os
, bparams
, column
, running_change
, change
);
1198 running_change
= change
;
1203 // Write font changes (ignore spelling markers)
1204 Font font2
= getFontSettings(bparams
, i
);
1205 font2
.setMisspelled(false);
1206 if (font2
!= font1
) {
1207 font2
.lyxWriteChanges(font1
, os
);
1212 char_type
const c
= d
->text_
[i
];
1215 if (Inset
const * inset
= getInset(i
)) {
1216 if (inset
->directWrite()) {
1217 // international char, let it write
1218 // code directly so it's shorter in
1224 os
<< "\\begin_inset ";
1226 os
<< "\n\\end_inset\n\n";
1232 os
<< "\n\\backslash\n";
1236 if (i
+ 1 < size() && d
->text_
[i
+ 1] == ' ') {
1243 if ((column
> 70 && c
== ' ')
1248 // this check is to amend a bug. LyX sometimes
1249 // inserts '\0' this could cause problems.
1251 os
<< to_utf8(docstring(1, c
));
1253 LYXERR0("NUL char in structure.");
1259 os
<< "\n\\end_layout\n";
1263 void Paragraph::validate(LaTeXFeatures
& features
) const
1265 d
->validate(features
, *d
->layout_
);
1269 void Paragraph::insert(pos_type start
, docstring
const & str
,
1270 Font
const & font
, Change
const & change
)
1272 for (size_t i
= 0, n
= str
.size(); i
!= n
; ++i
)
1273 insertChar(start
+ i
, str
[i
], font
, change
);
1277 void Paragraph::appendChar(char_type c
, Font
const & font
,
1278 Change
const & change
)
1281 d
->changes_
.insert(change
, d
->text_
.size());
1282 // when appending characters, no need to update tables
1283 d
->text_
.push_back(c
);
1284 setFont(d
->text_
.size() - 1, font
);
1288 void Paragraph::appendString(docstring
const & s
, Font
const & font
,
1289 Change
const & change
)
1291 pos_type end
= s
.size();
1292 size_t oldsize
= d
->text_
.size();
1293 size_t newsize
= oldsize
+ end
;
1294 size_t capacity
= d
->text_
.capacity();
1295 if (newsize
>= capacity
)
1296 d
->text_
.reserve(max(capacity
+ 100, newsize
));
1298 // when appending characters, no need to update tables
1301 // FIXME: Optimize this!
1302 for (size_t i
= oldsize
; i
!= newsize
; ++i
) {
1304 d
->changes_
.insert(change
, i
);
1306 d
->fontlist_
.set(oldsize
, font
);
1307 d
->fontlist_
.set(newsize
- 1, font
);
1311 void Paragraph::insertChar(pos_type pos
, char_type c
,
1314 d
->insertChar(pos
, c
, Change(trackChanges
?
1315 Change::INSERTED
: Change::UNCHANGED
));
1319 void Paragraph::insertChar(pos_type pos
, char_type c
,
1320 Font
const & font
, bool trackChanges
)
1322 d
->insertChar(pos
, c
, Change(trackChanges
?
1323 Change::INSERTED
: Change::UNCHANGED
));
1328 void Paragraph::insertChar(pos_type pos
, char_type c
,
1329 Font
const & font
, Change
const & change
)
1331 d
->insertChar(pos
, c
, change
);
1336 bool Paragraph::insertInset(pos_type pos
, Inset
* inset
,
1337 Font
const & font
, Change
const & change
)
1339 bool const success
= insertInset(pos
, inset
, change
);
1340 // Set the font/language of the inset...
1346 void Paragraph::resetFonts(Font
const & font
)
1348 d
->fontlist_
.clear();
1349 d
->fontlist_
.set(0, font
);
1350 d
->fontlist_
.set(d
->text_
.size() - 1, font
);
1353 // Gets uninstantiated font setting at position.
1354 Font
const & Paragraph::getFontSettings(BufferParams
const & bparams
,
1358 LYXERR0("pos: " << pos
<< " size: " << size());
1359 LASSERT(pos
<= size(), /**/);
1362 FontList::const_iterator cit
= d
->fontlist_
.fontIterator(pos
);
1363 if (cit
!= d
->fontlist_
.end())
1366 if (pos
== size() && !empty())
1367 return getFontSettings(bparams
, pos
- 1);
1369 // Optimisation: avoid a full font instantiation if there is no
1370 // language change from previous call.
1371 static Font previous_font
;
1372 static Language
const * previous_lang
= 0;
1373 Language
const * lang
= getParLanguage(bparams
);
1374 if (lang
!= previous_lang
) {
1375 previous_lang
= lang
;
1376 previous_font
= Font(inherit_font
, lang
);
1378 return previous_font
;
1382 FontSpan
Paragraph::fontSpan(pos_type pos
) const
1384 LASSERT(pos
<= size(), /**/);
1387 FontList::const_iterator cit
= d
->fontlist_
.begin();
1388 FontList::const_iterator end
= d
->fontlist_
.end();
1389 for (; cit
!= end
; ++cit
) {
1390 if (cit
->pos() >= pos
) {
1391 if (pos
>= beginOfBody())
1392 return FontSpan(max(start
, beginOfBody()),
1395 return FontSpan(start
,
1396 min(beginOfBody() - 1,
1399 start
= cit
->pos() + 1;
1402 // This should not happen, but if so, we take no chances.
1403 // LYXERR0("Paragraph::getEndPosOfFontSpan: This should not happen!");
1404 return FontSpan(pos
, pos
);
1408 // Gets uninstantiated font setting at position 0
1409 Font
const & Paragraph::getFirstFontSettings(BufferParams
const & bparams
) const
1411 if (!empty() && !d
->fontlist_
.empty())
1412 return d
->fontlist_
.begin()->font();
1414 // Optimisation: avoid a full font instantiation if there is no
1415 // language change from previous call.
1416 static Font previous_font
;
1417 static Language
const * previous_lang
= 0;
1418 if (bparams
.language
!= previous_lang
) {
1419 previous_lang
= bparams
.language
;
1420 previous_font
= Font(inherit_font
, bparams
.language
);
1423 return previous_font
;
1427 // Gets the fully instantiated font at a given position in a paragraph
1428 // This is basically the same function as Text::GetFont() in text2.cpp.
1429 // The difference is that this one is used for generating the LaTeX file,
1430 // and thus cosmetic "improvements" are disallowed: This has to deliver
1431 // the true picture of the buffer. (Asger)
1432 Font
const Paragraph::getFont(BufferParams
const & bparams
, pos_type pos
,
1433 Font
const & outerfont
) const
1435 LASSERT(pos
>= 0, /**/);
1437 Font font
= getFontSettings(bparams
, pos
);
1439 pos_type
const body_pos
= beginOfBody();
1440 FontInfo
& fi
= font
.fontInfo();
1442 fi
.realize(d
->layout_
->labelfont
);
1444 fi
.realize(d
->layout_
->font
);
1446 fi
.realize(outerfont
.fontInfo());
1447 fi
.realize(bparams
.getFont().fontInfo());
1453 Font
const Paragraph::getLabelFont
1454 (BufferParams
const & bparams
, Font
const & outerfont
) const
1456 FontInfo tmpfont
= d
->layout_
->labelfont
;
1457 tmpfont
.realize(outerfont
.fontInfo());
1458 tmpfont
.realize(bparams
.getFont().fontInfo());
1459 return Font(tmpfont
, getParLanguage(bparams
));
1463 Font
const Paragraph::getLayoutFont
1464 (BufferParams
const & bparams
, Font
const & outerfont
) const
1466 FontInfo tmpfont
= d
->layout_
->font
;
1467 tmpfont
.realize(outerfont
.fontInfo());
1468 tmpfont
.realize(bparams
.getFont().fontInfo());
1469 return Font(tmpfont
, getParLanguage(bparams
));
1473 /// Returns the height of the highest font in range
1474 FontSize
Paragraph::highestFontInRange
1475 (pos_type startpos
, pos_type endpos
, FontSize def_size
) const
1477 return d
->fontlist_
.highestInRange(startpos
, endpos
, def_size
);
1481 char_type
Paragraph::getUChar(BufferParams
const & bparams
, pos_type pos
) const
1483 char_type c
= d
->text_
[pos
];
1484 if (!lyxrc
.rtl_support
)
1514 if (uc
!= c
&& getFontSettings(bparams
, pos
).isRightToLeft())
1520 void Paragraph::setFont(pos_type pos
, Font
const & font
)
1522 LASSERT(pos
<= size(), /**/);
1524 // First, reduce font against layout/label font
1525 // Update: The setCharFont() routine in text2.cpp already
1526 // reduces font, so we don't need to do that here. (Asger)
1528 d
->fontlist_
.set(pos
, font
);
1532 void Paragraph::makeSameLayout(Paragraph
const & par
)
1534 d
->layout_
= par
.d
->layout_
;
1535 d
->params_
= par
.d
->params_
;
1539 bool Paragraph::stripLeadingSpaces(bool trackChanges
)
1541 if (isFreeSpacing())
1547 while (pos
< size() && (isNewline(pos
) || isLineSeparator(pos
))) {
1548 if (eraseChar(pos
, trackChanges
))
1554 return count
> 0 || pos
> 0;
1558 bool Paragraph::hasSameLayout(Paragraph
const & par
) const
1560 return par
.d
->layout_
== d
->layout_
1561 && d
->params_
.sameLayout(par
.d
->params_
);
1565 depth_type
Paragraph::getDepth() const
1567 return d
->params_
.depth();
1571 depth_type
Paragraph::getMaxDepthAfter() const
1573 if (d
->layout_
->isEnvironment())
1574 return d
->params_
.depth() + 1;
1576 return d
->params_
.depth();
1580 char Paragraph::getAlign() const
1582 if (d
->params_
.align() == LYX_ALIGN_LAYOUT
)
1583 return d
->layout_
->align
;
1585 return d
->params_
.align();
1589 docstring
const & Paragraph::labelString() const
1591 return d
->params_
.labelString();
1595 // the next two functions are for the manual labels
1596 docstring
const Paragraph::getLabelWidthString() const
1598 if (d
->layout_
->margintype
== MARGIN_MANUAL
1599 || d
->layout_
->latextype
== LATEX_BIB_ENVIRONMENT
)
1600 return d
->params_
.labelWidthString();
1602 return _("Senseless with this layout!");
1606 void Paragraph::setLabelWidthString(docstring
const & s
)
1608 d
->params_
.labelWidthString(s
);
1612 docstring
Paragraph::expandLabel(Layout
const & layout
,
1613 BufferParams
const & bparams
, bool process_appendix
) const
1615 DocumentClass
const & tclass
= bparams
.documentClass();
1616 string
const & lang
= getParLanguage(bparams
)->code();
1617 bool const in_appendix
= process_appendix
&& d
->params_
.appendix();
1618 docstring fmt
= translateIfPossible(layout
.labelstring(in_appendix
), lang
);
1620 if (fmt
.empty() && layout
.labeltype
== LABEL_COUNTER
1621 && !layout
.counter
.empty())
1622 return tclass
.counters().theCounter(layout
.counter
, lang
);
1624 // handle 'inherited level parts' in 'fmt',
1625 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1626 size_t const i
= fmt
.find('@', 0);
1627 if (i
!= docstring::npos
) {
1628 size_t const j
= fmt
.find('@', i
+ 1);
1629 if (j
!= docstring::npos
) {
1630 docstring
parent(fmt
, i
+ 1, j
- i
- 1);
1631 docstring label
= from_ascii("??");
1632 if (tclass
.hasLayout(parent
))
1633 docstring label
= expandLabel(tclass
[parent
], bparams
,
1635 fmt
= docstring(fmt
, 0, i
) + label
1636 + docstring(fmt
, j
+ 1, docstring::npos
);
1640 return tclass
.counters().counterLabel(fmt
, lang
);
1644 void Paragraph::applyLayout(Layout
const & new_layout
)
1646 d
->layout_
= &new_layout
;
1647 LyXAlignment
const oldAlign
= d
->params_
.align();
1649 if (!(oldAlign
& d
->layout_
->alignpossible
)) {
1650 frontend::Alert::warning(_("Alignment not permitted"),
1651 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1652 d
->params_
.align(LYX_ALIGN_LAYOUT
);
1657 pos_type
Paragraph::beginOfBody() const
1659 return d
->begin_of_body_
;
1663 void Paragraph::setBeginOfBody()
1665 if (d
->layout_
->labeltype
!= LABEL_MANUAL
) {
1666 d
->begin_of_body_
= 0;
1670 // Unroll the first two cycles of the loop
1671 // and remember the previous character to
1672 // remove unnecessary getChar() calls
1674 pos_type end
= size();
1675 if (i
< end
&& !isNewline(i
)) {
1677 char_type previous_char
= 0;
1680 previous_char
= d
->text_
[i
];
1681 if (!isNewline(i
)) {
1683 while (i
< end
&& previous_char
!= ' ') {
1688 previous_char
= temp
;
1694 d
->begin_of_body_
= i
;
1698 bool Paragraph::forcePlainLayout() const
1700 return inInset().forcePlainLayout();
1704 bool Paragraph::allowParagraphCustomization() const
1706 return inInset().allowParagraphCustomization();
1710 bool Paragraph::usePlainLayout() const
1712 return inInset().usePlainLayout();
1718 // paragraphs inside floats need different alignment tags to avoid
1721 bool noTrivlistCentering(InsetCode code
)
1723 return code
== FLOAT_CODE
1724 || code
== WRAP_CODE
1725 || code
== CELL_CODE
;
1729 string
correction(string
const & orig
)
1731 if (orig
== "flushleft")
1732 return "raggedright";
1733 if (orig
== "flushright")
1734 return "raggedleft";
1735 if (orig
== "center")
1741 string
const corrected_env(string
const & suffix
, string
const & env
,
1742 InsetCode code
, bool const lastpar
)
1744 string output
= suffix
+ "{";
1745 if (noTrivlistCentering(code
)) {
1747 // the last paragraph in non-trivlist-aligned
1748 // context is special (to avoid unwanted whitespace)
1749 if (suffix
== "\\begin")
1750 return "\\" + correction(env
) + "{}";
1753 output
+= correction(env
);
1757 if (suffix
== "\\begin")
1763 void adjust_row_column(string
const & str
, TexRow
& texrow
, int & column
)
1765 if (!contains(str
, "\n"))
1766 column
+= str
.size();
1770 column
= rsplit(str
, tmp
, '\n').size();
1777 int Paragraph::Private::startTeXParParams(BufferParams
const & bparams
,
1778 odocstream
& os
, TexRow
& texrow
,
1779 OutputParams
const & runparams
) const
1783 if (params_
.noindent()) {
1784 os
<< "\\noindent ";
1788 LyXAlignment
const curAlign
= params_
.align();
1790 if (curAlign
== layout_
->align
)
1794 case LYX_ALIGN_NONE
:
1795 case LYX_ALIGN_BLOCK
:
1796 case LYX_ALIGN_LAYOUT
:
1797 case LYX_ALIGN_SPECIAL
:
1799 case LYX_ALIGN_LEFT
:
1800 case LYX_ALIGN_RIGHT
:
1801 case LYX_ALIGN_CENTER
:
1802 if (runparams
.moving_arg
) {
1809 string
const begin_tag
= "\\begin";
1810 InsetCode code
= owner_
->ownerCode();
1811 bool const lastpar
= runparams
.isLastPar
;
1814 case LYX_ALIGN_NONE
:
1815 case LYX_ALIGN_BLOCK
:
1816 case LYX_ALIGN_LAYOUT
:
1817 case LYX_ALIGN_SPECIAL
:
1819 case LYX_ALIGN_LEFT
: {
1821 if (owner_
->getParLanguage(bparams
)->babel() != "hebrew")
1822 output
= corrected_env(begin_tag
, "flushleft", code
, lastpar
);
1824 output
= corrected_env(begin_tag
, "flushright", code
, lastpar
);
1825 os
<< from_ascii(output
);
1826 adjust_row_column(output
, texrow
, column
);
1828 } case LYX_ALIGN_RIGHT
: {
1830 if (owner_
->getParLanguage(bparams
)->babel() != "hebrew")
1831 output
= corrected_env(begin_tag
, "flushright", code
, lastpar
);
1833 output
= corrected_env(begin_tag
, "flushleft", code
, lastpar
);
1834 os
<< from_ascii(output
);
1835 adjust_row_column(output
, texrow
, column
);
1837 } case LYX_ALIGN_CENTER
: {
1839 output
= corrected_env(begin_tag
, "center", code
, lastpar
);
1840 os
<< from_ascii(output
);
1841 adjust_row_column(output
, texrow
, column
);
1850 int Paragraph::Private::endTeXParParams(BufferParams
const & bparams
,
1851 odocstream
& os
, TexRow
& texrow
,
1852 OutputParams
const & runparams
) const
1856 LyXAlignment
const curAlign
= params_
.align();
1858 if (curAlign
== layout_
->align
)
1862 case LYX_ALIGN_NONE
:
1863 case LYX_ALIGN_BLOCK
:
1864 case LYX_ALIGN_LAYOUT
:
1865 case LYX_ALIGN_SPECIAL
:
1867 case LYX_ALIGN_LEFT
:
1868 case LYX_ALIGN_RIGHT
:
1869 case LYX_ALIGN_CENTER
:
1870 if (runparams
.moving_arg
) {
1877 string
const end_tag
= "\n\\par\\end";
1878 InsetCode code
= owner_
->ownerCode();
1879 bool const lastpar
= runparams
.isLastPar
;
1882 case LYX_ALIGN_NONE
:
1883 case LYX_ALIGN_BLOCK
:
1884 case LYX_ALIGN_LAYOUT
:
1885 case LYX_ALIGN_SPECIAL
:
1887 case LYX_ALIGN_LEFT
: {
1889 if (owner_
->getParLanguage(bparams
)->babel() != "hebrew")
1890 output
= corrected_env(end_tag
, "flushleft", code
, lastpar
);
1892 output
= corrected_env(end_tag
, "flushright", code
, lastpar
);
1893 os
<< from_ascii(output
);
1894 adjust_row_column(output
, texrow
, column
);
1896 } case LYX_ALIGN_RIGHT
: {
1898 if (owner_
->getParLanguage(bparams
)->babel() != "hebrew")
1899 output
= corrected_env(end_tag
, "flushright", code
, lastpar
);
1901 output
= corrected_env(end_tag
, "flushleft", code
, lastpar
);
1902 os
<< from_ascii(output
);
1903 adjust_row_column(output
, texrow
, column
);
1905 } case LYX_ALIGN_CENTER
: {
1907 output
= corrected_env(end_tag
, "center", code
, lastpar
);
1908 os
<< from_ascii(output
);
1909 adjust_row_column(output
, texrow
, column
);
1918 // This one spits out the text of the paragraph
1919 bool Paragraph::latex(BufferParams
const & bparams
,
1920 Font
const & outerfont
,
1921 odocstream
& os
, TexRow
& texrow
,
1922 OutputParams
const & runparams
,
1923 int start_pos
, int end_pos
) const
1925 LYXERR(Debug::LATEX
, "Paragraph::latex... " << this);
1927 bool return_value
= false;
1929 bool const allowcust
= allowParagraphCustomization();
1931 // FIXME This check should not be needed. Perhaps issue an
1932 // error if it triggers.
1933 Layout
const & style
= forcePlainLayout() ?
1934 bparams
.documentClass().plainLayout() : *d
->layout_
;
1936 // Current base font for all inherited font changes, without any
1937 // change caused by an individual character, except for the language:
1938 // It is set to the language of the first character.
1939 // As long as we are in the label, this font is the base font of the
1940 // label. Before the first body character it is set to the base font
1944 // Maybe we have to create a optional argument.
1945 pos_type body_pos
= beginOfBody();
1946 unsigned int column
= 0;
1951 basefont
= getLabelFont(bparams
, outerfont
);
1953 basefont
= getLayoutFont(bparams
, outerfont
);
1956 // Which font is currently active?
1957 Font
running_font(basefont
);
1958 // Do we have an open font change?
1959 bool open_font
= false;
1961 Change runningChange
= Change(Change::UNCHANGED
);
1963 Encoding
const * const prev_encoding
= runparams
.encoding
;
1965 texrow
.start(id(), 0);
1967 // if the paragraph is empty, the loop will not be entered at all
1969 if (style
.isCommand()) {
1974 column
+= d
->startTeXParParams(bparams
, os
, texrow
,
1978 for (pos_type i
= 0; i
< size(); ++i
) {
1979 // First char in paragraph or after label?
1980 if (i
== body_pos
) {
1983 column
+= running_font
.latexWriteEndChanges(
1984 os
, bparams
, runparams
,
1985 basefont
, basefont
);
1988 basefont
= getLayoutFont(bparams
, outerfont
);
1989 running_font
= basefont
;
1991 column
+= Changes::latexMarkChange(os
, bparams
,
1992 runningChange
, Change(Change::UNCHANGED
));
1993 runningChange
= Change(Change::UNCHANGED
);
1998 if (style
.isCommand()) {
2004 column
+= d
->startTeXParParams(bparams
, os
,
2009 Change
const & change
= runparams
.inDeletedInset
? runparams
.changeOfDeletedInset
2012 if (bparams
.outputChanges
&& runningChange
!= change
) {
2014 column
+= running_font
.latexWriteEndChanges(
2015 os
, bparams
, runparams
, basefont
, basefont
);
2018 basefont
= getLayoutFont(bparams
, outerfont
);
2019 running_font
= basefont
;
2021 column
+= Changes::latexMarkChange(os
, bparams
, runningChange
, change
);
2022 runningChange
= change
;
2025 // do not output text which is marked deleted
2026 // if change tracking output is disabled
2027 if (!bparams
.outputChanges
&& change
.deleted()) {
2033 // Fully instantiated font
2034 Font
const font
= getFont(bparams
, i
, outerfont
);
2036 Font
const last_font
= running_font
;
2038 // Do we need to close the previous font?
2040 (font
!= running_font
||
2041 font
.language() != running_font
.language()))
2043 column
+= running_font
.latexWriteEndChanges(
2044 os
, bparams
, runparams
, basefont
,
2045 (i
== body_pos
-1) ? basefont
: font
);
2046 running_font
= basefont
;
2050 // close babel's font environment before opening CJK.
2051 if (!running_font
.language()->babel().empty() &&
2052 font
.language()->encoding()->package() == Encoding::CJK
) {
2053 string end_tag
= subst(lyxrc
.language_command_end
,
2055 running_font
.language()->babel());
2056 os
<< from_ascii(end_tag
);
2057 column
+= end_tag
.length();
2060 // Switch file encoding if necessary (and allowed)
2061 if (!runparams
.verbatim
&&
2062 runparams
.encoding
->package() != Encoding::none
&&
2063 font
.language()->encoding()->package() != Encoding::none
) {
2064 pair
<bool, int> const enc_switch
= switchEncoding(os
, bparams
,
2065 runparams
, *(font
.language()->encoding()));
2066 if (enc_switch
.first
) {
2067 column
+= enc_switch
.second
;
2068 runparams
.encoding
= font
.language()->encoding();
2072 char_type
const c
= d
->text_
[i
];
2074 // Do we need to change font?
2075 if ((font
!= running_font
||
2076 font
.language() != running_font
.language()) &&
2079 odocstringstream ods
;
2080 column
+= font
.latexWriteStartChanges(ods
, bparams
,
2081 runparams
, basefont
,
2083 running_font
= font
;
2085 docstring fontchange
= ods
.str();
2086 // check whether the fontchange ends with a \\textcolor
2087 // modifier and the text starts with a space (bug 4473)
2088 docstring
const last_modifier
= rsplit(fontchange
, '\\');
2089 if (prefixIs(last_modifier
, from_ascii("textcolor")) && c
== ' ')
2090 os
<< fontchange
<< from_ascii("{}");
2091 // check if the fontchange ends with a trailing blank
2092 // (like "\small " (see bug 3382)
2093 else if (suffixIs(fontchange
, ' ') && c
== ' ')
2094 os
<< fontchange
.substr(0, fontchange
.size() - 1)
2095 << from_ascii("{}");
2100 // FIXME: think about end_pos implementation...
2101 if (c
== ' ' && i
>= start_pos
&& (end_pos
== -1 || i
< end_pos
)) {
2102 // FIXME: integrate this case in latexSpecialChar
2103 // Do not print the separation of the optional argument
2104 // if style.pass_thru is false. This works because
2105 // latexSpecialChar ignores spaces if
2106 // style.pass_thru is false.
2107 if (i
!= body_pos
- 1) {
2108 if (d
->simpleTeXBlanks(
2109 runparams
, os
, texrow
,
2110 i
, column
, font
, style
)) {
2111 // A surrogate pair was output. We
2112 // must not call latexSpecialChar
2113 // in this iteration, since it would output
2114 // the combining character again.
2121 OutputParams rp
= runparams
;
2122 rp
.free_spacing
= style
.free_spacing
;
2123 rp
.local_font
= &font
;
2124 rp
.intitle
= style
.intitle
;
2126 // Two major modes: LaTeX or plain
2127 // Handle here those cases common to both modes
2128 // and then split to handle the two modes separately.
2129 if (c
== META_INSET
) {
2130 if (i
>= start_pos
&& (end_pos
== -1 || i
< end_pos
)) {
2131 d
->latexInset(bparams
, os
,
2132 texrow
, rp
, running_font
,
2133 basefont
, outerfont
, open_font
,
2134 runningChange
, style
, i
, column
);
2137 if (i
>= start_pos
&& (end_pos
== -1 || i
< end_pos
)) {
2139 d
->latexSpecialChar(os
, rp
, running_font
, runningChange
,
2141 } catch (EncodingException
& e
) {
2142 if (runparams
.dryrun
) {
2143 os
<< "<" << _("LyX Warning: ")
2144 << _("uncodable character") << " '";
2148 // add location information and throw again.
2157 // Set the encoding to that returned from latexSpecialChar (see
2158 // comment for encoding member in OutputParams.h)
2159 runparams
.encoding
= rp
.encoding
;
2162 // If we have an open font definition, we have to close it
2164 #ifdef FIXED_LANGUAGE_END_DETECTION
2167 .latexWriteEndChanges(os
, bparams
, runparams
,
2169 next_
->getFont(bparams
, 0, outerfont
));
2171 running_font
.latexWriteEndChanges(os
, bparams
,
2172 runparams
, basefont
, basefont
);
2175 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2176 //FIXME: there as we start another \selectlanguage with the next paragraph if
2177 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2178 running_font
.latexWriteEndChanges(os
, bparams
, runparams
,
2179 basefont
, basefont
);
2183 column
+= Changes::latexMarkChange(os
, bparams
, runningChange
, Change(Change::UNCHANGED
));
2185 // Needed if there is an optional argument but no contents.
2186 if (body_pos
> 0 && body_pos
== size()) {
2188 return_value
= false;
2191 if (allowcust
&& d
->endTeXParParams(bparams
, os
, texrow
, runparams
))
2192 runparams
.encoding
= prev_encoding
;
2194 LYXERR(Debug::LATEX
, "Paragraph::latex... done " << this);
2195 return return_value
;
2199 bool Paragraph::emptyTag() const
2201 for (pos_type i
= 0; i
< size(); ++i
) {
2202 if (Inset
const * inset
= getInset(i
)) {
2203 InsetCode lyx_code
= inset
->lyxCode();
2204 // FIXME testing like that is wrong. What is
2206 if (lyx_code
!= TOC_CODE
&&
2207 lyx_code
!= INCLUDE_CODE
&&
2208 lyx_code
!= GRAPHICS_CODE
&&
2209 lyx_code
!= ERT_CODE
&&
2210 lyx_code
!= LISTINGS_CODE
&&
2211 lyx_code
!= FLOAT_CODE
&&
2212 lyx_code
!= TABULAR_CODE
) {
2216 char_type c
= d
->text_
[i
];
2217 if (c
!= ' ' && c
!= '\t')
2225 string
Paragraph::getID(Buffer
const & buf
, OutputParams
const & runparams
)
2228 for (pos_type i
= 0; i
< size(); ++i
) {
2229 if (Inset
const * inset
= getInset(i
)) {
2230 InsetCode lyx_code
= inset
->lyxCode();
2231 if (lyx_code
== LABEL_CODE
) {
2232 InsetLabel
const * const il
= static_cast<InsetLabel
const *>(inset
);
2233 docstring
const & id
= il
->getParam("name");
2234 return "id='" + to_utf8(sgml::cleanID(buf
, runparams
, id
)) + "'";
2242 pos_type
Paragraph::firstWordDocBook(odocstream
& os
, OutputParams
const & runparams
)
2246 for (i
= 0; i
< size(); ++i
) {
2247 if (Inset
const * inset
= getInset(i
)) {
2248 inset
->docbook(os
, runparams
);
2250 char_type c
= d
->text_
[i
];
2253 os
<< sgml::escapeChar(c
);
2260 pos_type
Paragraph::firstWordLyXHTML(odocstream
& os
, OutputParams
const & runparams
)
2264 for (i
= 0; i
< size(); ++i
) {
2265 if (Inset
const * inset
= getInset(i
)) {
2266 inset
->xhtml(os
, runparams
);
2268 char_type c
= d
->text_
[i
];
2271 os
<< html::escapeChar(c
);
2278 bool Paragraph::Private::onlyText(Buffer
const & buf
, Font
const & outerfont
, pos_type initial
) const
2281 pos_type size
= text_
.size();
2282 for (pos_type i
= initial
; i
< size
; ++i
) {
2283 Font font
= owner_
->getFont(buf
.params(), i
, outerfont
);
2284 if (text_
[i
] == META_INSET
)
2286 if (i
!= initial
&& font
!= font_old
)
2295 void Paragraph::simpleDocBookOnePar(Buffer
const & buf
,
2297 OutputParams
const & runparams
,
2298 Font
const & outerfont
,
2299 pos_type initial
) const
2301 bool emph_flag
= false;
2303 Layout
const & style
= *d
->layout_
;
2305 style
.labeltype
== LABEL_MANUAL
? style
.labelfont
: style
.font
;
2307 if (style
.pass_thru
&& !d
->onlyText(buf
, outerfont
, initial
))
2310 // parsing main loop
2311 for (pos_type i
= initial
; i
< size(); ++i
) {
2312 Font font
= getFont(buf
.params(), i
, outerfont
);
2314 // handle <emphasis> tag
2315 if (font_old
.emph() != font
.fontInfo().emph()) {
2316 if (font
.fontInfo().emph() == FONT_ON
) {
2319 } else if (i
!= initial
) {
2320 os
<< "</emphasis>";
2325 if (Inset
const * inset
= getInset(i
)) {
2326 inset
->docbook(os
, runparams
);
2328 char_type c
= d
->text_
[i
];
2330 if (style
.pass_thru
)
2333 os
<< sgml::escapeChar(c
);
2335 font_old
= font
.fontInfo();
2339 os
<< "</emphasis>";
2342 if (style
.free_spacing
)
2344 if (style
.pass_thru
&& !d
->onlyText(buf
, outerfont
, initial
))
2349 docstring
Paragraph::simpleLyXHTMLOnePar(Buffer
const & buf
,
2351 OutputParams
const & runparams
,
2352 Font
const & outerfont
,
2353 pos_type initial
) const
2357 // FIXME We really need to manage the tag nesting here.
2358 // Probably in the same sort of way as in output_xhtml.
2359 bool emph_flag
= false;
2360 bool bold_flag
= false;
2361 std::string closing_tag
;
2363 Layout
const & style
= *d
->layout_
;
2365 style
.labeltype
== LABEL_MANUAL
? style
.labelfont
: style
.font
;
2367 //if (style.pass_thru && !d->onlyText(buf, outerfont, initial))
2370 // parsing main loop
2371 for (pos_type i
= initial
; i
< size(); ++i
) {
2372 Font font
= getFont(buf
.params(), i
, outerfont
);
2375 if (font_old
.emph() != font
.fontInfo().emph()) {
2376 if (font
.fontInfo().emph() == FONT_ON
) {
2379 } else if (emph_flag
&& i
!= initial
) {
2385 if (font_old
.series() != font
.fontInfo().series()) {
2386 if (font
.fontInfo().series() == BOLD_SERIES
) {
2389 } else if (bold_flag
&& i
!= initial
) {
2394 // FIXME Other such tags?
2396 if (Inset
const * inset
= getInset(i
)) {
2397 retval
+= inset
->xhtml(os
, runparams
);
2399 char_type c
= d
->text_
[i
];
2401 if (style
.pass_thru
)
2403 else if (c
== '-') {
2406 if (j
< size() && d
->text_
[j
] == '-') {
2408 if (j
< size() && d
->text_
[j
] == '-') {
2409 str
+= from_ascii("—");
2412 str
+= from_ascii("–");
2420 os
<< html::escapeChar(c
);
2422 font_old
= font
.fontInfo();
2425 // FIXME This could be out of order. See above.
2435 bool Paragraph::isHfill(pos_type pos
) const
2437 Inset
const * inset
= getInset(pos
);
2438 return inset
&& (inset
->lyxCode() == SPACE_CODE
&&
2439 inset
->isStretchableSpace());
2443 bool Paragraph::isNewline(pos_type pos
) const
2445 Inset
const * inset
= getInset(pos
);
2446 return inset
&& inset
->lyxCode() == NEWLINE_CODE
;
2450 bool Paragraph::isLineSeparator(pos_type pos
) const
2452 char_type
const c
= d
->text_
[pos
];
2453 if (isLineSeparatorChar(c
))
2455 Inset
const * inset
= getInset(pos
);
2456 return inset
&& inset
->isLineSeparator();
2460 bool Paragraph::isWordSeparator(pos_type pos
) const
2462 if (Inset
const * inset
= getInset(pos
))
2463 return !inset
->isLetter();
2464 char_type
const c
= d
->text_
[pos
];
2465 // We want to pass the ' and escape chars to the spellchecker
2466 static docstring
const quote
= from_utf8(lyxrc
.spellchecker_esc_chars
+ '\'');
2467 return (!isLetterChar(c
) && !isDigit(c
) && !contains(quote
, c
))
2472 bool Paragraph::isChar(pos_type pos
) const
2474 if (Inset
const * inset
= getInset(pos
))
2475 return inset
->isChar();
2476 char_type
const c
= d
->text_
[pos
];
2477 return !isLetterChar(c
) && !isDigit(c
) && !lyx::isSpace(c
);
2481 bool Paragraph::isSpace(pos_type pos
) const
2483 if (Inset
const * inset
= getInset(pos
))
2484 return inset
->isSpace();
2485 char_type
const c
= d
->text_
[pos
];
2486 return lyx::isSpace(c
);
2491 Paragraph::getParLanguage(BufferParams
const & bparams
) const
2494 return getFirstFontSettings(bparams
).language();
2495 // FIXME: we should check the prev par as well (Lgb)
2496 return bparams
.language
;
2500 bool Paragraph::isRTL(BufferParams
const & bparams
) const
2502 return lyxrc
.rtl_support
2503 && getParLanguage(bparams
)->rightToLeft()
2504 && !inInset().getLayout().forceLTR();
2508 void Paragraph::changeLanguage(BufferParams
const & bparams
,
2509 Language
const * from
, Language
const * to
)
2511 // change language including dummy font change at the end
2512 for (pos_type i
= 0; i
<= size(); ++i
) {
2513 Font font
= getFontSettings(bparams
, i
);
2514 if (font
.language() == from
) {
2515 font
.setLanguage(to
);
2522 bool Paragraph::isMultiLingual(BufferParams
const & bparams
) const
2524 Language
const * doc_language
= bparams
.language
;
2525 FontList::const_iterator cit
= d
->fontlist_
.begin();
2526 FontList::const_iterator end
= d
->fontlist_
.end();
2528 for (; cit
!= end
; ++cit
)
2529 if (cit
->font().language() != ignore_language
&&
2530 cit
->font().language() != latex_language
&&
2531 cit
->font().language() != doc_language
)
2537 docstring
Paragraph::asString(int options
) const
2539 return asString(0, size(), options
);
2543 docstring
Paragraph::asString(pos_type beg
, pos_type end
, int options
) const
2545 odocstringstream os
;
2548 && options
& AS_STR_LABEL
2549 && !d
->params_
.labelString().empty())
2550 os
<< d
->params_
.labelString() << ' ';
2552 for (pos_type i
= beg
; i
< end
; ++i
) {
2553 char_type
const c
= d
->text_
[i
];
2554 if (isPrintable(c
) || c
== '\t'
2555 || (c
== '\n' && (options
& AS_STR_NEWLINES
)))
2557 else if (c
== META_INSET
&& (options
& AS_STR_INSETS
)) {
2558 getInset(i
)->tocString(os
);
2559 if (getInset(i
)->asInsetMath())
2568 docstring
Paragraph::stringify(pos_type beg
, pos_type end
, int options
, OutputParams
& runparams
) const
2570 odocstringstream os
;
2573 && options
& AS_STR_LABEL
2574 && !d
->params_
.labelString().empty())
2575 os
<< d
->params_
.labelString() << ' ';
2577 for (pos_type i
= beg
; i
< end
; ++i
) {
2578 char_type
const c
= d
->text_
[i
];
2579 if (isPrintable(c
) || c
== '\t'
2580 || (c
== '\n' && (options
& AS_STR_NEWLINES
)))
2582 else if (c
== META_INSET
&& (options
& AS_STR_INSETS
)) {
2583 getInset(i
)->plaintext(os
, runparams
);
2591 void Paragraph::setInsetOwner(Inset
const * inset
)
2593 d
->inset_owner_
= inset
;
2597 int Paragraph::id() const
2603 Layout
const & Paragraph::layout() const
2609 void Paragraph::setLayout(Layout
const & layout
)
2611 d
->layout_
= &layout
;
2615 void Paragraph::setDefaultLayout(DocumentClass
const & tc
)
2617 setLayout(tc
.defaultLayout());
2621 void Paragraph::setPlainLayout(DocumentClass
const & tc
)
2623 setLayout(tc
.plainLayout());
2627 void Paragraph::setPlainOrDefaultLayout(DocumentClass
const & tclass
)
2629 if (usePlainLayout())
2630 setPlainLayout(tclass
);
2632 setDefaultLayout(tclass
);
2636 Inset
const & Paragraph::inInset() const
2638 LASSERT(d
->inset_owner_
, throw ExceptionMessage(BufferException
,
2639 _("Memory problem"), _("Paragraph not properly initialized")));
2640 return *d
->inset_owner_
;
2644 InsetCode
Paragraph::ownerCode() const
2646 return d
->inset_owner_
? d
->inset_owner_
->lyxCode() : NO_CODE
;
2650 ParagraphParameters
& Paragraph::params()
2656 ParagraphParameters
const & Paragraph::params() const
2662 bool Paragraph::isFreeSpacing() const
2664 if (d
->layout_
->free_spacing
)
2666 return d
->inset_owner_
&& d
->inset_owner_
->isFreeSpacing();
2670 bool Paragraph::allowEmpty() const
2672 if (d
->layout_
->keepempty
)
2674 return d
->inset_owner_
&& d
->inset_owner_
->allowEmpty();
2678 char_type
Paragraph::transformChar(char_type c
, pos_type pos
) const
2680 if (!Encodings::isArabicChar(c
))
2683 char_type prev_char
= ' ';
2684 char_type next_char
= ' ';
2686 for (pos_type i
= pos
- 1; i
>= 0; --i
) {
2687 char_type
const par_char
= d
->text_
[i
];
2688 if (!Encodings::isArabicComposeChar(par_char
)) {
2689 prev_char
= par_char
;
2694 for (pos_type i
= pos
+ 1, end
= size(); i
< end
; ++i
) {
2695 char_type
const par_char
= d
->text_
[i
];
2696 if (!Encodings::isArabicComposeChar(par_char
)) {
2697 next_char
= par_char
;
2702 if (Encodings::isArabicChar(next_char
)) {
2703 if (Encodings::isArabicChar(prev_char
) &&
2704 !Encodings::isArabicSpecialChar(prev_char
))
2705 return Encodings::transformChar(c
, Encodings::FORM_MEDIAL
);
2707 return Encodings::transformChar(c
, Encodings::FORM_INITIAL
);
2709 if (Encodings::isArabicChar(prev_char
) &&
2710 !Encodings::isArabicSpecialChar(prev_char
))
2711 return Encodings::transformChar(c
, Encodings::FORM_FINAL
);
2713 return Encodings::transformChar(c
, Encodings::FORM_ISOLATED
);
2718 int Paragraph::checkBiblio(Buffer
const & buffer
)
2721 // This is getting more and more a mess. ...We really should clean
2722 // up this bibitem issue for 1.6. See also bug 2743.
2724 // Add bibitem insets if necessary
2725 if (d
->layout_
->labeltype
!= LABEL_BIBLIO
)
2728 bool hasbibitem
= !d
->insetlist_
.empty()
2729 // Insist on it being in pos 0
2730 && d
->text_
[0] == META_INSET
2731 && d
->insetlist_
.begin()->inset
->lyxCode() == BIBITEM_CODE
;
2733 bool track_changes
= buffer
.params().trackChanges
;
2738 // remove a bibitem in pos != 0
2739 // restore it later in pos 0 if necessary
2740 // (e.g. if a user inserts contents _before_ the item)
2741 // we're assuming there's only one of these, which there
2743 int erasedInsetPosition
= -1;
2744 InsetList::iterator it
= d
->insetlist_
.begin();
2745 InsetList::iterator end
= d
->insetlist_
.end();
2746 for (; it
!= end
; ++it
)
2747 if (it
->inset
->lyxCode() == BIBITEM_CODE
2749 InsetBibitem
* olditem
= static_cast<InsetBibitem
*>(it
->inset
);
2750 oldkey
= olditem
->getParam("key");
2751 oldlabel
= olditem
->getParam("label");
2752 erasedInsetPosition
= it
->pos
;
2753 eraseChar(erasedInsetPosition
, track_changes
);
2757 // There was an InsetBibitem at the beginning, and we didn't
2758 // have to erase one.
2759 if (hasbibitem
&& erasedInsetPosition
< 0)
2762 // There was an InsetBibitem at the beginning and we did have to
2763 // erase one. So we give its properties to the beginning inset.
2765 InsetBibitem
* inset
=
2766 static_cast<InsetBibitem
*>(d
->insetlist_
.begin()->inset
);
2767 if (!oldkey
.empty())
2768 inset
->setParam("key", oldkey
);
2769 inset
->setParam("label", oldlabel
);
2770 return -erasedInsetPosition
;
2773 // There was no inset at the beginning, so we need to create one with
2774 // the key and label of the one we erased.
2775 InsetBibitem
* inset
=
2776 new InsetBibitem(buffer
, InsetCommandParams(BIBITEM_CODE
));
2777 // restore values of previously deleted item in this par.
2778 if (!oldkey
.empty())
2779 inset
->setParam("key", oldkey
);
2780 inset
->setParam("label", oldlabel
);
2781 insertInset(0, static_cast<Inset
*>(inset
),
2782 Change(track_changes
? Change::INSERTED
: Change::UNCHANGED
));
2788 void Paragraph::checkAuthors(AuthorList
const & authorList
)
2790 d
->changes_
.checkAuthors(authorList
);
2794 bool Paragraph::isChanged(pos_type pos
) const
2796 return lookupChange(pos
).changed();
2800 bool Paragraph::isInserted(pos_type pos
) const
2802 return lookupChange(pos
).inserted();
2806 bool Paragraph::isDeleted(pos_type pos
) const
2808 return lookupChange(pos
).deleted();
2812 InsetList
const & Paragraph::insetList() const
2814 return d
->insetlist_
;
2818 void Paragraph::setBuffer(Buffer
& b
)
2820 d
->insetlist_
.setBuffer(b
);
2824 Inset
* Paragraph::releaseInset(pos_type pos
)
2826 Inset
* inset
= d
->insetlist_
.release(pos
);
2827 /// does not honour change tracking!
2828 eraseChar(pos
, false);
2833 Inset
* Paragraph::getInset(pos_type pos
)
2835 return (pos
< pos_type(d
->text_
.size()) && d
->text_
[pos
] == META_INSET
)
2836 ? d
->insetlist_
.get(pos
) : 0;
2840 Inset
const * Paragraph::getInset(pos_type pos
) const
2842 return (pos
< pos_type(d
->text_
.size()) && d
->text_
[pos
] == META_INSET
)
2843 ? d
->insetlist_
.get(pos
) : 0;
2847 void Paragraph::changeCase(BufferParams
const & bparams
, pos_type pos
,
2848 pos_type
& right
, TextCase action
)
2850 // process sequences of modified characters; in change
2851 // tracking mode, this approach results in much better
2852 // usability than changing case on a char-by-char basis
2855 bool const trackChanges
= bparams
.trackChanges
;
2857 bool capitalize
= true;
2859 for (; pos
< right
; ++pos
) {
2860 char_type oldChar
= d
->text_
[pos
];
2861 char_type newChar
= oldChar
;
2863 // ignore insets and don't play with deleted text!
2864 if (oldChar
!= META_INSET
&& !isDeleted(pos
)) {
2866 case text_lowercase
:
2867 newChar
= lowercase(oldChar
);
2869 case text_capitalization
:
2871 newChar
= uppercase(oldChar
);
2875 case text_uppercase
:
2876 newChar
= uppercase(oldChar
);
2881 if (isWordSeparator(pos
) || isDeleted(pos
)) {
2882 // permit capitalization again
2886 if (oldChar
!= newChar
) {
2888 if (pos
!= right
- 1)
2890 // step behind the changing area
2894 int erasePos
= pos
- changes
.size();
2895 for (size_t i
= 0; i
< changes
.size(); i
++) {
2896 insertChar(pos
, changes
[i
],
2897 getFontSettings(bparams
,
2900 if (!eraseChar(erasePos
, trackChanges
)) {
2903 ++right
; // expand selection
2911 bool Paragraph::find(docstring
const & str
, bool cs
, bool mw
,
2912 pos_type pos
, bool del
) const
2914 int const strsize
= str
.length();
2916 pos_type
const parsize
= d
->text_
.size();
2917 for (i
= 0; pos
+ i
< parsize
; ++i
) {
2920 if (cs
&& str
[i
] != d
->text_
[pos
+ i
])
2922 if (!cs
&& uppercase(str
[i
]) != uppercase(d
->text_
[pos
+ i
]))
2924 if (!del
&& isDeleted(pos
+ i
))
2931 // if necessary, check whether string matches word
2933 if (pos
> 0 && !isWordSeparator(pos
- 1))
2935 if (pos
+ strsize
< parsize
2936 && !isWordSeparator(pos
+ strsize
))
2944 char_type
Paragraph::getChar(pos_type pos
) const
2946 return d
->text_
[pos
];
2950 pos_type
Paragraph::size() const
2952 return d
->text_
.size();
2956 bool Paragraph::empty() const
2958 return d
->text_
.empty();
2962 bool Paragraph::isInset(pos_type pos
) const
2964 return d
->text_
[pos
] == META_INSET
;
2968 bool Paragraph::isSeparator(pos_type pos
) const
2970 //FIXME: Are we sure this can be the only separator?
2971 return d
->text_
[pos
] == ' ';
2975 void Paragraph::deregisterWords()
2977 Private::Words::const_iterator it
;
2978 WordList
& wl
= theWordList();
2979 for (it
= d
->words_
.begin(); it
!= d
->words_
.end(); ++it
)
2985 void Paragraph::locateWord(pos_type
& from
, pos_type
& to
,
2986 word_location
const loc
) const
2989 case WHOLE_WORD_STRICT
:
2990 if (from
== 0 || from
== size()
2991 || isWordSeparator(from
)
2992 || isWordSeparator(from
- 1)) {
2996 // no break here, we go to the next
2999 // If we are already at the beginning of a word, do nothing
3000 if (!from
|| isWordSeparator(from
- 1))
3002 // no break here, we go to the next
3005 // always move the cursor to the beginning of previous word
3006 while (from
&& !isWordSeparator(from
- 1))
3010 LYXERR0("Paragraph::locateWord: NEXT_WORD not implemented yet");
3013 // no need to move the 'from' cursor
3017 while (to
< size() && !isWordSeparator(to
))
3022 void Paragraph::collectWords()
3024 pos_type n
= size();
3026 docstring_list suggestions
;
3027 for (pos_type pos
= 0; pos
< n
; ++pos
) {
3028 if (isWordSeparator(pos
))
3030 pos_type from
= pos
;
3031 locateWord(from
, pos
, WHOLE_WORD
);
3032 if (pos
- from
>= 6) {
3033 docstring word
= asString(from
, pos
, AS_STR_NONE
);
3034 d
->words_
.insert(word
);
3036 if (lyxrc
.spellcheck_continuously
3037 && spellCheck(from
, pos
, wl
, suggestions
)) {
3038 for (size_t i
= 0; i
!= suggestions
.size(); ++i
)
3039 d
->words_
.insert(suggestions
[i
]);
3045 void Paragraph::registerWords()
3047 Private::Words::const_iterator it
;
3048 WordList
& wl
= theWordList();
3049 for (it
= d
->words_
.begin(); it
!= d
->words_
.end(); ++it
)
3054 void Paragraph::updateWords()
3062 bool Paragraph::spellCheck(pos_type
& from
, pos_type
& to
, WordLangTuple
& wl
,
3063 docstring_list
& suggestions
, bool do_suggestion
) const
3065 SpellChecker
* speller
= theSpellChecker();
3069 locateWord(from
, to
, WHOLE_WORD
);
3070 if (from
== to
|| from
>= pos_type(d
->text_
.size()))
3073 docstring word
= asString(from
, to
, AS_STR_INSETS
);
3074 string
const lang_code
= lyxrc
.spellchecker_alt_lang
.empty()
3075 ? getFontSettings(d
->inset_owner_
->buffer().params(), from
).language()->code()
3076 : lyxrc
.spellchecker_alt_lang
;
3077 wl
= WordLangTuple(word
, lang_code
);
3078 SpellChecker::Result res
= speller
->check(wl
);
3079 // Just ignore any error that the spellchecker reports.
3080 // FIXME: we should through out an exception and catch it in the GUI to
3081 // display the error.
3082 if (!speller
->error().empty())
3085 bool const misspelled
= res
!= SpellChecker::OK
3086 && res
!= SpellChecker::IGNORED_WORD
;
3088 if (lyxrc
.spellcheck_continuously
)
3089 d
->fontlist_
.setMisspelled(from
, to
, misspelled
);
3091 if (misspelled
&& do_suggestion
)
3092 speller
->suggest(wl
, suggestions
);
3094 suggestions
.clear();
3100 bool Paragraph::isMisspelled(pos_type pos
) const
3102 pos_type from
= pos
;
3105 docstring_list suggestions
;
3106 return spellCheck(from
, to
, wl
, suggestions
, false);