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