diff --git a/src/buildHTML.js b/src/buildHTML.js
index 2822eec8..eeb27e1b 100644
--- a/src/buildHTML.js
+++ b/src/buildHTML.js
@@ -68,7 +68,7 @@ export const buildExpression = function(
for (let i = 0; i < expression.length; i++) {
const output = buildGroup(expression[i], options);
if (output instanceof DocumentFragment) {
- const children: HtmlDomNode[] = output.children;
+ const children: $ReadOnlyArray = output.children;
groups.push(...children);
} else {
groups.push(output);
@@ -154,6 +154,7 @@ const traverseNonSpaceNodes = function(
const node = nodes[i];
const partialGroup = checkPartialGroup(node);
if (partialGroup) { // Recursive DFS
+ // $FlowFixMe: make nodes a $ReadOnlyArray by returning a new array
traverseNonSpaceNodes(partialGroup.children, callback, prev);
continue;
}
diff --git a/src/buildMathML.js b/src/buildMathML.js
index d4e2cb96..70bc0dd4 100644
--- a/src/buildMathML.js
+++ b/src/buildMathML.js
@@ -44,7 +44,7 @@ export const makeText = function(
* Wrap the given array of nodes in an node if needed, i.e.,
* unless the array has length 1. Always returns a single node.
*/
-export const makeRow = function(body: MathDomNode[]): MathDomNode {
+export const makeRow = function(body: $ReadOnlyArray): MathDomNode {
if (body.length === 1) {
return body[0];
} else {
@@ -134,7 +134,19 @@ export const getVariant = function(
export const buildExpression = function(
expression: AnyParseNode[],
options: Options,
-): MathDomNode[] {
+ isOrdgroup?: boolean,
+): MathNode[] {
+ if (expression.length === 1) {
+ const group = buildGroup(expression[0], options);
+ if (isOrdgroup && group instanceof MathNode && group.type === "mo") {
+ // When TeX writers want to suppress spacing on an operator,
+ // they often put the operator by itself inside braces.
+ group.setAttribute("lspace", "0em");
+ group.setAttribute("rspace", "0em");
+ }
+ return [group];
+ }
+
const groups = [];
let lastGroup;
for (let i = 0; i < expression.length; i++) {
@@ -186,8 +198,9 @@ export const buildExpression = function(
export const buildExpressionRow = function(
expression: AnyParseNode[],
options: Options,
+ isOrdgroup?: boolean,
): MathDomNode {
- return makeRow(buildExpression(expression, options));
+ return makeRow(buildExpression(expression, options, isOrdgroup));
};
/**
@@ -197,7 +210,7 @@ export const buildExpressionRow = function(
export const buildGroup = function(
group: ?AnyParseNode,
options: Options,
-): MathDomNode {
+): MathNode {
if (!group) {
return new mathMLTree.MathNode("mrow");
}
diff --git a/src/functions/font.js b/src/functions/font.js
index f6f0743a..005182d2 100644
--- a/src/functions/font.js
+++ b/src/functions/font.js
@@ -3,6 +3,7 @@
import {binrelClass} from "./mclass";
import defineFunction from "../defineFunction";
+import utils from "../utils";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
@@ -71,6 +72,7 @@ defineFunction({
},
handler: ({parser}, args) => {
const body = args[0];
+ const isCharacterBox = utils.isCharacterBox(body);
// amsbsy.sty's \boldsymbol uses \binrel spacing to inherit the
// argument's bin|rel|ord status
return {
@@ -85,6 +87,7 @@ defineFunction({
body,
},
],
+ isCharacterBox: isCharacterBox,
};
},
});
diff --git a/src/functions/mclass.js b/src/functions/mclass.js
index 6f63c483..af18d7df 100644
--- a/src/functions/mclass.js
+++ b/src/functions/mclass.js
@@ -2,6 +2,7 @@
import defineFunction, {ordargument} from "../defineFunction";
import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree";
+import utils from "../utils";
import type {AnyParseNode} from "../parseNode";
import * as html from "../buildHTML";
@@ -17,8 +18,42 @@ function htmlBuilder(group: ParseNode<"mclass">, options) {
}
function mathmlBuilder(group: ParseNode<"mclass">, options) {
+ let node: mathMLTree.MathNode;
const inner = mml.buildExpression(group.body, options);
- return mathMLTree.newDocumentFragment(inner);
+
+ if (group.mclass === "minner") {
+ return mathMLTree.newDocumentFragment(inner);
+ } else if (group.mclass === "mord") {
+ if (group.isCharacterBox) {
+ node = inner[0];
+ node.type = "mi";
+ } else {
+ node = new mathMLTree.MathNode("mi", inner);
+ }
+ } else {
+ if (group.isCharacterBox) {
+ node = inner[0];
+ node.type = "mo";
+ } else {
+ node = new mathMLTree.MathNode("mo", inner);
+ }
+
+ // Set spacing based on what is the most likely adjacent atom type.
+ // See TeXbook p170.
+ if (group.mclass === "mbin") {
+ node.attributes.lspace = "0.22em"; // medium space
+ node.attributes.rspace = "0.22em";
+ } else if (group.mclass === "mpunct") {
+ node.attributes.lspace = "0em";
+ node.attributes.rspace = "0.17em"; // thinspace
+ } else if (group.mclass === "mopen" || group.mclass === "mclose") {
+ node.attributes.lspace = "0em";
+ node.attributes.rspace = "0em";
+ }
+ // MathML default space is 5/18 em, so needs no action.
+ // Ref: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mo
+ }
+ return node;
}
// Math class commands except \mathop
@@ -38,6 +73,7 @@ defineFunction({
mode: parser.mode,
mclass: "m" + funcName.substr(5),
body: ordargument(body),
+ isCharacterBox: utils.isCharacterBox(body),
};
},
htmlBuilder,
@@ -71,6 +107,7 @@ defineFunction({
mode: parser.mode,
mclass: binrelClass(args[0]),
body: [args[1]],
+ isCharacterBox: utils.isCharacterBox(args[1]),
};
},
});
@@ -118,6 +155,7 @@ defineFunction({
mode: parser.mode,
mclass,
body: [supsub],
+ isCharacterBox: utils.isCharacterBox(supsub),
};
},
htmlBuilder,
diff --git a/src/functions/ordgroup.js b/src/functions/ordgroup.js
index 72b25b25..f3555781 100644
--- a/src/functions/ordgroup.js
+++ b/src/functions/ordgroup.js
@@ -16,7 +16,7 @@ defineFunctionBuilders({
["mord"], html.buildExpression(group.body, options, true), options);
},
mathmlBuilder(group, options) {
- return mml.buildExpressionRow(group.body, options);
+ return mml.buildExpressionRow(group.body, options, true);
},
});
diff --git a/src/mathMLTree.js b/src/mathMLTree.js
index 610fea61..ba1dbde5 100644
--- a/src/mathMLTree.js
+++ b/src/mathMLTree.js
@@ -32,7 +32,9 @@ export interface MathDomNode extends VirtualNode {
}
export type documentFragment = DocumentFragment;
-export function newDocumentFragment(children: MathDomNode[]): documentFragment {
+export function newDocumentFragment(
+ children: $ReadOnlyArray
+): documentFragment {
return new DocumentFragment(children);
}
@@ -44,9 +46,9 @@ export function newDocumentFragment(children: MathDomNode[]): documentFragment {
export class MathNode implements MathDomNode {
type: MathNodeType;
attributes: {[string]: string};
- children: MathDomNode[];
+ children: $ReadOnlyArray;
- constructor(type: MathNodeType, children?: MathDomNode[]) {
+ constructor(type: MathNodeType, children?: $ReadOnlyArray) {
this.type = type;
this.attributes = {};
this.children = children || [];
diff --git a/src/parseNode.js b/src/parseNode.js
index 5b2f0c4b..1fe2a6f4 100644
--- a/src/parseNode.js
+++ b/src/parseNode.js
@@ -348,6 +348,7 @@ type ParseNodeTypes = {
loc?: ?SourceLocation,
mclass: string,
body: AnyParseNode[],
+ isCharacterBox: boolean,
|},
"operatorname": {|
type: "operatorname",
diff --git a/src/tree.js b/src/tree.js
index 0d982848..940a44d0 100644
--- a/src/tree.js
+++ b/src/tree.js
@@ -20,7 +20,7 @@ export interface VirtualNode {
*/
export class DocumentFragment
implements HtmlDomNode, MathDomNode {
- children: ChildType[];
+ children: $ReadOnlyArray;
// HtmlDomNode
classes: string[];
height: number;
@@ -28,7 +28,7 @@ export class DocumentFragment
maxFontSize: number;
style: CssStyle; // Never used; needed for satisfying interface.
- constructor(children: ChildType[]) {
+ constructor(children: $ReadOnlyArray) {
this.children = children;
this.classes = [];
this.height = 0;
diff --git a/test/__snapshots__/mathml-spec.js.snap b/test/__snapshots__/mathml-spec.js.snap
index c2975a3f..283f907d 100644
--- a/test/__snapshots__/mathml-spec.js.snap
+++ b/test/__snapshots__/mathml-spec.js.snap
@@ -8,15 +8,15 @@ exports[`A MathML builder \\html@mathml makes clean symbols 1`] = `
©
-
+
≠
-
-
+
+
∉
-
-
+
+
≘
-
+
KaTeX
@@ -306,7 +306,10 @@ exports[`A MathML builder should make prime operators into nodes 1`] = `
f
-
+
′
@@ -394,32 +397,34 @@ exports[`A MathML builder should render boldsymbol with the correct mathvariants