\kern fixes, \hskip support, \TeX, \LaTeX, \KaTeX (#974)
* Refactor \kern, proper \mkern support, add \hskip * Move \kern, \mkern into functions directory * Add \hskip, \mskip support (but without supporting plus/minus) * Properly separate \kern, \hskip from \mkern, \mskip. (The former work in both modes, and don't support mu units. The latter work only in math mode and only support mu units.) * Render \kern etc. using MathML <mspace> * Implement \TeX macro * Implement \LaTeX * New KaTeX logo \katex * Rename hskip.js -> kern.js * Tweak katex \kern to 0.16em * \katex kern -.17em * Compute A raise height in \LaTeX and \katex * Switch mu unit errors to warnings * LaTeX screenshot test * Replace \KaTeX with macro definition * Update screenshots with \KaTeX in them * Fix font selection for \*TeX macros
@@ -735,18 +735,6 @@ groupTypes.rule = function(group, options) {
|
||||
return rule;
|
||||
};
|
||||
|
||||
groupTypes.kern = function(group, options) {
|
||||
// Make an empty span for the rule
|
||||
const rule = makeSpan(["mord", "rule"], [], options);
|
||||
|
||||
if (group.value.dimension) {
|
||||
const dimension = calculateSize(group.value.dimension, options);
|
||||
rule.style.marginLeft = dimension + "em";
|
||||
}
|
||||
|
||||
return rule;
|
||||
};
|
||||
|
||||
groupTypes.accent = function(group, options) {
|
||||
// Accents are handled in the TeXbook pg. 443, rule 12.
|
||||
let base = group.value.base;
|
||||
|
@@ -468,13 +468,6 @@ groupTypes.rule = function(group) {
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.kern = function(group) {
|
||||
// TODO(kevin): Figure out if there's a way to add space in MathML
|
||||
const node = new mathMLTree.MathNode("mrow");
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.mclass = function(group, options) {
|
||||
const inner = buildExpression(group.value.value, options);
|
||||
return new mathMLTree.MathNode("mstyle", inner);
|
||||
|
@@ -163,19 +163,7 @@ defineFunction(["\\rule"], {
|
||||
};
|
||||
});
|
||||
|
||||
// TODO: In TeX, \mkern only accepts mu-units, and \kern does not accept
|
||||
// mu-units. In current KaTeX we relax this; both commands accept any unit.
|
||||
defineFunction(["\\kern", "\\mkern"], {
|
||||
numArgs: 1,
|
||||
argTypes: ["size"],
|
||||
}, function(context, args) {
|
||||
return {
|
||||
type: "kern",
|
||||
dimension: args[0].value,
|
||||
};
|
||||
});
|
||||
|
||||
import "./functions/katex";
|
||||
import "./functions/kern";
|
||||
|
||||
import "./functions/phantom";
|
||||
|
||||
|
@@ -1,51 +0,0 @@
|
||||
// @flow
|
||||
// A KaTeX logo
|
||||
import defineFunction from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
|
||||
defineFunction({
|
||||
type: "katex",
|
||||
names: ["\\KaTeX"],
|
||||
props: {
|
||||
numArgs: 0,
|
||||
allowedInText: true,
|
||||
},
|
||||
handler: (context, args) => {
|
||||
return {
|
||||
type: "katex",
|
||||
};
|
||||
},
|
||||
htmlBuilder: (group, options) => {
|
||||
// The KaTeX logo. The offsets for the K and a were chosen to look
|
||||
// good, but the offsets for the T, E, and X were taken from the
|
||||
// definition of \TeX in TeX (see TeXbook pg. 356)
|
||||
const k = buildCommon.makeSpan(
|
||||
["k"], [buildCommon.mathsym("K", group.mode)], options);
|
||||
const a = buildCommon.makeSpan(
|
||||
["a"], [buildCommon.mathsym("A", group.mode)], options);
|
||||
|
||||
a.height = (a.height + 0.2) * 0.75;
|
||||
a.depth = (a.height - 0.2) * 0.75;
|
||||
|
||||
const t = buildCommon.makeSpan(
|
||||
["t"], [buildCommon.mathsym("T", group.mode)], options);
|
||||
const e = buildCommon.makeSpan(
|
||||
["e"], [buildCommon.mathsym("E", group.mode)], options);
|
||||
|
||||
e.height = (e.height - 0.2155);
|
||||
e.depth = (e.depth + 0.2155);
|
||||
|
||||
const x = buildCommon.makeSpan(
|
||||
["x"], [buildCommon.mathsym("X", group.mode)], options);
|
||||
|
||||
return buildCommon.makeSpan(
|
||||
["mord", "katex-logo"], [k, a, t, e, x], options);
|
||||
},
|
||||
mathmlBuilder: (group, options) => {
|
||||
const node = new mathMLTree.MathNode(
|
||||
"mtext", [new mathMLTree.TextNode("KaTeX")]);
|
||||
|
||||
return node;
|
||||
},
|
||||
});
|
66
src/functions/kern.js
Normal file
@@ -0,0 +1,66 @@
|
||||
//@flow
|
||||
/* eslint no-console:0 */
|
||||
// Horizontal spacing commands
|
||||
|
||||
import defineFunction from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import { calculateSize } from "../units";
|
||||
import ParseError from "../ParseError";
|
||||
|
||||
// TODO: \hskip and \mskip should support plus and minus in lengths
|
||||
|
||||
defineFunction({
|
||||
type: "kern",
|
||||
names: ["\\kern", "\\mkern", "\\hskip", "\\mskip"],
|
||||
props: {
|
||||
numArgs: 1,
|
||||
argTypes: ["size"],
|
||||
allowedInText: true,
|
||||
},
|
||||
handler: (context, args) => {
|
||||
const mathFunction = (context.funcName[1] === 'm'); // \mkern, \mskip
|
||||
const muUnit = (args[0].value.unit === 'mu');
|
||||
if (mathFunction) {
|
||||
if (!muUnit) {
|
||||
typeof console !== "undefined" && console.warn(
|
||||
`In LaTeX, ${context.funcName} supports only mu units, ` +
|
||||
`not ${args[0].value.unit} units`);
|
||||
}
|
||||
if (context.parser.mode !== "math") {
|
||||
throw new ParseError(
|
||||
`Can't use function '${context.funcName}' in text mode`);
|
||||
}
|
||||
} else { // !mathFunction
|
||||
if (muUnit) {
|
||||
typeof console !== "undefined" && console.warn(
|
||||
`In LaTeX, ${context.funcName} does not support mu units`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: "kern",
|
||||
dimension: args[0].value,
|
||||
};
|
||||
},
|
||||
htmlBuilder: (group, options) => {
|
||||
// Make an empty span for the rule
|
||||
const rule = buildCommon.makeSpan(["mord", "rule"], [], options);
|
||||
|
||||
if (group.value.dimension) {
|
||||
const dimension = calculateSize(group.value.dimension, options);
|
||||
rule.style.marginLeft = dimension + "em";
|
||||
}
|
||||
|
||||
return rule;
|
||||
},
|
||||
mathmlBuilder: (group, options) => {
|
||||
const node = new mathMLTree.MathNode("mspace");
|
||||
|
||||
if (group.value.dimension) {
|
||||
const dimension = calculateSize(group.value.dimension, options);
|
||||
node.setAttribute("width", dimension + "em");
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
});
|
@@ -4,6 +4,7 @@
|
||||
* This can be used to define some commands in terms of others.
|
||||
*/
|
||||
|
||||
import fontMetricsData from "./fontMetricsData";
|
||||
import symbols from "./symbols";
|
||||
import utils from "./utils";
|
||||
import {Token} from "./Token";
|
||||
@@ -85,10 +86,6 @@ defineMacro("\u211A", "\\mathbb{Q}");
|
||||
defineMacro("\u211D", "\\mathbb{R}");
|
||||
defineMacro("\u2124", "\\mathbb{Z}");
|
||||
|
||||
// We don't distinguish between math and nonmath kerns.
|
||||
// (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}}");
|
||||
@@ -270,6 +267,37 @@ defineMacro("\\thickspace", "\\;"); // \let\thickspace\;
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// LaTeX source2e
|
||||
|
||||
// \def\TeX{T\kern-.1667em\lower.5ex\hbox{E}\kern-.125emX\@}
|
||||
// TODO: Doesn't normally work in math mode because \@ fails. KaTeX doesn't
|
||||
// support \@ yet, so that's omitted, and we add \text so that the result
|
||||
// doesn't look funny in math mode.
|
||||
defineMacro("\\TeX", "\\textrm{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}");
|
||||
|
||||
// \DeclareRobustCommand{\LaTeX}{L\kern-.36em%
|
||||
// {\sbox\z@ T%
|
||||
// \vbox to\ht\z@{\hbox{\check@mathfonts
|
||||
// \fontsize\sf@size\z@
|
||||
// \math@fontsfalse\selectfont
|
||||
// A}%
|
||||
// \vss}%
|
||||
// }%
|
||||
// \kern-.15em%
|
||||
// \TeX}
|
||||
// This code aligns the top of the A with the T (from the perspective of TeX's
|
||||
// boxes, though visually the A appears to extend above slightly).
|
||||
// We compute the corresponding \raisebox when A is rendered at \scriptsize,
|
||||
// which is size3, which has a scale factor of 0.7 (see Options.js).
|
||||
const latexRaiseA = fontMetricsData['Main-Regular']["T".charCodeAt(0)][1] -
|
||||
0.7 * fontMetricsData['Main-Regular']["A".charCodeAt(0)][1] + "em";
|
||||
defineMacro("\\LaTeX",
|
||||
`\\textrm{L\\kern-.36em\\raisebox{${latexRaiseA}}{\\scriptsize A}` +
|
||||
"\\kern-.15em\\TeX}");
|
||||
|
||||
// New KaTeX logo based on tweaking LaTeX logo
|
||||
defineMacro("\\KaTeX",
|
||||
`\\textrm{K\\kern-.17em\\raisebox{${latexRaiseA}}{\\scriptsize A}` +
|
||||
"\\kern-.15em\\TeX}");
|
||||
|
||||
// \DeclareRobustCommand\hspace{\@ifstar\@hspacer\@hspace}
|
||||
// \def\@hspace#1{\hskip #1\relax}
|
||||
// KaTeX doesn't do line breaks, so \hspace and \hspace* are the same as \kern
|
||||
|
@@ -1046,7 +1046,7 @@ describe("A rule parser", function() {
|
||||
describe("A kern parser", function() {
|
||||
const emKern = "\\kern{1em}";
|
||||
const exKern = "\\kern{1ex}";
|
||||
const muKern = "\\kern{1mu}";
|
||||
const muKern = "\\mkern{1mu}";
|
||||
const abKern = "a\\kern{1em}b";
|
||||
const badUnitRule = "\\kern{1au}";
|
||||
const noNumberRule = "\\kern{em}";
|
||||
@@ -1082,10 +1082,10 @@ describe("A kern parser", function() {
|
||||
describe("A non-braced kern parser", function() {
|
||||
const emKern = "\\kern1em";
|
||||
const exKern = "\\kern 1 ex";
|
||||
const muKern = "\\kern 1mu";
|
||||
const muKern = "\\mkern 1mu";
|
||||
const abKern1 = "a\\mkern1mub";
|
||||
const abKern2 = "a\\kern-1mub";
|
||||
const abKern3 = "a\\kern-1mu b";
|
||||
const abKern2 = "a\\mkern-1mub";
|
||||
const abKern3 = "a\\mkern-1mu b";
|
||||
const badUnitRule = "\\kern1au";
|
||||
const noNumberRule = "\\kern em";
|
||||
|
||||
@@ -1137,7 +1137,7 @@ describe("A non-braced kern parser", function() {
|
||||
});
|
||||
|
||||
it("should handle whitespace", function() {
|
||||
const abKern = "a\\kern\t-\r1 \n mu\nb";
|
||||
const abKern = "a\\mkern\t-\r1 \n mu\nb";
|
||||
const abParse = getParsed(abKern);
|
||||
|
||||
expect(abParse.length).toEqual(3);
|
||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.4 KiB |
BIN
test/screenshotter/images/LaTeX-chrome.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
test/screenshotter/images/LaTeX-firefox.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -119,12 +119,15 @@ GroupMacros:
|
||||
\endExp: \egroup
|
||||
tex: \startExp a+b\endExp
|
||||
HorizontalBraces: \overbrace{\displaystyle{\oint_S{\vec E\cdot\hat n\,\mathrm d a}}}^\text{emf} = \underbrace{\frac{q_{\text{enc}}}{\varepsilon_0}}_{\text{charge}}
|
||||
KaTeX: \KaTeX
|
||||
KaTeX:
|
||||
tex: \KaTeX
|
||||
nolatex: \KaTeX not supported by LaTeX
|
||||
Kern:
|
||||
tex: \frac{a\kern{1em}b}{c}a\kern{1em}b\kern{1ex}c\kern{-0.25em}d
|
||||
nolatex: LaTeX fails to typeset this, “Missing number, treated as zero.”
|
||||
Lap: ab\mathllap{f}cd\mathrlap{g}hij\mathclap{k}lm \; ab\llap{f}cd\rlap{g}hij\clap{k}lm
|
||||
LargeRuleNumerator: \frac{\textcolor{blue}{\rule{1em}{2em}}}{x}
|
||||
LaTeX: \text{\LaTeX}, \text{\TeX}
|
||||
LeftRight: \left( x^2 \right) \left\{ x^{x^{x^{x^x}}} \right.
|
||||
LeftRightListStyling: a+\left(x+y\right)-x
|
||||
LeftRightMiddle: \left( x^2 \middle/ \right) \left\{ x^{x^{x^{x^x}}} \middle/ y \right.\left(x\middle|y\,\middle|\,z\right)
|
||||
|