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 == "dynamic")
552 qs = InsetQuotesParams::DynamicQuotes;
558 docstring InsetQuotes::displayString() const
560 // In PassThru, we use straight quotes
562 return (level_ == InsetQuotesParams::PrimaryQuotes) ?
563 from_ascii("\"") : from_ascii("'");
565 InsetQuotesParams::QuoteStyle style =
566 (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
568 docstring retdisp = docstring(1, quoteparams.getQuoteChar(style, level_, side_));
570 // in French, thin spaces are added inside double guillemets
571 if (prefixIs(context_lang_, "fr")
572 && level_ == InsetQuotesParams::PrimaryQuotes
573 && (style == InsetQuotesParams::SwissQuotes
574 || style == InsetQuotesParams::FrenchQuotes
575 || style == InsetQuotesParams::FrenchINQuotes)) {
576 // THIN SPACE (U+2009)
577 char_type const thin_space = 0x2009;
578 if (side_ == InsetQuotesParams::OpeningQuote)
579 retdisp += thin_space;
581 retdisp = thin_space + retdisp;
588 void InsetQuotes::metrics(MetricsInfo & mi, Dimension & dim) const
590 FontInfo & font = mi.base.font;
591 frontend::FontMetrics const & fm = theFontMetrics(font);
592 dim.asc = fm.maxAscent();
593 dim.des = fm.maxDescent();
594 dim.wid = fm.width(displayString());
598 void InsetQuotes::draw(PainterInfo & pi, int x, int y) const
600 FontInfo font = pi.base.font;
601 if (style_ == InsetQuotesParams::DynamicQuotes)
602 font.setPaintColor(Color_special);
604 font.setPaintColor(pi.textColor(font.realColor()));
605 pi.pain.text(x, y, displayString(), font);
609 string InsetQuotes::getType() const
612 text += style_char[style_];
613 text += side_char[side_];
614 text += level_char[level_];
619 void InsetQuotes::write(ostream & os) const
621 os << "Quotes " << getType();
625 void InsetQuotes::read(Lexer & lex)
627 lex.setContext("InsetQuotes::read");
629 parseString(lex.getString());
630 lex >> "\\end_inset";
634 void InsetQuotes::doDispatch(Cursor & cur, FuncRequest & cmd)
636 switch (cmd.action()) {
637 case LFUN_INSET_MODIFY: {
638 string const first_arg = cmd.getArg(0);
639 bool const change_type = first_arg == "changetype";
642 // this will not be handled higher up
646 cur.recordUndoInset(this);
647 parseString(cmd.getArg(1), true);
648 cur.buffer()->updateBuffer();
652 Inset::doDispatch(cur, cmd);
658 bool InsetQuotes::getStatus(Cursor & cur, FuncRequest const & cmd,
659 FuncStatus & flag) const
661 switch (cmd.action()) {
663 case LFUN_INSET_MODIFY: {
664 string const first_arg = cmd.getArg(0);
665 if (first_arg == "changetype") {
666 string const type = cmd.getArg(1);
667 flag.setOnOff(type == getType());
668 flag.setEnabled(!pass_thru_);
671 return Inset::getStatus(cur, cmd, flag);
675 return Inset::getStatus(cur, cmd, flag);
680 void InsetQuotes::latex(otexstream & os, OutputParams const & runparams) const
682 InsetQuotesParams::QuoteStyle style =
683 (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
684 char_type quotechar = quoteparams.getQuoteChar(style, level_, side_);
687 // In pass-thru context, we output plain quotes
688 if (runparams.pass_thru)
689 qstr = (level_ == InsetQuotesParams::PrimaryQuotes) ? from_ascii("\"") : from_ascii("'");
690 else if (style == InsetQuotesParams::PlainQuotes && runparams.isFullUnicode()) {
691 // For XeTeX and LuaTeX,we need to disable mapping to get straight
692 // quotes. We define our own commands that do this
693 qstr = (level_ == InsetQuotesParams::PrimaryQuotes) ?
694 from_ascii("\\textquotedblplain") : from_ascii("\\textquotesingleplain");
696 else if (runparams.use_polyglossia) {
697 // For polyglossia, we directly output the respective unicode chars
698 // (spacing and kerning is then handled respectively)
699 qstr = docstring(1, quotechar);
701 else if ((style == InsetQuotesParams::SwissQuotes
702 || style == InsetQuotesParams::FrenchQuotes
703 || style == InsetQuotesParams::FrenchINQuotes)
704 && level_ == InsetQuotesParams::PrimaryQuotes
705 && prefixIs(runparams.local_font->language()->code(), "fr")) {
706 // Specific guillemets of French babel
707 // including correct French spacing
708 if (side_ == InsetQuotesParams::OpeningQuote)
709 qstr = from_ascii("\\og");
711 qstr = from_ascii("\\fg");
712 } else if (fontenc_ == "T1"
713 && !runparams.local_font->language()->internalFontEncoding()) {
714 // Quotation marks for T1 font encoding
716 qstr = quoteparams.getLaTeXQuote(quotechar, "t1");
717 } else if (runparams.local_font->language()->internalFontEncoding()) {
718 // Quotation marks for internal font encodings
719 // (ligatures not featured)
720 qstr = quoteparams.getLaTeXQuote(quotechar, "int");
721 #ifdef DO_USE_DEFAULT_LANGUAGE
722 } else if (doclang == "default") {
724 } else if (!runparams.use_babel || runparams.isFullUnicode()) {
726 // Standard quotation mark macros
727 // These are also used by babel
728 // without fontenc (XeTeX/LuaTeX)
729 qstr = quoteparams.getLaTeXQuote(quotechar, "ot1");
731 // Babel shorthand quotation marks (for T1/OT1)
732 qstr = quoteparams.getLaTeXQuote(quotechar, "babel");
735 if (!runparams.pass_thru) {
736 // Guard against unwanted ligatures with preceding text
737 char_type const lastchar = os.lastChar();
738 // !` ?` => !{}` ?{}`
739 if (prefixIs(qstr, from_ascii("`"))
740 && (lastchar == '!' || lastchar == '?'))
742 // ``` ''' ,,, <<< >>>
743 // => `{}`` '{}'' ,{},, <{}<< >{}>>
744 if (contains(from_ascii(",'`<>"), lastchar)
745 && prefixIs(qstr, lastchar))
751 if (prefixIs(qstr, from_ascii("\\")))
752 // properly terminate the command depending on the context
757 int InsetQuotes::plaintext(odocstringstream & os,
758 OutputParams const &, size_t) const
760 docstring const str = displayString();
766 docstring InsetQuotes::getQuoteEntity() const {
767 InsetQuotesParams::QuoteStyle style =
768 (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
769 docstring res = quoteparams.getHTMLQuote(quoteparams.getQuoteChar(style, level_, side_));
770 // in French, thin spaces are added inside double guillemets
771 if (prefixIs(context_lang_, "fr")
772 && level_ == InsetQuotesParams::PrimaryQuotes
773 && (style == InsetQuotesParams::FrenchQuotes
774 || style == InsetQuotesParams::FrenchINQuotes
775 || style == InsetQuotesParams::SwissQuotes)) {
776 // THIN SPACE (U+2009)
777 docstring const thin_space = from_ascii(" ");
778 if (side_ == InsetQuotesParams::OpeningQuote)
781 res = thin_space + res;
787 int InsetQuotes::docbook(odocstream & os, OutputParams const &) const
789 os << getQuoteEntity();
794 docstring InsetQuotes::xhtml(XHTMLStream & xs, OutputParams const &) const
796 xs << XHTMLStream::ESCAPE_NONE << getQuoteEntity();
801 void InsetQuotes::toString(odocstream & os) const
803 os << displayString();
807 void InsetQuotes::forOutliner(docstring & os, size_t const, bool const) const
809 os += displayString();
813 void InsetQuotes::updateBuffer(ParIterator const & it, UpdateType /* utype*/)
815 BufferParams const & bp = buffer().masterBuffer()->params();
816 pass_thru_ = it.paragraph().isPassThru();
817 context_lang_ = it.paragraph().getFontSettings(bp, it.pos()).language()->code();
818 fontenc_ = (bp.fontenc == "global") ? lyxrc.fontenc : bp.fontenc;
819 global_style_ = bp.quotes_style;
823 void InsetQuotes::validate(LaTeXFeatures & features) const
825 InsetQuotesParams::QuoteStyle style =
826 (style_ == InsetQuotesParams::DynamicQuotes) ? global_style_ : style_;
827 char_type type = quoteparams.getQuoteChar(style, level_, side_);
829 // Handle characters that are not natively supported by
830 // specific font encodings (we roll our own definitions)
831 #ifdef DO_USE_DEFAULT_LANGUAGE
832 if (features.bufferParams().language->lang() == "default"
834 if (!features.useBabel()
836 && !features.runparams().isFullUnicode() && fontenc_ != "T1") {
839 features.require("quotesinglbase");
842 features.require("guilsinglleft");
845 features.require("guilsinglright");
848 features.require("quotedblbase");
851 features.require("guillemotleft");
854 features.require("guillemotright");
860 // Handle straight quotation marks. These need special care
861 // in most output formats
864 if (features.runparams().isFullUnicode())
865 features.require("textquotesinglep");
867 features.require("textcomp");
871 if (features.runparams().isFullUnicode())
872 features.require("textquotedblp");
873 else if (fontenc_ != "T1")
874 features.require("textquotedbl");
883 string InsetQuotes::contextMenuName() const
885 return "context-quote";