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