/**
 * \file InsetSpace.cpp
 * This file is part of LyX, the document processor.
 * Licence details can be found in the file COPYING.
 *
 * \author Asger Alstrup Nielsen
 * \author Jean-Marc Lasgouttes
 * \author Lars Gullik Bjønnes
 * \author Jürgen Spitzmüller
 *
 * Full author contact details are available in file CREDITS.
 */

#include <config.h>

#include "InsetSpace.h"

#include "BufferView.h"
#include "Cursor.h"
#include "Dimension.h"
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "Language.h"
#include "LaTeXFeatures.h"
#include "Lexer.h"
#include "MetricsInfo.h"
#include "texstream.h"
#include "xml.h"

#include "support/debug.h"
#include "support/docstream.h"
#include "support/gettext.h"
#include "support/lassert.h"
#include "support/Length.h"
#include "support/lstrings.h"
#include "support/qstring_helpers.h"

#include "frontends/Application.h"
#include "frontends/FontMetrics.h"
#include "frontends/Painter.h"

using namespace std;

namespace lyx {


InsetSpace::InsetSpace(InsetSpaceParams const & params)
	: Inset(nullptr), params_(params)
{}


InsetSpaceParams::Kind InsetSpace::kind() const
{
	return params_.kind;
}


GlueLength InsetSpace::length() const
{
	return params_.length;
}


docstring InsetSpace::toolTip(BufferView const &, int, int) const
{
	docstring message;
	switch (params_.kind) {
	case InsetSpaceParams::NORMAL:
		message = _("Normal Space");
		break;
	case InsetSpaceParams::PROTECTED:
		message = _("Non-Breaking Normal Space");
		break;
	case InsetSpaceParams::VISIBLE:
		message = _("Non-Breaking Visible Normal Space");
		break;
	case InsetSpaceParams::THIN:
		message = _("Non-Breaking Thin Space (1/6 em)");
		break;
	case InsetSpaceParams::MEDIUM:
		message = _("Non-Breaking Medium Space (2/9 em)");
		break;
	case InsetSpaceParams::THICK:
		message = _("Non-Breaking Thick Space (5/18 em)");
		break;
	case InsetSpaceParams::QUAD:
		message = _("Quad Space (1 em)");
		break;
	case InsetSpaceParams::QQUAD:
		message = _("Double Quad Space (2 em)");
		break;
	case InsetSpaceParams::ENSPACE:
		message = _("Non-Breaking Half Quad Space (1/2 em)");
		break;
	case InsetSpaceParams::ENSKIP:
		message = _("Half Quad Space (1/2 em)");
		break;
	case InsetSpaceParams::NEGTHIN:
		message = _("Non-Breaking Negative Thin Space (-1/6 em)");
		break;
	case InsetSpaceParams::NEGMEDIUM:
		message = _("Non-Breaking Negative Medium Space (-2/9 em)");
		break;
	case InsetSpaceParams::NEGTHICK:
		message = _("Non-Breaking Negative Thick Space (-5/18 em)");
		break;
	case InsetSpaceParams::HFILL:
		message = _("Horizontal Fill");
		break;
	case InsetSpaceParams::HFILL_PROTECTED:
		message = _("Non-Breaking Horizontal Fill");
		break;
	case InsetSpaceParams::DOTFILL:
		message = _("Non-Breaking Horizontal Fill (Dots)");
		break;
	case InsetSpaceParams::HRULEFILL:
		message = _("Non-Breaking Horizontal Fill (Rule)");
		break;
	case InsetSpaceParams::LEFTARROWFILL:
		message = _("Non-Breaking Horizontal Fill (Left Arrow)");
		break;
	case InsetSpaceParams::RIGHTARROWFILL:
		message = _("Non-Breaking Horizontal Fill (Right Arrow)");
		break;
	case InsetSpaceParams::UPBRACEFILL:
		message = _("Non-Breaking Horizontal Fill (Up Brace)");
		break;
	case InsetSpaceParams::DOWNBRACEFILL:
		message = _("Non-Breaking Horizontal Fill (Down Brace)");
		break;
	case InsetSpaceParams::CUSTOM:
		// FIXME unicode
		message = support::bformat(_("Horizontal Space (%1$s)"),
					   locLengthDocString(from_ascii(params_.length.asString())));
		break;
	case InsetSpaceParams::CUSTOM_PROTECTED:
		// FIXME unicode
		message = support::bformat(_("Non-Breaking Horizontal Space (%1$s)"),
					   locLengthDocString(from_ascii(params_.length.asString())));
		break;
	}
	return message;
}


void InsetSpace::doDispatch(Cursor & cur, FuncRequest & cmd)
{
	switch (cmd.action()) {

	case LFUN_INSET_MODIFY: {
		cur.recordUndo();
		string arg = to_utf8(cmd.argument());
		if (arg == "space \\hspace{}")
			arg += params_.length.len().empty()
				? " \\length 1" + string(stringFromUnit(Length::defaultUnit()))
				: " \\length " + params_.length.asString();
		string2params(arg, params_);
		break;
	}

	case LFUN_INSET_DIALOG_UPDATE:
		cur.bv().updateDialog("space", params2string(params()));
		break;

	default:
		Inset::doDispatch(cur, cmd);
		break;
	}
}


bool InsetSpace::getStatus(Cursor & cur, FuncRequest const & cmd,
	FuncStatus & status) const
{
	switch (cmd.action()) {
	// we handle these
	case LFUN_INSET_MODIFY:
		if (cmd.getArg(0) == "space") {
			InsetSpaceParams params;
			string2params(to_utf8(cmd.argument()), params);
			status.setOnOff(params_.kind == params.kind);
			status.setEnabled(true);
		} else
			status.setEnabled(false);
		return true;

	case LFUN_INSET_DIALOG_UPDATE:
		status.setEnabled(true);
		return true;
	default:
		return Inset::getStatus(cur, cmd, status);
	}
}


int InsetSpace::rowFlags() const
{
	switch (params_.kind) {
		case InsetSpaceParams::PROTECTED:
		case InsetSpaceParams::CUSTOM_PROTECTED:
		case InsetSpaceParams::HFILL_PROTECTED:
		case InsetSpaceParams::THIN:
		case InsetSpaceParams::NEGTHIN:
		case InsetSpaceParams::MEDIUM:
		case InsetSpaceParams::NEGMEDIUM:
		case InsetSpaceParams::THICK:
		case InsetSpaceParams::NEGTHICK:
		case InsetSpaceParams::ENSPACE:
		case InsetSpaceParams::VISIBLE:
			// no break after these
			return Inline;
		case InsetSpaceParams::NORMAL:
		case InsetSpaceParams::QUAD:
		case InsetSpaceParams::QQUAD:
		case InsetSpaceParams::ENSKIP:
		case InsetSpaceParams::CUSTOM:
		case InsetSpaceParams::HFILL:
		case InsetSpaceParams::DOTFILL:
		case InsetSpaceParams::HRULEFILL:
		case InsetSpaceParams::LEFTARROWFILL:
		case InsetSpaceParams::RIGHTARROWFILL:
		case InsetSpaceParams::UPBRACEFILL:
		case InsetSpaceParams::DOWNBRACEFILL:
			// these allow line breaking
			break;
	}
	return CanBreakAfter;
}


namespace {
int const arrow_size = 8;
}


void InsetSpace::metrics(MetricsInfo & mi, Dimension & dim) const
{
	if (isHfill()) {
		// The width for hfills is calculated externally in
		// TextMetrics::setRowAlignment. The value of 5 is the
		// minimal value when the hfill is not active.
		dim = Dimension(5, 10, 10);
		return;
	}

	frontend::FontMetrics const & fm = theFontMetrics(mi.base.font);
	dim.asc = fm.maxAscent();
	dim.des = fm.maxDescent();
	int const em = fm.em();

	switch (params_.kind) {
		case InsetSpaceParams::THIN:
		case InsetSpaceParams::NEGTHIN:
			dim.wid = em / 6;
			break;
		case InsetSpaceParams::MEDIUM:
		case InsetSpaceParams::NEGMEDIUM:
			dim.wid = em / 4;
			break;
		case InsetSpaceParams::THICK:
		case InsetSpaceParams::NEGTHICK:
			dim.wid = em / 2;
			break;
		case InsetSpaceParams::PROTECTED:
		case InsetSpaceParams::VISIBLE:
		case InsetSpaceParams::NORMAL:
			dim.wid = fm.width(char_type(' '));
			break;
		case InsetSpaceParams::QUAD:
			dim.wid = em;
			break;
		case InsetSpaceParams::QQUAD:
			dim.wid = 2 * em;
			break;
		case InsetSpaceParams::ENSPACE:
		case InsetSpaceParams::ENSKIP:
			dim.wid = int(0.5 * em);
			break;
		case InsetSpaceParams::CUSTOM:
		case InsetSpaceParams::CUSTOM_PROTECTED: {
			int const w = mi.base.inPixels(params_.length.len());
			int const minw = (w < 0) ? 3 * arrow_size : 4;
			dim.wid = max(minw, abs(w));
			break;
		}
		case InsetSpaceParams::HFILL:
		case InsetSpaceParams::HFILL_PROTECTED:
		case InsetSpaceParams::DOTFILL:
		case InsetSpaceParams::HRULEFILL:
		case InsetSpaceParams::LEFTARROWFILL:
		case InsetSpaceParams::RIGHTARROWFILL:
		case InsetSpaceParams::UPBRACEFILL:
		case InsetSpaceParams::DOWNBRACEFILL:
			// shut up compiler
			break;
	}
}


void InsetSpace::draw(PainterInfo & pi, int x, int y) const
{
	Dimension const dim = dimension(*pi.base.bv);

	if (isHfill() || params_.length.len().value() < 0) {
		int const asc = theFontMetrics(pi.base.font).ascent('M');
		int const desc = theFontMetrics(pi.base.font).descent('M');
		// Pixel height divisible by 2 for prettier fill graphics:
		int const oddheight = (asc ^ desc) & 1;
		int const x0 = x + 1;
		int const x1 = x + dim.wid - 2;
		int const y0 = y + desc - 1;
		int const y1 = y - asc + oddheight - 1;
		int const y2 = (y0 + y1) / 2;
		int xoffset = (y0 - y1) / 2;

		// Two tests for very narrow insets
		if (xoffset > x1 - x0
		     && (params_.kind == InsetSpaceParams::LEFTARROWFILL
			 || params_.kind == InsetSpaceParams::RIGHTARROWFILL))
				xoffset = x1 - x0;
		if (xoffset * 6 > (x1 - x0)
		     && (params_.kind == InsetSpaceParams::UPBRACEFILL
			 || params_.kind == InsetSpaceParams::DOWNBRACEFILL))
				xoffset = (x1 - x0) / 6;

		int const x2 = x0 + xoffset;
		int const x3 = x1 - xoffset;
		int const xm = (x0 + x1) / 2;
		int const xml = xm - xoffset;
		int const xmr = xm + xoffset;

		if (params_.kind == InsetSpaceParams::HFILL) {
			pi.pain.line(x0, y1, x0, y0, Color_added_space);
			pi.pain.line(x0, y2, x1, y2, Color_added_space,
				frontend::Painter::line_onoffdash);
			pi.pain.line(x1, y1, x1, y0, Color_added_space);
		} else if (params_.kind == InsetSpaceParams::HFILL_PROTECTED) {
			pi.pain.line(x0, y1, x0, y0, Color_latex);
			pi.pain.line(x0, y2, x1, y2, Color_latex,
				frontend::Painter::line_onoffdash);
			pi.pain.line(x1, y1, x1, y0, Color_latex);
		} else if (params_.kind == InsetSpaceParams::DOTFILL) {
			pi.pain.line(x0, y1, x0, y0, Color_special);
			pi.pain.line(x0, y0, x1, y0, Color_special,
				frontend::Painter::line_onoffdash);
			pi.pain.line(x1, y1, x1, y0, Color_special);
		} else if (params_.kind == InsetSpaceParams::HRULEFILL) {
			pi.pain.line(x0, y1, x0, y0, Color_special);
			pi.pain.line(x0, y0, x1, y0, Color_special);
			pi.pain.line(x1, y1, x1, y0, Color_special);
		} else if (params_.kind == InsetSpaceParams::LEFTARROWFILL) {
			pi.pain.line(x2, y1 + 1 , x0 + 1, y2, Color_special);
			pi.pain.line(x0 + 1, y2 + 1 , x2, y0, Color_special);
			pi.pain.line(x0, y2 , x1, y2, Color_special);
		} else if (params_.kind == InsetSpaceParams::RIGHTARROWFILL) {
			pi.pain.line(x3 + 1, y1 + 1 , x1, y2, Color_special);
			pi.pain.line(x1, y2 + 1 , x3 + 1, y0, Color_special);
			pi.pain.line(x0, y2 , x1, y2, Color_special);
		} else if (params_.kind == InsetSpaceParams::UPBRACEFILL) {
			pi.pain.line(x0 + 1, y1 + 1 , x2, y2, Color_special);
			pi.pain.line(x2, y2 , xml, y2, Color_special);
			pi.pain.line(xml + 1, y2 + 1 , xm, y0, Color_special);
			pi.pain.line(xm + 1, y0 , xmr, y2 + 1, Color_special);
			pi.pain.line(xmr, y2 , x3, y2, Color_special);
			pi.pain.line(x3 + 1, y2 , x1, y1 + 1, Color_special);
		} else if (params_.kind == InsetSpaceParams::DOWNBRACEFILL) {
			pi.pain.line(x0 + 1, y0 , x2, y2 + 1, Color_special);
			pi.pain.line(x2, y2 , xml, y2, Color_special);
			pi.pain.line(xml + 1, y2 , xm, y1 + 1, Color_special);
			pi.pain.line(xm + 1, y1 + 1 , xmr, y2, Color_special);
			pi.pain.line(xmr, y2 , x3, y2, Color_special);
			pi.pain.line(x3 + 1, y2 + 1 , x1, y0, Color_special);
		} else if (params_.kind == InsetSpaceParams::CUSTOM) {
			pi.pain.line(x0, y1 + 1 , x2 + 1, y2, Color_special);
			pi.pain.line(x2 + 1, y2 + 1 , x0, y0, Color_special);
			pi.pain.line(x1 + 1, y1 + 1 , x3, y2, Color_special);
			pi.pain.line(x3, y2 + 1 , x1 + 1, y0, Color_special);
			pi.pain.line(x2, y2 , x3, y2, Color_special);
		} else if (params_.kind == InsetSpaceParams::CUSTOM_PROTECTED) {
			pi.pain.line(x0, y1 + 1 , x2 + 1, y2, Color_latex);
			pi.pain.line(x2 + 1, y2 + 1 , x0, y0, Color_latex);
			pi.pain.line(x1 + 1, y1 + 1 , x3, y2, Color_latex);
			pi.pain.line(x3, y2 + 1 , x1 + 1, y0, Color_latex);
			pi.pain.line(x2, y2 , x3, y2, Color_latex);
		}
		return;
	}

	int const w = dim.wid;
	int const h = theFontMetrics(pi.base.font).xHeight();
	int xp[4], yp[4];

	xp[0] = x;
	yp[0] = y - max(h / 4, 1);
	if (params_.kind == InsetSpaceParams::NORMAL ||
	    params_.kind == InsetSpaceParams::PROTECTED ||
	    params_.kind == InsetSpaceParams::VISIBLE) {
		xp[1] = x;         yp[1] = y;
		xp[2] = x + w - 1; yp[2] = y;
	} else {
		xp[1] = x;         yp[1] = y + max(h / 4, 1);
		xp[2] = x + w - 1; yp[2] = y + max(h / 4, 1);
	}
	xp[3] = x + w - 1;
	yp[3] = y - max(h / 4, 1);

	Color col = Color_special;
	if (params_.kind == InsetSpaceParams::PROTECTED ||
	    params_.kind == InsetSpaceParams::ENSPACE ||
	    params_.kind == InsetSpaceParams::THIN ||
	    params_.kind == InsetSpaceParams::NEGTHIN ||
	    params_.kind == InsetSpaceParams::MEDIUM ||
	    params_.kind == InsetSpaceParams::NEGMEDIUM ||
	    params_.kind == InsetSpaceParams::THICK ||
	    params_.kind == InsetSpaceParams::NEGTHICK ||
	    params_.kind == InsetSpaceParams::CUSTOM_PROTECTED)
		col = Color_latex;
	else if (params_.kind == InsetSpaceParams::VISIBLE)
		col =  Color_foreground;

	pi.pain.lines(xp, yp, 4, col);
}


void InsetSpaceParams::write(ostream & os) const
{
	switch (kind) {
	case InsetSpaceParams::NORMAL:
		os << "\\space{}";
		break;
	case InsetSpaceParams::PROTECTED:
		os <<  "~";
		break;
	case InsetSpaceParams::VISIBLE:
		os <<  "\\textvisiblespace{}";
		break;
	case InsetSpaceParams::THIN:
		os <<  "\\thinspace{}";
		break;
	case InsetSpaceParams::MEDIUM:
		os <<  "\\medspace{}";
		break;
	case InsetSpaceParams::THICK:
		os <<  "\\thickspace{}";
		break;
	case InsetSpaceParams::QUAD:
		os <<  "\\quad{}";
		break;
	case InsetSpaceParams::QQUAD:
		os <<  "\\qquad{}";
		break;
	case InsetSpaceParams::ENSPACE:
		os <<  "\\enspace{}";
		break;
	case InsetSpaceParams::ENSKIP:
		os <<  "\\enskip{}";
		break;
	case InsetSpaceParams::NEGTHIN:
		os <<  "\\negthinspace{}";
		break;
	case InsetSpaceParams::NEGMEDIUM:
		os <<  "\\negmedspace{}";
		break;
	case InsetSpaceParams::NEGTHICK:
		os <<  "\\negthickspace{}";
		break;
	case InsetSpaceParams::HFILL:
		os <<  "\\hfill{}";
		break;
	case InsetSpaceParams::HFILL_PROTECTED:
		os <<  "\\hspace*{\\fill}";
		break;
	case InsetSpaceParams::DOTFILL:
		os <<  "\\dotfill{}";
		break;
	case InsetSpaceParams::HRULEFILL:
		os <<  "\\hrulefill{}";
		break;
	case InsetSpaceParams::LEFTARROWFILL:
		os <<  "\\leftarrowfill{}";
		break;
	case InsetSpaceParams::RIGHTARROWFILL:
		os <<  "\\rightarrowfill{}";
		break;
	case InsetSpaceParams::UPBRACEFILL:
		os <<  "\\upbracefill{}";
		break;
	case InsetSpaceParams::DOWNBRACEFILL:
		os <<  "\\downbracefill{}";
		break;
	case InsetSpaceParams::CUSTOM:
		os <<  "\\hspace{}";
		break;
	case InsetSpaceParams::CUSTOM_PROTECTED:
		os <<  "\\hspace*{}";
		break;
	}

	if (!length.len().empty())
		os << "\n\\length " << length.asString();
}


void InsetSpaceParams::read(Lexer & lex)
{
	lex.setContext("InsetSpaceParams::read");
	string command;
	lex >> command;

	// The tests for math might be disabled after a file format change
	if (command == "\\space{}")
		kind = InsetSpaceParams::NORMAL;
	else if (command == "~")
		kind = InsetSpaceParams::PROTECTED;
	else if (command == "\\textvisiblespace{}")
		kind = InsetSpaceParams::VISIBLE;
	else if (command == "\\thinspace{}")
		kind = InsetSpaceParams::THIN;
	else if (command == "\\medspace{}")
		kind = InsetSpaceParams::MEDIUM;
	else if (command == "\\thickspace{}")
		kind = InsetSpaceParams::THICK;
	else if (command == "\\quad{}")
		kind = InsetSpaceParams::QUAD;
	else if (command == "\\qquad{}")
		kind = InsetSpaceParams::QQUAD;
	else if (command == "\\enspace{}")
		kind = InsetSpaceParams::ENSPACE;
	else if (command == "\\enskip{}")
		kind = InsetSpaceParams::ENSKIP;
	else if (command == "\\negthinspace{}")
		kind = InsetSpaceParams::NEGTHIN;
	else if (command == "\\negmedspace{}")
		kind = InsetSpaceParams::NEGMEDIUM;
	else if (command == "\\negthickspace{}")
		kind = InsetSpaceParams::NEGTHICK;
	else if (command == "\\hfill{}")
		kind = InsetSpaceParams::HFILL;
	else if (command == "\\hspace*{\\fill}")
		kind = InsetSpaceParams::HFILL_PROTECTED;
	else if (command == "\\dotfill{}")
		kind = InsetSpaceParams::DOTFILL;
	else if (command == "\\hrulefill{}")
		kind = InsetSpaceParams::HRULEFILL;
	else if (command == "\\hspace{}")
		kind = InsetSpaceParams::CUSTOM;
	else if (command == "\\leftarrowfill{}")
		kind = InsetSpaceParams::LEFTARROWFILL;
	else if (command == "\\rightarrowfill{}")
		kind = InsetSpaceParams::RIGHTARROWFILL;
	else if (command == "\\upbracefill{}")
		kind = InsetSpaceParams::UPBRACEFILL;
	else if (command == "\\downbracefill{}")
		kind = InsetSpaceParams::DOWNBRACEFILL;
	else if (command == "\\hspace*{}")
		kind = InsetSpaceParams::CUSTOM_PROTECTED;
	else
		lex.printError("InsetSpace: Unknown kind: `$$Token'");

	if (lex.checkFor("\\length"))
		lex >> length;
}


void InsetSpace::write(ostream & os) const
{
	os << "space ";
	params_.write(os);
}


void InsetSpace::read(Lexer & lex)
{
	params_.read(lex);
	lex >> "\\end_inset";
}


void InsetSpace::latex(otexstream & os, OutputParams const & runparams) const
{
	switch (params_.kind) {
	case InsetSpaceParams::NORMAL:
		if (runparams.find_effective())
			os << "~";
		else
			os << (runparams.free_spacing ? " " : "\\ ");
		break;
	case InsetSpaceParams::PROTECTED:
		if (runparams.find_effective())
			os.put(0xa0);
		else if (getLocalOrDefaultLang(runparams)->lang() == "polutonikogreek")
			// in babel's polutonikogreek, ~ is active
			os << (runparams.free_spacing ? " " : "\\nobreakspace{}");
		else
			os << (runparams.free_spacing ? ' ' : '~');
		break;
	case InsetSpaceParams::VISIBLE:
		if (runparams.find_effective())
			os.put(0x2423);
		else
			os << (runparams.free_spacing ? " " : "\\textvisiblespace{}");
		break;
	case InsetSpaceParams::THIN:
		if (runparams.find_effective())
			os.put(0x202f);
		else
			os << (runparams.free_spacing ? " " : "\\,");
		break;
	case InsetSpaceParams::MEDIUM:
		if (runparams.find_effective())
			os.put(0x2005);
		else if (params_.math)
			os << (runparams.free_spacing ? " " : "\\:");
		else
			os << (runparams.free_spacing ? " " : "\\medspace{}");
		break;
	case InsetSpaceParams::THICK:
		if (runparams.find_effective())
			os.put(0x2004);
		else if (params_.math)
			os << (runparams.free_spacing ? " " : "\\;");
		else
			os << (runparams.free_spacing ? " " : "\\thickspace{}");
		break;
	case InsetSpaceParams::QUAD:
		if (runparams.find_effective())
			os.put(0x2003);
		else
			os << (runparams.free_spacing ? " " : "\\quad{}");
		break;
	case InsetSpaceParams::QQUAD:
		if (runparams.find_effective()) {
			os.put(0x2003);
			os.put(0x2003);
		}
		else
			os << (runparams.free_spacing ? " " : "\\qquad{}");
		break;
	case InsetSpaceParams::ENSPACE:
		if (runparams.find_effective())
			os.put(0x2002);
		else
			os << (runparams.free_spacing ? " " : "\\enspace{}");
		break;
	case InsetSpaceParams::ENSKIP:
		if (runparams.find_effective())
			os.put(0x2002);
		else
			os << (runparams.free_spacing ? " " : "\\enskip{}");
		break;
	case InsetSpaceParams::NEGTHIN:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\negthinspace{}");
		break;
	case InsetSpaceParams::NEGMEDIUM:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\negmedspace{}");
		break;
	case InsetSpaceParams::NEGTHICK:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\negthickspace{}");
		break;
	case InsetSpaceParams::HFILL:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\hfill{}");
		break;
	case InsetSpaceParams::HFILL_PROTECTED:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\hspace*{\\fill}");
		break;
	case InsetSpaceParams::DOTFILL:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\dotfill{}");
		break;
	case InsetSpaceParams::HRULEFILL:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\hrulefill{}");
		break;
	case InsetSpaceParams::LEFTARROWFILL:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\leftarrowfill{}");
		break;
	case InsetSpaceParams::RIGHTARROWFILL:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\rightarrowfill{}");
		break;
	case InsetSpaceParams::UPBRACEFILL:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\upbracefill{}");
		break;
	case InsetSpaceParams::DOWNBRACEFILL:
		os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\downbracefill{}");
		break;
	case InsetSpaceParams::CUSTOM:
		if (runparams.find_effective())
			os.put(0x00a0);
		else if (runparams.free_spacing)
			os << " ";
		else
			os << "\\hspace{" << from_ascii(params_.length.asLatexString()) << "}";
		break;
	case InsetSpaceParams::CUSTOM_PROTECTED:
		if (runparams.find_effective())
			os.put(0x00a0);
		else if (runparams.free_spacing)
			os << " ";
		else
			os << "\\hspace*{" << from_ascii(params_.length.asLatexString()) << "}";
		break;
	}
}


int InsetSpace::plaintext(odocstringstream & os,
        OutputParams const & rp, size_t) const
{
	switch (params_.kind) {
	case InsetSpaceParams::HFILL:
	case InsetSpaceParams::HFILL_PROTECTED:
		os << "     ";
		return 5;
	case InsetSpaceParams::DOTFILL:
		os << ".....";
		return 5;
	case InsetSpaceParams::HRULEFILL:
		os << "_____";
		return 5;
	case InsetSpaceParams::LEFTARROWFILL:
		os << "<----";
		return 5;
	case InsetSpaceParams::RIGHTARROWFILL:
		os << "---->";
		return 5;
	case InsetSpaceParams::UPBRACEFILL:
		os << "\\-v-/";
		return 5;
	case InsetSpaceParams::DOWNBRACEFILL:
		os << "/-^-\\";
		return 5;
	case InsetSpaceParams::VISIBLE:
		os.put(0x2423);
		return 1;
	case InsetSpaceParams::ENSKIP:
		os.put(0x2002);
		return 1;
	case InsetSpaceParams::ENSPACE:
		os.put(0x2060); // WORD JOINER, makes the breakable en space unbreakable
		os.put(0x2002);
		os.put(0x2060); // WORD JOINER, makes the breakable en space unbreakable
		return 3;
	case InsetSpaceParams::QUAD:
		os.put(0x2003);
		return 1;
	case InsetSpaceParams::QQUAD:
		os.put(0x2003);
		os.put(0x2003);
		return 2;
	case InsetSpaceParams::THIN:
		if (rp.find_effective())
			// simple search
			os << ' ';
		else
			os.put(0x202f);
		return 1;
	case InsetSpaceParams::MEDIUM:
		os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable medium space breakable
		os.put(0x2005);
		os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable medium space breakable
		return 1;
	case InsetSpaceParams::THICK:
		os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable thick space breakable
		os.put(0x2004);
		os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable thick space breakable
		return 1;
	case InsetSpaceParams::PROTECTED:
	case InsetSpaceParams::CUSTOM_PROTECTED:
		if (rp.find_effective())
			// simple search
			os << ' ';
		else
			os.put(0x00a0);
		return 1;
	case InsetSpaceParams::NEGTHIN:
	case InsetSpaceParams::NEGMEDIUM:
	case InsetSpaceParams::NEGTHICK:
		return 0;
	default:
		os << ' ';
		return 1;
	}
}


namespace {
std::string spaceToXMLEntity(InsetSpaceParams::Kind kind) {
	switch (kind) {
	case InsetSpaceParams::NORMAL:
		return " ";
	case InsetSpaceParams::QUAD:
		return "&#x2003;"; // HTML: &emsp;
	case InsetSpaceParams::QQUAD:
		return "&#x2003;&#x2003;"; // HTML: &emsp;&emsp;
	case InsetSpaceParams::ENSKIP:
		return "&#x2002;"; // HTML: &ensp;
	case InsetSpaceParams::VISIBLE:
		return "&#x2423;";
	case InsetSpaceParams::ENSPACE: // HTML: &#x2060;&ensp;&#x2060; (word joiners)
		return "&#x2060;&#x2002;&#x2060;";
	case InsetSpaceParams::THIN:
		return "&#x202F;"; // HTML: &thinspace;
	case InsetSpaceParams::MEDIUM:
		return "&#x2005;"; // HTML: &emsp14;
	case InsetSpaceParams::THICK:
		return "&#x2004;"; // HTML: &emsp13;
	case InsetSpaceParams::PROTECTED:
	case InsetSpaceParams::NEGTHIN:
	case InsetSpaceParams::NEGMEDIUM:
	case InsetSpaceParams::NEGTHICK:
		return "&#xA0;"; // HTML: &nbsp;
	case InsetSpaceParams::CUSTOM_PROTECTED:
		// FIXME XHTML/DocBook
		// Probably we could do some sort of blank span?
		return "&#160;";
	case InsetSpaceParams::HFILL:
	case InsetSpaceParams::HFILL_PROTECTED:
	case InsetSpaceParams::DOTFILL:
	case InsetSpaceParams::HRULEFILL:
	case InsetSpaceParams::LEFTARROWFILL:
	case InsetSpaceParams::RIGHTARROWFILL:
	case InsetSpaceParams::UPBRACEFILL:
	case InsetSpaceParams::DOWNBRACEFILL:
	case InsetSpaceParams::CUSTOM:
		// FIXME XHTML/DocBook
		// Can we do anything with those?
		return "\n";
	}
	return "";
}
}


void InsetSpace::docbook(XMLStream & xs, OutputParams const &) const
{
	xs << XMLStream::ESCAPE_NONE << from_ascii(spaceToXMLEntity(params_.kind));
}


docstring InsetSpace::xhtml(XMLStream & xs, OutputParams const &) const
{
	xs << XMLStream::ESCAPE_NONE << from_ascii(spaceToXMLEntity(params_.kind));
	return docstring();
}


void InsetSpace::validate(LaTeXFeatures & features) const
{
	if (features.isAvailableAtLeastFrom("LaTeX", 2020, 10))
		// As of this version, the LaTeX kernel
		// includes all spaces.
		return;

	// In earlier versions, we require amsmath
	// for some text and math spaces
	if ((params_.kind == InsetSpaceParams::NEGMEDIUM
	     || params_.kind == InsetSpaceParams::NEGTHICK)
	    || (!params_.math
		&& (params_.kind == InsetSpaceParams::MEDIUM
		    || params_.kind == InsetSpaceParams::THICK)))
		features.require("amsmath");
}


void InsetSpace::toString(odocstream & os) const
{
	odocstringstream ods;
	plaintext(ods, OutputParams(0));
	os << ods.str();
}


void InsetSpace::forOutliner(docstring & os, size_t const, bool const) const
{
	// There's no need to be cute here.
	os += " ";
}


bool InsetSpace::isHfill() const
{
	return params_.kind == InsetSpaceParams::HFILL
		|| params_.kind == InsetSpaceParams::HFILL_PROTECTED
		|| params_.kind == InsetSpaceParams::DOTFILL
		|| params_.kind == InsetSpaceParams::HRULEFILL
		|| params_.kind == InsetSpaceParams::LEFTARROWFILL
		|| params_.kind == InsetSpaceParams::RIGHTARROWFILL
		|| params_.kind == InsetSpaceParams::UPBRACEFILL
		|| params_.kind == InsetSpaceParams::DOWNBRACEFILL;
}


string InsetSpace::contextMenuName() const
{
	return "context-space";
}


void InsetSpace::string2params(string const & in, InsetSpaceParams & params)
{
	params = InsetSpaceParams();
	if (in.empty())
		return;

	istringstream data(in);
	Lexer lex;
	lex.setStream(data);
	lex.setContext("InsetSpace::string2params");
	lex.next();
	string const name = lex.getString();
	if (name == "mathspace")
		params.math = true;
	else {
		params.math = false;
		// we can try to read this even if the name is wrong
		LATTEST(name == "space");
	}

	// There are cases, such as when we are called via getStatus() from
	// Dialog::canApply(), where we are just called with "space" rather
	// than a full "space \type{}\n\\end_inset".
	if (lex.isOK())
		params.read(lex);
}


string InsetSpace::params2string(InsetSpaceParams const & params)
{
	ostringstream data;
	if (params.math)
		data << "math";
	data << "space" << ' ';
	params.write(data);
	return data.str();
}


} // namespace lyx
