]> git.lyx.org Git - lyx.git/blob - src/mathed/math_support.C
swallow <Return> events in mathed. Should mimic 1.3.x behaviour.
[lyx.git] / src / mathed / math_support.C
1 /**
2  * \file math_support.C
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 "math_support.h"
15 #include "math_data.h"
16 #include "math_inset.h"
17 #include "math_mathmlstream.h"
18 #include "math_parser.h"
19
20 #include "debug.h"
21 #include "LColor.h"
22
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
25 #include "frontends/lyx_gui.h"
26
27 #include "support/std_sstream.h"
28
29 #include <map>
30
31
32 using std::string;
33 using std::max;
34 using std::endl;
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 hline3[] = {
205         1, 0.1,   0,  0.15,  0,
206         1, 0.475, 0,  0.525, 0,
207         1, 0.85,  0,  0.9,   0,
208         0
209 };
210
211
212 double const dline3[] = {
213         1, 0.1,   0.1,   0.15,  0.15,
214         1, 0.475, 0.475, 0.525, 0.525,
215         1, 0.85,  0.85,  0.9,   0.9,
216         0
217 };
218
219
220 double const hlinesmall[] = {
221         1, 0.4, 0.5, 0.6, 0.5,
222         0
223 };
224
225
226 double const ring[] = {
227         2, 5,
228         0.5, 0.8,  0.8, 0.5,  0.5, 0.2,  0.2, 0.5,  0.5, 0.8,
229         0
230 };
231
232
233 double const vert[] = {
234         1, 0.5, 0.05,  0.5, 0.95,
235         0
236 };
237
238
239 double const  Vert[] = {
240         1, 0.3, 0.05,  0.3, 0.95,
241         1, 0.7, 0.05,  0.7, 0.95,
242         0
243 };
244
245
246 double const tilde[] = {
247         2, 4,
248         0.00, 0.8,  0.25, 0.2,  0.75, 0.8,  1.00, 0.2,
249         0
250 };
251
252
253 struct deco_struct {
254         double const * data;
255         int angle;
256 };
257
258 struct named_deco_struct {
259         char const * name;
260         double const * data;
261         int angle;
262 };
263
264 named_deco_struct deco_table[] = {
265         // Decorations
266         {"widehat",             angle,    3 },
267         {"widetilde",           tilde,    0 },
268         {"underbar",            hline,    0 },
269         {"underline",           hline,    0 },
270         {"overline",            hline,    0 },
271         {"underbrace",          brace,    1 },
272         {"overbrace",           brace,    3 },
273         {"overleftarrow",       arrow,    1 },
274         {"overrightarrow",      arrow,    3 },
275         {"overleftrightarrow",  udarrow,  1 },
276         {"xleftarrow",          arrow,    1 },
277         {"xrightarrow",         arrow,    3 },
278         {"underleftarrow",      arrow,    1 },
279         {"underrightarrow",     arrow,    3 },
280         {"underleftrightarrow", udarrow,  1 },
281
282         // Delimiters
283         {"(",              parenth,    0 },
284         {")",              parenth,    2 },
285         {"{",              brace,      0 },
286         {"}",              brace,      2 },
287         {"[",              brack,      0 },
288         {"]",              brack,      2 },
289         {"|",              vert,       0 },
290         {"/",              slash,      0 },
291         {"vert",           vert,       0 },
292         {"Vert",           Vert,       0 },
293         {"'",              slash,      1 },
294         {"backslash",      slash,      1 },
295         {"langle",         angle,      0 },
296         {"lceil",          corner,     0 },
297         {"lfloor",         corner,     1 },
298         {"rangle",         angle,      2 },
299         {"rceil",          corner,     3 },
300         {"rfloor",         corner,     2 },
301         {"downarrow",      arrow,      2 },
302         {"Downarrow",      Arrow,      2 },
303         {"uparrow",        arrow,      0 },
304         {"Uparrow",        Arrow,      0 },
305         {"updownarrow",    udarrow,    0 },
306         {"Updownarrow",    Udarrow,    0 },
307
308         // Accents
309         {"ddot",           ddot,       0 },
310         {"dddot",          dddot,      0 },
311         {"hat",            angle,      3 },
312         {"grave",          slash,      1 },
313         {"acute",          slash,      0 },
314         {"tilde",          tilde,      0 },
315         {"bar",            hline,      0 },
316         {"dot",            hlinesmall, 0 },
317         {"check",          angle,      1 },
318         {"breve",          parenth,    1 },
319         {"vec",            arrow,      3 },
320         {"mathring",       ring,       0 },
321
322         // Dots
323         {"dots",           hline3,     0 },
324         {"ldots",          hline3,     0 },
325         {"cdots",          hline3,     0 },
326         {"vdots",          hline3,     1 },
327         {"ddots",          dline3,     0 },
328         {"dotsb",          hline3,     0 },
329         {"dotsc",          hline3,     0 },
330         {"dotsi",          hline3,     0 },
331         {"dotsm",          hline3,     0 },
332         {"dotso",          hline3,     0 }
333 };
334
335
336 std::map<string, deco_struct> deco_list;
337
338 // sort the table on startup
339 struct init_deco_table {
340         init_deco_table() {
341                 unsigned const n = sizeof(deco_table) / sizeof(deco_table[0]);
342                 for (named_deco_struct * p = deco_table; p != deco_table + n; ++p) {
343                         deco_struct d;
344                         d.data  = p->data;
345                         d.angle = p->angle;
346                         deco_list[p->name]= d;
347                 }
348         }
349 };
350
351 static init_deco_table dummy;
352
353
354 deco_struct const * search_deco(string const & name)
355 {
356         std::map<string, deco_struct>::const_iterator p = deco_list.find(name);
357         return (p == deco_list.end()) ? 0 : &(p->second);
358 }
359
360
361 } // namespace anon
362
363
364 void mathed_char_dim(LyXFont const & font, unsigned char c, Dimension & dim)
365 {
366         dim.des = font_metrics::descent(c, font);
367         dim.asc = font_metrics::ascent(c, font);
368         dim.wid = mathed_char_width(font, c);
369 }
370
371
372 int mathed_char_ascent(LyXFont const & font, unsigned char c)
373 {
374         return font_metrics::ascent(c, font);
375 }
376
377
378 int mathed_char_descent(LyXFont const & font, unsigned char c)
379 {
380         return font_metrics::descent(c, font);
381 }
382
383
384 int mathed_char_width(LyXFont const & font, unsigned char c)
385 {
386         return font_metrics::width(c, font);
387 }
388
389
390 void mathed_string_dim(LyXFont const & font, string const & s, Dimension & dim)
391 {
392 #if 1
393         dim.asc = 0;
394         dim.des = 0;
395         for (string::const_iterator it = s.begin(); it != s.end(); ++it) {
396                 dim.asc = max(dim.asc, font_metrics::ascent(*it, font));
397                 dim.des = max(dim.des, font_metrics::descent(*it, font));
398         }
399 #else
400         dim.asc = font_metrics::maxAscent(font);
401         dim.des = font_metrics::maxDescent(font);
402 #endif
403         dim.wid = font_metrics::width(s, font);
404 }
405
406
407 int mathed_string_width(LyXFont const & font, string const & s)
408 {
409         return font_metrics::width(s, font);
410 }
411
412
413 void mathed_draw_deco(PainterInfo & pi, int x, int y, int w, int h,
414         string const & name)
415 {
416         if (name == ".") {
417                 pi.pain.line(x + w/2, y, x + w/2, y + h,
418                           LColor::cursor, Painter::line_onoffdash);
419                 return;
420         }
421
422         deco_struct const * mds = search_deco(name);
423         if (!mds) {
424                 lyxerr << "Deco was not found. Programming error?" << endl;
425                 lyxerr << "name: '" << name << "'" << endl;
426                 return;
427         }
428
429         int const n = (w < h) ? w : h;
430         int const r = mds->angle;
431         double const * d = mds->data;
432
433         if (h > 70 && (name == "(" || name == ")"))
434                 d = parenthHigh;
435
436         Matrix mt(r, w, h);
437         Matrix sqmt(r, n, n);
438
439         if (r > 0 && r < 3)
440                 y += h;
441
442         if (r >= 2)
443                 x += w;
444
445         for (int i = 0; d[i]; ) {
446                 int code = int(d[i++]);
447                 if (code & 1) {  // code == 1 || code == 3
448                         double xx = d[i++];
449                         double yy = d[i++];
450                         double x2 = d[i++];
451                         double y2 = d[i++];
452                         if (code == 3)
453                                 sqmt.transform(xx, yy);
454                         else
455                                 mt.transform(xx, yy);
456                         mt.transform(x2, y2);
457                         pi.pain.line(
458                                 int(x + xx + 0.5), int(y + yy + 0.5),
459                                 int(x + x2 + 0.5), int(y + y2 + 0.5),
460                                 LColor::math);
461                 } else {
462                         int xp[32];
463                         int yp[32];
464                         int const n = int(d[i++]);
465                         for (int j = 0; j < n; ++j) {
466                                 double xx = d[i++];
467                                 double yy = d[i++];
468 //           lyxerr << ' ' << xx << ' ' << yy << ' ';
469                                 if (code == 4)
470                                         sqmt.transform(xx, yy);
471                                 else
472                                         mt.transform(xx, yy);
473                                 xp[j] = int(x + xx + 0.5);
474                                 yp[j] = int(y + yy + 0.5);
475                                 //  lyxerr << "P[" << j ' ' << xx << ' ' << yy << ' ' << x << ' ' << y << ']';
476                         }
477                         pi.pain.lines(xp, yp, n, LColor::math);
478                 }
479         }
480 }
481
482
483 // In the future maybe we use a better fonts renderer
484 void drawStr(PainterInfo & pi, LyXFont const & font,
485         int x, int y, string const & str)
486 {
487         pi.pain.text(x, y, str, font);
488 }
489
490
491 void drawStrRed(PainterInfo & pi, int x, int y, string const & str)
492 {
493         LyXFont f = pi.base.font;
494         f.setColor(LColor::latex);
495         pi.pain.text(x, y, str, f);
496 }
497
498
499 void drawStrBlack(PainterInfo & pi, int x, int y, string const & str)
500 {
501         LyXFont f = pi.base.font;
502         f.setColor(LColor::foreground);
503         pi.pain.text(x, y, str, f);
504 }
505
506
507 void drawChar(PainterInfo & pi, LyXFont const & font, int x, int y, char c)
508 {
509         pi.pain.text(x, y, c, font);
510 }
511
512
513 void math_font_max_dim(LyXFont const & font, int & asc, int & des)
514 {
515         asc = font_metrics::maxAscent(font);
516         des = font_metrics::maxDescent(font);
517 }
518
519
520 struct fontinfo {
521         string cmd_;
522         LyXFont::FONT_FAMILY family_;
523         LyXFont::FONT_SERIES series_;
524         LyXFont::FONT_SHAPE  shape_;
525         LColor::color        color_;
526 };
527
528
529 LyXFont::FONT_FAMILY const inh_family = LyXFont::INHERIT_FAMILY;
530 LyXFont::FONT_SERIES const inh_series = LyXFont::INHERIT_SERIES;
531 LyXFont::FONT_SHAPE  const inh_shape  = LyXFont::INHERIT_SHAPE;
532
533
534 // mathnormal should be the first, otherwise the fallback further down
535 // does not work
536 fontinfo fontinfos[] = {
537         // math fonts
538         {"mathnormal",    LyXFont::ROMAN_FAMILY, LyXFont::MEDIUM_SERIES,
539                           LyXFont::ITALIC_SHAPE, LColor::math},
540         {"mathbf",        inh_family, LyXFont::BOLD_SERIES,
541                           inh_shape, LColor::math},
542         {"mathcal",       LyXFont::CMSY_FAMILY, inh_series,
543                           inh_shape, LColor::math},
544         {"mathfrak",      LyXFont::EUFRAK_FAMILY, inh_series,
545                           inh_shape, LColor::math},
546         {"mathrm",        LyXFont::ROMAN_FAMILY, inh_series,
547                           LyXFont::UP_SHAPE, LColor::math},
548         {"mathsf",        LyXFont::SANS_FAMILY, inh_series,
549                           inh_shape, LColor::math},
550         {"mathbb",        LyXFont::MSB_FAMILY, inh_series,
551                           inh_shape, LColor::math},
552         {"mathtt",        LyXFont::TYPEWRITER_FAMILY, inh_series,
553                           inh_shape, LColor::math},
554         {"mathit",        inh_family, inh_series,
555                           LyXFont::ITALIC_SHAPE, LColor::math},
556         {"cmex",          LyXFont::CMEX_FAMILY, inh_series,
557                           inh_shape, LColor::math},
558         {"cmm",           LyXFont::CMM_FAMILY, inh_series,
559                           inh_shape, LColor::math},
560         {"cmr",           LyXFont::CMR_FAMILY, inh_series,
561                           inh_shape, LColor::math},
562         {"cmsy",          LyXFont::CMSY_FAMILY, inh_series,
563                           inh_shape, LColor::math},
564         {"eufrak",        LyXFont::EUFRAK_FAMILY, inh_series,
565                           inh_shape, LColor::math},
566         {"msa",           LyXFont::MSA_FAMILY, inh_series,
567                           inh_shape, LColor::math},
568         {"msb",           LyXFont::MSB_FAMILY, inh_series,
569                           inh_shape, LColor::math},
570         {"wasy",          LyXFont::WASY_FAMILY, inh_series,
571                           inh_shape, LColor::none},
572
573         // Text fonts
574         {"text",          inh_family, inh_series,
575                           inh_shape, LColor::foreground},
576         {"textbf",        inh_family, LyXFont::BOLD_SERIES,
577                           inh_shape, LColor::foreground},
578         {"textit",        inh_family, inh_series,
579                           LyXFont::ITALIC_SHAPE, LColor::foreground},
580         {"textmd",        inh_family, LyXFont::MEDIUM_SERIES,
581                           inh_shape, LColor::foreground},
582         {"textnormal",    inh_family, inh_series,
583                           LyXFont::UP_SHAPE, LColor::foreground},
584         {"textrm",        LyXFont::ROMAN_FAMILY,
585                           inh_series,LyXFont::UP_SHAPE,LColor::foreground},
586         {"textsc",        inh_family, inh_series,
587                           LyXFont::SMALLCAPS_SHAPE, LColor::foreground},
588         {"textsf",        LyXFont::SANS_FAMILY, inh_series,
589                           inh_shape, LColor::foreground},
590         {"textsl",        inh_family, inh_series,
591                           LyXFont::SLANTED_SHAPE, LColor::foreground},
592         {"texttt",        LyXFont::TYPEWRITER_FAMILY, inh_series,
593                           inh_shape, LColor::foreground},
594         {"textup",        inh_family, inh_series,
595                           LyXFont::UP_SHAPE, LColor::foreground},
596
597         // TIPA support
598         {"textipa",       inh_family, inh_series,
599                           inh_shape, LColor::foreground},
600
601         // LyX internal usage
602         {"lyxtex",        inh_family, inh_series,
603                           LyXFont::UP_SHAPE, LColor::latex},
604         {"lyxert",        LyXFont::TYPEWRITER_FAMILY, inh_series,
605                           LyXFont::UP_SHAPE, LColor::latex},
606         {"lyxsymbol",     LyXFont::SYMBOL_FAMILY, inh_series,
607                           inh_shape, LColor::math},
608         {"lyxboldsymbol", LyXFont::SYMBOL_FAMILY, LyXFont::BOLD_SERIES,
609                           inh_shape, LColor::math},
610         {"lyxblacktext",  LyXFont::ROMAN_FAMILY, LyXFont::MEDIUM_SERIES,
611                     LyXFont::UP_SHAPE, LColor::foreground},
612         {"lyxnochange",   inh_family, inh_series,
613                     inh_shape, LColor::foreground},
614         {"lyxfakebb",     LyXFont::TYPEWRITER_FAMILY, LyXFont::BOLD_SERIES,
615                           LyXFont::UP_SHAPE, LColor::math},
616         {"lyxfakecal",    LyXFont::SANS_FAMILY, LyXFont::MEDIUM_SERIES,
617                           LyXFont::ITALIC_SHAPE, LColor::math},
618         {"lyxfakefrak",   LyXFont::ROMAN_FAMILY, LyXFont::BOLD_SERIES,
619                           LyXFont::ITALIC_SHAPE, LColor::math}
620 };
621
622
623 fontinfo * lookupFont(string const & name)
624 {
625         //lyxerr << "searching font '" << name << "'" << endl;
626         int const n = sizeof(fontinfos) / sizeof(fontinfo);
627         for (int i = 0; i < n; ++i)
628                 if (fontinfos[i].cmd_ == name) {
629                         //lyxerr << "found '" << i << "'" << endl;
630                         return fontinfos + i;
631                 }
632         return 0;
633 }
634
635
636 fontinfo * searchFont(string const & name)
637 {
638         fontinfo * f = lookupFont(name);
639         return f ? f : fontinfos;
640         // this should be mathnormal
641         //return searchFont("mathnormal");
642 }
643
644
645 bool isFontName(string const & name)
646 {
647         return lookupFont(name);
648 }
649
650
651 LyXFont getFont(string const & name)
652 {
653         LyXFont font;
654         augmentFont(font, name);
655         return font;
656 }
657
658
659 void fakeFont(string const & orig, string const & fake)
660 {
661         fontinfo * forig = searchFont(orig);
662         fontinfo * ffake = searchFont(fake);
663         if (forig && ffake) {
664                 forig->family_ = ffake->family_;
665                 forig->series_ = ffake->series_;
666                 forig->shape_  = ffake->shape_;
667                 forig->color_  = ffake->color_;
668         } else {
669                 lyxerr << "Can't fake font '" << orig << "' with '"
670                        << fake << "'" << endl;
671         }
672 }
673
674
675 void augmentFont(LyXFont & font, string const & name)
676 {
677         static bool initialized = false;
678         if (!initialized) {
679                 initialized = true;
680                 // fake fonts if necessary
681                 if (!lyx_gui::font_available(getFont("mathfrak")))
682                         fakeFont("mathfrak", "lyxfakefrak");
683                 if (!lyx_gui::font_available(getFont("mathcal")))
684                         fakeFont("mathcal", "lyxfakecal");
685         }
686         fontinfo * info = searchFont(name);
687         if (info->family_ != inh_family)
688                 font.setFamily(info->family_);
689         if (info->series_ != inh_series)
690                 font.setSeries(info->series_);
691         if (info->shape_ != inh_shape)
692                 font.setShape(info->shape_);
693         if (info->color_ != LColor::none)
694                 font.setColor(info->color_);
695 }
696
697
698 string asString(MathArray const & ar)
699 {
700         std::ostringstream os;
701         WriteStream ws(os);
702         ws << ar;
703         return os.str();
704 }
705
706
707 void asArray(string const & str, MathArray & ar)
708 {
709         mathed_parse_cell(ar, str);
710 }
711
712
713 string asString(MathInset const & inset)
714 {
715         std::ostringstream os;
716         WriteStream ws(os);
717         inset.write(ws);
718         return os.str();
719 }
720
721
722 string asString(MathAtom const & at)
723 {
724         std::ostringstream os;
725         WriteStream ws(os);
726         at->write(ws);
727         return os.str();
728 }