]> git.lyx.org Git - lyx.git/blob - src/paragraph_pimpl.C
isTextAt improvements from John
[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 "texrow.h"
19 #include "language.h"
20 #include "bufferparams.h"
21 #include "encoding.h"
22 #include "lyxrc.h"
23 #include "debug.h"
24 #include "support/LAssert.h"
25
26 using lyx::pos_type;
27
28 extern int tex_code_break_column;
29
30
31 // Initialize static member.
32 ShareContainer<LyXFont> Paragraph::Pimpl::FontTable::container;
33 // Initialization of the counter for the paragraph id's,
34 unsigned int Paragraph::Pimpl::paragraph_id = 0;
35
36 namespace {
37  
38 string special_phrases[][2] = {
39         { "LyX", "\\LyX{}" },
40         { "TeX", "\\TeX{}" },
41         { "LaTeX2e", "\\LaTeXe{}" },
42         { "LaTeX", "\\LaTeX{}" },
43         };
44  
45 size_t phrases_nr = sizeof(special_phrases)/sizeof(special_phrases[0]);
46  
47 } // namespace anon
48  
49
50 Paragraph::Pimpl::Pimpl(Paragraph * owner)
51         : owner_(owner)
52 {
53         inset_owner = 0;
54         id_ = paragraph_id++;
55 }
56
57
58 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner, bool same_ids)
59         : params(p.params), owner_(owner)
60 {
61         inset_owner = p.inset_owner;
62         text = p.text;
63         fontlist = p.fontlist;
64         if (same_ids)
65                 id_ = p.id_;
66         else
67                 id_ = paragraph_id++;
68 }
69
70
71 void Paragraph::Pimpl::clear()
72 {
73         text.clear();
74 }
75
76
77 void Paragraph::Pimpl::setContentsFromPar(Paragraph const * par)
78 {
79         lyx::Assert(par);
80         text = par->pimpl_->text;
81 }
82
83
84 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
85 {
86         lyx::Assert(pos <= size());
87         // This is stronger, and I belive that this is the assertion
88         // that we should really use. (Lgb)
89         //Assert(pos < size());
90
91         // Then this has no meaning. (Lgb)
92         if (!size() || pos == size()) return '\0';
93         
94         return text[pos];
95 }
96
97
98 void Paragraph::Pimpl::setChar(pos_type pos, value_type c)
99 {
100         text[pos] = c;
101 }
102
103
104 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c,
105                                   LyXFont const & font)
106 {
107         lyx::Assert(pos <= size());
108
109         text.insert(text.begin() + pos, c);
110
111         // Update the font table.
112         FontTable search_font(pos, LyXFont());
113         for (FontList::iterator it = std::lower_bound(fontlist.begin(),
114                                                       fontlist.end(),
115                                                       search_font, matchFT());
116              it != fontlist.end(); ++it)
117         {
118                 it->pos(it->pos() + 1);
119         }
120    
121         // Update the inset table.
122         InsetTable search_inset(pos, 0);
123         for (InsetList::iterator it = std::lower_bound(owner_->insetlist.begin(),
124                                                        owner_->insetlist.end(),
125                                                        search_inset, matchIT());
126              it != owner_->insetlist.end(); ++it)
127         {
128                 ++it->pos;
129         }
130         owner_->setFont(pos, font);
131 }
132
133
134 void Paragraph::Pimpl::insertInset(pos_type pos,
135                                    Inset * inset, LyXFont const & font)
136 {
137         lyx::Assert(inset);
138         lyx::Assert(pos <= size());
139         
140         insertChar(pos, META_INSET, font);
141         lyx::Assert(text[pos] == META_INSET);
142         
143         // Add a new entry in the inset table.
144         InsetTable search_inset(pos, 0);
145         InsetList::iterator it = std::lower_bound(owner_->insetlist.begin(),
146                                                   owner_->insetlist.end(),
147                                                   search_inset, matchIT());
148         if (it != owner_->insetlist.end() && it->pos == pos) {
149                 lyxerr << "ERROR (Paragraph::InsertInset): "
150                         "there is an inset in position: " << pos << std::endl;
151         } else {
152                 owner_->insetlist.insert(it, InsetTable(pos, inset));
153         }
154         
155         if (inset_owner)
156                 inset->setOwner(inset_owner);
157 }
158
159
160 void Paragraph::Pimpl::erase(pos_type pos)
161 {
162         lyx::Assert(pos < size());
163         // if it is an inset, delete the inset entry 
164         if (text[pos] == Paragraph::META_INSET) {
165                 // find the entry
166                 InsetTable search_inset(pos, 0);
167                 InsetList::iterator it =
168                         std::lower_bound(owner_->insetlist.begin(),
169                                          owner_->insetlist.end(),
170                                          search_inset, matchIT());
171                 if (it != owner_->insetlist.end() && it->pos == pos) {
172                         delete it->inset;
173                         owner_->insetlist.erase(it);
174                 }
175         }
176         
177         text.erase(text.begin() + pos);
178         
179         // Erase entries in the tables.
180         FontTable search_font(pos, LyXFont());
181         
182         FontList::iterator it =
183                 std::lower_bound(fontlist.begin(),
184                             fontlist.end(),
185                             search_font, matchFT());
186         if (it != fontlist.end() && it->pos() == pos &&
187             (pos == 0 || 
188              (it != fontlist.begin() 
189               && boost::prior(it)->pos() == pos - 1))) {
190                 // If it is a multi-character font
191                 // entry, we just make it smaller
192                 // (see update below), otherwise we
193                 // should delete it.
194                 unsigned int const i = it - fontlist.begin();
195                 fontlist.erase(fontlist.begin() + i);
196                 it = fontlist.begin() + i;
197                 if (i > 0 && i < fontlist.size() &&
198                     fontlist[i - 1].font() == fontlist[i].font()) {
199                         fontlist.erase(fontlist.begin() + i - 1);
200                         it = fontlist.begin() + i - 1;
201                 }
202         }
203         
204         // Update all other entries.
205         FontList::iterator fend = fontlist.end();
206         for (; it != fend; ++it)
207                 it->pos(it->pos() - 1);
208         
209         // Update the inset table.
210         InsetTable search_inset(pos, 0);
211         InsetList::iterator lend = owner_->insetlist.end();
212         for (InsetList::iterator it =
213                      std::upper_bound(owner_->insetlist.begin(),
214                                       lend,
215                                       search_inset, matchIT());
216              it != lend; ++it)
217                 --it->pos;
218 }
219
220
221 void Paragraph::Pimpl::simpleTeXBlanks(std::ostream & os, TexRow & texrow,
222                                        pos_type const i,
223                                        int & column, LyXFont const & font,
224                                        LyXLayout const & style)
225 {
226         if (style.pass_thru) return;
227         if (column > tex_code_break_column
228             && i 
229             && getChar(i - 1) != ' '
230             && (i < size() - 1)
231             // same in FreeSpacing mode
232             && !style.free_spacing
233             // In typewriter mode, we want to avoid 
234             // ! . ? : at the end of a line
235             && !(font.family() == LyXFont::TYPEWRITER_FAMILY
236                  && (getChar(i - 1) == '.'
237                      || getChar(i - 1) == '?' 
238                      || getChar(i - 1) == ':'
239                      || getChar(i - 1) == '!'))) {
240                 if (tex_code_break_column == 0) {
241                         // in batchmode we need LaTeX to still
242                         // see it as a space not as an extra '\n'
243                         os << " %\n";
244                 } else {
245                         os << '\n';
246                 }
247                 texrow.newline();
248                 texrow.start(owner_, i + 1);
249                 column = 0;
250         } else if (style.free_spacing) {
251                 os << '~';
252         } else {
253                 os << ' ';
254         }
255 }
256
257
258 bool Paragraph::Pimpl::isTextAt(BufferParams const & bp, LyXFont & font,
259                                 string const & str, pos_type pos)
260 {
261         for (string::size_type i = 0; i < str.length(); ++i) {
262                 if (pos + static_cast<pos_type>(i) >= size())
263                         return false;
264                 if (str[i] != getChar(pos + i))
265                         return false;
266                 if (owner_->getFont(bp, pos + i) != font)
267                         return false;
268         }
269         return true;
270 }
271
272  
273 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const * buf,
274                                              BufferParams const & bparams,
275                                              std::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                                              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                         int tmp = inset->latex(buf, os, moving_arg,
310                                                style.free_spacing);
311
312                         if (close)
313                                 os << "}";
314
315                         if (tmp) {
316                                 for (int j = 0; j < tmp; ++j) {
317                                         texrow.newline();
318                                 }
319                                 texrow.start(owner_, i + 1);
320                                 column = 0;
321                         } else {
322                                 column += int(os.tellp()) - len;
323                         }
324                 }
325         }
326         break;
327
328         case Paragraph::META_NEWLINE:
329                 if (open_font) {
330                         column += running_font.latexWriteEndChanges(os,
331                                                                     basefont,
332                                                                     basefont);
333                         open_font = false;
334                 }
335                 basefont = owner_->getLayoutFont(bparams);
336                 running_font = basefont;
337                 break;
338
339         case Paragraph::META_HFILL: 
340                 os << "\\hfill{}";
341                 column += 7;
342                 break;
343
344         default:
345                 // And now for the special cases within each mode
346
347                 switch (c) {
348                 case '\\': 
349                         os << "\\textbackslash{}";
350                         column += 15;
351                         break;
352                         
353                 case '°': case '±': case '²': case '³':  
354                 case '×': case '÷': case '¹': case 'ª':
355                 case 'º': case '¬': case 'µ':
356                         if ((bparams.inputenc == "latin1" ||
357                              bparams.inputenc == "latin9") ||
358                             (bparams.inputenc == "auto" &&
359                              (font.language()->encoding()->LatexName()
360                               == "latin1" ||
361                               font.language()->encoding()->LatexName()
362                               == "latin9"))) {
363                                 os << "\\ensuremath{"
364                                    << c
365                                    << '}';
366                                 column += 13;
367                         } else {
368                                 os << c;
369                         }
370                         break;
371                         
372                 case '|': case '<': case '>':
373                         // In T1 encoding, these characters exist
374                         if (lyxrc.fontenc == "T1") {
375                                 os << c;
376                                 //... but we should avoid ligatures
377                                 if ((c == '>' || c == '<')
378                                     && i <= size() - 2
379                                     && getChar(i + 1) == c) {
380                                         //os << "\\textcompwordmark{}";
381                                         // Jean-Marc, have a look at
382                                         // this. I think this works
383                                         // equally well:
384                                         os << "\\,{}";
385                                         // Lgb
386                                         column += 19;
387                                 }
388                                 break;
389                         }
390                         // Typewriter font also has them
391                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
392                                 os << c;
393                                 break;
394                         } 
395                         // Otherwise, we use what LaTeX
396                         // provides us.
397                         switch (c) {
398                         case '<':
399                                 os << "\\textless{}";
400                                 column += 10;
401                                 break;
402                         case '>':
403                                 os << "\\textgreater{}";
404                                 column += 13;
405                                 break;
406                         case '|':
407                                 os << "\\textbar{}";
408                                 column += 9;
409                                 break;
410                         }
411                         break;
412                         
413                 case '-': // "--" in Typewriter mode -> "-{}-"
414                         if (i <= size() - 2
415                             && getChar(i + 1) == '-'
416                             && font.family() == LyXFont::TYPEWRITER_FAMILY) {
417                                 os << "-{}";
418                                 column += 2;
419                         } else {
420                                 os << '-';
421                         }
422                         break;
423                         
424                 case '\"': 
425                         os << "\\char`\\\"{}";
426                         column += 9;
427                         break;
428                         
429                 case '£':
430                         if (bparams.inputenc == "default") {
431                                 os << "\\pounds{}";
432                                 column += 8;
433                         } else {
434                                 os << c;
435                         }
436                         break;
437                         
438                 case '$': case '&':
439                 case '%': case '#': case '{':
440                 case '}': case '_':
441                         os << '\\' << c;
442                         column += 1;
443                         break;
444                         
445                 case '~':
446                         os << "\\textasciitilde{}";
447                         column += 16;
448                         break;
449                         
450                 case '^':
451                         os << "\\textasciicircum{}";
452                         column += 17;
453                         break;
454                         
455                 case '*': case '[': case ']':
456                         // avoid being mistaken for optional arguments
457                         os << '{' << c << '}';
458                         column += 2;
459                         break;
460
461                 case ' ':
462                         // Blanks are printed before font switching.
463                         // Sure? I am not! (try nice-latex)
464                         // I am sure it's correct. LyX might be smarter
465                         // in the future, but for now, nothing wrong is
466                         // written. (Asger)
467                         break;
468
469                 default:
470  
471                         // I assume this is hack treating typewriter as verbatim
472                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
473                                 if (c != '\0') {
474                                         os << c;
475                                 }
476                                 break;
477                         }
478                 
479                         // LyX, LaTeX etc.
480  
481                         // FIXME: if we have "LaTeX" with a font change in the middle (before
482                         // the 'T', then the "TeX" part is still special cased. Really we
483                         // should only operate this on "words" for some definition of word
484  
485                         size_t pnr = 0;
486  
487                         for (; pnr < phrases_nr; ++pnr) {
488                                 if (isTextAt(bparams, font, special_phrases[pnr][0], i)) {
489                                         os << special_phrases[pnr][1];
490                                         i += special_phrases[pnr][0].length() - 1;
491                                         column += special_phrases[pnr][1].length() - 1;
492                                         break;
493                                 }
494                         }
495
496                         if (pnr == phrases_nr && c != '\0') {
497                                 os << c;
498                         }
499                         break;
500                 }
501         }
502 }
503
504
505
506 Paragraph * Paragraph::Pimpl::TeXDeeper(Buffer const * buf,
507                                         BufferParams const & bparams,
508                                         std::ostream & os, TexRow & texrow)
509 {
510         lyxerr[Debug::LATEX] << "TeXDeeper...     " << this << std::endl;
511         Paragraph * par = owner_;
512
513         while (par && par->params().depth() == owner_->params().depth()) {
514                 if (textclasslist.Style(bparams.textclass, 
515                                         par->layout).isEnvironment()) {
516                         par = par->TeXEnvironment(buf, bparams,
517                                                   os, texrow);
518                 } else {
519                         par = par->TeXOnePar(buf, bparams,
520                                              os, texrow, false);
521                 }
522         }
523         lyxerr[Debug::LATEX] << "TeXDeeper...done " << par << std::endl;
524
525         return par;
526 }
527
528
529 Paragraph * Paragraph::Pimpl::getParFromID(int id) const
530 {
531         InsetList::const_iterator cit = owner_->insetlist.begin();
532         InsetList::const_iterator lend = owner_->insetlist.end();
533         Paragraph * result;
534         for (; cit != lend; ++cit) {
535                 if ((result = cit->inset->getParFromID(id)))
536                         return result;
537         }
538         return 0;
539 }
540
541
542 LyXFont const Paragraph::Pimpl::realizeFont(LyXFont const & font,
543                                             BufferParams const & bparams) const
544 {
545         LyXFont tmpfont(font);
546         
547         // check for environment font information
548         char par_depth = owner_->getDepth();
549         Paragraph const * par = owner_;
550         while (par && par->getDepth() && !tmpfont.resolved()) {
551                 par = par->outerHook();
552                 if (par) {
553                         tmpfont.realize(textclasslist.
554                                         Style(bparams.textclass,
555                                               par->getLayout()).font
556 #ifdef INHERIT_LANGUAGE
557                                         , bparams.language
558 #endif
559                                         );
560                         par_depth = par->getDepth();
561                 }
562         }
563
564         tmpfont.realize(textclasslist.TextClass(bparams.textclass)
565                         .defaultfont()
566 #ifdef INHERIT_LANGUAGE
567                 , bparams.language
568 #endif
569                 );
570         return tmpfont; 
571 }