]> git.lyx.org Git - lyx.git/blob - src/paragraph_pimpl.C
Fix bug 2029 (RtL space width)
[lyx.git] / src / paragraph_pimpl.C
1 /**
2  * \file paragraph_pimpl.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  * \author John Levon
9  * \author André Pönitz
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "paragraph_pimpl.h"
17
18 #include "bufferparams.h"
19 #include "debug.h"
20 #include "encoding.h"
21 #include "language.h"
22 #include "LaTeXFeatures.h"
23 #include "LColor.h"
24 #include "lyxlength.h"
25 #include "lyxrc.h"
26 #include "outputparams.h"
27 #include "texrow.h"
28
29 #include <boost/next_prior.hpp>
30
31 using lyx::pos_type;
32
33 using std::endl;
34 using std::upper_bound;
35 using std::lower_bound;
36 using std::string;
37 using std::ostream;
38
39
40 // Initialization of the counter for the paragraph id's,
41 unsigned int Paragraph::Pimpl::paragraph_id = 0;
42
43 namespace {
44
45 struct special_phrase {
46         string phrase;
47         string macro;
48         bool builtin;
49 };
50
51 special_phrase special_phrases[] = {
52         { "LyX", "\\LyX{}", false },
53         { "TeX", "\\TeX{}", true },
54         { "LaTeX2e", "\\LaTeXe{}", true },
55         { "LaTeX", "\\LaTeX{}", true },
56 };
57
58 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
59
60 } // namespace anon
61
62
63 Paragraph::Pimpl::Pimpl(Paragraph * owner)
64         : owner_(owner)
65 {
66         inset_owner = 0;
67         id_ = paragraph_id++;
68 }
69
70
71 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
72         : params(p.params), owner_(owner)
73 {
74         inset_owner = p.inset_owner;
75         fontlist = p.fontlist;
76         id_ = paragraph_id++;
77
78         if (p.tracking())
79                 changes_.reset(new Changes(*p.changes_.get()));
80 }
81
82
83 void Paragraph::Pimpl::setContentsFromPar(Paragraph const & par)
84 {
85         owner_->text_ = par.text_;
86         if (par.pimpl_->tracking()) {
87                 changes_.reset(new Changes(*(par.pimpl_->changes_.get())));
88         }
89 }
90
91
92 void Paragraph::Pimpl::trackChanges(Change::Type type)
93 {
94         if (tracking()) {
95                 lyxerr[Debug::CHANGES] << "already tracking for par " << id_ << endl;
96                 return;
97         }
98
99         lyxerr[Debug::CHANGES] << "track changes for par "
100                 << id_ << " type " << type << endl;
101         changes_.reset(new Changes(type));
102         changes_->set(type, 0, size());
103 }
104
105
106 void Paragraph::Pimpl::untrackChanges()
107 {
108         changes_.reset(0);
109 }
110
111
112 void Paragraph::Pimpl::cleanChanges()
113 {
114         // if we're not tracking, we don't want to reset...
115         if (!tracking())
116                 return;
117
118         changes_.reset(new Changes(Change::INSERTED));
119         changes_->set(Change::INSERTED, 0, size());
120 }
121
122
123 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
124 {
125         if (!tracking())
126                 return false;
127
128         return changes_->isChange(start, end);
129 }
130
131
132 bool Paragraph::Pimpl::isChangeEdited(pos_type start, pos_type end) const
133 {
134         if (!tracking())
135                 return false;
136
137         return changes_->isChangeEdited(start, end);
138 }
139
140
141 void Paragraph::Pimpl::setChange(pos_type pos, Change::Type type)
142 {
143         if (!tracking())
144                 return;
145
146         changes_->set(type, pos);
147 }
148
149
150 Change::Type Paragraph::Pimpl::lookupChange(pos_type pos) const
151 {
152         if (!tracking())
153                 return Change::UNCHANGED;
154
155         return changes_->lookup(pos);
156 }
157
158
159 Change const Paragraph::Pimpl::lookupChangeFull(pos_type pos) const
160 {
161         if (!tracking())
162                 return Change(Change::UNCHANGED);
163
164         return changes_->lookupFull(pos);
165 }
166
167
168 void Paragraph::Pimpl::markErased(bool erased)
169 {
170         BOOST_ASSERT(tracking());
171
172         if (erased) {
173                 erase(0, size());
174         } else {
175                 pos_type i = 0;
176
177                 for (pos_type i = 0; i < size(); ++i) {
178                         changes_->set(Change::UNCHANGED, i);
179                         if (owner_->isInset(i))
180                                 owner_->getInset(i)->markErased(false);
181                 }
182         }
183 }
184
185
186 void Paragraph::Pimpl::acceptChange(pos_type start, pos_type end)
187 {
188         if (!tracking())
189                 return;
190
191         if (!size()) {
192                 changes_.reset(new Changes(Change::UNCHANGED));
193                 return;
194         }
195
196         lyxerr[Debug::CHANGES] << "acceptchange" << endl;
197         pos_type i = start;
198
199         for (; i < end; ++i) {
200                 switch (lookupChange(i)) {
201                         case Change::UNCHANGED:
202                                 break;
203
204                         case Change::INSERTED:
205                                 changes_->set(Change::UNCHANGED, i);
206                                 break;
207
208                         case Change::DELETED:
209                                 eraseIntern(i);
210                                 changes_->erase(i);
211                                 --end;
212                                 --i;
213                                 break;
214                 }
215         }
216
217         lyxerr[Debug::CHANGES] << "endacceptchange" << endl;
218         changes_->reset(Change::UNCHANGED);
219 }
220
221
222 void Paragraph::Pimpl::rejectChange(pos_type start, pos_type end)
223 {
224         if (!tracking())
225                 return;
226
227         if (!size()) {
228                 changes_.reset(new Changes(Change::UNCHANGED));
229                 return;
230         }
231
232         pos_type i = start;
233
234         for (; i < end; ++i) {
235                 switch (lookupChange(i)) {
236                         case Change::UNCHANGED:
237                                 break;
238
239                         case Change::INSERTED:
240                                 eraseIntern(i);
241                                 changes_->erase(i);
242                                 --end;
243                                 --i;
244                                 break;
245
246                         case Change::DELETED:
247                                 changes_->set(Change::UNCHANGED, i);
248                                 if (owner_->isInset(i))
249                                         owner_->getInset(i)->markErased(false);
250                                 break;
251                 }
252         }
253         changes_->reset(Change::UNCHANGED);
254 }
255
256
257 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
258 {
259         return owner_->getChar(pos);
260 }
261
262
263 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change change)
264 {
265         BOOST_ASSERT(pos <= size());
266
267         if (tracking()) {
268                 changes_->record(change, pos);
269         }
270
271         // This is actually very common when parsing buffers (and
272         // maybe inserting ascii text)
273         if (pos == size()) {
274                 // when appending characters, no need to update tables
275                 owner_->text_.push_back(c);
276                 return;
277         }
278
279         owner_->text_.insert(owner_->text_.begin() + pos, c);
280
281         // Update the font table.
282         FontTable search_font(pos, LyXFont());
283         for (FontList::iterator it = lower_bound(fontlist.begin(),
284                                                       fontlist.end(),
285                                                       search_font, matchFT());
286              it != fontlist.end(); ++it)
287         {
288                 it->pos(it->pos() + 1);
289         }
290
291         // Update the insets
292         owner_->insetlist.increasePosAfterPos(pos);
293 }
294
295
296 void Paragraph::Pimpl::insertInset(pos_type pos,
297                                    InsetBase * inset, Change change)
298 {
299         BOOST_ASSERT(inset);
300         BOOST_ASSERT(pos <= size());
301
302         insertChar(pos, META_INSET, change);
303         BOOST_ASSERT(owner_->text_[pos] == META_INSET);
304
305         // Add a new entry in the insetlist.
306         owner_->insetlist.insert(inset, pos);
307 }
308
309
310 void Paragraph::Pimpl::eraseIntern(pos_type pos)
311 {
312         // if it is an inset, delete the inset entry
313         if (owner_->text_[pos] == Paragraph::META_INSET) {
314                 owner_->insetlist.erase(pos);
315         }
316
317         owner_->text_.erase(owner_->text_.begin() + pos);
318
319         // Erase entries in the tables.
320         FontTable search_font(pos, LyXFont());
321
322         FontList::iterator it =
323                 lower_bound(fontlist.begin(),
324                             fontlist.end(),
325                             search_font, matchFT());
326         if (it != fontlist.end() && it->pos() == pos &&
327             (pos == 0 ||
328              (it != fontlist.begin()
329               && boost::prior(it)->pos() == pos - 1))) {
330                 // If it is a multi-character font
331                 // entry, we just make it smaller
332                 // (see update below), otherwise we
333                 // should delete it.
334                 unsigned int const i = it - fontlist.begin();
335                 fontlist.erase(fontlist.begin() + i);
336                 it = fontlist.begin() + i;
337                 if (i > 0 && i < fontlist.size() &&
338                     fontlist[i - 1].font() == fontlist[i].font()) {
339                         fontlist.erase(fontlist.begin() + i - 1);
340                         it = fontlist.begin() + i - 1;
341                 }
342         }
343
344         // Update all other entries.
345         FontList::iterator fend = fontlist.end();
346         for (; it != fend; ++it)
347                 it->pos(it->pos() - 1);
348
349         // Update the insetlist.
350         owner_->insetlist.decreasePosAfterPos(pos);
351 }
352
353
354 bool Paragraph::Pimpl::erase(pos_type pos)
355 {
356         BOOST_ASSERT(pos < size());
357
358         if (tracking()) {
359                 Change::Type changetype(changes_->lookup(pos));
360                 changes_->record(Change(Change::DELETED), pos);
361
362                 // only allow the actual removal if it was /new/ text
363                 if (changetype != Change::INSERTED) {
364                         if (owner_->isInset(pos))
365                                 owner_->getInset(pos)->markErased(true);
366                         return false;
367                 }
368         }
369
370         eraseIntern(pos);
371         return true;
372 }
373
374
375 int Paragraph::Pimpl::erase(pos_type start, pos_type end)
376 {
377         pos_type i = start;
378         for (pos_type count = end - start; count; --count) {
379                 if (!erase(i))
380                         ++i;
381         }
382         return end - i;
383 }
384
385
386 void Paragraph::Pimpl::simpleTeXBlanks(ostream & os, TexRow & texrow,
387                                        pos_type const i,
388                                        unsigned int & column,
389                                        LyXFont const & font,
390                                        LyXLayout const & style)
391 {
392         if (style.pass_thru)
393                 return;
394
395         if (column > lyxrc.ascii_linelen
396             && i
397             && getChar(i - 1) != ' '
398             && (i < size() - 1)
399             // same in FreeSpacing mode
400             && !owner_->isFreeSpacing()
401             // In typewriter mode, we want to avoid
402             // ! . ? : at the end of a line
403             && !(font.family() == LyXFont::TYPEWRITER_FAMILY
404                  && (getChar(i - 1) == '.'
405                      || getChar(i - 1) == '?'
406                      || getChar(i - 1) == ':'
407                      || getChar(i - 1) == '!'))) {
408                 os << '\n';
409                 texrow.newline();
410                 texrow.start(owner_->id(), i + 1);
411                 column = 0;
412         } else if (style.free_spacing) {
413                 os << '~';
414         } else {
415                 os << ' ';
416         }
417 }
418
419
420 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
421 {
422         pos_type const len = str.length();
423
424         // is the paragraph large enough?
425         if (pos + len > size())
426                 return false;
427
428         // does the wanted text start at point?
429         for (string::size_type i = 0; i < str.length(); ++i) {
430                 if (str[i] != owner_->text_[pos + i])
431                         return false;
432         }
433
434         // is there a font change in middle of the word?
435         FontList::const_iterator cit = fontlist.begin();
436         FontList::const_iterator end = fontlist.end();
437         for (; cit != end; ++cit) {
438                 if (cit->pos() >= pos)
439                         break;
440         }
441         if (cit != end && pos + len - 1 > cit->pos())
442                 return false;
443
444         return true;
445 }
446
447
448 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
449                                              BufferParams const & bparams,
450                                              ostream & os,
451                                              TexRow & texrow,
452                                              OutputParams const & runparams,
453                                              LyXFont & font,
454                                              LyXFont & running_font,
455                                              LyXFont & basefont,
456                                              LyXFont const & outerfont,
457                                              bool & open_font,
458                                              Change::Type & running_change,
459                                              LyXLayout const & style,
460                                              pos_type & i,
461                                              unsigned int & column,
462                                              value_type const c)
463 {
464         if (style.pass_thru) {
465                 if (c != Paragraph::META_INSET) {
466                         if (c != '\0')
467                                 os << c;
468                 } else {
469                         owner_->getInset(i)->plaintext(buf, os, runparams);
470                 }
471                 return;
472         }
473
474         // Two major modes:  LaTeX or plain
475         // Handle here those cases common to both modes
476         // and then split to handle the two modes separately.
477         switch (c) {
478         case Paragraph::META_INSET: {
479                 InsetBase * inset = owner_->getInset(i);
480
481                 // FIXME: remove this check
482                 if (!inset)
483                         break;
484
485                 // FIXME: move this to InsetNewline::latex
486                 if (inset->lyxCode() == InsetBase::NEWLINE_CODE) {
487                         // newlines are handled differently here than
488                         // the default in simpleTeXSpecialChars().
489                         if (!style.newline_allowed) {
490                                 os << '\n';
491                         } else {
492                                 if (open_font) {
493                                         column += running_font.latexWriteEndChanges(os, basefont, basefont);
494                                         open_font = false;
495                                 }
496                                 basefont = owner_->getLayoutFont(bparams, outerfont);
497                                 running_font = basefont;
498
499                                 if (font.family() == LyXFont::TYPEWRITER_FAMILY)
500                                         os << '~';
501
502                                 if (runparams.moving_arg)
503                                         os << "\\protect ";
504
505                                 os << "\\\\\n";
506                         }
507                         texrow.newline();
508                         texrow.start(owner_->id(), i + 1);
509                         column = 0;
510                         break;
511                 }
512
513                 // output change tracking marks only if desired,
514                 // if dvipost is installed,
515                 // and with dvi/ps (other formats don't work)
516                 LaTeXFeatures features(buf, bparams, runparams.nice);
517                 bool const output = bparams.output_changes
518                         && runparams.flavor == OutputParams::LATEX
519                         && features.isAvailable("dvipost");
520
521                 if (inset->isTextInset()) {
522                         column += Changes::latexMarkChange(os, running_change,
523                                 Change::UNCHANGED, output);
524                         running_change = Change::UNCHANGED;
525                 }
526
527                 bool close = false;
528                 ostream::pos_type const len = os.tellp();
529
530                 if ((inset->lyxCode() == InsetBase::GRAPHICS_CODE
531                      || inset->lyxCode() == InsetBase::MATH_CODE
532                      || inset->lyxCode() == InsetBase::URL_CODE)
533                     && running_font.isRightToLeft()) {
534                         os << "\\L{";
535                         close = true;
536                 }
537
538 #ifdef WITH_WARNINGS
539 #warning Bug: we can have an empty font change here!
540 // if there has just been a font change, we are going to close it
541 // right now, which means stupid latex code like \textsf{}. AFAIK,
542 // this does not harm dvi output. A minor bug, thus (JMarc)
543 #endif
544                 // some insets cannot be inside a font change command
545                 if (open_font && inset->noFontChange()) {
546                         column +=running_font.
547                                 latexWriteEndChanges(os,
548                                                      basefont,
549                                                      basefont);
550                         open_font = false;
551                         basefont = owner_->getLayoutFont(bparams, outerfont);
552                         running_font = basefont;
553                 }
554
555                 int tmp = inset->latex(buf, os, runparams);
556
557                 if (close)
558                         os << '}';
559
560                 if (tmp) {
561                         for (int j = 0; j < tmp; ++j) {
562                                 texrow.newline();
563                         }
564                         texrow.start(owner_->id(), i + 1);
565                         column = 0;
566                 } else {
567                         column += os.tellp() - len;
568                 }
569         }
570         break;
571
572         default:
573                 // And now for the special cases within each mode
574
575                 switch (c) {
576                 case '\\':
577                         os << "\\textbackslash{}";
578                         column += 15;
579                         break;
580
581                 case '±': case '²': case '³':
582                 case '×': case '÷': case '¹':
583                 case '¬': case 'µ':
584                         if ((bparams.inputenc == "latin1" ||
585                              bparams.inputenc == "latin9") ||
586                             (bparams.inputenc == "auto" &&
587                              (font.language()->encoding()->LatexName()
588                               == "latin1" ||
589                               font.language()->encoding()->LatexName()
590                               == "latin9"))) {
591                                 os << "\\ensuremath{"
592                                    << c
593                                    << '}';
594                                 column += 13;
595                         } else {
596                                 os << c;
597                         }
598                         break;
599
600                 case '|': case '<': case '>':
601                         // In T1 encoding, these characters exist
602                         if (lyxrc.fontenc == "T1") {
603                                 os << c;
604                                 //... but we should avoid ligatures
605                                 if ((c == '>' || c == '<')
606                                     && i <= size() - 2
607                                     && getChar(i + 1) == c) {
608                                         //os << "\\textcompwordmark{}";
609                                         // Jean-Marc, have a look at
610                                         // this. I think this works
611                                         // equally well:
612                                         os << "\\,{}";
613                                         // Lgb
614                                         column += 19;
615                                 }
616                                 break;
617                         }
618                         // Typewriter font also has them
619                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
620                                 os << c;
621                                 break;
622                         }
623                         // Otherwise, we use what LaTeX
624                         // provides us.
625                         switch (c) {
626                         case '<':
627                                 os << "\\textless{}";
628                                 column += 10;
629                                 break;
630                         case '>':
631                                 os << "\\textgreater{}";
632                                 column += 13;
633                                 break;
634                         case '|':
635                                 os << "\\textbar{}";
636                                 column += 9;
637                                 break;
638                         }
639                         break;
640
641                 case '-': // "--" in Typewriter mode -> "-{}-"
642                         if (i <= size() - 2
643                             && getChar(i + 1) == '-'
644                             && font.family() == LyXFont::TYPEWRITER_FAMILY) {
645                                 os << "-{}";
646                                 column += 2;
647                         } else {
648                                 os << '-';
649                         }
650                         break;
651
652                 case '\"':
653                         os << "\\char`\\\"{}";
654                         column += 9;
655                         break;
656
657                 case '£':
658                         if (bparams.inputenc == "default") {
659                                 os << "\\pounds{}";
660                                 column += 8;
661                         } else {
662                                 os << c;
663                         }
664                         break;
665
666                 case '$': case '&':
667                 case '%': case '#': case '{':
668                 case '}': case '_':
669                         os << '\\' << c;
670                         column += 1;
671                         break;
672
673                 case '~':
674                         os << "\\textasciitilde{}";
675                         column += 16;
676                         break;
677
678                 case '^':
679                         os << "\\textasciicircum{}";
680                         column += 17;
681                         break;
682
683                 case '*': case '[':
684                         // avoid being mistaken for optional arguments
685                         os << '{' << c << '}';
686                         column += 2;
687                         break;
688
689                 case ' ':
690                         // Blanks are printed before font switching.
691                         // Sure? I am not! (try nice-latex)
692                         // I am sure it's correct. LyX might be smarter
693                         // in the future, but for now, nothing wrong is
694                         // written. (Asger)
695                         break;
696
697                 default:
698
699                         // I assume this is hack treating typewriter as verbatim
700                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
701                                 if (c != '\0') {
702                                         os << c;
703                                 }
704                                 break;
705                         }
706
707                         // LyX, LaTeX etc.
708
709                         // FIXME: if we have "LaTeX" with a font
710                         // change in the middle (before the 'T', then
711                         // the "TeX" part is still special cased.
712                         // Really we should only operate this on
713                         // "words" for some definition of word
714
715                         size_t pnr = 0;
716
717                         for (; pnr < phrases_nr; ++pnr) {
718                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
719                                         os << special_phrases[pnr].macro;
720                                         i += special_phrases[pnr].phrase.length() - 1;
721                                         column += special_phrases[pnr].macro.length() - 1;
722                                         break;
723                                 }
724                         }
725
726                         if (pnr == phrases_nr && c != '\0') {
727                                 os << c;
728                         }
729                         break;
730                 }
731         }
732 }
733
734
735 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
736                                 LyXLayout const & layout) const
737 {
738         BufferParams const & bparams = features.bufferParams();
739
740         // check the params.
741         if (!params.spacing().isDefault())
742                 features.require("setspace");
743
744         // then the layouts
745         features.useLayout(layout.name());
746
747         // then the fonts
748         Language const * doc_language = bparams.language;
749
750         FontList::const_iterator fcit = fontlist.begin();
751         FontList::const_iterator fend = fontlist.end();
752         for (; fcit != fend; ++fcit) {
753                 if (fcit->font().noun() == LyXFont::ON) {
754                         lyxerr[Debug::LATEX] << "font.noun: "
755                                              << fcit->font().noun()
756                                              << endl;
757                         features.require("noun");
758                         lyxerr[Debug::LATEX] << "Noun enabled. Font: "
759                                              << fcit->font().stateText(0)
760                                              << endl;
761                 }
762                 switch (fcit->font().color()) {
763                 case LColor::none:
764                 case LColor::inherit:
765                 case LColor::ignore:
766                         // probably we should put here all interface colors used for
767                         // font displaying! For now I just add this ones I know of (Jug)
768                 case LColor::latex:
769                 case LColor::note:
770                         break;
771                 default:
772                         features.require("color");
773                         lyxerr[Debug::LATEX] << "Color enabled. Font: "
774                                              << fcit->font().stateText(0)
775                                              << endl;
776                 }
777
778                 Language const * language = fcit->font().language();
779                 if (language->babel() != doc_language->babel() &&
780                     language != ignore_language &&
781                     language != latex_language)
782                 {
783                         features.useLanguage(language);
784                         lyxerr[Debug::LATEX] << "Found language "
785                                              << language->babel() << endl;
786                 }
787         }
788
789         if (!params.leftIndent().zero())
790                 features.require("ParagraphLeftIndent");
791
792         // then the insets
793         InsetList::const_iterator icit = owner_->insetlist.begin();
794         InsetList::const_iterator iend = owner_->insetlist.end();
795         for (; icit != iend; ++icit) {
796                 if (icit->inset) {
797                         icit->inset->validate(features);
798                         if (layout.needprotect &&
799                             icit->inset->lyxCode() == InsetBase::FOOT_CODE)
800                                 features.require("NeedLyXFootnoteCode");
801                 }
802         }
803
804         // then the contents
805         for (pos_type i = 0; i < size() ; ++i) {
806                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
807                         if (!special_phrases[pnr].builtin
808                             && isTextAt(special_phrases[pnr].phrase, i)) {
809                                 features.require(special_phrases[pnr].phrase);
810                                 break;
811                         }
812                 }
813         }
814 }