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