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:
Ron Kok
2019-07-04 22:14:05 -07:00
committed by Kevin Barabash
parent 19d9d83ad3
commit 2b83935064
9 changed files with 108 additions and 45 deletions

View File

@@ -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<HtmlDomNode> = 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;
}

View File

@@ -44,7 +44,7 @@ export const makeText = function(
* 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.
*/
export const makeRow = function(body: MathDomNode[]): MathDomNode {
export const makeRow = function(body: $ReadOnlyArray<MathDomNode>): 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");
}

View File

@@ -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,
};
},
});

View File

@@ -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 <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
@@ -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,

View File

@@ -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);
},
});

View File

@@ -32,7 +32,9 @@ export interface MathDomNode extends VirtualNode {
}
export type documentFragment = DocumentFragment<MathDomNode>;
export function newDocumentFragment(children: MathDomNode[]): documentFragment {
export function newDocumentFragment(
children: $ReadOnlyArray<MathDomNode>
): 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<MathDomNode>;
constructor(type: MathNodeType, children?: MathDomNode[]) {
constructor(type: MathNodeType, children?: $ReadOnlyArray<MathDomNode>) {
this.type = type;
this.attributes = {};
this.children = children || [];

View File

@@ -348,6 +348,7 @@ type ParseNodeTypes = {
loc?: ?SourceLocation,
mclass: string,
body: AnyParseNode[],
isCharacterBox: boolean,
|},
"operatorname": {|
type: "operatorname",

View File

@@ -20,7 +20,7 @@ export interface VirtualNode {
*/
export class DocumentFragment<ChildType: VirtualNode>
implements HtmlDomNode, MathDomNode {
children: ChildType[];
children: $ReadOnlyArray<ChildType>;
// HtmlDomNode
classes: string[];
height: number;
@@ -28,7 +28,7 @@ export class DocumentFragment<ChildType: VirtualNode>
maxFontSize: number;
style: CssStyle; // Never used; needed for satisfying interface.
constructor(children: ChildType[]) {
constructor(children: $ReadOnlyArray<ChildType>) {
this.children = children;
this.classes = [];
this.height = 0;

View File

@@ -8,15 +8,15 @@ exports[`A MathML builder \\html@mathml makes clean symbols 1`] = `
<mtext>
©
</mtext>
<mi mathvariant="normal">
<mo mathvariant="normal">
</mi>
<mi mathvariant="normal">
</mo>
<mo mathvariant="normal">
</mi>
<mi mathvariant="normal">
</mo>
<mo mathvariant="normal">
</mi>
</mo>
<mtext>
KaTeX
</mtext>
@@ -306,7 +306,10 @@ exports[`A MathML builder should make prime operators into <mo> nodes 1`] = `
<mi>
f
</mi>
<mo mathvariant="normal">
<mo mathvariant="normal"
lspace="0em"
rspace="0em"
>
</mo>
</msup>
@@ -394,32 +397,34 @@ exports[`A MathML builder should render boldsymbol with the correct mathvariants
<math xmlns="http://www.w3.org/1998/Math/MathML">
<semantics>
<mrow>
<mrow>
<mi mathvariant="bold-italic">
A
</mi>
<mi mathvariant="bold-italic">
x
</mi>
<mn mathvariant="bold-italic">
2
</mn>
<mi mathvariant="bold-italic">
k
</mi>
<mi mathvariant="bold-italic">
ω
</mi>
<mi mathvariant="bold-italic">
Ω
</mi>
<mi mathvariant="bold-italic">
ı
</mi>
<mo mathvariant="bold-italic">
+
</mo>
</mrow>
<mi>
<mrow>
<mi mathvariant="bold-italic">
A
</mi>
<mi mathvariant="bold-italic">
x
</mi>
<mn mathvariant="bold-italic">
2
</mn>
<mi mathvariant="bold-italic">
k
</mi>
<mi mathvariant="bold-italic">
ω
</mi>
<mi mathvariant="bold-italic">
Ω
</mi>
<mi mathvariant="bold-italic">
ı
</mi>
<mo mathvariant="bold-italic">
+
</mo>
</mrow>
</mi>
</mrow>
<annotation encoding="application/x-tex">
\\boldsymbol{Ax2k\\omega\\Omega\\imath+}