mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 11:18:39 +00:00
Improve MathML for classes (#1929)
* Improve MathML for classes * Fix lint errors * Fix another lint error * Simplify MathML resulting from single character box * Fix lint errors * make some of the arrays in our html/mathml helper functions readonly
This commit is contained in:
@@ -68,7 +68,7 @@ export const buildExpression = function(
|
|||||||
for (let i = 0; i < expression.length; i++) {
|
for (let i = 0; i < expression.length; i++) {
|
||||||
const output = buildGroup(expression[i], options);
|
const output = buildGroup(expression[i], options);
|
||||||
if (output instanceof DocumentFragment) {
|
if (output instanceof DocumentFragment) {
|
||||||
const children: HtmlDomNode[] = output.children;
|
const children: $ReadOnlyArray<HtmlDomNode> = output.children;
|
||||||
groups.push(...children);
|
groups.push(...children);
|
||||||
} else {
|
} else {
|
||||||
groups.push(output);
|
groups.push(output);
|
||||||
@@ -154,6 +154,7 @@ const traverseNonSpaceNodes = function(
|
|||||||
const node = nodes[i];
|
const node = nodes[i];
|
||||||
const partialGroup = checkPartialGroup(node);
|
const partialGroup = checkPartialGroup(node);
|
||||||
if (partialGroup) { // Recursive DFS
|
if (partialGroup) { // Recursive DFS
|
||||||
|
// $FlowFixMe: make nodes a $ReadOnlyArray by returning a new array
|
||||||
traverseNonSpaceNodes(partialGroup.children, callback, prev);
|
traverseNonSpaceNodes(partialGroup.children, callback, prev);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@@ -44,7 +44,7 @@ export const makeText = function(
|
|||||||
* Wrap the given array of nodes in an <mrow> node if needed, i.e.,
|
* Wrap the given array of nodes in an <mrow> node if needed, i.e.,
|
||||||
* unless the array has length 1. Always returns a single node.
|
* 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>): MathDomNode {
|
||||||
if (body.length === 1) {
|
if (body.length === 1) {
|
||||||
return body[0];
|
return body[0];
|
||||||
} else {
|
} else {
|
||||||
@@ -134,7 +134,19 @@ export const getVariant = function(
|
|||||||
export const buildExpression = function(
|
export const buildExpression = function(
|
||||||
expression: AnyParseNode[],
|
expression: AnyParseNode[],
|
||||||
options: Options,
|
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 = [];
|
const groups = [];
|
||||||
let lastGroup;
|
let lastGroup;
|
||||||
for (let i = 0; i < expression.length; i++) {
|
for (let i = 0; i < expression.length; i++) {
|
||||||
@@ -186,8 +198,9 @@ export const buildExpression = function(
|
|||||||
export const buildExpressionRow = function(
|
export const buildExpressionRow = function(
|
||||||
expression: AnyParseNode[],
|
expression: AnyParseNode[],
|
||||||
options: Options,
|
options: Options,
|
||||||
|
isOrdgroup?: boolean,
|
||||||
): MathDomNode {
|
): MathDomNode {
|
||||||
return makeRow(buildExpression(expression, options));
|
return makeRow(buildExpression(expression, options, isOrdgroup));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -197,7 +210,7 @@ export const buildExpressionRow = function(
|
|||||||
export const buildGroup = function(
|
export const buildGroup = function(
|
||||||
group: ?AnyParseNode,
|
group: ?AnyParseNode,
|
||||||
options: Options,
|
options: Options,
|
||||||
): MathDomNode {
|
): MathNode {
|
||||||
if (!group) {
|
if (!group) {
|
||||||
return new mathMLTree.MathNode("mrow");
|
return new mathMLTree.MathNode("mrow");
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import {binrelClass} from "./mclass";
|
import {binrelClass} from "./mclass";
|
||||||
import defineFunction from "../defineFunction";
|
import defineFunction from "../defineFunction";
|
||||||
|
import utils from "../utils";
|
||||||
|
|
||||||
import * as html from "../buildHTML";
|
import * as html from "../buildHTML";
|
||||||
import * as mml from "../buildMathML";
|
import * as mml from "../buildMathML";
|
||||||
@@ -71,6 +72,7 @@ defineFunction({
|
|||||||
},
|
},
|
||||||
handler: ({parser}, args) => {
|
handler: ({parser}, args) => {
|
||||||
const body = args[0];
|
const body = args[0];
|
||||||
|
const isCharacterBox = utils.isCharacterBox(body);
|
||||||
// amsbsy.sty's \boldsymbol uses \binrel spacing to inherit the
|
// amsbsy.sty's \boldsymbol uses \binrel spacing to inherit the
|
||||||
// argument's bin|rel|ord status
|
// argument's bin|rel|ord status
|
||||||
return {
|
return {
|
||||||
@@ -85,6 +87,7 @@ defineFunction({
|
|||||||
body,
|
body,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
isCharacterBox: isCharacterBox,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import defineFunction, {ordargument} from "../defineFunction";
|
import defineFunction, {ordargument} from "../defineFunction";
|
||||||
import buildCommon from "../buildCommon";
|
import buildCommon from "../buildCommon";
|
||||||
import mathMLTree from "../mathMLTree";
|
import mathMLTree from "../mathMLTree";
|
||||||
|
import utils from "../utils";
|
||||||
import type {AnyParseNode} from "../parseNode";
|
import type {AnyParseNode} from "../parseNode";
|
||||||
|
|
||||||
import * as html from "../buildHTML";
|
import * as html from "../buildHTML";
|
||||||
@@ -17,8 +18,42 @@ function htmlBuilder(group: ParseNode<"mclass">, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mathmlBuilder(group: ParseNode<"mclass">, options) {
|
function mathmlBuilder(group: ParseNode<"mclass">, options) {
|
||||||
|
let node: mathMLTree.MathNode;
|
||||||
const inner = mml.buildExpression(group.body, options);
|
const inner = mml.buildExpression(group.body, options);
|
||||||
|
|
||||||
|
if (group.mclass === "minner") {
|
||||||
return mathMLTree.newDocumentFragment(inner);
|
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 <mo> default space is 5/18 em, so <mrel> needs no action.
|
||||||
|
// Ref: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mo
|
||||||
|
}
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Math class commands except \mathop
|
// Math class commands except \mathop
|
||||||
@@ -38,6 +73,7 @@ defineFunction({
|
|||||||
mode: parser.mode,
|
mode: parser.mode,
|
||||||
mclass: "m" + funcName.substr(5),
|
mclass: "m" + funcName.substr(5),
|
||||||
body: ordargument(body),
|
body: ordargument(body),
|
||||||
|
isCharacterBox: utils.isCharacterBox(body),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
htmlBuilder,
|
htmlBuilder,
|
||||||
@@ -71,6 +107,7 @@ defineFunction({
|
|||||||
mode: parser.mode,
|
mode: parser.mode,
|
||||||
mclass: binrelClass(args[0]),
|
mclass: binrelClass(args[0]),
|
||||||
body: [args[1]],
|
body: [args[1]],
|
||||||
|
isCharacterBox: utils.isCharacterBox(args[1]),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -118,6 +155,7 @@ defineFunction({
|
|||||||
mode: parser.mode,
|
mode: parser.mode,
|
||||||
mclass,
|
mclass,
|
||||||
body: [supsub],
|
body: [supsub],
|
||||||
|
isCharacterBox: utils.isCharacterBox(supsub),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
htmlBuilder,
|
htmlBuilder,
|
||||||
|
@@ -16,7 +16,7 @@ defineFunctionBuilders({
|
|||||||
["mord"], html.buildExpression(group.body, options, true), options);
|
["mord"], html.buildExpression(group.body, options, true), options);
|
||||||
},
|
},
|
||||||
mathmlBuilder(group, options) {
|
mathmlBuilder(group, options) {
|
||||||
return mml.buildExpressionRow(group.body, options);
|
return mml.buildExpressionRow(group.body, options, true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -32,7 +32,9 @@ export interface MathDomNode extends VirtualNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type documentFragment = DocumentFragment<MathDomNode>;
|
export type documentFragment = DocumentFragment<MathDomNode>;
|
||||||
export function newDocumentFragment(children: MathDomNode[]): documentFragment {
|
export function newDocumentFragment(
|
||||||
|
children: $ReadOnlyArray<MathDomNode>
|
||||||
|
): documentFragment {
|
||||||
return new DocumentFragment(children);
|
return new DocumentFragment(children);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +46,9 @@ export function newDocumentFragment(children: MathDomNode[]): documentFragment {
|
|||||||
export class MathNode implements MathDomNode {
|
export class MathNode implements MathDomNode {
|
||||||
type: MathNodeType;
|
type: MathNodeType;
|
||||||
attributes: {[string]: string};
|
attributes: {[string]: string};
|
||||||
children: MathDomNode[];
|
children: $ReadOnlyArray<MathDomNode>;
|
||||||
|
|
||||||
constructor(type: MathNodeType, children?: MathDomNode[]) {
|
constructor(type: MathNodeType, children?: $ReadOnlyArray<MathDomNode>) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.attributes = {};
|
this.attributes = {};
|
||||||
this.children = children || [];
|
this.children = children || [];
|
||||||
|
@@ -348,6 +348,7 @@ type ParseNodeTypes = {
|
|||||||
loc?: ?SourceLocation,
|
loc?: ?SourceLocation,
|
||||||
mclass: string,
|
mclass: string,
|
||||||
body: AnyParseNode[],
|
body: AnyParseNode[],
|
||||||
|
isCharacterBox: boolean,
|
||||||
|},
|
|},
|
||||||
"operatorname": {|
|
"operatorname": {|
|
||||||
type: "operatorname",
|
type: "operatorname",
|
||||||
|
@@ -20,7 +20,7 @@ export interface VirtualNode {
|
|||||||
*/
|
*/
|
||||||
export class DocumentFragment<ChildType: VirtualNode>
|
export class DocumentFragment<ChildType: VirtualNode>
|
||||||
implements HtmlDomNode, MathDomNode {
|
implements HtmlDomNode, MathDomNode {
|
||||||
children: ChildType[];
|
children: $ReadOnlyArray<ChildType>;
|
||||||
// HtmlDomNode
|
// HtmlDomNode
|
||||||
classes: string[];
|
classes: string[];
|
||||||
height: number;
|
height: number;
|
||||||
@@ -28,7 +28,7 @@ export class DocumentFragment<ChildType: VirtualNode>
|
|||||||
maxFontSize: number;
|
maxFontSize: number;
|
||||||
style: CssStyle; // Never used; needed for satisfying interface.
|
style: CssStyle; // Never used; needed for satisfying interface.
|
||||||
|
|
||||||
constructor(children: ChildType[]) {
|
constructor(children: $ReadOnlyArray<ChildType>) {
|
||||||
this.children = children;
|
this.children = children;
|
||||||
this.classes = [];
|
this.classes = [];
|
||||||
this.height = 0;
|
this.height = 0;
|
||||||
|
@@ -8,15 +8,15 @@ exports[`A MathML builder \\html@mathml makes clean symbols 1`] = `
|
|||||||
<mtext>
|
<mtext>
|
||||||
©
|
©
|
||||||
</mtext>
|
</mtext>
|
||||||
<mi mathvariant="normal">
|
<mo mathvariant="normal">
|
||||||
≠
|
≠
|
||||||
</mi>
|
</mo>
|
||||||
<mi mathvariant="normal">
|
<mo mathvariant="normal">
|
||||||
∉
|
∉
|
||||||
</mi>
|
</mo>
|
||||||
<mi mathvariant="normal">
|
<mo mathvariant="normal">
|
||||||
≘
|
≘
|
||||||
</mi>
|
</mo>
|
||||||
<mtext>
|
<mtext>
|
||||||
KaTeX
|
KaTeX
|
||||||
</mtext>
|
</mtext>
|
||||||
@@ -306,7 +306,10 @@ exports[`A MathML builder should make prime operators into <mo> nodes 1`] = `
|
|||||||
<mi>
|
<mi>
|
||||||
f
|
f
|
||||||
</mi>
|
</mi>
|
||||||
<mo mathvariant="normal">
|
<mo mathvariant="normal"
|
||||||
|
lspace="0em"
|
||||||
|
rspace="0em"
|
||||||
|
>
|
||||||
′
|
′
|
||||||
</mo>
|
</mo>
|
||||||
</msup>
|
</msup>
|
||||||
@@ -394,6 +397,7 @@ exports[`A MathML builder should render boldsymbol with the correct mathvariants
|
|||||||
<math xmlns="http://www.w3.org/1998/Math/MathML">
|
<math xmlns="http://www.w3.org/1998/Math/MathML">
|
||||||
<semantics>
|
<semantics>
|
||||||
<mrow>
|
<mrow>
|
||||||
|
<mi>
|
||||||
<mrow>
|
<mrow>
|
||||||
<mi mathvariant="bold-italic">
|
<mi mathvariant="bold-italic">
|
||||||
A
|
A
|
||||||
@@ -420,6 +424,7 @@ exports[`A MathML builder should render boldsymbol with the correct mathvariants
|
|||||||
+
|
+
|
||||||
</mo>
|
</mo>
|
||||||
</mrow>
|
</mrow>
|
||||||
|
</mi>
|
||||||
</mrow>
|
</mrow>
|
||||||
<annotation encoding="application/x-tex">
|
<annotation encoding="application/x-tex">
|
||||||
\\boldsymbol{Ax2k\\omega\\Omega\\imath+}
|
\\boldsymbol{Ax2k\\omega\\Omega\\imath+}
|
||||||
|
Reference in New Issue
Block a user