]> git.lyx.org Git - lyx.git/blob - src/insets/InsetSpace.cpp
Amend 6c3447c8: FindAdv: sometimes a space is added on some math symbols
[lyx.git] / src / insets / InsetSpace.cpp
1 /**
2  * \file InsetSpace.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup Nielsen
7  * \author Jean-Marc Lasgouttes
8  * \author Lars Gullik Bjønnes
9  * \author Jürgen Spitzmüller
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "InsetSpace.h"
17
18 #include "BufferView.h"
19 #include "Cursor.h"
20 #include "Dimension.h"
21 #include "FuncRequest.h"
22 #include "FuncStatus.h"
23 #include "Language.h"
24 #include "LaTeXFeatures.h"
25 #include "MetricsInfo.h"
26 #include "texstream.h"
27 #include "xml.h"
28
29 #include "support/debug.h"
30 #include "support/docstream.h"
31 #include "support/gettext.h"
32 #include "support/lassert.h"
33 #include "support/Length.h"
34 #include "support/lstrings.h"
35 #include "support/qstring_helpers.h"
36
37 #include "frontends/Application.h"
38 #include "frontends/FontMetrics.h"
39 #include "support/Lexer.h"
40 #include "frontends/Painter.h"
41
42 using namespace std;
43
44 namespace lyx {
45
46 using support::Lexer;
47
48 InsetSpace::InsetSpace(InsetSpaceParams const & params)
49         : Inset(nullptr), params_(params)
50 {}
51
52
53 InsetSpaceParams::Kind InsetSpace::kind() const
54 {
55         return params_.kind;
56 }
57
58
59 GlueLength InsetSpace::length() const
60 {
61         return params_.length;
62 }
63
64
65 docstring InsetSpace::toolTip(BufferView const &, int, int) const
66 {
67         docstring message;
68         switch (params_.kind) {
69         case InsetSpaceParams::NORMAL:
70                 message = _("Normal Space");
71                 break;
72         case InsetSpaceParams::PROTECTED:
73                 message = _("Non-Breaking Normal Space");
74                 break;
75         case InsetSpaceParams::VISIBLE:
76                 message = _("Non-Breaking Visible Normal Space");
77                 break;
78         case InsetSpaceParams::THIN:
79                 message = _("Non-Breaking Thin Space (1/6 em)");
80                 break;
81         case InsetSpaceParams::MEDIUM:
82                 message = _("Non-Breaking Medium Space (2/9 em)");
83                 break;
84         case InsetSpaceParams::THICK:
85                 message = _("Non-Breaking Thick Space (5/18 em)");
86                 break;
87         case InsetSpaceParams::QUAD:
88                 message = _("Quad Space (1 em)");
89                 break;
90         case InsetSpaceParams::QQUAD:
91                 message = _("Double Quad Space (2 em)");
92                 break;
93         case InsetSpaceParams::ENSPACE:
94                 message = _("Non-Breaking Half Quad Space (1/2 em)");
95                 break;
96         case InsetSpaceParams::ENSKIP:
97                 message = _("Half Quad Space (1/2 em)");
98                 break;
99         case InsetSpaceParams::NEGTHIN:
100                 message = _("Non-Breaking Negative Thin Space (-1/6 em)");
101                 break;
102         case InsetSpaceParams::NEGMEDIUM:
103                 message = _("Non-Breaking Negative Medium Space (-2/9 em)");
104                 break;
105         case InsetSpaceParams::NEGTHICK:
106                 message = _("Non-Breaking Negative Thick Space (-5/18 em)");
107                 break;
108         case InsetSpaceParams::HFILL:
109                 message = _("Horizontal Fill");
110                 break;
111         case InsetSpaceParams::HFILL_PROTECTED:
112                 message = _("Non-Breaking Horizontal Fill");
113                 break;
114         case InsetSpaceParams::DOTFILL:
115                 message = _("Non-Breaking Horizontal Fill (Dots)");
116                 break;
117         case InsetSpaceParams::HRULEFILL:
118                 message = _("Non-Breaking Horizontal Fill (Rule)");
119                 break;
120         case InsetSpaceParams::LEFTARROWFILL:
121                 message = _("Non-Breaking Horizontal Fill (Left Arrow)");
122                 break;
123         case InsetSpaceParams::RIGHTARROWFILL:
124                 message = _("Non-Breaking Horizontal Fill (Right Arrow)");
125                 break;
126         case InsetSpaceParams::UPBRACEFILL:
127                 message = _("Non-Breaking Horizontal Fill (Up Brace)");
128                 break;
129         case InsetSpaceParams::DOWNBRACEFILL:
130                 message = _("Non-Breaking Horizontal Fill (Down Brace)");
131                 break;
132         case InsetSpaceParams::CUSTOM:
133                 // FIXME unicode
134                 message = support::bformat(_("Horizontal Space (%1$s)"),
135                                            locLengthDocString(from_ascii(params_.length.asString())));
136                 break;
137         case InsetSpaceParams::CUSTOM_PROTECTED:
138                 // FIXME unicode
139                 message = support::bformat(_("Non-Breaking Horizontal Space (%1$s)"),
140                                            locLengthDocString(from_ascii(params_.length.asString())));
141                 break;
142         }
143         return message;
144 }
145
146
147 void InsetSpace::doDispatch(Cursor & cur, FuncRequest & cmd)
148 {
149         switch (cmd.action()) {
150
151         case LFUN_INSET_MODIFY: {
152                 cur.recordUndo();
153                 string arg = to_utf8(cmd.argument());
154                 if (arg == "space \\hspace{}")
155                         arg += params_.length.len().empty()
156                                 ? " \\length 1" + string(stringFromUnit(Length::defaultUnit()))
157                                 : " \\length " + params_.length.asString();
158                 string2params(arg, params_);
159                 break;
160         }
161
162         case LFUN_INSET_DIALOG_UPDATE:
163                 cur.bv().updateDialog("space", params2string(params()));
164                 break;
165
166         default:
167                 Inset::doDispatch(cur, cmd);
168                 break;
169         }
170 }
171
172
173 bool InsetSpace::getStatus(Cursor & cur, FuncRequest const & cmd,
174         FuncStatus & status) const
175 {
176         switch (cmd.action()) {
177         // we handle these
178         case LFUN_INSET_MODIFY:
179                 if (cmd.getArg(0) == "space") {
180                         InsetSpaceParams params;
181                         string2params(to_utf8(cmd.argument()), params);
182                         status.setOnOff(params_.kind == params.kind);
183                         status.setEnabled(true);
184                 } else
185                         status.setEnabled(false);
186                 return true;
187
188         case LFUN_INSET_DIALOG_UPDATE:
189                 status.setEnabled(true);
190                 return true;
191         default:
192                 return Inset::getStatus(cur, cmd, status);
193         }
194 }
195
196
197 int InsetSpace::rowFlags() const
198 {
199         switch (params_.kind) {
200                 case InsetSpaceParams::PROTECTED:
201                 case InsetSpaceParams::CUSTOM_PROTECTED:
202                 case InsetSpaceParams::HFILL_PROTECTED:
203                 case InsetSpaceParams::THIN:
204                 case InsetSpaceParams::NEGTHIN:
205                 case InsetSpaceParams::MEDIUM:
206                 case InsetSpaceParams::NEGMEDIUM:
207                 case InsetSpaceParams::THICK:
208                 case InsetSpaceParams::NEGTHICK:
209                 case InsetSpaceParams::ENSPACE:
210                 case InsetSpaceParams::VISIBLE:
211                         // no break after these
212                         return Inline;
213                 case InsetSpaceParams::NORMAL:
214                 case InsetSpaceParams::QUAD:
215                 case InsetSpaceParams::QQUAD:
216                 case InsetSpaceParams::ENSKIP:
217                 case InsetSpaceParams::CUSTOM:
218                 case InsetSpaceParams::HFILL:
219                 case InsetSpaceParams::DOTFILL:
220                 case InsetSpaceParams::HRULEFILL:
221                 case InsetSpaceParams::LEFTARROWFILL:
222                 case InsetSpaceParams::RIGHTARROWFILL:
223                 case InsetSpaceParams::UPBRACEFILL:
224                 case InsetSpaceParams::DOWNBRACEFILL:
225                         // these allow line breaking
226                         break;
227         }
228         return CanBreakAfter;
229 }
230
231
232 namespace {
233 int const arrow_size = 8;
234 }
235
236
237 void InsetSpace::metrics(MetricsInfo & mi, Dimension & dim) const
238 {
239         if (isHfill()) {
240                 // The width for hfills is calculated externally in
241                 // TextMetrics::setRowAlignment. The value of 5 is the
242                 // minimal value when the hfill is not active.
243                 dim = Dimension(5, 10, 10);
244                 return;
245         }
246
247         frontend::FontMetrics const & fm = theFontMetrics(mi.base.font);
248         dim.asc = fm.maxAscent();
249         dim.des = fm.maxDescent();
250         int const em = fm.em();
251
252         switch (params_.kind) {
253                 case InsetSpaceParams::THIN:
254                 case InsetSpaceParams::NEGTHIN:
255                         dim.wid = em / 6;
256                         break;
257                 case InsetSpaceParams::MEDIUM:
258                 case InsetSpaceParams::NEGMEDIUM:
259                         dim.wid = em / 4;
260                         break;
261                 case InsetSpaceParams::THICK:
262                 case InsetSpaceParams::NEGTHICK:
263                         dim.wid = em / 2;
264                         break;
265                 case InsetSpaceParams::PROTECTED:
266                 case InsetSpaceParams::VISIBLE:
267                 case InsetSpaceParams::NORMAL:
268                         dim.wid = fm.width(char_type(' '));
269                         break;
270                 case InsetSpaceParams::QUAD:
271                         dim.wid = em;
272                         break;
273                 case InsetSpaceParams::QQUAD:
274                         dim.wid = 2 * em;
275                         break;
276                 case InsetSpaceParams::ENSPACE:
277                 case InsetSpaceParams::ENSKIP:
278                         dim.wid = int(0.5 * em);
279                         break;
280                 case InsetSpaceParams::CUSTOM:
281                 case InsetSpaceParams::CUSTOM_PROTECTED: {
282                         int const w = mi.base.inPixels(params_.length.len());
283                         int const minw = (w < 0) ? 3 * arrow_size : 4;
284                         dim.wid = max(minw, abs(w));
285                         break;
286                 }
287                 case InsetSpaceParams::HFILL:
288                 case InsetSpaceParams::HFILL_PROTECTED:
289                 case InsetSpaceParams::DOTFILL:
290                 case InsetSpaceParams::HRULEFILL:
291                 case InsetSpaceParams::LEFTARROWFILL:
292                 case InsetSpaceParams::RIGHTARROWFILL:
293                 case InsetSpaceParams::UPBRACEFILL:
294                 case InsetSpaceParams::DOWNBRACEFILL:
295                         // shut up compiler
296                         break;
297         }
298 }
299
300
301 void InsetSpace::draw(PainterInfo & pi, int x, int y) const
302 {
303         Dimension const dim = dimension(*pi.base.bv);
304
305         if (isHfill() || params_.length.len().value() < 0) {
306                 int const asc = theFontMetrics(pi.base.font).ascent('M');
307                 int const desc = theFontMetrics(pi.base.font).descent('M');
308                 // Pixel height divisible by 2 for prettier fill graphics:
309                 int const oddheight = (asc ^ desc) & 1;
310                 int const x0 = x + 1;
311                 int const x1 = x + dim.wid - 2;
312                 int const y0 = y + desc - 1;
313                 int const y1 = y - asc + oddheight - 1;
314                 int const y2 = (y0 + y1) / 2;
315                 int xoffset = (y0 - y1) / 2;
316
317                 // Two tests for very narrow insets
318                 if (xoffset > x1 - x0
319                      && (params_.kind == InsetSpaceParams::LEFTARROWFILL
320                          || params_.kind == InsetSpaceParams::RIGHTARROWFILL))
321                                 xoffset = x1 - x0;
322                 if (xoffset * 6 > (x1 - x0)
323                      && (params_.kind == InsetSpaceParams::UPBRACEFILL
324                          || params_.kind == InsetSpaceParams::DOWNBRACEFILL))
325                                 xoffset = (x1 - x0) / 6;
326
327                 int const x2 = x0 + xoffset;
328                 int const x3 = x1 - xoffset;
329                 int const xm = (x0 + x1) / 2;
330                 int const xml = xm - xoffset;
331                 int const xmr = xm + xoffset;
332
333                 if (params_.kind == InsetSpaceParams::HFILL) {
334                         pi.pain.line(x0, y1, x0, y0, Color_added_space);
335                         pi.pain.line(x0, y2, x1, y2, Color_added_space,
336                                 frontend::Painter::line_onoffdash);
337                         pi.pain.line(x1, y1, x1, y0, Color_added_space);
338                 } else if (params_.kind == InsetSpaceParams::HFILL_PROTECTED) {
339                         pi.pain.line(x0, y1, x0, y0, Color_latex);
340                         pi.pain.line(x0, y2, x1, y2, Color_latex,
341                                 frontend::Painter::line_onoffdash);
342                         pi.pain.line(x1, y1, x1, y0, Color_latex);
343                 } else if (params_.kind == InsetSpaceParams::DOTFILL) {
344                         pi.pain.line(x0, y1, x0, y0, Color_special);
345                         pi.pain.line(x0, y0, x1, y0, Color_special,
346                                 frontend::Painter::line_onoffdash);
347                         pi.pain.line(x1, y1, x1, y0, Color_special);
348                 } else if (params_.kind == InsetSpaceParams::HRULEFILL) {
349                         pi.pain.line(x0, y1, x0, y0, Color_special);
350                         pi.pain.line(x0, y0, x1, y0, Color_special);
351                         pi.pain.line(x1, y1, x1, y0, Color_special);
352                 } else if (params_.kind == InsetSpaceParams::LEFTARROWFILL) {
353                         pi.pain.line(x2, y1 + 1 , x0 + 1, y2, Color_special);
354                         pi.pain.line(x0 + 1, y2 + 1 , x2, y0, Color_special);
355                         pi.pain.line(x0, y2 , x1, y2, Color_special);
356                 } else if (params_.kind == InsetSpaceParams::RIGHTARROWFILL) {
357                         pi.pain.line(x3 + 1, y1 + 1 , x1, y2, Color_special);
358                         pi.pain.line(x1, y2 + 1 , x3 + 1, y0, Color_special);
359                         pi.pain.line(x0, y2 , x1, y2, Color_special);
360                 } else if (params_.kind == InsetSpaceParams::UPBRACEFILL) {
361                         pi.pain.line(x0 + 1, y1 + 1 , x2, y2, Color_special);
362                         pi.pain.line(x2, y2 , xml, y2, Color_special);
363                         pi.pain.line(xml + 1, y2 + 1 , xm, y0, Color_special);
364                         pi.pain.line(xm + 1, y0 , xmr, y2 + 1, Color_special);
365                         pi.pain.line(xmr, y2 , x3, y2, Color_special);
366                         pi.pain.line(x3 + 1, y2 , x1, y1 + 1, Color_special);
367                 } else if (params_.kind == InsetSpaceParams::DOWNBRACEFILL) {
368                         pi.pain.line(x0 + 1, y0 , x2, y2 + 1, Color_special);
369                         pi.pain.line(x2, y2 , xml, y2, Color_special);
370                         pi.pain.line(xml + 1, y2 , xm, y1 + 1, Color_special);
371                         pi.pain.line(xm + 1, y1 + 1 , xmr, y2, Color_special);
372                         pi.pain.line(xmr, y2 , x3, y2, Color_special);
373                         pi.pain.line(x3 + 1, y2 + 1 , x1, y0, Color_special);
374                 } else if (params_.kind == InsetSpaceParams::CUSTOM) {
375                         pi.pain.line(x0, y1 + 1 , x2 + 1, y2, Color_special);
376                         pi.pain.line(x2 + 1, y2 + 1 , x0, y0, Color_special);
377                         pi.pain.line(x1 + 1, y1 + 1 , x3, y2, Color_special);
378                         pi.pain.line(x3, y2 + 1 , x1 + 1, y0, Color_special);
379                         pi.pain.line(x2, y2 , x3, y2, Color_special);
380                 } else if (params_.kind == InsetSpaceParams::CUSTOM_PROTECTED) {
381                         pi.pain.line(x0, y1 + 1 , x2 + 1, y2, Color_latex);
382                         pi.pain.line(x2 + 1, y2 + 1 , x0, y0, Color_latex);
383                         pi.pain.line(x1 + 1, y1 + 1 , x3, y2, Color_latex);
384                         pi.pain.line(x3, y2 + 1 , x1 + 1, y0, Color_latex);
385                         pi.pain.line(x2, y2 , x3, y2, Color_latex);
386                 }
387                 return;
388         }
389
390         int const w = dim.wid;
391         int const h = theFontMetrics(pi.base.font).xHeight();
392         int xp[4], yp[4];
393
394         xp[0] = x;
395         yp[0] = y - max(h / 4, 1);
396         if (params_.kind == InsetSpaceParams::NORMAL ||
397             params_.kind == InsetSpaceParams::PROTECTED ||
398             params_.kind == InsetSpaceParams::VISIBLE) {
399                 xp[1] = x;         yp[1] = y;
400                 xp[2] = x + w - 1; yp[2] = y;
401         } else {
402                 xp[1] = x;         yp[1] = y + max(h / 4, 1);
403                 xp[2] = x + w - 1; yp[2] = y + max(h / 4, 1);
404         }
405         xp[3] = x + w - 1;
406         yp[3] = y - max(h / 4, 1);
407
408         Color col = Color_special;
409         if (params_.kind == InsetSpaceParams::PROTECTED ||
410             params_.kind == InsetSpaceParams::ENSPACE ||
411             params_.kind == InsetSpaceParams::THIN ||
412             params_.kind == InsetSpaceParams::NEGTHIN ||
413             params_.kind == InsetSpaceParams::MEDIUM ||
414             params_.kind == InsetSpaceParams::NEGMEDIUM ||
415             params_.kind == InsetSpaceParams::THICK ||
416             params_.kind == InsetSpaceParams::NEGTHICK ||
417             params_.kind == InsetSpaceParams::CUSTOM_PROTECTED)
418                 col = Color_latex;
419         else if (params_.kind == InsetSpaceParams::VISIBLE)
420                 col =  Color_foreground;
421
422         pi.pain.lines(xp, yp, 4, col);
423 }
424
425
426 void InsetSpaceParams::write(ostream & os) const
427 {
428         switch (kind) {
429         case InsetSpaceParams::NORMAL:
430                 os << "\\space{}";
431                 break;
432         case InsetSpaceParams::PROTECTED:
433                 os <<  "~";
434                 break;
435         case InsetSpaceParams::VISIBLE:
436                 os <<  "\\textvisiblespace{}";
437                 break;
438         case InsetSpaceParams::THIN:
439                 os <<  "\\thinspace{}";
440                 break;
441         case InsetSpaceParams::MEDIUM:
442                 os <<  "\\medspace{}";
443                 break;
444         case InsetSpaceParams::THICK:
445                 os <<  "\\thickspace{}";
446                 break;
447         case InsetSpaceParams::QUAD:
448                 os <<  "\\quad{}";
449                 break;
450         case InsetSpaceParams::QQUAD:
451                 os <<  "\\qquad{}";
452                 break;
453         case InsetSpaceParams::ENSPACE:
454                 os <<  "\\enspace{}";
455                 break;
456         case InsetSpaceParams::ENSKIP:
457                 os <<  "\\enskip{}";
458                 break;
459         case InsetSpaceParams::NEGTHIN:
460                 os <<  "\\negthinspace{}";
461                 break;
462         case InsetSpaceParams::NEGMEDIUM:
463                 os <<  "\\negmedspace{}";
464                 break;
465         case InsetSpaceParams::NEGTHICK:
466                 os <<  "\\negthickspace{}";
467                 break;
468         case InsetSpaceParams::HFILL:
469                 os <<  "\\hfill{}";
470                 break;
471         case InsetSpaceParams::HFILL_PROTECTED:
472                 os <<  "\\hspace*{\\fill}";
473                 break;
474         case InsetSpaceParams::DOTFILL:
475                 os <<  "\\dotfill{}";
476                 break;
477         case InsetSpaceParams::HRULEFILL:
478                 os <<  "\\hrulefill{}";
479                 break;
480         case InsetSpaceParams::LEFTARROWFILL:
481                 os <<  "\\leftarrowfill{}";
482                 break;
483         case InsetSpaceParams::RIGHTARROWFILL:
484                 os <<  "\\rightarrowfill{}";
485                 break;
486         case InsetSpaceParams::UPBRACEFILL:
487                 os <<  "\\upbracefill{}";
488                 break;
489         case InsetSpaceParams::DOWNBRACEFILL:
490                 os <<  "\\downbracefill{}";
491                 break;
492         case InsetSpaceParams::CUSTOM:
493                 os <<  "\\hspace{}";
494                 break;
495         case InsetSpaceParams::CUSTOM_PROTECTED:
496                 os <<  "\\hspace*{}";
497                 break;
498         }
499
500         if (!length.len().empty())
501                 os << "\n\\length " << length.asString();
502 }
503
504
505 void InsetSpaceParams::read(Lexer & lex)
506 {
507         lex.setContext("InsetSpaceParams::read");
508         string command;
509         lex >> command;
510
511         // The tests for math might be disabled after a file format change
512         if (command == "\\space{}")
513                 kind = InsetSpaceParams::NORMAL;
514         else if (command == "~")
515                 kind = InsetSpaceParams::PROTECTED;
516         else if (command == "\\textvisiblespace{}")
517                 kind = InsetSpaceParams::VISIBLE;
518         else if (command == "\\thinspace{}")
519                 kind = InsetSpaceParams::THIN;
520         else if (command == "\\medspace{}")
521                 kind = InsetSpaceParams::MEDIUM;
522         else if (command == "\\thickspace{}")
523                 kind = InsetSpaceParams::THICK;
524         else if (command == "\\quad{}")
525                 kind = InsetSpaceParams::QUAD;
526         else if (command == "\\qquad{}")
527                 kind = InsetSpaceParams::QQUAD;
528         else if (command == "\\enspace{}")
529                 kind = InsetSpaceParams::ENSPACE;
530         else if (command == "\\enskip{}")
531                 kind = InsetSpaceParams::ENSKIP;
532         else if (command == "\\negthinspace{}")
533                 kind = InsetSpaceParams::NEGTHIN;
534         else if (command == "\\negmedspace{}")
535                 kind = InsetSpaceParams::NEGMEDIUM;
536         else if (command == "\\negthickspace{}")
537                 kind = InsetSpaceParams::NEGTHICK;
538         else if (command == "\\hfill{}")
539                 kind = InsetSpaceParams::HFILL;
540         else if (command == "\\hspace*{\\fill}")
541                 kind = InsetSpaceParams::HFILL_PROTECTED;
542         else if (command == "\\dotfill{}")
543                 kind = InsetSpaceParams::DOTFILL;
544         else if (command == "\\hrulefill{}")
545                 kind = InsetSpaceParams::HRULEFILL;
546         else if (command == "\\hspace{}")
547                 kind = InsetSpaceParams::CUSTOM;
548         else if (command == "\\leftarrowfill{}")
549                 kind = InsetSpaceParams::LEFTARROWFILL;
550         else if (command == "\\rightarrowfill{}")
551                 kind = InsetSpaceParams::RIGHTARROWFILL;
552         else if (command == "\\upbracefill{}")
553                 kind = InsetSpaceParams::UPBRACEFILL;
554         else if (command == "\\downbracefill{}")
555                 kind = InsetSpaceParams::DOWNBRACEFILL;
556         else if (command == "\\hspace*{}")
557                 kind = InsetSpaceParams::CUSTOM_PROTECTED;
558         else
559                 lex.printError("InsetSpace: Unknown kind: `$$Token'");
560
561         if (lex.checkFor("\\length"))
562                 lex >> length;
563 }
564
565
566 void InsetSpace::write(ostream & os) const
567 {
568         os << "space ";
569         params_.write(os);
570 }
571
572
573 void InsetSpace::read(Lexer & lex)
574 {
575         params_.read(lex);
576         lex >> "\\end_inset";
577 }
578
579
580 void InsetSpace::latex(otexstream & os, OutputParams const & runparams) const
581 {
582         switch (params_.kind) {
583         case InsetSpaceParams::NORMAL:
584                 if (runparams.find_effective())
585                         os << "~";
586                 else
587                         os << (runparams.free_spacing ? " " : "\\ ");
588                 break;
589         case InsetSpaceParams::PROTECTED:
590                 if (runparams.find_effective())
591                         os.put(0xa0);
592                 else if (getLocalOrDefaultLang(runparams)->lang() == "polutonikogreek")
593                         // in babel's polutonikogreek, ~ is active
594                         os << (runparams.free_spacing ? " " : "\\nobreakspace{}");
595                 else
596                         os << (runparams.free_spacing ? ' ' : '~');
597                 break;
598         case InsetSpaceParams::VISIBLE:
599                 if (runparams.find_effective())
600                         os.put(0x2423);
601                 else
602                         os << (runparams.free_spacing ? " " : "\\textvisiblespace{}");
603                 break;
604         case InsetSpaceParams::THIN:
605                 if (runparams.find_effective())
606                         os.put(0x202f);
607                 else
608                         os << (runparams.free_spacing ? " " : "\\,");
609                 break;
610         case InsetSpaceParams::MEDIUM:
611                 if (runparams.find_effective())
612                         os.put(0x2005);
613                 else if (params_.math)
614                         os << (runparams.free_spacing ? " " : "\\:");
615                 else
616                         os << (runparams.free_spacing ? " " : "\\medspace{}");
617                 break;
618         case InsetSpaceParams::THICK:
619                 if (runparams.find_effective())
620                         os.put(0x2004);
621                 else if (params_.math)
622                         os << (runparams.free_spacing ? " " : "\\;");
623                 else
624                         os << (runparams.free_spacing ? " " : "\\thickspace{}");
625                 break;
626         case InsetSpaceParams::QUAD:
627                 if (runparams.find_effective())
628                         os.put(0x2003);
629                 else
630                         os << (runparams.free_spacing ? " " : "\\quad{}");
631                 break;
632         case InsetSpaceParams::QQUAD:
633                 if (runparams.find_effective()) {
634                         os.put(0x2003);
635                         os.put(0x2003);
636                 }
637                 else
638                         os << (runparams.free_spacing ? " " : "\\qquad{}");
639                 break;
640         case InsetSpaceParams::ENSPACE:
641                 if (runparams.find_effective())
642                         os.put(0x2002);
643                 else
644                         os << (runparams.free_spacing ? " " : "\\enspace{}");
645                 break;
646         case InsetSpaceParams::ENSKIP:
647                 if (runparams.find_effective())
648                         os.put(0x2002);
649                 else
650                         os << (runparams.free_spacing ? " " : "\\enskip{}");
651                 break;
652         case InsetSpaceParams::NEGTHIN:
653                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\negthinspace{}");
654                 break;
655         case InsetSpaceParams::NEGMEDIUM:
656                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\negmedspace{}");
657                 break;
658         case InsetSpaceParams::NEGTHICK:
659                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\negthickspace{}");
660                 break;
661         case InsetSpaceParams::HFILL:
662                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\hfill{}");
663                 break;
664         case InsetSpaceParams::HFILL_PROTECTED:
665                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\hspace*{\\fill}");
666                 break;
667         case InsetSpaceParams::DOTFILL:
668                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\dotfill{}");
669                 break;
670         case InsetSpaceParams::HRULEFILL:
671                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\hrulefill{}");
672                 break;
673         case InsetSpaceParams::LEFTARROWFILL:
674                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\leftarrowfill{}");
675                 break;
676         case InsetSpaceParams::RIGHTARROWFILL:
677                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\rightarrowfill{}");
678                 break;
679         case InsetSpaceParams::UPBRACEFILL:
680                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\upbracefill{}");
681                 break;
682         case InsetSpaceParams::DOWNBRACEFILL:
683                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\downbracefill{}");
684                 break;
685         case InsetSpaceParams::CUSTOM:
686                 if (runparams.find_effective())
687                         os.put(0x00a0);
688                 else if (runparams.free_spacing)
689                         os << " ";
690                 else
691                         os << "\\hspace{" << from_ascii(params_.length.asLatexString()) << "}";
692                 break;
693         case InsetSpaceParams::CUSTOM_PROTECTED:
694                 if (runparams.find_effective())
695                         os.put(0x00a0);
696                 else if (runparams.free_spacing)
697                         os << " ";
698                 else
699                         os << "\\hspace*{" << from_ascii(params_.length.asLatexString()) << "}";
700                 break;
701         }
702 }
703
704
705 int InsetSpace::plaintext(odocstringstream & os,
706         OutputParams const & rp, size_t) const
707 {
708         switch (params_.kind) {
709         case InsetSpaceParams::HFILL:
710         case InsetSpaceParams::HFILL_PROTECTED:
711                 os << "     ";
712                 return 5;
713         case InsetSpaceParams::DOTFILL:
714                 os << ".....";
715                 return 5;
716         case InsetSpaceParams::HRULEFILL:
717                 os << "_____";
718                 return 5;
719         case InsetSpaceParams::LEFTARROWFILL:
720                 os << "<----";
721                 return 5;
722         case InsetSpaceParams::RIGHTARROWFILL:
723                 os << "---->";
724                 return 5;
725         case InsetSpaceParams::UPBRACEFILL:
726                 os << "\\-v-/";
727                 return 5;
728         case InsetSpaceParams::DOWNBRACEFILL:
729                 os << "/-^-\\";
730                 return 5;
731         case InsetSpaceParams::VISIBLE:
732                 os.put(0x2423);
733                 return 1;
734         case InsetSpaceParams::ENSKIP:
735                 os.put(0x2002);
736                 return 1;
737         case InsetSpaceParams::ENSPACE:
738                 os.put(0x2060); // WORD JOINER, makes the breakable en space unbreakable
739                 os.put(0x2002);
740                 os.put(0x2060); // WORD JOINER, makes the breakable en space unbreakable
741                 return 3;
742         case InsetSpaceParams::QUAD:
743                 os.put(0x2003);
744                 return 1;
745         case InsetSpaceParams::QQUAD:
746                 os.put(0x2003);
747                 os.put(0x2003);
748                 return 2;
749         case InsetSpaceParams::THIN:
750                 if (rp.find_effective())
751                         // simple search
752                         os << ' ';
753                 else
754                         os.put(0x202f);
755                 return 1;
756         case InsetSpaceParams::MEDIUM:
757                 os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable medium space breakable
758                 os.put(0x2005);
759                 os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable medium space breakable
760                 return 1;
761         case InsetSpaceParams::THICK:
762                 os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable thick space breakable
763                 os.put(0x2004);
764                 os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable thick space breakable
765                 return 1;
766         case InsetSpaceParams::PROTECTED:
767         case InsetSpaceParams::CUSTOM_PROTECTED:
768                 if (rp.find_effective())
769                         // simple search
770                         os << ' ';
771                 else
772                         os.put(0x00a0);
773                 return 1;
774         case InsetSpaceParams::NEGTHIN:
775         case InsetSpaceParams::NEGMEDIUM:
776         case InsetSpaceParams::NEGTHICK:
777                 if (rp.find_effective()) {
778                         os << ' ';
779                         return 1;
780                 }
781                 else
782                         return 0;
783         default:
784                 os << ' ';
785                 return 1;
786         }
787 }
788
789
790 namespace {
791 std::string spaceToXMLEntity(InsetSpaceParams::Kind kind) {
792         switch (kind) {
793         case InsetSpaceParams::NORMAL:
794                 return " ";
795         case InsetSpaceParams::QUAD:
796                 return "&#x2003;"; // HTML: &emsp;
797         case InsetSpaceParams::QQUAD:
798                 return "&#x2003;&#x2003;"; // HTML: &emsp;&emsp;
799         case InsetSpaceParams::ENSKIP:
800                 return "&#x2002;"; // HTML: &ensp;
801         case InsetSpaceParams::VISIBLE:
802                 return "&#x2423;";
803         case InsetSpaceParams::ENSPACE: // HTML: &#x2060;&ensp;&#x2060; (word joiners)
804                 return "&#x2060;&#x2002;&#x2060;";
805         case InsetSpaceParams::THIN:
806                 return "&#x202F;"; // HTML: &thinspace;
807         case InsetSpaceParams::MEDIUM:
808                 return "&#x2005;"; // HTML: &emsp14;
809         case InsetSpaceParams::THICK:
810                 return "&#x2004;"; // HTML: &emsp13;
811         case InsetSpaceParams::PROTECTED:
812         case InsetSpaceParams::NEGTHIN:
813         case InsetSpaceParams::NEGMEDIUM:
814         case InsetSpaceParams::NEGTHICK:
815                 return "&#xA0;"; // HTML: &nbsp;
816         case InsetSpaceParams::CUSTOM_PROTECTED:
817                 // FIXME XHTML/DocBook
818                 // Probably we could do some sort of blank span?
819                 return "&#160;";
820         case InsetSpaceParams::HFILL:
821         case InsetSpaceParams::HFILL_PROTECTED:
822         case InsetSpaceParams::DOTFILL:
823         case InsetSpaceParams::HRULEFILL:
824         case InsetSpaceParams::LEFTARROWFILL:
825         case InsetSpaceParams::RIGHTARROWFILL:
826         case InsetSpaceParams::UPBRACEFILL:
827         case InsetSpaceParams::DOWNBRACEFILL:
828         case InsetSpaceParams::CUSTOM:
829                 // FIXME XHTML/DocBook
830                 // Can we do anything with those?
831                 return "\n";
832         }
833         return "";
834 }
835 }
836
837
838 void InsetSpace::docbook(XMLStream & xs, OutputParams const &) const
839 {
840         xs << XMLStream::ESCAPE_NONE << from_ascii(spaceToXMLEntity(params_.kind));
841 }
842
843
844 docstring InsetSpace::xhtml(XMLStream & xs, OutputParams const &) const
845 {
846         xs << XMLStream::ESCAPE_NONE << from_ascii(spaceToXMLEntity(params_.kind));
847         return docstring();
848 }
849
850
851 void InsetSpace::validate(LaTeXFeatures & features) const
852 {
853         if (features.isAvailableAtLeastFrom("LaTeX", 2020, 10))
854                 // As of this version, the LaTeX kernel
855                 // includes all spaces.
856                 return;
857
858         // In earlier versions, we require amsmath
859         // for some text and math spaces
860         if ((params_.kind == InsetSpaceParams::NEGMEDIUM
861              || params_.kind == InsetSpaceParams::NEGTHICK)
862             || (!params_.math
863                 && (params_.kind == InsetSpaceParams::MEDIUM
864                     || params_.kind == InsetSpaceParams::THICK)))
865                 features.require("amsmath");
866 }
867
868
869 void InsetSpace::toString(odocstream & os) const
870 {
871         odocstringstream ods;
872         plaintext(ods, OutputParams(0));
873         os << ods.str();
874 }
875
876
877 void InsetSpace::forOutliner(docstring & os, size_t const, bool const) const
878 {
879         // There's no need to be cute here.
880         os += " ";
881 }
882
883
884 bool InsetSpace::isHfill() const
885 {
886         return params_.kind == InsetSpaceParams::HFILL
887                 || params_.kind == InsetSpaceParams::HFILL_PROTECTED
888                 || params_.kind == InsetSpaceParams::DOTFILL
889                 || params_.kind == InsetSpaceParams::HRULEFILL
890                 || params_.kind == InsetSpaceParams::LEFTARROWFILL
891                 || params_.kind == InsetSpaceParams::RIGHTARROWFILL
892                 || params_.kind == InsetSpaceParams::UPBRACEFILL
893                 || params_.kind == InsetSpaceParams::DOWNBRACEFILL;
894 }
895
896
897 string InsetSpace::contextMenuName() const
898 {
899         return "context-space";
900 }
901
902
903 void InsetSpace::string2params(string const & in, InsetSpaceParams & params)
904 {
905         params = InsetSpaceParams();
906         if (in.empty())
907                 return;
908
909         istringstream data(in);
910         Lexer lex;
911         lex.setStream(data);
912         lex.setContext("InsetSpace::string2params");
913         lex.next();
914         string const name = lex.getString();
915         if (name == "mathspace")
916                 params.math = true;
917         else {
918                 params.math = false;
919                 // we can try to read this even if the name is wrong
920                 LATTEST(name == "space");
921         }
922
923         // There are cases, such as when we are called via getStatus() from
924         // Dialog::canApply(), where we are just called with "space" rather
925         // than a full "space \type{}\n\\end_inset".
926         if (lex.isOK())
927                 params.read(lex);
928 }
929
930
931 string InsetSpace::params2string(InsetSpaceParams const & params)
932 {
933         ostringstream data;
934         if (params.math)
935                 data << "math";
936         data << "space" << ' ';
937         params.write(data);
938         return data.str();
939 }
940
941
942 } // namespace lyx