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