mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-14 07:18:39 +00:00
Add \smash, laps, spaces, and phantoms (#833)
* Add \smash, laps, spaces, and phantoms 1. Support `\smash`, including the optional argument from AMS. 2. Change `\llap` and `\rlap` so that they render in text style. Repeat: This *changes* KaTeX behavior. 3. Add `\mathllap` and `\mathrlap`. These will act as they do in `mathtools` and as in previous KaTeX versions of `\llap` and `\rlap`. 4. Add `\mathclap` and `\clap`. 5. Add `\hphantom` and \vphantom`. 6. Add `\thinspace`, `\medspace`, `\thickspace` 7. Add `\hspace`. This work will resolve issue #270 and parts of #50 and #164. A. Perlis has written a [concise description](https://www.math.lsu.edu/~aperlis/publications/mathclap/perlis_mathclap_24Jun2003.pdf) of items 1 thru 5. Except for `\smash`'s optional argument. It's described in the [AMS User's Guide](http://texdoc.net/texmf-dist/doc/latex/amsmath/amsldoc.pdf). Item 6 also comes from the AMS User's Guide. * Fix test spec * Exploit makeVList for smash * update smash and phantom screenshots for chrome * Pick up review comments * Change test from \llap to \mathlap \llap is fundamentally a text-mode function. We should not expect it to work correctly when given math-mode arguments. So test \mathllap instead. * Correct \llap macro A correction. The previous macro returned an error if given an argument with math-mode content, such as x^2. The corrected macro will not return an error. It will instead return well rendered math, but letters are in `\mathrm` font. * update \llap, \rlap, \clap macros to use \textrm * update Lap screenshots
This commit is contained in:
@@ -762,20 +762,57 @@ groupTypes.spacing = function(group, options) {
|
||||
}
|
||||
};
|
||||
|
||||
groupTypes.llap = function(group, options) {
|
||||
const inner = makeSpan(
|
||||
["inner"], [buildGroup(group.value.body, options)]);
|
||||
groupTypes.lap = function(group, options) {
|
||||
// mathllap, mathrlap, mathclap
|
||||
let inner;
|
||||
if (group.value.alignment === "clap") {
|
||||
// ref: https://www.math.lsu.edu/~aperlis/publications/mathclap/
|
||||
inner = makeSpan([], [buildGroup(group.value.body, options)]);
|
||||
// wrap, since CSS will center a .clap > .inner > span
|
||||
inner = makeSpan(["inner"], [inner], options);
|
||||
} else {
|
||||
inner = makeSpan(
|
||||
["inner"], [buildGroup(group.value.body, options)]);
|
||||
}
|
||||
const fix = makeSpan(["fix"], []);
|
||||
return makeSpan(
|
||||
["mord", "llap"], [inner, fix], options);
|
||||
["mord", group.value.alignment], [inner, fix], options);
|
||||
};
|
||||
|
||||
groupTypes.rlap = function(group, options) {
|
||||
const inner = makeSpan(
|
||||
["inner"], [buildGroup(group.value.body, options)]);
|
||||
const fix = makeSpan(["fix"], []);
|
||||
return makeSpan(
|
||||
["mord", "rlap"], [inner, fix], options);
|
||||
groupTypes.smash = function(group, options) {
|
||||
const node = makeSpan(["mord"], [buildGroup(group.value.body, options)]);
|
||||
|
||||
if (!group.value.smashHeight && !group.value.smashDepth) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (group.value.smashHeight) {
|
||||
node.height = 0;
|
||||
// In order to influence makeVList, we have to reset the children.
|
||||
if (node.children) {
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
node.children[i].height = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (group.value.smashDepth) {
|
||||
node.depth = 0;
|
||||
if (node.children) {
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
node.children[i].depth = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, we've reset the TeX-like height and depth values.
|
||||
// But the span still has an HTML line height.
|
||||
// makeVList applies "display: table-cell", which prevents the browser
|
||||
// from acting on that line height. So we'll call makeVList now.
|
||||
|
||||
return buildCommon.makeVList([
|
||||
{type: "elem", elem: node},
|
||||
], "firstBaseline", null, options);
|
||||
};
|
||||
|
||||
groupTypes.op = function(group, options) {
|
||||
@@ -1698,6 +1735,34 @@ groupTypes.phantom = function(group, options) {
|
||||
return new buildCommon.makeFragment(elements);
|
||||
};
|
||||
|
||||
groupTypes.hphantom = function(group, options) {
|
||||
let node = makeSpan(
|
||||
[], [buildGroup(group.value.body, options.withPhantom())]);
|
||||
node.height = 0;
|
||||
node.depth = 0;
|
||||
if (node.children) {
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
node.children[i].height = 0;
|
||||
node.children[i].depth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// See smash for comment re: use of makeVList
|
||||
node = buildCommon.makeVList([
|
||||
{type: "elem", elem: node},
|
||||
], "firstBaseline", null, options);
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.vphantom = function(group, options) {
|
||||
const inner = makeSpan(
|
||||
["inner"], [buildGroup(group.value.body, options.withPhantom())]);
|
||||
const fix = makeSpan(["fix"], []);
|
||||
return makeSpan(
|
||||
["mord", "rlap"], [inner, fix], options);
|
||||
};
|
||||
|
||||
groupTypes.mclass = function(group, options) {
|
||||
const elements = buildExpression(group.value.value, options, true);
|
||||
|
||||
|
@@ -621,21 +621,31 @@ groupTypes.kern = function(group) {
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.llap = function(group, options) {
|
||||
groupTypes.lap = function(group, options) {
|
||||
// mathllap, mathrlap, mathclap
|
||||
const node = new mathMLTree.MathNode(
|
||||
"mpadded", [buildGroup(group.value.body, options)]);
|
||||
|
||||
node.setAttribute("lspace", "-1width");
|
||||
if (group.value.alignment !== "rlap") {
|
||||
const offset = (group.value.alignment === "llap" ? "-1" : "-0.5");
|
||||
node.setAttribute("lspace", offset + "width");
|
||||
}
|
||||
node.setAttribute("width", "0px");
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.rlap = function(group, options) {
|
||||
groupTypes.smash = function(group, options) {
|
||||
const node = new mathMLTree.MathNode(
|
||||
"mpadded", [buildGroup(group.value.body, options)]);
|
||||
|
||||
node.setAttribute("width", "0px");
|
||||
if (group.value.smashHeight) {
|
||||
node.setAttribute("height", "0px");
|
||||
}
|
||||
|
||||
if (group.value.smashDepth) {
|
||||
node.setAttribute("depth", "0px");
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
@@ -645,6 +655,20 @@ groupTypes.phantom = function(group, options) {
|
||||
return new mathMLTree.MathNode("mphantom", inner);
|
||||
};
|
||||
|
||||
groupTypes.hphantom = function(group, options) {
|
||||
const inner = buildExpression(group.value.value, options);
|
||||
const node = new mathMLTree.MathNode("mphantom", inner);
|
||||
node.setAttribute("height", "0px");
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.vphantom = function(group, options) {
|
||||
const inner = buildExpression(group.value.value, options);
|
||||
const node = new mathMLTree.MathNode("mphantom", inner);
|
||||
node.setAttribute("width", "0px");
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.mclass = function(group, options) {
|
||||
const inner = buildExpression(group.value.value, options);
|
||||
return new mathMLTree.MathNode("mstyle", inner);
|
||||
|
@@ -234,13 +234,14 @@ defineFunction("\\KaTeX", {
|
||||
};
|
||||
});
|
||||
|
||||
defineFunction("\\phantom", {
|
||||
defineFunction(["\\phantom", "\\hphantom", "\\vphantom"], {
|
||||
numArgs: 1,
|
||||
}, function(context, args) {
|
||||
const body = args[0];
|
||||
return {
|
||||
type: "phantom",
|
||||
type: context.funcName.slice(1),
|
||||
value: ordargument(body),
|
||||
body: body,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -516,18 +517,59 @@ defineFunction([
|
||||
};
|
||||
});
|
||||
|
||||
// Left and right overlap functions
|
||||
defineFunction(["\\llap", "\\rlap"], {
|
||||
// Horizontal overlap functions
|
||||
defineFunction(["\\mathllap", "\\mathrlap", "\\mathclap"], {
|
||||
numArgs: 1,
|
||||
allowedInText: true,
|
||||
}, function(context, args) {
|
||||
const body = args[0];
|
||||
return {
|
||||
type: context.funcName.slice(1),
|
||||
type: "lap",
|
||||
alignment: context.funcName.slice(5),
|
||||
body: body,
|
||||
};
|
||||
});
|
||||
|
||||
// smash, with optional [tb], as in AMS
|
||||
defineFunction("\\smash", {
|
||||
numArgs: 1,
|
||||
numOptionalArgs: 1,
|
||||
allowedInText: true,
|
||||
}, function(context, args) {
|
||||
let smashHeight = false;
|
||||
let smashDepth = false;
|
||||
const tbArg = args[0];
|
||||
if (tbArg) {
|
||||
// Optional [tb] argument is engaged.
|
||||
// ref: amsmath: \renewcommand{\smash}[1][tb]{%
|
||||
// def\mb@t{\ht}\def\mb@b{\dp}\def\mb@tb{\ht\z@\z@\dp}%
|
||||
let letter = "";
|
||||
for (let i = 0; i < tbArg.value.length; ++i) {
|
||||
letter = tbArg.value[i].value;
|
||||
if (letter === "t") {
|
||||
smashHeight = true;
|
||||
} else if (letter === "b") {
|
||||
smashDepth = true;
|
||||
} else {
|
||||
smashHeight = false;
|
||||
smashDepth = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
smashHeight = true;
|
||||
smashDepth = true;
|
||||
}
|
||||
|
||||
const body = args[1];
|
||||
return {
|
||||
type: "smash",
|
||||
body: body,
|
||||
smashHeight: smashHeight,
|
||||
smashDepth: smashDepth,
|
||||
};
|
||||
});
|
||||
|
||||
// Delimiter functions
|
||||
const checkDelimiter = function(delim, context) {
|
||||
if (utils.contains(delimiters, delim.value)) {
|
||||
|
@@ -19,6 +19,11 @@ defineMacro("\\endgroup", "}");
|
||||
// (In TeX, the mu unit works only with \mkern.)
|
||||
defineMacro("\\mkern", "\\kern");
|
||||
|
||||
// \llap and \rlap render their contents in text mode
|
||||
defineMacro("\\llap", "\\mathllap{\\textrm{#1}}");
|
||||
defineMacro("\\rlap", "\\mathrlap{\\textrm{#1}}");
|
||||
defineMacro("\\clap", "\\mathclap{\\textrm{#1}}");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// amsmath.sty
|
||||
|
||||
@@ -38,6 +43,19 @@ defineMacro("\\iff", "\\;\\Longleftrightarrow\\;");
|
||||
defineMacro("\\implies", "\\;\\Longrightarrow\\;");
|
||||
defineMacro("\\impliedby", "\\;\\Longleftarrow\\;");
|
||||
|
||||
// http://texdoc.net/texmf-dist/doc/latex/amsmath/amsmath.pdf
|
||||
defineMacro("\\thinspace", "\\,"); // \let\thinspace\,
|
||||
defineMacro("\\medspace", "\\:"); // \let\medspace\:
|
||||
defineMacro("\\thickspace", "\\;"); // \let\thickspace\;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// LaTeX source2e
|
||||
|
||||
// \DeclareRobustCommand\hspace{\@ifstar\@hspacer\@hspace}
|
||||
// \def\@hspace#1{\hskip #1\relax}
|
||||
// KaTeX doesn't do line breaks, so \hspace is the same as \kern
|
||||
defineMacro("\\hspace", "\\kern{#1}");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// mathtools.sty
|
||||
|
||||
|
Reference in New Issue
Block a user