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