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