2 * \file InsetQuotes.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Jean-Marc Lasgouttes
7 * \author Jürgen Spitzmüller
9 * Full author contact details are available in file CREDITS.
14 #include "InsetQuotes.h"
17 #include "BufferParams.h"
18 #include "BufferView.h"
20 #include "Dimension.h"
22 #include "FuncStatus.h"
23 #include "FuncRequest.h"
25 #include "LaTeXFeatures.h"
28 #include "MetricsInfo.h"
29 #include "OutputParams.h"
30 #include "output_xhtml.h"
31 #include "Paragraph.h"
32 #include "ParIterator.h"
33 #include "texstream.h"
35 #include "frontends/FontMetrics.h"
36 #include "frontends/Painter.h"
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"
47 using namespace lyx::support;
53 /* codes used to read/write quotes to LyX files
55 * e ``english'' (`inner quotation')
56 * s ''swedish'' ('inner quotation')
57 * g ,,german`` (,inner quotation`)
58 * p ,,polish'' (,inner quotation')
59 * c <<swiss>> (<inner quotation>)
60 * a >>danish<< (>inner quotation<)
61 * q "plain" ('inner quotation')
62 * b `british' (``inner quotation'')
63 * w >>swedishg>> ('inner quotation') ["g" = Guillemets]
64 * f <<french>> (``inner quotation'')
65 * i <<frenchin>> (<<inner quotation>>) ["in" = Imprimerie Nationale]
66 * r <<russian>> (,,inner quotation``)
67 * x dynamic style (inherits document settings)
70 char const * const style_char = "esgpcaqbwfirx";
71 char const * const side_char = "lr" ;
72 char const * const level_char = "sd";
77 /////////////////////////////////////////////////////////////////////
81 ///////////////////////////////////////////////////////////////////////
83 InsetQuotesParams quoteparams;
86 int InsetQuotesParams::stylescount() const
88 return strlen(style_char);
92 char_type InsetQuotesParams::getQuoteChar(QuoteStyle const & style, QuoteLevel const & level,
93 QuoteSide const & side) const
95 // main opening quotation mark
96 char_type left_primary;
97 // main closing quotation mark
98 char_type right_primary;
99 // secondary (inner, 'single') opening quotation mark
100 char_type left_secondary;
101 // secondary (inner, 'single') closing quotation mark
102 char_type right_secondary;
105 case EnglishQuotes: {
106 left_primary = 0x201c; // ``
107 right_primary = 0x201d; // ''
108 left_secondary = 0x2018; // `
109 right_secondary = 0x2019; // '
112 case SwedishQuotes: {
113 left_primary = 0x201d; // ''
114 right_primary = 0x201d; // ''
115 left_secondary = 0x2019; // '
116 right_secondary = 0x2019; // '
120 left_primary = 0x201e; // ,,
121 right_primary = 0x201c; // ``
122 left_secondary = 0x201a; // ,
123 right_secondary = 0x2018; // `
127 left_primary = 0x201e; // ,,
128 right_primary = 0x201d; // ''
129 left_secondary = 0x201a; // ,
130 right_secondary = 0x2019; // '
134 left_primary = 0x00ab; // <<
135 right_primary = 0x00bb; // >>
136 left_secondary = 0x2039; // <
137 right_secondary = 0x203a; // >
141 left_primary = 0x00bb; // >>
142 right_primary = 0x00ab; // <<
143 left_secondary = 0x203a; // >
144 right_secondary = 0x2039; // <
148 left_primary = 0x0022; // "
149 right_primary = 0x0022; // "
150 left_secondary = 0x0027; // '
151 right_secondary = 0x0027; // '
154 case BritishQuotes: {
155 left_primary = 0x2018; // `
156 right_primary = 0x2019; // '
157 left_secondary = 0x201c; // ``
158 right_secondary = 0x201d; // ''
161 case SwedishGQuotes: {
162 left_primary = 0x00bb; // >>
163 right_primary = 0x00bb; // >>
164 left_secondary = 0x2019; // '
165 right_secondary = 0x2019; // '
169 left_primary = 0x00ab; // <<
170 right_primary = 0x00bb; // >>
171 left_secondary = 0x201c; // ``
172 right_secondary = 0x201d; // ''
175 case FrenchINQuotes:{
176 left_primary = 0x00ab; // <<
177 right_primary = 0x00bb; // >>
178 left_secondary = 0x00ab; // <<
179 right_secondary = 0x00bb; // >>
183 left_primary = 0x00ab; // <<
184 right_primary = 0x00bb; // >>
185 left_secondary = 0x201e; // ,,
186 right_secondary = 0x201c; // ``
192 left_primary = 0x003f; // ?
193 right_primary = 0x003f; // ?
194 left_secondary = 0x003f; // ?
195 right_secondary = 0x003f; // ?
200 case SecondaryQuotes:
201 return (side == OpeningQuote) ? left_secondary : right_secondary;
203 return (side == OpeningQuote) ? left_primary : right_primary;
213 docstring InsetQuotesParams::getLaTeXQuote(char_type c, string const & op) const
222 res = "\\quotesinglbase";
227 res = "\\textquoteleft";
234 res = "\\textquoteright";
243 res = "\\guilsinglleft";
250 res = "\\guilsinglright";
253 case 0x0027: {// ' (plain)
254 res = "\\textquotesingle";
260 else if (op == "babel")
263 res = "\\quotedblbase";
268 res = "\\textquotedblleft";
275 res = "\\textquotedblright";
283 else if (op == "babel")
286 res = "\\guillemotleft";
292 else if (op == "babel")
295 res = "\\guillemotright";
299 res = "\\textquotedbl";
306 return from_ascii(res);
310 docstring InsetQuotesParams::getHTMLQuote(char_type c) const
330 case 0x0027: // ' (plain)
355 return from_ascii(res);
359 map<string, docstring> InsetQuotesParams::getTypes() const
361 map<string, docstring> res;
369 // get all quote types
370 for (sty = 0; sty < stylescount(); ++sty) {
371 style = QuoteStyle(sty);
372 if (style == DynamicQuotes)
374 for (sid = 0; sid < 2; ++sid) {
375 side = QuoteSide(sid);
376 for (lev = 0; lev < 2; ++lev) {
377 type += style_char[style];
378 type += side_char[sid];
379 level = QuoteLevel(lev);
380 type += level_char[lev];
381 res[type] = docstring(1, getQuoteChar(style, level, side));
390 docstring const InsetQuotesParams::getGuiLabel(QuoteStyle const & qs)
392 return bformat(_("%1$souter%2$s and %3$sinner%4$s[[quotation marks]]"),
393 docstring(1, quoteparams.getQuoteChar(qs, PrimaryQuotes, OpeningQuote)),
394 docstring(1, quoteparams.getQuoteChar(qs, PrimaryQuotes, ClosingQuote)),
395 docstring(1, quoteparams.getQuoteChar(qs, SecondaryQuotes, OpeningQuote)),
396 docstring(1, quoteparams.getQuoteChar(qs, SecondaryQuotes, ClosingQuote))
401 /////////////////////////////////////////////////////////////////////
405 ///////////////////////////////////////////////////////////////////////
407 InsetQuotes::InsetQuotes(Buffer * buf, string const & str) : Inset(buf)
410 global_style_ = buf->masterBuffer()->params().quotes_style;
412 global_style_ = InsetQuotesParams::EnglishQuotes;
418 InsetQuotes::InsetQuotes(Buffer * buf, char_type c, InsetQuotesParams::QuoteLevel level,
419 string const & side, string const & style)
420 : Inset(buf), level_(level), pass_thru_(false)
422 bool dynamic = false;
424 global_style_ = buf->masterBuffer()->params().quotes_style;
425 fontenc_ = (buf->masterBuffer()->params().fontenc == "global")
426 ? lyxrc.fontenc : buf->params().fontenc;
427 dynamic = buf->masterBuffer()->params().dynamic_quotes;
429 global_style_ = InsetQuotesParams::EnglishQuotes;
430 fontenc_ = lyxrc.fontenc;
433 style_ = dynamic ? InsetQuotesParams::DynamicQuotes : global_style_;
435 style_ = getStyle(style);
437 if (side == "left" || side == "opening")
438 side_ = InsetQuotesParams::OpeningQuote;
439 else if (side == "right" || side == "closing")
440 side_ = InsetQuotesParams::ClosingQuote;
446 docstring InsetQuotes::layoutName() const
448 return from_ascii("Quotes");
452 void InsetQuotes::setSide(char_type c)
454 // Decide whether opening or closing quote
459 side_ = InsetQuotesParams::OpeningQuote;// opening quote
462 side_ = InsetQuotesParams::ClosingQuote;// closing quote
467 void InsetQuotes::parseString(string const & s, bool const allow_wildcards)
470 if (str.length() != 3) {
471 lyxerr << "ERROR (InsetQuotes::InsetQuotes):"
472 " bad string length." << endl;
478 // '.' wildcard means: keep current stylee
479 if (!allow_wildcards || str[0] != '.') {
480 for (i = 0; i < quoteparams.stylescount(); ++i) {
481 if (str[0] == style_char[i]) {
482 style_ = InsetQuotesParams::QuoteStyle(i);
486 if (i >= quoteparams.stylescount()) {
487 LYXERR0("ERROR (InsetQuotes::InsetQuotes):"
488 " bad style specification.");
489 style_ = InsetQuotesParams::EnglishQuotes;
493 // '.' wildcard means: keep current side
494 if (!allow_wildcards || str[1] != '.') {
495 for (i = 0; i < 2; ++i) {
496 if (str[1] == side_char[i]) {
497 side_ = InsetQuotesParams::QuoteSide(i);
502 LYXERR0("ERROR (InsetQuotes::InsetQuotes):"
503 " bad side specification.");
504 side_ = InsetQuotesParams::OpeningQuote;
508 // '.' wildcard means: keep current level
509 if (!allow_wildcards || str[2] != '.') {
510 for (i = 0; i < 2; ++i) {
511 if (str[2] == level_char[i]) {
512 level_ = InsetQuotesParams::QuoteLevel(i);
517 LYXERR0("ERROR (InsetQuotes::InsetQuotes):"
518 " bad level specification.");
519 level_ = InsetQuotesParams::PrimaryQuotes;
525 InsetQuotesParams::QuoteStyle InsetQuotes::getStyle(string const & s)
527 InsetQuotesParams::QuoteStyle qs = InsetQuotesParams::EnglishQuotes;
530 qs = InsetQuotesParams::EnglishQuotes;
531 else if (s == "swedish")
532 qs = InsetQuotesParams::SwedishQuotes;
533 else if (s == "german")
534 qs = InsetQuotesParams::GermanQuotes;
535 else if (s == "polish")
536 qs = InsetQuotesParams::PolishQuotes;
537 else if (s == "swiss")
538 qs = InsetQuotesParams::SwissQuotes;
539 else if (s == "danish")
540 qs = InsetQuotesParams::DanishQuotes;
541 else if (s == "plain")
542 qs = InsetQuotesParams::PlainQuotes;
543 else if (s == "british")
544 qs = InsetQuotesParams::BritishQuotes;
545 else if (s == "swedishg")
546 qs = InsetQuotesParams::SwedishGQuotes;
547 else if (s == "french")
548 qs = InsetQuotesParams::FrenchQuotes;
549 else if (s == "frenchin")
550 qs = InsetQuotesParams::FrenchINQuotes;
551 else if (s == "russian")
552 qs = InsetQuotesParams::RussianQuotes;
553 else if (s == "dynamic")
554 qs = InsetQuotesParams::DynamicQuotes;
560 docstring InsetQuotes::displayString() const
562 // In PassThru, we use straight quotes
564 return (level_ == InsetQuotesParams::PrimaryQuotes) ?
565 from_ascii("\"") : from_ascii("'");
567 InsetQuotesParams::QuoteStyle style =
568 (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
570 docstring retdisp = docstring(1, quoteparams.getQuoteChar(style, level_, side_));
572 // in French, thin spaces are added inside double guillemets
573 if (prefixIs(context_lang_, "fr")
574 && level_ == InsetQuotesParams::PrimaryQuotes
575 && (style == InsetQuotesParams::SwissQuotes
576 || style == InsetQuotesParams::FrenchQuotes
577 || style == InsetQuotesParams::FrenchINQuotes)) {
578 // THIN SPACE (U+2009)
579 char_type const thin_space = 0x2009;
580 if (side_ == InsetQuotesParams::OpeningQuote)
581 retdisp += thin_space;
583 retdisp = thin_space + retdisp;
590 void InsetQuotes::metrics(MetricsInfo & mi, Dimension & dim) const
592 FontInfo & font = mi.base.font;
593 frontend::FontMetrics const & fm = theFontMetrics(font);
594 dim.asc = fm.maxAscent();
595 dim.des = fm.maxDescent();
596 dim.wid = fm.width(displayString());
600 void InsetQuotes::draw(PainterInfo & pi, int x, int y) const
602 FontInfo font = pi.base.font;
603 if (style_ == InsetQuotesParams::DynamicQuotes)
604 font.setPaintColor(Color_special);
606 font.setPaintColor(pi.textColor(font.realColor()));
607 pi.pain.text(x, y, displayString(), font);
611 string InsetQuotes::getType() const
614 text += style_char[style_];
615 text += side_char[side_];
616 text += level_char[level_];
621 void InsetQuotes::write(ostream & os) const
623 os << "Quotes " << getType();
627 void InsetQuotes::read(Lexer & lex)
629 lex.setContext("InsetQuotes::read");
631 parseString(lex.getString());
632 lex >> "\\end_inset";
636 void InsetQuotes::doDispatch(Cursor & cur, FuncRequest & cmd)
638 switch (cmd.action()) {
639 case LFUN_INSET_MODIFY: {
640 string const first_arg = cmd.getArg(0);
641 bool const change_type = first_arg == "changetype";
644 // this will not be handled higher up
648 cur.recordUndoInset(this);
649 parseString(cmd.getArg(1), true);
650 cur.buffer()->updateBuffer();
654 Inset::doDispatch(cur, cmd);
660 bool InsetQuotes::getStatus(Cursor & cur, FuncRequest const & cmd,
661 FuncStatus & flag) const
663 switch (cmd.action()) {
665 case LFUN_INSET_MODIFY: {
666 string const first_arg = cmd.getArg(0);
667 if (first_arg == "changetype") {
668 string const type = cmd.getArg(1);
669 flag.setOnOff(type == getType());
670 flag.setEnabled(!pass_thru_);
673 return Inset::getStatus(cur, cmd, flag);
677 return Inset::getStatus(cur, cmd, flag);
682 void InsetQuotes::latex(otexstream & os, OutputParams const & runparams) const
684 InsetQuotesParams::QuoteStyle style =
685 (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
686 char_type quotechar = quoteparams.getQuoteChar(style, level_, side_);
689 // In pass-thru context, we output plain quotes
690 if (runparams.pass_thru)
691 qstr = (level_ == InsetQuotesParams::PrimaryQuotes) ? from_ascii("\"") : from_ascii("'");
692 else if (style == InsetQuotesParams::PlainQuotes && runparams.isFullUnicode()) {
693 // For XeTeX and LuaTeX,we need to disable mapping to get straight
694 // quotes. We define our own commands that do this
695 qstr = (level_ == InsetQuotesParams::PrimaryQuotes) ?
696 from_ascii("\\textquotedblplain") : from_ascii("\\textquotesingleplain");
698 else if (runparams.use_polyglossia) {
699 // For polyglossia, we directly output the respective unicode chars
700 // (spacing and kerning is then handled respectively)
701 qstr = docstring(1, quotechar);
703 else if ((style == InsetQuotesParams::SwissQuotes
704 || style == InsetQuotesParams::FrenchQuotes
705 || style == InsetQuotesParams::FrenchINQuotes)
706 && level_ == InsetQuotesParams::PrimaryQuotes
707 && prefixIs(runparams.local_font->language()->code(), "fr")) {
708 // Specific guillemets of French babel
709 // including correct French spacing
710 if (side_ == InsetQuotesParams::OpeningQuote)
711 qstr = from_ascii("\\og");
713 qstr = from_ascii("\\fg");
714 } else if (fontenc_ == "T1"
715 && !runparams.local_font->language()->internalFontEncoding()) {
716 // Quotation marks for T1 font encoding
718 qstr = quoteparams.getLaTeXQuote(quotechar, "t1");
719 } else if (runparams.local_font->language()->internalFontEncoding()) {
720 // Quotation marks for internal font encodings
721 // (ligatures not featured)
722 qstr = quoteparams.getLaTeXQuote(quotechar, "int");
723 #ifdef DO_USE_DEFAULT_LANGUAGE
724 } else if (doclang == "default") {
726 } else if (!runparams.use_babel || runparams.isFullUnicode()) {
728 // Standard quotation mark macros
729 // These are also used by babel
730 // without fontenc (XeTeX/LuaTeX)
731 qstr = quoteparams.getLaTeXQuote(quotechar, "ot1");
733 // Babel shorthand quotation marks (for T1/OT1)
734 qstr = quoteparams.getLaTeXQuote(quotechar, "babel");
737 if (!runparams.pass_thru) {
738 // Guard against unwanted ligatures with preceding text
739 char_type const lastchar = os.lastChar();
740 // !` ?` => !{}` ?{}`
741 if (prefixIs(qstr, from_ascii("`"))
742 && (lastchar == '!' || lastchar == '?'))
744 // ``` ''' ,,, <<< >>>
745 // => `{}`` '{}'' ,{},, <{}<< >{}>>
746 if (contains(from_ascii(",'`<>"), lastchar)
747 && prefixIs(qstr, lastchar))
753 if (prefixIs(qstr, from_ascii("\\")))
754 // properly terminate the command depending on the context
759 int InsetQuotes::plaintext(odocstringstream & os,
760 OutputParams const &, size_t) const
762 docstring const str = displayString();
768 docstring InsetQuotes::getQuoteEntity() const {
769 InsetQuotesParams::QuoteStyle style =
770 (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
771 docstring res = quoteparams.getHTMLQuote(quoteparams.getQuoteChar(style, level_, side_));
772 // in French, thin spaces are added inside double guillemets
773 if (prefixIs(context_lang_, "fr")
774 && level_ == InsetQuotesParams::PrimaryQuotes
775 && (style == InsetQuotesParams::FrenchQuotes
776 || style == InsetQuotesParams::FrenchINQuotes
777 || style == InsetQuotesParams::SwissQuotes)) {
778 // THIN SPACE (U+2009)
779 docstring const thin_space = from_ascii(" ");
780 if (side_ == InsetQuotesParams::OpeningQuote)
783 res = thin_space + res;
789 int InsetQuotes::docbook(odocstream & os, OutputParams const &) const
791 os << getQuoteEntity();
796 docstring InsetQuotes::xhtml(XHTMLStream & xs, OutputParams const &) const
798 xs << XHTMLStream::ESCAPE_NONE << getQuoteEntity();
803 void InsetQuotes::toString(odocstream & os) const
805 os << displayString();
809 void InsetQuotes::forOutliner(docstring & os, size_t const, bool const) const
811 os += displayString();
815 void InsetQuotes::updateBuffer(ParIterator const & it, UpdateType /* utype*/)
817 BufferParams const & bp = buffer().masterBuffer()->params();
818 pass_thru_ = it.paragraph().isPassThru();
819 context_lang_ = it.paragraph().getFontSettings(bp, it.pos()).language()->code();
820 fontenc_ = (bp.fontenc == "global") ? lyxrc.fontenc : bp.fontenc;
821 global_style_ = bp.quotes_style;
825 void InsetQuotes::validate(LaTeXFeatures & features) const
827 InsetQuotesParams::QuoteStyle style =
828 (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
829 char_type type = quoteparams.getQuoteChar(style, level_, side_);
831 // Handle characters that are not natively supported by
832 // specific font encodings (we roll our own definitions)
833 #ifdef DO_USE_DEFAULT_LANGUAGE
834 if (features.bufferParams().language->lang() == "default"
836 if (!features.useBabel()
838 && !features.runparams().isFullUnicode() && fontenc_ != "T1") {
841 features.require("quotesinglbase");
844 features.require("guilsinglleft");
847 features.require("guilsinglright");
850 features.require("quotedblbase");
853 features.require("guillemotleft");
856 features.require("guillemotright");
862 // Handle straight quotation marks. These need special care
863 // in most output formats
866 if (features.runparams().isFullUnicode())
867 features.require("textquotesinglep");
869 features.require("textcomp");
873 if (features.runparams().isFullUnicode())
874 features.require("textquotedblp");
875 else if (fontenc_ != "T1")
876 features.require("textquotedbl");
885 string InsetQuotes::contextMenuName() const
887 return "context-quote";