]> git.lyx.org Git - lyx.git/blob - src/insets/InsetQuotes.cpp
Support for CJK quotation marks
[lyx.git] / src / insets / InsetQuotes.cpp
1 /**
2  * \file InsetQuotes.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Jean-Marc Lasgouttes
7  * \author Jürgen Spitzmüller
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "InsetQuotes.h"
15
16 #include "Buffer.h"
17 #include "BufferParams.h"
18 #include "BufferView.h"
19 #include "Cursor.h"
20 #include "Dimension.h"
21 #include "Encoding.h"
22 #include "Font.h"
23 #include "FuncStatus.h"
24 #include "FuncRequest.h"
25 #include "Language.h"
26 #include "LaTeXFeatures.h"
27 #include "Lexer.h"
28 #include "LyXRC.h"
29 #include "MetricsInfo.h"
30 #include "OutputParams.h"
31 #include "output_xhtml.h"
32 #include "Paragraph.h"
33 #include "ParIterator.h"
34 #include "texstream.h"
35
36 #include "frontends/FontMetrics.h"
37 #include "frontends/Painter.h"
38
39 #include "support/debug.h"
40 #include "support/docstring.h"
41 #include "support/docstream.h"
42 #include "support/gettext.h"
43 #include "support/lstrings.h"
44
45 #include <string.h>
46
47 using namespace std;
48 using namespace lyx::support;
49
50 namespace lyx {
51
52 namespace {
53
54 /* codes used to read/write quotes to LyX files
55  * available styles:
56  * e    ``english''  (`inner quotation')
57  * s    ''swedish''  ('inner quotation')
58  * g    ,,german``   (,inner quotation`)
59  * p    ,,polish''   (,inner quotation')
60  * c    <<swiss>>    (<inner quotation>)
61  * a    >>danish<<   (>inner quotation<)
62  * q    "plain"      ('inner quotation')
63  * b    `british'    (``inner quotation'')
64  * w    >>swedishg>> ('inner quotation') ["g" = Guillemets]
65  * f    <<french>>   (``inner quotation'')
66  * i    <<frenchin>> (<<inner quotation>>) ["in" = Imprimerie Nationale]
67  * r    <<russian>>  (,,inner quotation``)
68  * j    [U+300C]cjk[U+300D]  ([U+300E]inner quotation[U+300F]) [CORNER BRACKETS]
69  * k    [U+300A]cjkangle[U+300B]  ([U+3008]inner quotation[U+3009]) [ANGLE BRACKETS]
70  * x    dynamic style (inherits document settings)
71  */
72
73 char const * const style_char = "esgpcaqbwfirjkx";
74 char const * const side_char = "lr" ;
75 char const * const level_char = "sd";
76
77 } // namespace anon
78
79
80 /////////////////////////////////////////////////////////////////////
81 //
82 // InsetQuotesParams
83 //
84 ///////////////////////////////////////////////////////////////////////
85
86 InsetQuotesParams quoteparams;
87
88
89 int InsetQuotesParams::stylescount() const
90 {
91         return strlen(style_char);
92 }
93
94
95 char_type InsetQuotesParams::getQuoteChar(QuoteStyle const & style, QuoteLevel const & level,
96                                     QuoteSide const & side) const
97 {
98         // main opening quotation mark
99         char_type left_primary;
100         // main closing quotation mark
101         char_type right_primary;
102         // secondary (inner, 'single') opening quotation mark
103         char_type left_secondary;
104         // secondary (inner, 'single') closing quotation mark
105         char_type right_secondary;
106
107         switch (style) {
108         case EnglishQuotes: {
109                 left_primary = 0x201c; // ``
110                 right_primary = 0x201d; // ''
111                 left_secondary = 0x2018; // `
112                 right_secondary = 0x2019; // '
113                 break;
114         }
115         case SwedishQuotes: {
116                 left_primary = 0x201d; // ''
117                 right_primary = 0x201d; // ''
118                 left_secondary = 0x2019; // '
119                 right_secondary = 0x2019; // '
120                 break;
121         }
122         case GermanQuotes: {
123                 left_primary = 0x201e; // ,,
124                 right_primary = 0x201c; // ``
125                 left_secondary = 0x201a; // ,
126                 right_secondary = 0x2018; // `
127                 break;
128         }
129         case PolishQuotes: {
130                 left_primary =  0x201e; // ,,
131                 right_primary = 0x201d; // ''
132                 left_secondary = 0x201a; // ,
133                 right_secondary = 0x2019; // '
134                 break;
135         }
136         case SwissQuotes: {
137                 left_primary = 0x00ab; // <<
138                 right_primary = 0x00bb; // >>
139                 left_secondary = 0x2039; // <
140                 right_secondary = 0x203a; // >
141                 break;
142         }
143         case DanishQuotes: {
144                 left_primary = 0x00bb; // >>
145                 right_primary = 0x00ab; // <<
146                 left_secondary = 0x203a; // >
147                 right_secondary = 0x2039; // <
148                 break;
149         }
150         case PlainQuotes: {
151                 left_primary = 0x0022; // "
152                 right_primary = 0x0022; // "
153                 left_secondary = 0x0027; // '
154                 right_secondary = 0x0027; // '
155                 break;
156         }
157         case BritishQuotes: {
158                 left_primary = 0x2018; // `
159                 right_primary = 0x2019; // '
160                 left_secondary = 0x201c; // ``
161                 right_secondary = 0x201d; // ''
162                 break;
163         }
164         case SwedishGQuotes: {
165                 left_primary = 0x00bb; // >>
166                 right_primary = 0x00bb; // >>
167                 left_secondary = 0x2019; // '
168                 right_secondary = 0x2019; // '
169                 break;
170         }
171         case FrenchQuotes: {
172                 left_primary = 0x00ab; // <<
173                 right_primary = 0x00bb; // >>
174                 left_secondary = 0x201c; // ``
175                 right_secondary = 0x201d; // ''
176                 break;
177         }
178         case FrenchINQuotes:{
179                 left_primary = 0x00ab; // <<
180                 right_primary = 0x00bb; // >>
181                 left_secondary =  0x00ab; // <<
182                 right_secondary = 0x00bb; // >>
183                 break;
184         }
185         case RussianQuotes:{
186                 left_primary = 0x00ab; // <<
187                 right_primary = 0x00bb; // >>
188                 left_secondary =  0x201e; // ,,
189                 right_secondary = 0x201c; // ``
190                 break;
191         }
192         case CJKQuotes:{
193                 left_primary = 0x300c; // LEFT CORNER BRACKET
194                 right_primary = 0x300d; // RIGHT CORNER BRACKET
195                 left_secondary =  0x300e; // LEFT WHITE CORNER BRACKET
196                 right_secondary = 0x300f; // RIGHT WHITE CORNER BRACKET
197                 break;
198         }
199         case CJKAngleQuotes:{
200                 left_primary = 0x300a; // LEFT DOUBLE ANGLE BRACKET
201                 right_primary = 0x300b; // RIGHT DOUBLE ANGLE BRACKET
202                 left_secondary =  0x3008; // LEFT ANGLE BRACKET
203                 right_secondary = 0x3009; // RIGHT ANGLE BRACKET
204                 break;
205         }
206         case DynamicQuotes:
207         default:
208                 // should not happen
209                 left_primary = 0x003f; // ?
210                 right_primary = 0x003f; // ?
211                 left_secondary =  0x003f; // ?
212                 right_secondary = 0x003f; // ?
213                 break;
214         }
215
216         switch (level) {
217         case SecondaryQuotes:
218                 return (side == OpeningQuote) ? left_secondary : right_secondary;
219         case PrimaryQuotes:
220                 return (side == OpeningQuote) ? left_primary : right_primary;
221         default:
222                 break;
223         }
224
225         // should not happen
226         return 0x003f;
227 }
228
229
230 docstring InsetQuotesParams::getLaTeXQuote(char_type c, string const & op) const
231 {
232         string res;
233
234         switch (c){
235         case 0x201a: {// ,
236                 if (op == "babel")
237                         res = "\\glq";
238                 else
239                         res = "\\quotesinglbase";
240                 break;
241         }
242         case 0x2019: {// '
243                 if (op == "int")
244                         res = "\\textquoteleft";
245                 else
246                         res = "'";
247                 break;
248         }
249         case 0x2018: {// `
250                 if (op == "int")
251                         res = "\\textquoteright";
252                 else
253                         res = "`";
254                 break;
255         }
256         case 0x2039: {// <
257                 if (op == "babel")
258                         res = "\\flq";
259                 else
260                         res = "\\guilsinglleft";
261                 break;
262         }
263         case 0x203a: {// >
264                 if (op == "babel")
265                         res = "\\frq";
266                 else
267                         res = "\\guilsinglright";
268                 break;
269         }
270         case 0x0027: {// ' (plain)
271                 res = "\\textquotesingle";
272                 break;
273         }
274         case 0x201e: {// ,,
275                 if (op == "t1")
276                         res = ",,";
277                 else if (op == "babel")
278                         res = "\\glqq";
279                 else
280                         res = "\\quotedblbase";
281                 break;
282         }
283         case 0x201d: {// ''
284                 if (op == "int")
285                         res = "\\textquotedblleft";
286                 else
287                         res = "''";
288                 break;
289         }
290         case 0x201c: {// ``
291                 if (op == "int")
292                         res = "\\textquotedblright";
293                 else
294                         res = "``";
295                 break;
296         }
297         case 0x00ab: {// <<
298                 if (op == "t1")
299                         res = "<<";
300                 else if (op == "babel")
301                         res = "\\flqq";
302                 else
303                         res = "\\guillemotleft";
304                 break;
305         }
306         case 0x00bb: {// >>
307                 if (op == "t1")
308                         res = ">>";
309                 else if (op == "babel")
310                         res = "\\frqq";
311                 else
312                         res = "\\guillemotright";
313                 break;
314         }
315         case 0x0022: {// "
316                 res = "\\textquotedbl";
317                 break;
318         }
319         // The following are fakes
320         // This is just to get something symbolic
321         // in encodings where this chars would not be used ayway
322         case 0x300c: // LEFT CORNER BRACKET
323                 res = "\\ensuremath{\\lceil}";
324                 break;
325         case 0x300d: // RIGHT CORNER BRACKET
326                 res = "\\ensuremath{\\rfloor}";
327                 break;
328         case 0x300e: // LEFT WHITE CORNER BRACKET
329                 res = "\\ensuremath{\\llceil}";
330                 break;
331         case 0x300f: // RIGHT WHITE CORNER BRACKET
332                 res = "\\ensuremath{\\rrfloor}";
333                 break;
334         case 0x300a: // LEFT DOUBLE ANGLE BRACKET
335                 res = "\\ensuremath{\\langle\\kern-2.5pt\\langle}";
336                 break;
337         case 0x300b: // RIGHT DOUBLE ANGLE BRACKET
338                 res = "\\ensuremath{\\rangle\\kern-2.5pt\\rangle}";
339                 break;
340         case 0x3008: // LEFT ANGLE BRACKET
341                 res = "\\ensuremath{\\langle}";
342                 break;
343         case 0x3009: // RIGHT ANGLE BRACKET
344                 res = "\\ensuremath{\\rangle}";
345                 break;
346         default:
347                 break;
348         }
349         
350         return from_ascii(res);
351 }
352
353
354 docstring InsetQuotesParams::getHTMLQuote(char_type c) const
355 {
356         string res;
357
358         switch (c){
359         case 0x201a: // ,
360                 res = "&sbquo;";
361                 break;
362         case 0x2019: // '
363                 res = "&rsquo;";
364                 break;
365         case 0x2018: // `
366                 res = "&lsquo;";
367                 break;
368         case 0x2039: // <
369                 res = "&lsaquo;";
370                 break;
371         case 0x203a: // >
372                 res = "&rsaquo;";
373                 break;
374         case 0x0027: // ' (plain)
375                 res = "&#x27;";
376                 break;
377         case 0x201e: // ,,
378                 res = "&bdquo;";
379                 break;
380         case 0x201d: // ''
381                 res = "&rdquo;";
382                 break;
383         case 0x201c: // ``
384                 res = "&ldquo;";
385                 break;
386         case 0x00ab: // <<
387                 res = "&laquo;";
388                 break;
389         case 0x00bb: // >>
390                 res = "&raquo;";
391                 break;
392         case 0x0022: // "
393                 res = "&quot;";
394                 break;
395         case 0x300c: // LEFT CORNER BRACKET
396                 res = "&#x300c;";
397                 break;
398         case 0x300d: // RIGHT CORNER BRACKET
399                 res = "&#x300d;";
400                 break;
401         case 0x300e: // LEFT WHITE CORNER BRACKET
402                 res = "&#x300e;";
403                 break;
404         case 0x300f: // RIGHT WHITE CORNER BRACKET
405                 res = "&#x300f;";
406                 break;
407         case 0x300a: // LEFT DOUBLE ANGLE BRACKET
408                 res = "&#x300a;";
409                 break;
410         case 0x300b: // RIGHT DOUBLE ANGLE BRACKET
411                 res = "&#x300b;";
412                 break;
413         case 0x3008: // LEFT ANGLE BRACKET
414                 res = "&#x3008;";
415                 break;
416         case 0x3009: // RIGHT ANGLE BRACKET
417                 res = "&#x3009;";
418                 break;
419         default:
420                 break;
421         }
422         
423         return from_ascii(res);
424 }
425
426
427 map<string, docstring> InsetQuotesParams::getTypes() const
428 {
429         map<string, docstring> res;
430
431         int sty, sid, lev;
432         QuoteStyle style;
433         QuoteSide side;
434         QuoteLevel level;
435         string type;
436
437         // get all quote types
438         for (sty = 0; sty < stylescount(); ++sty) {
439                 style = QuoteStyle(sty);
440                 if (style == DynamicQuotes)
441                         continue;
442                 for (sid = 0; sid < 2; ++sid) {
443                         side = QuoteSide(sid);
444                         for (lev = 0; lev < 2; ++lev) {
445                                 type += style_char[style];
446                                 type += side_char[sid];
447                                 level = QuoteLevel(lev);
448                                 type += level_char[lev];
449                                 res[type] = docstring(1, getQuoteChar(style, level, side));
450                                 type.clear();
451                         }
452                 }
453         }
454         return res;
455 }
456
457
458 docstring const InsetQuotesParams::getGuiLabel(QuoteStyle const & qs)
459 {
460         return bformat(_("%1$souter%2$s and %3$sinner%4$s[[quotation marks]]"),
461                 docstring(1, quoteparams.getQuoteChar(qs, PrimaryQuotes, OpeningQuote)),
462                 docstring(1, quoteparams.getQuoteChar(qs, PrimaryQuotes, ClosingQuote)),
463                 docstring(1, quoteparams.getQuoteChar(qs, SecondaryQuotes, OpeningQuote)),
464                 docstring(1, quoteparams.getQuoteChar(qs, SecondaryQuotes, ClosingQuote))
465                 );
466 }
467
468
469 /////////////////////////////////////////////////////////////////////
470 //
471 // InsetQuotes
472 //
473 ///////////////////////////////////////////////////////////////////////
474
475 InsetQuotes::InsetQuotes(Buffer * buf, string const & str) : Inset(buf)
476 {
477         if (buf)
478                 global_style_ = buf->masterBuffer()->params().quotes_style;
479         else
480                 global_style_ = InsetQuotesParams::EnglishQuotes;
481
482         parseString(str);
483 }
484
485
486 InsetQuotes::InsetQuotes(Buffer * buf, char_type c, InsetQuotesParams::QuoteLevel level,
487                          string const & side, string const & style)
488         : Inset(buf), level_(level), pass_thru_(false)
489 {
490         bool dynamic = false;
491         if (buf) {
492                 global_style_ = buf->masterBuffer()->params().quotes_style;
493                 fontenc_ = (buf->masterBuffer()->params().fontenc == "global")
494                         ? lyxrc.fontenc : buf->params().fontenc;
495                 dynamic = buf->masterBuffer()->params().dynamic_quotes;
496         } else {
497                 global_style_ = InsetQuotesParams::EnglishQuotes;
498                 fontenc_ = lyxrc.fontenc;
499         }
500         if (style.empty())
501                 style_ = dynamic ? InsetQuotesParams::DynamicQuotes : global_style_;
502         else
503                 style_ = getStyle(style);
504
505         if (side == "left" || side == "opening")
506                 side_ = InsetQuotesParams::OpeningQuote;
507         else if (side == "right" || side == "closing")
508                 side_ = InsetQuotesParams::ClosingQuote;
509         else
510                 setSide(c);
511 }
512
513
514 docstring InsetQuotes::layoutName() const
515 {
516         return from_ascii("Quotes");
517 }
518
519
520 void InsetQuotes::setSide(char_type c)
521 {
522         // Decide whether opening or closing quote
523         switch (c) {
524         case ' ':
525         case '(':
526         case '[':
527                 side_ = InsetQuotesParams::OpeningQuote;// opening quote
528                 break;
529         default:
530                 side_ = InsetQuotesParams::ClosingQuote;// closing quote
531         }
532 }
533
534
535 void InsetQuotes::parseString(string const & s, bool const allow_wildcards)
536 {
537         string str = s;
538         if (str.length() != 3) {
539                 lyxerr << "ERROR (InsetQuotes::InsetQuotes):"
540                         " bad string length." << endl;
541                 str = "eld";
542         }
543
544         int i;
545
546         // '.' wildcard means: keep current stylee
547         if (!allow_wildcards || str[0] != '.') {
548                 for (i = 0; i < quoteparams.stylescount(); ++i) {
549                         if (str[0] == style_char[i]) {
550                                 style_ = InsetQuotesParams::QuoteStyle(i);
551                                 break;
552                         }
553                 }
554                 if (i >= quoteparams.stylescount()) {
555                         LYXERR0("ERROR (InsetQuotes::InsetQuotes):"
556                                 " bad style specification.");
557                         style_ = InsetQuotesParams::EnglishQuotes;
558                 }
559         }
560
561         // '.' wildcard means: keep current side
562         if (!allow_wildcards || str[1] != '.') {
563                 for (i = 0; i < 2; ++i) {
564                         if (str[1] == side_char[i]) {
565                                 side_ = InsetQuotesParams::QuoteSide(i);
566                                 break;
567                         }
568                 }
569                 if (i >= 2) {
570                         LYXERR0("ERROR (InsetQuotes::InsetQuotes):"
571                                 " bad side specification.");
572                         side_ = InsetQuotesParams::OpeningQuote;
573                 }
574         }
575
576         // '.' wildcard means: keep current level
577         if (!allow_wildcards || str[2] != '.') {
578                 for (i = 0; i < 2; ++i) {
579                         if (str[2] == level_char[i]) {
580                                 level_ = InsetQuotesParams::QuoteLevel(i);
581                                 break;
582                         }
583                 }
584                 if (i >= 2) {
585                         LYXERR0("ERROR (InsetQuotes::InsetQuotes):"
586                                 " bad level specification.");
587                         level_ = InsetQuotesParams::PrimaryQuotes;
588                 }
589         }
590 }
591
592
593 InsetQuotesParams::QuoteStyle InsetQuotes::getStyle(string const & s)
594 {
595         InsetQuotesParams::QuoteStyle qs = InsetQuotesParams::EnglishQuotes;
596         
597         if (s == "english")
598                 qs = InsetQuotesParams::EnglishQuotes;
599         else if (s == "swedish")
600                 qs = InsetQuotesParams::SwedishQuotes;
601         else if (s == "german")
602                 qs = InsetQuotesParams::GermanQuotes;
603         else if (s == "polish")
604                 qs = InsetQuotesParams::PolishQuotes;
605         else if (s == "swiss")
606                 qs = InsetQuotesParams::SwissQuotes;
607         else if (s == "danish")
608                 qs = InsetQuotesParams::DanishQuotes;
609         else if (s == "plain")
610                 qs = InsetQuotesParams::PlainQuotes;
611         else if (s == "british")
612                 qs = InsetQuotesParams::BritishQuotes;
613         else if (s == "swedishg")
614                 qs = InsetQuotesParams::SwedishGQuotes;
615         else if (s == "french")
616                 qs = InsetQuotesParams::FrenchQuotes;
617         else if (s == "frenchin")
618                 qs = InsetQuotesParams::FrenchINQuotes;
619         else if (s == "russian")
620                 qs = InsetQuotesParams::RussianQuotes;
621         else if (s == "cjk")
622                 qs = InsetQuotesParams::CJKQuotes;
623         else if (s == "cjkangle")
624                 qs = InsetQuotesParams::CJKAngleQuotes;
625         else if (s == "dynamic")
626                 qs = InsetQuotesParams::DynamicQuotes;
627
628         return qs;
629 }
630
631
632 docstring InsetQuotes::displayString() const
633 {
634         // In PassThru, we use straight quotes
635         if (pass_thru_)
636                 return (level_ == InsetQuotesParams::PrimaryQuotes) ?
637                                         from_ascii("\"") : from_ascii("'");
638
639         InsetQuotesParams::QuoteStyle style =
640                         (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
641
642         docstring retdisp = docstring(1, quoteparams.getQuoteChar(style, level_, side_));
643
644         // in French, thin spaces are added inside double guillemets
645         if (prefixIs(context_lang_, "fr")
646             && level_ == InsetQuotesParams::PrimaryQuotes
647             && (style == InsetQuotesParams::SwissQuotes
648                 || style == InsetQuotesParams::FrenchQuotes
649                 || style == InsetQuotesParams::FrenchINQuotes)) {
650                 // THIN SPACE (U+2009)
651                 char_type const thin_space = 0x2009;
652                 if (side_ == InsetQuotesParams::OpeningQuote)
653                         retdisp += thin_space;
654                 else
655                         retdisp = thin_space + retdisp;
656         }
657
658         return retdisp;
659 }
660
661
662 void InsetQuotes::metrics(MetricsInfo & mi, Dimension & dim) const
663 {
664         FontInfo & font = mi.base.font;
665         frontend::FontMetrics const & fm = theFontMetrics(font);
666         dim.asc = fm.maxAscent();
667         dim.des = fm.maxDescent();
668         dim.wid = fm.width(displayString());
669 }
670
671
672 void InsetQuotes::draw(PainterInfo & pi, int x, int y) const
673 {
674         FontInfo font = pi.base.font;
675         if (style_ == InsetQuotesParams::DynamicQuotes)
676                 font.setPaintColor(Color_special);
677         else
678                 font.setPaintColor(pi.textColor(font.realColor()));
679         pi.pain.text(x, y, displayString(), font);
680 }
681
682
683 string InsetQuotes::getType() const
684 {
685         string text;
686         text += style_char[style_];
687         text += side_char[side_];
688         text += level_char[level_];
689         return text;
690 }
691
692
693 void InsetQuotes::write(ostream & os) const
694 {
695         os << "Quotes " << getType();
696 }
697
698
699 void InsetQuotes::read(Lexer & lex)
700 {
701         lex.setContext("InsetQuotes::read");
702         lex.next();
703         parseString(lex.getString());
704         lex >> "\\end_inset";
705 }
706
707
708 void InsetQuotes::doDispatch(Cursor & cur, FuncRequest & cmd)
709 {
710         switch (cmd.action()) {
711         case LFUN_INSET_MODIFY: {
712                 string const first_arg = cmd.getArg(0);
713                 bool const change_type = first_arg == "changetype";
714                 if (!change_type) {
715                         // not for us
716                         // this will not be handled higher up
717                         cur.undispatched();
718                         return;
719                 }
720                 cur.recordUndoInset(this);
721                 parseString(cmd.getArg(1), true);
722                 cur.buffer()->updateBuffer();
723                 break;
724         }
725         default:
726                 Inset::doDispatch(cur, cmd);
727                 break;
728         }
729 }
730
731
732 bool InsetQuotes::getStatus(Cursor & cur, FuncRequest const & cmd,
733                 FuncStatus & flag) const
734 {
735         switch (cmd.action()) {
736
737         case LFUN_INSET_MODIFY: {
738                 string const first_arg = cmd.getArg(0);
739                 if (first_arg == "changetype") {
740                         string const type = cmd.getArg(1);
741                         flag.setOnOff(type == getType());
742                         flag.setEnabled(!pass_thru_);
743                         return true;
744                 }
745                 return Inset::getStatus(cur, cmd, flag);
746         }
747
748         default:
749                 return Inset::getStatus(cur, cmd, flag);
750         }
751 }
752
753
754 void InsetQuotes::latex(otexstream & os, OutputParams const & runparams) const
755 {
756         InsetQuotesParams::QuoteStyle style =
757                         (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
758         char_type quotechar = quoteparams.getQuoteChar(style, level_, side_);
759         docstring qstr;
760
761         // In pass-thru context, we output plain quotes
762         if (runparams.pass_thru)
763                 qstr = (level_ == InsetQuotesParams::PrimaryQuotes) ? from_ascii("\"") : from_ascii("'");
764         else if (style == InsetQuotesParams::PlainQuotes && runparams.isFullUnicode()) {
765                 // For XeTeX and LuaTeX,we need to disable mapping to get straight
766                 // quotes. We define our own commands that do this
767                 qstr = (level_ == InsetQuotesParams::PrimaryQuotes) ?
768                         from_ascii("\\textquotedblplain") : from_ascii("\\textquotesingleplain");
769         }
770         else if (runparams.use_polyglossia) {
771                 // For polyglossia, we directly output the respective unicode chars 
772                 // (spacing and kerning is then handled respectively)
773                 qstr = docstring(1, quotechar);
774         }
775         else if (style == InsetQuotesParams::CJKQuotes || style  == InsetQuotesParams::CJKAngleQuotes) {
776                 if (runparams.encoding && runparams.encoding->encodable(quotechar))
777                         qstr = docstring(1, quotechar);
778                 else
779                         qstr = quoteparams.getLaTeXQuote(quotechar, "int");
780         }
781         else if ((style == InsetQuotesParams::SwissQuotes
782                  || style == InsetQuotesParams::FrenchQuotes
783                  || style == InsetQuotesParams::FrenchINQuotes)
784                  && level_ == InsetQuotesParams::PrimaryQuotes
785                  && prefixIs(runparams.local_font->language()->code(), "fr")) {
786                 // Specific guillemets of French babel
787                 // including correct French spacing
788                 if (side_ == InsetQuotesParams::OpeningQuote)
789                         qstr = from_ascii("\\og");
790                 else
791                         qstr = from_ascii("\\fg");
792         } else if (fontenc_ == "T1"
793                    && !runparams.local_font->language()->internalFontEncoding()) {
794                 // Quotation marks for T1 font encoding
795                 // (using ligatures)
796                 qstr = quoteparams.getLaTeXQuote(quotechar, "t1");
797         } else if (runparams.local_font->language()->internalFontEncoding()) {
798                 // Quotation marks for internal font encodings
799                 // (ligatures not featured)
800                 qstr = quoteparams.getLaTeXQuote(quotechar, "int");
801 #ifdef DO_USE_DEFAULT_LANGUAGE
802         } else if (doclang == "default") {
803 #else
804         } else if (!runparams.use_babel || runparams.isFullUnicode()) {
805 #endif
806                 // Standard quotation mark macros
807                 // These are also used by babel
808                 // without fontenc (XeTeX/LuaTeX)
809                 qstr = quoteparams.getLaTeXQuote(quotechar, "ot1");
810         } else {
811                 // Babel shorthand quotation marks (for T1/OT1)
812                 qstr = quoteparams.getLaTeXQuote(quotechar, "babel");
813         }
814
815         if (!runparams.pass_thru) {
816                 // Guard against unwanted ligatures with preceding text
817                 char_type const lastchar = os.lastChar();
818                 // !` ?` => !{}` ?{}`
819                 if (prefixIs(qstr, from_ascii("`"))
820                     && (lastchar == '!' || lastchar == '?'))
821                         os << "{}";
822                 // ``` ''' ,,, <<< >>>
823                 // => `{}`` '{}'' ,{},, <{}<< >{}>>
824                 if (contains(from_ascii(",'`<>"), lastchar)
825                     && prefixIs(qstr, lastchar))
826                         os << "{}";
827         }
828
829         os << qstr;
830
831         if (prefixIs(qstr, from_ascii("\\")) && !suffixIs(qstr, '}'))
832                 // properly terminate the command depending on the context
833                 os << termcmd;
834 }
835
836
837 int InsetQuotes::plaintext(odocstringstream & os, 
838         OutputParams const &, size_t) const
839 {
840         docstring const str = displayString();
841         os << str;
842         return str.size();
843 }
844
845
846 docstring InsetQuotes::getQuoteEntity() const {
847         InsetQuotesParams::QuoteStyle style =
848                         (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
849         docstring res = quoteparams.getHTMLQuote(quoteparams.getQuoteChar(style, level_, side_));
850         // in French, thin spaces are added inside double guillemets
851         if (prefixIs(context_lang_, "fr")
852             && level_ == InsetQuotesParams::PrimaryQuotes
853             && (style == InsetQuotesParams::FrenchQuotes
854                 || style == InsetQuotesParams::FrenchINQuotes
855                 || style == InsetQuotesParams::SwissQuotes)) {
856                 // THIN SPACE (U+2009)
857                 docstring const thin_space = from_ascii("&#x2009;");
858                 if (side_ == InsetQuotesParams::OpeningQuote)
859                         res += thin_space;
860                 else
861                         res = thin_space + res;
862         }
863         return res;
864 }
865
866
867 int InsetQuotes::docbook(odocstream & os, OutputParams const &) const
868 {
869         os << getQuoteEntity();
870         return 0;
871 }
872
873
874 docstring InsetQuotes::xhtml(XHTMLStream & xs, OutputParams const &) const
875 {
876         xs << XHTMLStream::ESCAPE_NONE << getQuoteEntity();
877         return docstring();
878 }
879
880
881 void InsetQuotes::toString(odocstream & os) const
882 {
883         os << displayString();
884 }
885
886
887 void InsetQuotes::forOutliner(docstring & os, size_t const, bool const) const
888 {
889         os += displayString();
890 }
891
892
893 void InsetQuotes::updateBuffer(ParIterator const & it, UpdateType /* utype*/)
894 {
895         BufferParams const & bp = buffer().masterBuffer()->params();
896         pass_thru_ = it.paragraph().isPassThru();
897         context_lang_ = it.paragraph().getFontSettings(bp, it.pos()).language()->code();
898         fontenc_ = (bp.fontenc == "global") ? lyxrc.fontenc : bp.fontenc;
899         global_style_ = bp.quotes_style;
900 }
901
902
903 void InsetQuotes::validate(LaTeXFeatures & features) const
904 {
905         InsetQuotesParams::QuoteStyle style =
906                         (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
907         char_type type = quoteparams.getQuoteChar(style, level_, side_);
908
909         // Handle characters that are not natively supported by
910         // specific font encodings (we roll our own definitions)
911 #ifdef DO_USE_DEFAULT_LANGUAGE
912         if (features.bufferParams().language->lang() == "default"
913 #else
914         if (!features.useBabel()
915 #endif
916             && !features.runparams().isFullUnicode() && fontenc_ != "T1") {
917                 switch (type) {
918                 case 0x201a:
919                         features.require("quotesinglbase");
920                         break;
921                 case 0x2039:
922                         features.require("guilsinglleft");
923                         break;
924                 case 0x203a:
925                         features.require("guilsinglright");
926                         break;
927                 case 0x201e:
928                         features.require("quotedblbase");
929                         break;
930                 case 0x00ab:
931                         features.require("guillemotleft");
932                         break;
933                 case 0x00bb:
934                         features.require("guillemotright");
935                         break;
936                 default:
937                         break;
938                 }
939         }
940         // Handle straight quotation marks. These need special care
941         // in most output formats
942         switch (type) {
943         case 0x0027: {
944                 if (features.runparams().isFullUnicode())
945                                 features.require("textquotesinglep");
946                         else
947                                 features.require("textcomp");
948                         break;
949         }
950         case 0x0022: {
951                 if (features.runparams().isFullUnicode())
952                         features.require("textquotedblp");
953                 else if (fontenc_ != "T1")
954                         features.require("textquotedbl");
955                 break;
956         }
957         // we fake these from math
958         case 0x300e: // LEFT WHITE CORNER BRACKET
959         case 0x300f: // RIGHT WHITE CORNER BRACKET
960                 if (!features.runparams().encoding
961                     || !features.runparams().encoding->encodable(type))
962                         features.require("stmaryrd");
963                 break;
964         default:
965                 break;
966         }
967 }
968
969
970 string InsetQuotes::contextMenuName() const
971 {
972         return "context-quote";
973 }
974
975 } // namespace lyx