]> git.lyx.org Git - lyx.git/blob - src/mathed/MathSupport.cpp
Attempt to fix #8137 (arrived at r40862).
[lyx.git] / src / mathed / MathSupport.cpp
1 /**
2  * \file MathSupport.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  * \author André Pönitz
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "MathSupport.h"
15
16 #include "InsetMath.h"
17 #include "MathData.h"
18 #include "MathParser.h"
19 #include "MathStream.h"
20
21 #include "frontends/FontLoader.h"
22 #include "frontends/FontMetrics.h"
23 #include "frontends/Painter.h"
24
25 #include "support/debug.h"
26 #include "support/docstream.h"
27
28 #include <map>
29
30 using namespace std;
31
32 namespace lyx {
33
34 using frontend::Painter;
35
36
37 ///
38 class Matrix {
39 public:
40         ///
41         Matrix(int, double, double);
42         ///
43         void transform(double &, double &);
44 private:
45         ///
46         double m_[2][2];
47 };
48
49
50 Matrix::Matrix(int code, double x, double y)
51 {
52         double const cs = (code & 1) ? 0 : (1 - code);
53         double const sn = (code & 1) ? (2 - code) : 0;
54         m_[0][0] =  cs * x;
55         m_[0][1] =  sn * x;
56         m_[1][0] = -sn * y;
57         m_[1][1] =  cs * y;
58 }
59
60
61 void Matrix::transform(double & x, double & y)
62 {
63         double xx = m_[0][0] * x + m_[0][1] * y;
64         double yy = m_[1][0] * x + m_[1][1] * y;
65         x = xx;
66         y = yy;
67 }
68
69
70
71 namespace {
72
73 /*
74  * Internal struct of a drawing: code n x1 y1 ... xn yn, where code is:
75  * 0 = end, 1 = line, 2 = polyline, 3 = square line, 4 = square polyline
76  */
77
78
79 double const parenthHigh[] = {
80         2, 13,
81         0.9840, 0.0014, 0.7143, 0.0323, 0.4603, 0.0772,
82         0.2540, 0.1278, 0.1746, 0.1966, 0.0952, 0.3300,
83         0.0950, 0.5000, 0.0952, 0.6700, 0.1746, 0.8034,
84         0.2540, 0.8722, 0.4603, 0.9228, 0.7143, 0.9677,
85         0.9840, 0.9986,
86         0
87 };
88
89
90 double const parenth[] = {
91         2, 13,
92         0.9930, 0.0071, 0.7324, 0.0578, 0.5141, 0.1126,
93         0.3380, 0.1714, 0.2183, 0.2333, 0.0634, 0.3621,
94         0.0141, 0.5000, 0.0563, 0.6369, 0.2113, 0.7647,
95         0.3310, 0.8276, 0.5070, 0.8864, 0.7254, 0.9412,
96         0.9930, 0.9919,
97         0
98 };
99
100
101 double const brace[] = {
102         2, 21,
103         0.9492, 0.0020, 0.9379, 0.0020, 0.7458, 0.0243,
104         0.5819, 0.0527, 0.4859, 0.0892, 0.4463, 0.1278,
105         0.4463, 0.3732, 0.4011, 0.4199, 0.2712, 0.4615,
106         0.0734, 0.4919, 0.0113, 0.5000, 0.0734, 0.5081,
107         0.2712, 0.5385, 0.4011, 0.5801, 0.4463, 0.6268,
108         0.4463, 0.8722, 0.4859, 0.9108, 0.5819, 0.9473,
109         0.7458, 0.9757, 0.9379, 0.9980, 0.9492, 0.9980,
110         0
111 };
112
113
114 double const arrow[] = {
115         4, 7,
116         0.0150, 0.7500, 0.2000, 0.6000, 0.3500, 0.3500,
117         0.5000, 0.0500, 0.6500, 0.3500, 0.8000, 0.6000,
118         0.9500, 0.7500,
119         3, 0.5000, 0.1500, 0.5000, 0.9500,
120         0
121 };
122
123
124 double const Arrow[] = {
125         4, 7,
126         0.0150, 0.7500, 0.2000, 0.6000, 0.3500, 0.3500,
127         0.5000, 0.0500, 0.6500, 0.3500, 0.8000, 0.6000,
128         0.9500, 0.7500,
129         3, 0.3500, 0.5000, 0.3500, 0.9500,
130         3, 0.6500, 0.5000, 0.6500, 0.9500,
131         0
132 };
133
134
135 double const udarrow[] = {
136         2, 3,
137         0.015, 0.25,  0.5, 0.05, 0.95, 0.25,
138         2, 3,
139         0.015, 0.75,  0.5, 0.95, 0.95, 0.75,
140         1, 0.5, 0.2,  0.5, 0.8,
141         0
142 };
143
144
145 double const Udarrow[] = {
146         2, 3,
147         0.015, 0.25,  0.5, 0.05, 0.95, 0.25,
148         2, 3,
149         0.015, 0.75,  0.5, 0.95, 0.95, 0.75,
150         1, 0.35, 0.2, 0.35, 0.8,
151         1, 0.65, 0.2, 0.65, 0.8,
152         0
153 };
154
155
156 double const brack[] = {
157         2, 4,
158         0.95, 0.05,  0.05, 0.05,  0.05, 0.95,  0.95, 0.95,
159         0
160 };
161
162
163 double const corner[] = {
164         2, 3,
165         0.95, 0.05,  0.05, 0.05,  0.05, 0.95,
166         0
167 };
168
169
170 double const angle[] = {
171         2, 3,
172         1, 0,  0.05, 0.5,  1, 1,
173         0
174 };
175
176
177 double const slash[] = {
178         1, 0.95, 0.05, 0.05, 0.95,
179         0
180 };
181
182
183 double const hline[] = {
184         1, 0.00, 0.5, 1.0, 0.5,
185         0
186 };
187
188
189 double const ddot[] = {
190         1, 0.2, 0.5, 0.3, 0.5,
191         1, 0.7, 0.5, 0.8, 0.5,
192         0
193 };
194
195
196 double const dddot[] = {
197         1, 0.1,  0.5, 0.2,  0.5,
198         1, 0.45, 0.5, 0.55, 0.5,
199         1, 0.8,  0.5, 0.9,  0.5,
200         0
201 };
202
203
204 double const ddddot[] = {
205         1, 0.1,  0.5, 0.2,  0.5,
206         1, 0.45, 0.5, 0.55, 0.5,
207         1, 0.8,  0.5, 0.9,  0.5,
208         1, 1.15, 0.5, 1.25, 0.5,
209         0
210 };
211
212
213 double const hline3[] = {
214         1, 0.1,   0,  0.15,  0,
215         1, 0.475, 0,  0.525, 0,
216         1, 0.85,  0,  0.9,   0,
217         0
218 };
219
220
221 double const dline3[] = {
222         1, 0.1,   0.1,   0.15,  0.15,
223         1, 0.475, 0.475, 0.525, 0.525,
224         1, 0.85,  0.85,  0.9,   0.9,
225         0
226 };
227
228
229 double const hlinesmall[] = {
230         1, 0.4, 0.5, 0.6, 0.5,
231         0
232 };
233
234
235 double const ring[] = {
236         2, 5,
237         0.5, 0.8,  0.8, 0.5,  0.5, 0.2,  0.2, 0.5,  0.5, 0.8,
238         0
239 };
240
241
242 double const vert[] = {
243         1, 0.5, 0.05,  0.5, 0.95,
244         0
245 };
246
247
248 double const  Vert[] = {
249         1, 0.3, 0.05,  0.3, 0.95,
250         1, 0.7, 0.05,  0.7, 0.95,
251         0
252 };
253
254
255 double const tilde[] = {
256         2, 4,
257         0.00, 0.8,  0.25, 0.2,  0.75, 0.8,  1.00, 0.2,
258         0
259 };
260
261
262 struct deco_struct {
263         double const * data;
264         int angle;
265 };
266
267 struct named_deco_struct {
268         char const * name;
269         double const * data;
270         int angle;
271 };
272
273 named_deco_struct deco_table[] = {
274         // Decorations
275         {"widehat",             angle,    3 },
276         {"widetilde",           tilde,    0 },
277         {"underbar",            hline,    0 },
278         {"underline",           hline,    0 },
279         {"overline",            hline,    0 },
280         {"underbrace",          brace,    1 },
281         {"overbrace",           brace,    3 },
282         {"overleftarrow",       arrow,    1 },
283         {"overrightarrow",      arrow,    3 },
284         {"overleftrightarrow",  udarrow,  1 },
285         {"xleftarrow",          arrow,    1 },
286         {"xrightarrow",         arrow,    3 },
287         {"underleftarrow",      arrow,    1 },
288         {"underrightarrow",     arrow,    3 },
289         {"underleftrightarrow", udarrow,  1 },
290         {"undertilde",          tilde,    0 },
291         {"utilde",              tilde,    0 },
292
293         // Delimiters
294         {"(",              parenth,    0 },
295         {")",              parenth,    2 },
296         {"{",              brace,      0 },
297         {"}",              brace,      2 },
298         {"lbrace",         brace,      0 },
299         {"rbrace",         brace,      2 },
300         {"[",              brack,      0 },
301         {"]",              brack,      2 },
302         {"|",              vert,       0 },
303         {"/",              slash,      0 },
304         {"slash",          slash,      0 },
305         {"vert",           vert,       0 },
306         {"Vert",           Vert,       0 },
307         {"'",              slash,      1 },
308         {"<",              angle,      0 },
309         {">",              angle,      2 },
310         {"\\",             slash,      1 },
311         {"backslash",      slash,      1 },
312         {"langle",         angle,      0 },
313         {"lceil",          corner,     0 },
314         {"lfloor",         corner,     1 },
315         {"rangle",         angle,      2 },
316         {"rceil",          corner,     3 },
317         {"rfloor",         corner,     2 },
318         {"downarrow",      arrow,      2 },
319         {"Downarrow",      Arrow,      2 },
320         {"uparrow",        arrow,      0 },
321         {"Uparrow",        Arrow,      0 },
322         {"updownarrow",    udarrow,    0 },
323         {"Updownarrow",    Udarrow,    0 },
324
325         // Accents
326         {"ddot",           ddot,       0 },
327         {"dddot",          dddot,      0 },
328         {"ddddot",         ddddot,     0 },
329         {"hat",            angle,      3 },
330         {"grave",          slash,      1 },
331         {"acute",          slash,      0 },
332         {"tilde",          tilde,      0 },
333         {"bar",            hline,      0 },
334         {"dot",            hlinesmall, 0 },
335         {"check",          angle,      1 },
336         {"breve",          parenth,    1 },
337         {"vec",            arrow,      3 },
338         {"mathring",       ring,       0 },
339
340         // Dots
341         {"dots",           hline3,     0 },
342         {"ldots",          hline3,     0 },
343         {"cdots",          hline3,     0 },
344         {"vdots",          hline3,     1 },
345         {"ddots",          dline3,     0 },
346         {"adots",          dline3,     1 },
347         {"iddots",         dline3,     1 },
348         {"dotsb",          hline3,     0 },
349         {"dotsc",          hline3,     0 },
350         {"dotsi",          hline3,     0 },
351         {"dotsm",          hline3,     0 },
352         {"dotso",          hline3,     0 }
353 };
354
355
356 map<docstring, deco_struct> deco_list;
357
358 // sort the table on startup
359 class init_deco_table {
360 public:
361         init_deco_table() {
362                 unsigned const n = sizeof(deco_table) / sizeof(deco_table[0]);
363                 for (named_deco_struct * p = deco_table; p != deco_table + n; ++p) {
364                         deco_struct d;
365                         d.data  = p->data;
366                         d.angle = p->angle;
367                         deco_list[from_ascii(p->name)] = d;
368                 }
369         }
370 };
371
372 static init_deco_table dummy;
373
374
375 deco_struct const * search_deco(docstring const & name)
376 {
377         map<docstring, deco_struct>::const_iterator p = deco_list.find(name);
378         return p == deco_list.end() ? 0 : &(p->second);
379 }
380
381
382 } // namespace anon
383
384
385 int mathed_char_width(FontInfo const & font, char_type c)
386 {
387         return theFontMetrics(font).width(c);
388 }
389
390
391 int mathed_char_kerning(FontInfo const & font, char_type c)
392 {
393         frontend::FontMetrics const & fm = theFontMetrics(font);
394         return fm.rbearing(c) - fm.width(c);
395 }
396
397
398 void mathed_string_dim(FontInfo const & font,
399                        docstring const & s,
400                        Dimension & dim)
401 {
402         frontend::FontMetrics const & fm = theFontMetrics(font);
403         dim.asc = 0;
404         dim.des = 0;
405         for (docstring::const_iterator it = s.begin();
406              it != s.end();
407              ++it) {
408                 dim.asc = max(dim.asc, fm.ascent(*it));
409                 dim.des = max(dim.des, fm.descent(*it));
410         }
411         dim.wid = fm.width(s);
412 }
413
414
415 int mathed_string_width(FontInfo const & font, docstring const & s)
416 {
417         return theFontMetrics(font).width(s);
418 }
419
420
421 void mathed_draw_deco(PainterInfo & pi, int x, int y, int w, int h,
422         docstring const & name)
423 {
424         if (name == ".") {
425                 pi.pain.line(x + w/2, y, x + w/2, y + h,
426                           Color_cursor, Painter::line_onoffdash);
427                 return;
428         }
429
430         deco_struct const * mds = search_deco(name);
431         if (!mds) {
432                 lyxerr << "Deco was not found. Programming error?" << endl;
433                 lyxerr << "name: '" << to_utf8(name) << "'" << endl;
434                 return;
435         }
436
437         int const n = (w < h) ? w : h;
438         int const r = mds->angle;
439         double const * d = mds->data;
440
441         if (h > 70 && (name == "(" || name == ")"))
442                 d = parenthHigh;
443
444         Matrix mt(r, w, h);
445         Matrix sqmt(r, n, n);
446
447         if (r > 0 && r < 3)
448                 y += h;
449
450         if (r >= 2)
451                 x += w;
452
453         for (int i = 0; d[i]; ) {
454                 int code = int(d[i++]);
455                 if (code & 1) {  // code == 1 || code == 3
456                         double xx = d[i++];
457                         double yy = d[i++];
458                         double x2 = d[i++];
459                         double y2 = d[i++];
460                         if (code == 3)
461                                 sqmt.transform(xx, yy);
462                         else
463                                 mt.transform(xx, yy);
464                         mt.transform(x2, y2);
465                         pi.pain.line(
466                                 int(x + xx + 0.5), int(y + yy + 0.5),
467                                 int(x + x2 + 0.5), int(y + y2 + 0.5),
468                                 pi.base.font.color());
469                 } else {
470                         int xp[32];
471                         int yp[32];
472                         int const n = int(d[i++]);
473                         for (int j = 0; j < n; ++j) {
474                                 double xx = d[i++];
475                                 double yy = d[i++];
476 //           lyxerr << ' ' << xx << ' ' << yy << ' ';
477                                 if (code == 4)
478                                         sqmt.transform(xx, yy);
479                                 else
480                                         mt.transform(xx, yy);
481                                 xp[j] = int(x + xx + 0.5);
482                                 yp[j] = int(y + yy + 0.5);
483                                 //  lyxerr << "P[" << j ' ' << xx << ' ' << yy << ' ' << x << ' ' << y << ']';
484                         }
485                         pi.pain.lines(xp, yp, n, pi.base.font.color());
486                 }
487         }
488 }
489
490
491 void drawStrRed(PainterInfo & pi, int x, int y, docstring const & str)
492 {
493         FontInfo f = pi.base.font;
494         f.setColor(Color_latex);
495         pi.pain.text(x, y, str, f);
496 }
497
498
499 void drawStrBlack(PainterInfo & pi, int x, int y, docstring const & str)
500 {
501         FontInfo f = pi.base.font;
502         f.setColor(Color_foreground);
503         pi.pain.text(x, y, str, f);
504 }
505
506
507 void math_font_max_dim(FontInfo const & font, int & asc, int & des)
508 {
509         frontend::FontMetrics const & fm = theFontMetrics(font);
510         asc = fm.maxAscent();
511         des = fm.maxDescent();
512 }
513
514
515 struct fontinfo {
516         string cmd_;
517         FontFamily family_;
518         FontSeries series_;
519         FontShape  shape_;
520         ColorCode        color_;
521 };
522
523
524 FontFamily const inh_family = INHERIT_FAMILY;
525 FontSeries const inh_series = INHERIT_SERIES;
526 FontShape  const inh_shape  = INHERIT_SHAPE;
527
528
529 // mathnormal should be the first, otherwise the fallback further down
530 // does not work
531 fontinfo fontinfos[] = {
532         // math fonts
533         {"mathnormal",    ROMAN_FAMILY, MEDIUM_SERIES,
534                           ITALIC_SHAPE, Color_math},
535         {"mathbf",        inh_family, BOLD_SERIES,
536                           inh_shape, Color_math},
537         {"mathcal",       CMSY_FAMILY, inh_series,
538                           inh_shape, Color_math},
539         {"mathfrak",      EUFRAK_FAMILY, inh_series,
540                           inh_shape, Color_math},
541         {"mathrm",        ROMAN_FAMILY, inh_series,
542                           UP_SHAPE, Color_math},
543         {"mathsf",        SANS_FAMILY, inh_series,
544                           inh_shape, Color_math},
545         {"mathbb",        MSB_FAMILY, inh_series,
546                           inh_shape, Color_math},
547         {"mathtt",        TYPEWRITER_FAMILY, inh_series,
548                           inh_shape, Color_math},
549         {"mathit",        inh_family, inh_series,
550                           ITALIC_SHAPE, Color_math},
551         {"mathscr",       RSFS_FAMILY, inh_series,
552                   inh_shape, Color_math}, 
553         {"cmex",          CMEX_FAMILY, inh_series,
554                           inh_shape, Color_math},
555         {"cmm",           CMM_FAMILY, inh_series,
556                           inh_shape, Color_math},
557         {"cmr",           CMR_FAMILY, inh_series,
558                           inh_shape, Color_math},
559         {"cmsy",          CMSY_FAMILY, inh_series,
560                           inh_shape, Color_math},
561         {"eufrak",        EUFRAK_FAMILY, inh_series,
562                           inh_shape, Color_math},
563         {"msa",           MSA_FAMILY, inh_series,
564                           inh_shape, Color_math},
565         {"msb",           MSB_FAMILY, inh_series,
566                           inh_shape, Color_math},
567         {"wasy",          WASY_FAMILY, inh_series,
568                           inh_shape, Color_math},
569         {"esint",         ESINT_FAMILY, inh_series,
570                           inh_shape, Color_math},
571
572         // Text fonts
573         {"text",          inh_family, inh_series,
574                           inh_shape, Color_foreground},
575         {"textbf",        inh_family, BOLD_SERIES,
576                           inh_shape, Color_foreground},
577         {"textit",        inh_family, inh_series,
578                           ITALIC_SHAPE, Color_foreground},
579         {"textmd",        inh_family, MEDIUM_SERIES,
580                           inh_shape, Color_foreground},
581         {"textnormal",    inh_family, inh_series,
582                           UP_SHAPE, Color_foreground},
583         {"textrm",        ROMAN_FAMILY,
584                           inh_series, UP_SHAPE,Color_foreground},
585         {"textsc",        inh_family, inh_series,
586                           SMALLCAPS_SHAPE, Color_foreground},
587         {"textsf",        SANS_FAMILY, inh_series,
588                           inh_shape, Color_foreground},
589         {"textsl",        inh_family, inh_series,
590                           SLANTED_SHAPE, Color_foreground},
591         {"texttt",        TYPEWRITER_FAMILY, inh_series,
592                           inh_shape, Color_foreground},
593         {"textup",        inh_family, inh_series,
594                           UP_SHAPE, Color_foreground},
595
596         // TIPA support
597         {"textipa",       inh_family, inh_series,
598                           inh_shape, Color_foreground},
599
600         // mhchem support
601         {"ce",            inh_family, inh_series,
602                           inh_shape, Color_foreground},
603         {"cf",            inh_family, inh_series,
604                           inh_shape, Color_foreground},
605
606         // LyX internal usage
607         {"lyxtex",        inh_family, inh_series,
608                           UP_SHAPE, Color_latex},
609         {"lyxsymbol",     SYMBOL_FAMILY, inh_series,
610                           inh_shape, Color_math},
611         {"lyxboldsymbol", SYMBOL_FAMILY, BOLD_SERIES,
612                           inh_shape, Color_math},
613         {"lyxblacktext",  ROMAN_FAMILY, MEDIUM_SERIES,
614                           UP_SHAPE, Color_foreground},
615         {"lyxnochange",   inh_family, inh_series,
616                           inh_shape, Color_foreground},
617         {"lyxfakebb",     TYPEWRITER_FAMILY, BOLD_SERIES,
618                           UP_SHAPE, Color_math},
619         {"lyxfakecal",    SANS_FAMILY, MEDIUM_SERIES,
620                           ITALIC_SHAPE, Color_math},
621         {"lyxfakefrak",   ROMAN_FAMILY, BOLD_SERIES,
622                           ITALIC_SHAPE, Color_math}
623 };
624
625
626 fontinfo * lookupFont(docstring const & name0)
627 {
628         //lyxerr << "searching font '" << name << "'" << endl;
629         int const n = sizeof(fontinfos) / sizeof(fontinfo);
630         string name = to_utf8(name0);
631         for (int i = 0; i < n; ++i)
632                 if (fontinfos[i].cmd_ == name) {
633                         //lyxerr << "found '" << i << "'" << endl;
634                         return fontinfos + i;
635                 }
636         return 0;
637 }
638
639
640 fontinfo * searchFont(docstring const & name)
641 {
642         fontinfo * f = lookupFont(name);
643         return f ? f : fontinfos;
644         // this should be mathnormal
645         //return searchFont("mathnormal");
646 }
647
648
649 bool isFontName(docstring const & name)
650 {
651         return lookupFont(name);
652 }
653
654
655 bool isMathFont(docstring const & name)
656 {
657         fontinfo * f = lookupFont(name);
658         return f && f->color_ == Color_math;
659 }
660
661
662 bool isTextFont(docstring const & name)
663 {
664         fontinfo * f = lookupFont(name);
665         return f && f->color_ == Color_foreground;
666 }
667
668
669 FontInfo getFont(docstring const & name)
670 {
671         FontInfo font;
672         augmentFont(font, name);
673         return font;
674 }
675
676
677 void fakeFont(docstring const & orig, docstring const & fake)
678 {
679         fontinfo * forig = searchFont(orig);
680         fontinfo * ffake = searchFont(fake);
681         if (forig && ffake) {
682                 forig->family_ = ffake->family_;
683                 forig->series_ = ffake->series_;
684                 forig->shape_  = ffake->shape_;
685                 forig->color_  = ffake->color_;
686         } else {
687                 lyxerr << "Can't fake font '" << to_utf8(orig) << "' with '"
688                        << to_utf8(fake) << "'" << endl;
689         }
690 }
691
692
693 void augmentFont(FontInfo & font, docstring const & name)
694 {
695         static bool initialized = false;
696         if (!initialized) {
697                 initialized = true;
698                 // fake fonts if necessary
699                 if (!theFontLoader().available(getFont(from_ascii("mathfrak"))))
700                         fakeFont(from_ascii("mathfrak"), from_ascii("lyxfakefrak"));
701                 if (!theFontLoader().available(getFont(from_ascii("mathcal"))))
702                         fakeFont(from_ascii("mathcal"), from_ascii("lyxfakecal"));
703         }
704         fontinfo * info = searchFont(name);
705         if (info->family_ != inh_family)
706                 font.setFamily(info->family_);
707         if (info->series_ != inh_series)
708                 font.setSeries(info->series_);
709         if (info->shape_ != inh_shape)
710                 font.setShape(info->shape_);
711         if (info->color_ != Color_none)
712                 font.setColor(info->color_);
713 }
714
715
716 docstring asString(MathData const & ar)
717 {
718         odocstringstream os;
719         WriteStream ws(os);
720         ws << ar;
721         return os.str();
722 }
723
724
725 void asArray(docstring const & str, MathData & ar, Parse::flags pf)
726 {
727         bool quiet = pf & Parse::QUIET;
728         if ((str.size() == 1 && quiet) || (!mathed_parse_cell(ar, str, pf) && quiet))
729                 mathed_parse_cell(ar, str, pf | Parse::VERBATIM);
730 }
731
732
733 docstring asString(InsetMath const & inset)
734 {
735         odocstringstream os;
736         WriteStream ws(os);
737         inset.write(ws);
738         return os.str();
739 }
740
741
742 docstring asString(MathAtom const & at)
743 {
744         odocstringstream os;
745         WriteStream ws(os);
746         at->write(ws);
747         return os.str();
748 }
749
750
751 } // namespace lyx