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