]> 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                                        int & column, LyXFont const & font,
215                                        LyXLayout const & style)
216 {
217         if (style.pass_thru) return;
218         if (column > lyxrc.ascii_linelen
219             && i
220             && getChar(i - 1) != ' '
221             && (i < size() - 1)
222             // same in FreeSpacing mode
223             && !style.free_spacing
224                 && !owner_->isFreeSpacing()
225             // In typewriter mode, we want to avoid
226             // ! . ? : at the end of a line
227             && !(font.family() == LyXFont::TYPEWRITER_FAMILY
228                  && (getChar(i - 1) == '.'
229                      || getChar(i - 1) == '?'
230                      || getChar(i - 1) == ':'
231                      || getChar(i - 1) == '!'))) {
232                 os << '\n';
233                 texrow.newline();
234                 texrow.start(owner_, i + 1);
235                 column = 0;
236         } else if (style.free_spacing) {
237                 os << '~';
238         } else {
239                 os << ' ';
240         }
241 }
242
243
244 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
245 {
246         pos_type const len = str.length();
247
248         // is the paragraph large enough?
249         if (pos + len > size())
250                 return false;
251
252         // does the wanted text start at point?
253         for (string::size_type i = 0; i < str.length(); ++i) {
254                 if (str[i] != text[pos + i])
255                         return false;
256         }
257
258         // is there a font change in middle of the word?
259         FontList::const_iterator cit = fontlist.begin();
260         FontList::const_iterator end = fontlist.end();
261         for (; cit != end; ++cit) {
262                 if (cit->pos() >= pos)
263                         break;
264         }
265         if (cit != end && pos + len - 1 > cit->pos())
266                 return false;
267
268         return true;
269 }
270
271
272 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const * buf,
273                                              BufferParams const & bparams,
274                                              ostream & os,
275                                              TexRow & texrow,
276                                              bool moving_arg,
277                                              LyXFont & font,
278                                              LyXFont & running_font,
279                                              LyXFont & basefont,
280                                              bool & open_font,
281                                              LyXLayout const & style,
282                                              pos_type & i,
283                                              int & column,
284                                              value_type const c)
285 {
286         if (style.pass_thru) {
287                 if (c != '\0') os << c;
288                 return;
289         }
290         // Two major modes:  LaTeX or plain
291         // Handle here those cases common to both modes
292         // and then split to handle the two modes separately.
293         switch (c) {
294         case Paragraph::META_INSET: {
295                 Inset * inset = owner_->getInset(i);
296                 if (inset) {
297                         bool close = false;
298                         int const len = os.tellp();
299                         //ostream::pos_type const len = os.tellp();
300                         if ((inset->lyxCode() == Inset::GRAPHICS_CODE
301                              || inset->lyxCode() == Inset::MATH_CODE
302                              || inset->lyxCode() == Inset::URL_CODE)
303                             && running_font.isRightToLeft()) {
304                                 os << "\\L{";
305                                 close = true;
306                         }
307
308 #ifdef WITH_WARNINGS
309 #warning Bug: we can have an empty font change here!
310 // if there has just been a font change, we are going to close it
311 // right now, which means stupid latex code like \textsf{}. AFAIK,
312 // this does not harm dvi output. A minor bug, thus (JMarc)
313 #endif
314                         // some insets cannot be inside a font change command
315                         if (open_font && inset->noFontChange()) {
316                                 column +=running_font.
317                                         latexWriteEndChanges(os,
318                                                              basefont,
319                                                              basefont);
320                                 open_font = false;
321                                 basefont = owner_->getLayoutFont(bparams);
322                                 running_font = basefont;
323                         }
324
325                         int tmp = inset->latex(buf, os, moving_arg,
326                                                style.free_spacing);
327
328                         if (close)
329                                 os << "}";
330
331                         if (tmp) {
332                                 for (int j = 0; j < tmp; ++j) {
333                                         texrow.newline();
334                                 }
335                                 texrow.start(owner_, i + 1);
336                                 column = 0;
337                         } else {
338                                 column += int(os.tellp()) - len;
339                         }
340                 }
341         }
342         break;
343
344         case Paragraph::META_NEWLINE:
345                 if (open_font) {
346                         column += running_font.latexWriteEndChanges(os,
347                                                                     basefont,
348                                                                     basefont);
349                         open_font = false;
350                 }
351                 basefont = owner_->getLayoutFont(bparams);
352                 running_font = basefont;
353                 break;
354
355         case Paragraph::META_HFILL:
356                 os << "\\hfill{}";
357                 column += 7;
358                 break;
359
360         default:
361                 // And now for the special cases within each mode
362
363                 switch (c) {
364                 case '\\':
365                         os << "\\textbackslash{}";
366                         column += 15;
367                         break;
368
369                 case '±': case '²': case '³':
370                 case '×': case '÷': case '¹':
371                 case '¬': case 'µ':
372                         if ((bparams.inputenc == "latin1" ||
373                              bparams.inputenc == "latin9") ||
374                             (bparams.inputenc == "auto" &&
375                              (font.language()->encoding()->LatexName()
376                               == "latin1" ||
377                               font.language()->encoding()->LatexName()
378                               == "latin9"))) {
379                                 os << "\\ensuremath{"
380                                    << c
381                                    << '}';
382                                 column += 13;
383                         } else {
384                                 os << c;
385                         }
386                         break;
387
388                 case '|': case '<': case '>':
389                         // In T1 encoding, these characters exist
390                         if (lyxrc.fontenc == "T1") {
391                                 os << c;
392                                 //... but we should avoid ligatures
393                                 if ((c == '>' || c == '<')
394                                     && i <= size() - 2
395                                     && getChar(i + 1) == c) {
396                                         //os << "\\textcompwordmark{}";
397                                         // Jean-Marc, have a look at
398                                         // this. I think this works
399                                         // equally well:
400                                         os << "\\,{}";
401                                         // Lgb
402                                         column += 19;
403                                 }
404                                 break;
405                         }
406                         // Typewriter font also has them
407                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
408                                 os << c;
409                                 break;
410                         }
411                         // Otherwise, we use what LaTeX
412                         // provides us.
413                         switch (c) {
414                         case '<':
415                                 os << "\\textless{}";
416                                 column += 10;
417                                 break;
418                         case '>':
419                                 os << "\\textgreater{}";
420                                 column += 13;
421                                 break;
422                         case '|':
423                                 os << "\\textbar{}";
424                                 column += 9;
425                                 break;
426                         }
427                         break;
428
429                 case '-': // "--" in Typewriter mode -> "-{}-"
430                         if (i <= size() - 2
431                             && getChar(i + 1) == '-'
432                             && font.family() == LyXFont::TYPEWRITER_FAMILY) {
433                                 os << "-{}";
434                                 column += 2;
435                         } else {
436                                 os << '-';
437                         }
438                         break;
439
440                 case '\"':
441                         os << "\\char`\\\"{}";
442                         column += 9;
443                         break;
444
445                 case '£':
446                         if (bparams.inputenc == "default") {
447                                 os << "\\pounds{}";
448                                 column += 8;
449                         } else {
450                                 os << c;
451                         }
452                         break;
453
454                 case '$': case '&':
455                 case '%': case '#': case '{':
456                 case '}': case '_':
457                         os << '\\' << c;
458                         column += 1;
459                         break;
460
461                 case '~':
462                         os << "\\textasciitilde{}";
463                         column += 16;
464                         break;
465
466                 case '^':
467                         os << "\\textasciicircum{}";
468                         column += 17;
469                         break;
470
471                 case '*': case '[': case ']':
472                         // avoid being mistaken for optional arguments
473                         os << '{' << c << '}';
474                         column += 2;
475                         break;
476
477                 case ' ':
478                         // Blanks are printed before font switching.
479                         // Sure? I am not! (try nice-latex)
480                         // I am sure it's correct. LyX might be smarter
481                         // in the future, but for now, nothing wrong is
482                         // written. (Asger)
483                         break;
484
485                 default:
486
487                         // I assume this is hack treating typewriter as verbatim
488                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
489                                 if (c != '\0') {
490                                         os << c;
491                                 }
492                                 break;
493                         }
494
495                         // LyX, LaTeX etc.
496
497                         // FIXME: if we have "LaTeX" with a font
498                         // change in the middle (before the 'T', then
499                         // the "TeX" part is still special cased.
500                         // Really we should only operate this on
501                         // "words" for some definition of word
502
503                         size_t pnr = 0;
504
505                         for (; pnr < phrases_nr; ++pnr) {
506                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
507                                         os << special_phrases[pnr].macro;
508                                         i += special_phrases[pnr].phrase.length() - 1;
509                                         column += special_phrases[pnr].macro.length() - 1;
510                                         break;
511                                 }
512                         }
513
514                         if (pnr == phrases_nr && c != '\0') {
515                                 os << c;
516                         }
517                         break;
518                 }
519         }
520 }
521
522
523
524 Paragraph * Paragraph::Pimpl::TeXDeeper(Buffer const * buf,
525                                         BufferParams const & bparams,
526                                         ostream & os, TexRow & texrow)
527 {
528         lyxerr[Debug::LATEX] << "TeXDeeper...     " << this << endl;
529         Paragraph * par = owner_;
530
531         while (par && par->params().depth() == owner_->params().depth()) {
532                 if (par->layout()->isEnvironment()) {
533                         par = par->TeXEnvironment(buf, bparams,
534                                                   os, texrow);
535                 } else {
536                         par = par->TeXOnePar(buf, bparams,
537                                              os, texrow, false);
538                 }
539         }
540         lyxerr[Debug::LATEX] << "TeXDeeper...done " << par << endl;
541
542         return par;
543 }
544
545
546 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
547                                 LyXLayout const & layout) const
548 {
549         BufferParams const & bparams = features.bufferParams();
550
551         // check the params.
552         if (params.lineTop() || params.lineBottom())
553                 features.require("lyxline");
554         if (!params.spacing().isDefault())
555                 features.require("setspace");
556
557         // then the layouts
558         features.useLayout(layout.name());
559
560         // then the fonts
561         Language const * doc_language = bparams.language;
562
563         FontList::const_iterator fcit = fontlist.begin();
564         FontList::const_iterator fend = fontlist.end();
565         for (; fcit != fend; ++fcit) {
566                 if (fcit->font().noun() == LyXFont::ON) {
567                         lyxerr[Debug::LATEX] << "font.noun: "
568                                              << fcit->font().noun()
569                                              << endl;
570                         features.require("noun");
571                         lyxerr[Debug::LATEX] << "Noun enabled. Font: "
572                                              << fcit->font().stateText(0)
573                                              << endl;
574                 }
575                 switch (fcit->font().color()) {
576                 case LColor::none:
577                 case LColor::inherit:
578                 case LColor::ignore:
579                         // probably we should put here all interface colors used for
580                         // font displaying! For now I just add this ones I know of (Jug)
581                 case LColor::latex:
582                 case LColor::note:
583                         break;
584                 default:
585                         features.require("color");
586                         lyxerr[Debug::LATEX] << "Color enabled. Font: "
587                                              << fcit->font().stateText(0)
588                                              << endl;
589                 }
590
591                 Language const * language = fcit->font().language();
592                 if (language->babel() != doc_language->babel() &&
593                     language != ignore_language &&
594                     language != latex_language)
595                 {
596                         features.useLanguage(language);
597                         lyxerr[Debug::LATEX] << "Found language "
598                                              << language->babel() << endl;
599                 }
600         }
601
602         if (!params.leftIndent().zero())
603                 features.require("ParagraphLeftIndent");
604
605         // then the insets
606         InsetList::iterator icit = owner_->insetlist.begin();
607         InsetList::iterator iend = owner_->insetlist.end();
608         for (; icit != iend; ++icit) {
609                 if (icit.getInset()) {
610                         icit.getInset()->validate(features);
611                         if (layout.needprotect &&
612                             icit.getInset()->lyxCode() == Inset::FOOT_CODE)
613                                 features.require("NeedLyXFootnoteCode");
614                 }
615         }
616
617         // then the contents
618         for (pos_type i = 0; i < size() ; ++i) {
619                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
620                         if (!special_phrases[pnr].builtin
621                             && isTextAt(special_phrases[pnr].phrase, i)) {
622                                 features.require(special_phrases[pnr].phrase);
623                                 break;
624                         }
625                 }
626         }
627 }
628
629
630 Paragraph * Paragraph::Pimpl::getParFromID(int id) const
631 {
632         InsetList::iterator cit = owner_->insetlist.begin();
633         InsetList::iterator lend = owner_->insetlist.end();
634         Paragraph * result;
635         for (; cit != lend; ++cit) {
636                 if ((result = cit.getInset()->getParFromID(id)))
637                         return result;
638         }
639         return 0;
640 }
641
642
643 LyXFont const Paragraph::Pimpl::realizeFont(LyXFont const & font,
644                                             BufferParams const & bparams) const
645 {
646         LyXFont tmpfont(font);
647
648         // check for environment font information
649         char par_depth = owner_->getDepth();
650         Paragraph const * par = owner_;
651         LyXTextClass const & tclass = bparams.getLyXTextClass();
652
653         while (par && par->getDepth() && !tmpfont.resolved()) {
654                 par = par->outerHook();
655                 if (par) {
656                         tmpfont.realize(par->layout()->font);
657                         par_depth = par->getDepth();
658                 }
659         }
660
661         tmpfont.realize(tclass.defaultfont());
662         return tmpfont;
663 }