\@binrel command and refactor (#1487)

* \@binrel command and refactor

New `\@binrel{x}{y}` command renders `y` as if it's a bin/rel/ord like `x`.
This is equivalent to AMSTeX's `\binrel@{x}\binrel@@{y}`.
It will hopefully be useful for macros, such as `\pmb` (#1418).

Also refactor the (already duplicated) code for implementing this
functionality into buildCommon.binrelClass.

* Add tests; fix flow error

* Another test

* Move binrelClass to functions/mclass.js

* Remove expensive array destructuring polyfill
This commit is contained in:
Erik Demaine
2018-07-17 22:49:50 -04:00
committed by Kevin Barabash
parent 8f5239c272
commit 578877764d
3 changed files with 58 additions and 23 deletions

View File

@@ -1,6 +1,7 @@
// @flow
// TODO(kevinb): implement \\sl and \\sc
import {binrelClass} from "./mclass";
import defineFunction from "../defineFunction";
import ParseNode from "../ParseNode";
@@ -69,17 +70,11 @@ defineFunction({
},
handler: ({parser}, args) => {
const body = args[0];
// amsbsy.sty's \boldsymbol inherits the argument's bin|rel|ord status
// (similar to \stackrel in functions/mclass.js)
let mclass = "mord";
const atomType = (body.type === "ordgroup" && body.value.length ?
body.value[0].type : body.type);
if (/^(bin|rel)$/.test(atomType)) {
mclass = "m" + atomType;
}
// amsbsy.sty's \boldsymbol uses \binrel spacing to inherit the
// argument's bin|rel|ord status
return new ParseNode("mclass", {
type: "mclass",
mclass,
mclass: binrelClass(body),
value: [
new ParseNode("font", {
type: "font",

View File

@@ -3,6 +3,7 @@ import defineFunction, {ordargument} from "../defineFunction";
import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree";
import ParseNode from "../ParseNode";
import type {AnyParseNode} from "../ParseNode";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
@@ -41,6 +42,37 @@ defineFunction({
mathmlBuilder,
});
export const binrelClass = (arg: AnyParseNode) => {
// \binrel@ spacing varies with (bin|rel|ord) of the atom in the argument.
// (by rendering separately and with {}s before and after, and measuring
// the change in spacing). We'll do roughly the same by detecting the
// atom type directly.
const atomType = (arg.type === "ordgroup" &&
arg.value.length ? arg.value[0].type : arg.type);
if (/^(bin|rel)$/.test(atomType)) {
return "m" + atomType;
} else {
return "mord";
}
};
// \@binrel{x}{y} renders like y but as mbin/mrel/mord if x is mbin/mrel/mord.
// This is equivalent to \binrel@{x}\binrel@@{y} in AMSTeX.
defineFunction({
type: "mclass",
names: ["\\@binrel"],
props: {
numArgs: 2,
},
handler({parser}, args) {
return new ParseNode("mclass", {
type: "mclass",
mclass: binrelClass(args[0]),
value: [args[1]],
}, parser.mode);
},
});
// Build a relation or stacked op by placing one symbol on top of another
defineFunction({
type: "mclass",
@@ -52,21 +84,12 @@ defineFunction({
const baseArg = args[1];
const shiftedArg = args[0];
let mclass = "mrel"; // default. May change below.
let mclass;
if (funcName !== "\\stackrel") {
// LaTeX applies \binrel spacing to \overset and \underset. \binrel
// spacing varies with (bin|rel|ord) of the atom in the argument.
// We'll do the same.
const atomType = (baseArg.type === "ordgroup" &&
baseArg.value.length ? baseArg.value[0].type : baseArg.type);
if (/^(bin|rel)$/.test(atomType)) {
mclass = "m" + atomType;
} else {
// This may capture some instances in which the baseArg is more
// than just a single symbol. Say a \overset inside an \overset.
// TODO: A more comprehensive way to determine the baseArg type.
mclass = "mord";
}
// LaTeX applies \binrel spacing to \overset and \underset.
mclass = binrelClass(baseArg);
} else {
mclass = "mrel"; // for \stackrel
}
const baseOp = new ParseNode("op", {

View File

@@ -2999,6 +2999,23 @@ describe("\\tag support", function() {
});
});
describe("\\@binrel automatic bin/rel/ord", () => {
it("should generate proper class", () => {
expect("L\\@binrel+xR").toParseLike("L\\mathbin xR");
expect("L\\@binrel=xR").toParseLike("L\\mathrel xR");
expect("L\\@binrel xxR").toParseLike("L\\mathord xR");
expect("L\\@binrel{+}{x}R").toParseLike("L\\mathbin{{x}}R");
expect("L\\@binrel{=}{x}R").toParseLike("L\\mathrel{{x}}R");
expect("L\\@binrel{x}{x}R").toParseLike("L\\mathord{{x}}R");
});
it("should base on just first character in group", () => {
expect("L\\@binrel{+x}xR").toParseLike("L\\mathbin xR");
expect("L\\@binrel{=x}xR").toParseLike("L\\mathrel xR");
expect("L\\@binrel{xx}xR").toParseLike("L\\mathord xR");
});
});
describe("A parser taking String objects", function() {
it("should not fail on an empty String object", function() {
expect(new String("")).toParse();