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