]> git.lyx.org Git - lyx.git/blob - src/paragraph_pimpl.C
Use lyxconvert script for reading old files
[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                         int tmp = inset->latex(buf, os, moving_arg,
349                                                style.free_spacing);
350
351                         if (close)
352                                 os << "}";
353
354                         if (tmp) {
355                                 for (int j = 0; j < tmp; ++j) {
356                                         texrow.newline();
357                                 }
358                                 texrow.start(owner_, i + 1);
359                                 column = 0;
360                         } else {
361                                 column += int(os.tellp()) - len;
362                         }
363                 }
364         }
365         break;
366
367         case Paragraph::META_NEWLINE:
368                 if (open_font) {
369                         column += running_font.latexWriteEndChanges(os,
370                                                                     basefont,
371                                                                     basefont);
372                         open_font = false;
373                 }
374                 basefont = owner_->getLayoutFont(bparams);
375                 running_font = basefont;
376                 break;
377
378         case Paragraph::META_HFILL:
379                 os << "\\hfill{}";
380                 column += 7;
381                 break;
382
383         default:
384                 // And now for the special cases within each mode
385
386                 switch (c) {
387                 case '\\':
388                         os << "\\textbackslash{}";
389                         column += 15;
390                         break;
391
392                 case '±': case '²': case '³':
393                 case '×': case '÷': case '¹':
394                 case '¬': case 'µ':
395                         if ((bparams.inputenc == "latin1" ||
396                              bparams.inputenc == "latin9") ||
397                             (bparams.inputenc == "auto" &&
398                              (font.language()->encoding()->LatexName()
399                               == "latin1" ||
400                               font.language()->encoding()->LatexName()
401                               == "latin9"))) {
402                                 os << "\\ensuremath{"
403                                    << c
404                                    << '}';
405                                 column += 13;
406                         } else {
407                                 os << c;
408                         }
409                         break;
410
411                 case '|': case '<': case '>':
412                         // In T1 encoding, these characters exist
413                         if (lyxrc.fontenc == "T1") {
414                                 os << c;
415                                 //... but we should avoid ligatures
416                                 if ((c == '>' || c == '<')
417                                     && i <= size() - 2
418                                     && getChar(i + 1) == c) {
419                                         //os << "\\textcompwordmark{}";
420                                         // Jean-Marc, have a look at
421                                         // this. I think this works
422                                         // equally well:
423                                         os << "\\,{}";
424                                         // Lgb
425                                         column += 19;
426                                 }
427                                 break;
428                         }
429                         // Typewriter font also has them
430                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
431                                 os << c;
432                                 break;
433                         }
434                         // Otherwise, we use what LaTeX
435                         // provides us.
436                         switch (c) {
437                         case '<':
438                                 os << "\\textless{}";
439                                 column += 10;
440                                 break;
441                         case '>':
442                                 os << "\\textgreater{}";
443                                 column += 13;
444                                 break;
445                         case '|':
446                                 os << "\\textbar{}";
447                                 column += 9;
448                                 break;
449                         }
450                         break;
451
452                 case '-': // "--" in Typewriter mode -> "-{}-"
453                         if (i <= size() - 2
454                             && getChar(i + 1) == '-'
455                             && font.family() == LyXFont::TYPEWRITER_FAMILY) {
456                                 os << "-{}";
457                                 column += 2;
458                         } else {
459                                 os << '-';
460                         }
461                         break;
462
463                 case '\"':
464                         os << "\\char`\\\"{}";
465                         column += 9;
466                         break;
467
468                 case '£':
469                         if (bparams.inputenc == "default") {
470                                 os << "\\pounds{}";
471                                 column += 8;
472                         } else {
473                                 os << c;
474                         }
475                         break;
476
477                 case '$': case '&':
478                 case '%': case '#': case '{':
479                 case '}': case '_':
480                         os << '\\' << c;
481                         column += 1;
482                         break;
483
484                 case '~':
485                         os << "\\textasciitilde{}";
486                         column += 16;
487                         break;
488
489                 case '^':
490                         os << "\\textasciicircum{}";
491                         column += 17;
492                         break;
493
494                 case '*': case '[': case ']':
495                         // avoid being mistaken for optional arguments
496                         os << '{' << c << '}';
497                         column += 2;
498                         break;
499
500                 case ' ':
501                         // Blanks are printed before font switching.
502                         // Sure? I am not! (try nice-latex)
503                         // I am sure it's correct. LyX might be smarter
504                         // in the future, but for now, nothing wrong is
505                         // written. (Asger)
506                         break;
507
508                 default:
509
510                         // I assume this is hack treating typewriter as verbatim
511                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
512                                 if (c != '\0') {
513                                         os << c;
514                                 }
515                                 break;
516                         }
517
518                         // LyX, LaTeX etc.
519
520                         // FIXME: if we have "LaTeX" with a font
521                         // change in the middle (before the 'T', then
522                         // the "TeX" part is still special cased.
523                         // Really we should only operate this on
524                         // "words" for some definition of word
525
526                         size_t pnr = 0;
527
528                         for (; pnr < phrases_nr; ++pnr) {
529                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
530                                         os << special_phrases[pnr].macro;
531                                         i += special_phrases[pnr].phrase.length() - 1;
532                                         column += special_phrases[pnr].macro.length() - 1;
533                                         break;
534                                 }
535                         }
536
537                         if (pnr == phrases_nr && c != '\0') {
538                                 os << c;
539                         }
540                         break;
541                 }
542         }
543 }
544
545
546
547 Paragraph * Paragraph::Pimpl::TeXDeeper(Buffer const * buf,
548                                         BufferParams const & bparams,
549                                         ostream & os, TexRow & texrow)
550 {
551         lyxerr[Debug::LATEX] << "TeXDeeper...     " << this << endl;
552         Paragraph * par = owner_;
553
554         while (par && par->params().depth() == owner_->params().depth()) {
555                 if (par->layout()->isEnvironment()) {
556                         par = par->TeXEnvironment(buf, bparams,
557                                                   os, texrow);
558                 } else {
559                         par = par->TeXOnePar(buf, bparams,
560                                              os, texrow, false);
561                 }
562         }
563         lyxerr[Debug::LATEX] << "TeXDeeper...done " << par << endl;
564
565         return par;
566 }
567
568
569 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
570                                 LyXLayout const & layout) const
571 {
572         BufferParams const & bparams = features.bufferParams();
573
574         // check the params.
575         if (params.lineTop() || params.lineBottom())
576                 features.require("lyxline");
577         if (!params.spacing().isDefault())
578                 features.require("setspace");
579
580         // then the layouts
581         features.useLayout(layout.name());
582
583         // then the fonts
584         Language const * doc_language = bparams.language;
585
586         FontList::const_iterator fcit = fontlist.begin();
587         FontList::const_iterator fend = fontlist.end();
588         for (; fcit != fend; ++fcit) {
589                 if (fcit->font().noun() == LyXFont::ON) {
590                         lyxerr[Debug::LATEX] << "font.noun: "
591                                              << fcit->font().noun()
592                                              << endl;
593                         features.require("noun");
594                         lyxerr[Debug::LATEX] << "Noun enabled. Font: "
595                                              << fcit->font().stateText(0)
596                                              << endl;
597                 }
598                 switch (fcit->font().color()) {
599                 case LColor::none:
600                 case LColor::inherit:
601                 case LColor::ignore:
602                         // probably we should put here all interface colors used for
603                         // font displaying! For now I just add this ones I know of (Jug)
604                 case LColor::latex:
605                 case LColor::note:
606                         break;
607                 default:
608                         features.require("color");
609                         lyxerr[Debug::LATEX] << "Color enabled. Font: "
610                                              << fcit->font().stateText(0)
611                                              << endl;
612                 }
613
614                 Language const * language = fcit->font().language();
615                 if (language->babel() != doc_language->babel() &&
616                     language != ignore_language &&
617 #ifdef INHERIT_LANGUAGE
618                     language != inherit_language &&
619 #endif
620                     language != latex_language)
621                 {
622                         features.useLanguage(language);
623                         lyxerr[Debug::LATEX] << "Found language "
624                                              << language->babel() << endl;
625                 }
626         }
627
628         if (!params.leftIndent().zero())
629                 features.require("ParagraphLeftIndent");
630
631         // then the insets
632         InsetList::const_iterator icit = owner_->insetlist.begin();
633         InsetList::const_iterator iend = owner_->insetlist.end();
634         for (; icit != iend; ++icit) {
635                 if (icit->inset) {
636                         icit->inset->validate(features);
637                         if (layout.needprotect &&
638                             icit->inset->lyxCode() == Inset::FOOT_CODE)
639                                 features.require("NeedLyXFootnoteCode");
640                 }
641         }
642
643         // then the contents
644         for (pos_type i = 0; i < size() ; ++i) {
645                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
646                         if (!special_phrases[pnr].builtin
647                             && isTextAt(special_phrases[pnr].phrase, i)) {
648                                 features.require(special_phrases[pnr].phrase);
649                                 break;
650                         }
651                 }
652         }               
653 }
654
655
656 Paragraph * Paragraph::Pimpl::getParFromID(int id) const
657 {
658         InsetList::const_iterator cit = owner_->insetlist.begin();
659         InsetList::const_iterator lend = owner_->insetlist.end();
660         Paragraph * result;
661         for (; cit != lend; ++cit) {
662                 if ((result = cit->inset->getParFromID(id)))
663                         return result;
664         }
665         return 0;
666 }
667
668
669 LyXFont const Paragraph::Pimpl::realizeFont(LyXFont const & font,
670                                             BufferParams const & bparams) const
671 {
672         LyXFont tmpfont(font);
673
674         // check for environment font information
675         char par_depth = owner_->getDepth();
676         Paragraph const * par = owner_;
677         LyXTextClass const & tclass = bparams.getLyXTextClass();
678
679         while (par && par->getDepth() && !tmpfont.resolved()) {
680                 par = par->outerHook();
681                 if (par) {
682                         tmpfont.realize(par->layout()->font
683 #ifdef INHERIT_LANGUAGE
684                                         , bparams.language
685 #endif
686                                         );
687                         par_depth = par->getDepth();
688                 }
689         }
690
691         tmpfont.realize(tclass.defaultfont()
692 #ifdef INHERIT_LANGUAGE
693                 , bparams.language
694 #endif
695                 );
696         return tmpfont;
697 }