extract sizing and styling implicit functions (#1116)

* extract sizing and styling implicit functions

* add BreakToken type
This commit is contained in:
Kevin Barabash
2018-01-30 07:13:01 -05:00
committed by GitHub
parent 03c5ce7f87
commit f80d0afee8
9 changed files with 184 additions and 139 deletions

View File

@@ -5,7 +5,6 @@ import functions from "./functions";
import environments from "./environments";
import MacroExpander from "./MacroExpander";
import symbols from "./symbols";
import utils from "./utils";
import { validUnit } from "./units";
import { supportedCodepoint } from "./unicodeScripts";
import unicodeAccents from "./unicodeAccents";
@@ -16,7 +15,7 @@ import { combiningDiacriticalMarksEndRegex } from "./Lexer.js";
import Settings from "./Settings";
import { Token } from "./Token";
import type { Mode, ArgType } from "./types";
import type { Mode, ArgType, BreakToken } from "./types";
import type { FunctionContext, FunctionSpec } from "./defineFunction" ;
import type { EnvSpec } from "./defineEnvironment";
@@ -184,7 +183,7 @@ export default class Parser {
*/
parseExpression(
breakOnInfix: boolean,
breakOnTokenText?: "]" | "}" | "$",
breakOnTokenText?: BreakToken,
): ParseNode[] {
const body = [];
// Keep adding atoms to the body until we can't parse any more atoms (either
@@ -348,7 +347,7 @@ export default class Parser {
/**
* Parses a group with optional super/subscripts.
*/
parseAtom(breakOnTokenText?: "]" | "}" | "$"): ?ParseNode {
parseAtom(breakOnTokenText?: BreakToken): ?ParseNode {
// The body of an atom is an implicit group, so that things like
// \left(x\right)^2 work correctly.
const base = this.parseImplicitGroup(breakOnTokenText);
@@ -435,17 +434,6 @@ export default class Parser {
}
}
// A list of the size-changing functions, for use in parseImplicitGroup
static sizeFuncs = [
"\\tiny", "\\sixptsize", "\\scriptsize", "\\footnotesize", "\\small",
"\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge",
];
// A list of the style-changing functions, for use in parseImplicitGroup
static styleFuncs = [
"\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle",
];
// Old font functions
static oldFontFuncs = {
"\\rm": "mathrm",
@@ -465,7 +453,7 @@ export default class Parser {
* implicit grouping after it until the end of the group. E.g.
* small text {\Large large text} small text again
*/
parseImplicitGroup(breakOnTokenText?: "]" | "}" | "$"): ?ParseNode {
parseImplicitGroup(breakOnTokenText?: BreakToken): ?ParseNode {
const start = this.parseSymbol();
if (start == null) {
@@ -527,25 +515,6 @@ export default class Parser {
endNameToken);
}
return result;
} else if (utils.contains(Parser.sizeFuncs, func)) {
// If we see a sizing function, parse out the implicit body
this.consumeSpaces();
const body = this.parseExpression(false, breakOnTokenText);
return new ParseNode("sizing", {
// Figure out what size to use based on the list of functions above
size: utils.indexOf(Parser.sizeFuncs, func) + 1,
value: body,
}, this.mode);
} else if (utils.contains(Parser.styleFuncs, func)) {
// If we see a styling function, parse out the implicit body
this.consumeSpaces();
const body = this.parseExpression(true, breakOnTokenText);
return new ParseNode("styling", {
// Figure out what style to use by pulling out the style from
// the function name
style: func.slice(1, func.length - 5),
value: body,
}, this.mode);
} else if (func in Parser.oldFontFuncs) {
const style = Parser.oldFontFuncs[func];
// If we see an old font function, parse out the implicit body
@@ -576,7 +545,7 @@ export default class Parser {
}, this.mode);
} else {
// Defer to parseGivenFunction if it's not a function we handle
return this.parseGivenFunction(start);
return this.parseGivenFunction(start, breakOnTokenText);
}
}
@@ -593,7 +562,10 @@ export default class Parser {
* Same as parseFunction(), except that the base is provided, guaranteeing a
* non-nullable result.
*/
parseGivenFunction(baseGroup: ParsedFuncOrArgOrDollar): ParseNode {
parseGivenFunction(
baseGroup: ParsedFuncOrArgOrDollar,
breakOnTokenText?: BreakToken,
): ParseNode {
baseGroup = assertFuncOrArg(baseGroup);
if (baseGroup.type === "fn") {
const func = baseGroup.result;
@@ -611,7 +583,8 @@ export default class Parser {
const {args, optArgs} = this.parseArguments(func, funcData);
const token = baseGroup.token;
const result = this.callFunction(func, args, optArgs, token);
const result =
this.callFunction(func, args, optArgs, token, breakOnTokenText);
return new ParseNode(result.type, result, this.mode);
} else {
return baseGroup.result;
@@ -626,11 +599,13 @@ export default class Parser {
args: ParseNode[],
optArgs: (?ParseNode)[],
token?: Token,
breakOnTokenText?: BreakToken,
): * {
const context: FunctionContext = {
funcName: name,
parser: this,
token,
breakOnTokenText,
};
const func = functions[name];
if (func && func.handler) {

View File

@@ -422,46 +422,6 @@ groupTypes.spacing = function(group, options) {
}
};
function sizingGroup(value, options, baseOptions) {
const inner = buildExpression(value, options, false);
const multiplier = options.sizeMultiplier / baseOptions.sizeMultiplier;
// Add size-resetting classes to the inner list and set maxFontSize
// manually. Handle nested size changes.
for (let i = 0; i < inner.length; i++) {
const pos = utils.indexOf(inner[i].classes, "sizing");
if (pos < 0) {
Array.prototype.push.apply(inner[i].classes,
options.sizingClasses(baseOptions));
} else if (inner[i].classes[pos + 1] === "reset-size" + options.size) {
// This is a nested size change: e.g., inner[i] is the "b" in
// `\Huge a \small b`. Override the old size (the `reset-` class)
// but not the new size.
inner[i].classes[pos + 1] = "reset-size" + baseOptions.size;
}
inner[i].height *= multiplier;
inner[i].depth *= multiplier;
}
return buildCommon.makeFragment(inner);
}
groupTypes.sizing = function(group, options) {
// Handle sizing operators like \Huge. Real TeX doesn't actually allow
// these functions inside of math expressions, so we do some special
// handling.
const newOptions = options.havingSize(group.value.size);
return sizingGroup(group.value.value, newOptions, options);
};
groupTypes.styling = function(group, options) {
// Style changes are handled in the TeXbook on pg. 442, Rule 3.
const newStyle = styleMap[group.value.style];
const newOptions = options.havingStyle(newStyle);
return sizingGroup(group.value.value, newOptions, options);
};
groupTypes.font = function(group, options) {
const font = group.value.font;
return buildGroup(group.value.body, options.withFontFamily(font));

View File

@@ -247,55 +247,6 @@ groupTypes.font = function(group, options) {
return buildGroup(group.value.body, options.withFontFamily(font));
};
groupTypes.styling = function(group, options) {
// Figure out what style we're changing to.
// TODO(kevinb): dedupe this with buildHTML.js
// This will be easier of handling of styling nodes is in the same file.
const styleMap = {
"display": Style.DISPLAY,
"text": Style.TEXT,
"script": Style.SCRIPT,
"scriptscript": Style.SCRIPTSCRIPT,
};
const newStyle = styleMap[group.value.style];
const newOptions = options.havingStyle(newStyle);
const inner = buildExpression(group.value.value, newOptions);
const node = new mathMLTree.MathNode("mstyle", inner);
const styleAttributes = {
"display": ["0", "true"],
"text": ["0", "false"],
"script": ["1", "false"],
"scriptscript": ["2", "false"],
};
const attr = styleAttributes[group.value.style];
node.setAttribute("scriptlevel", attr[0]);
node.setAttribute("displaystyle", attr[1]);
return node;
};
groupTypes.sizing = function(group, options) {
const newOptions = options.havingSize(group.value.size);
const inner = buildExpression(group.value.value, newOptions);
const node = new mathMLTree.MathNode("mstyle", inner);
// TODO(emily): This doesn't produce the correct size for nested size
// changes, because we don't keep state of what style we're currently
// in, so we can't reset the size to normal before changing it. Now
// that we're passing an options parameter we should be able to fix
// this.
node.setAttribute("mathsize", newOptions.sizeMultiplier + "em");
return node;
};
groupTypes.horizBrace = function(group, options) {
const accentNode = stretchy.mathMLnode(group.value.label);
return new mathMLTree.MathNode(

View File

@@ -5,7 +5,7 @@ import {groupTypes as mathmlGroupTypes} from "./buildMathML";
import type Parser from "./Parser" ;
import type ParseNode from "./ParseNode" ;
import type Options from "./Options";
import type {ArgType} from "./types" ;
import type {ArgType, BreakToken} from "./types" ;
import type {Token} from "./Token" ;
/** Context provided to function handlers for error messages. */
@@ -13,6 +13,7 @@ export type FunctionContext = {|
funcName: string,
parser: Parser,
token?: Token,
breakOnTokenText?: BreakToken,
|};
// TODO: Enumerate all allowed output types.

View File

@@ -179,18 +179,9 @@ import "./functions/smash";
import "./functions/delimsizing";
// Sizing functions (handled in Parser.js explicitly, hence no handler)
defineFunction([
"\\tiny", "\\scriptsize", "\\footnotesize", "\\small",
"\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge",
], {numArgs: 0}, null);
import "./functions/sizing";
// Style changing functions (handled in Parser.js explicitly, hence no
// handler)
defineFunction([
"\\displaystyle", "\\textstyle", "\\scriptstyle",
"\\scriptscriptstyle",
], {numArgs: 0}, null);
import "./functions/styling";
// Old font changing functions
defineFunction([

84
src/functions/sizing.js Normal file
View File

@@ -0,0 +1,84 @@
// @flow
import buildCommon from "../buildCommon";
import defineFunction from "../defineFunction";
import mathMLTree from "../mathMLTree";
import utils from "../utils";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import type Options from "../Options";
export function sizingGroup(value: *, options: Options, baseOptions: Options) {
const inner = html.buildExpression(value, options, false);
const multiplier = options.sizeMultiplier / baseOptions.sizeMultiplier;
// Add size-resetting classes to the inner list and set maxFontSize
// manually. Handle nested size changes.
for (let i = 0; i < inner.length; i++) {
const pos = utils.indexOf(inner[i].classes, "sizing");
if (pos < 0) {
Array.prototype.push.apply(inner[i].classes,
options.sizingClasses(baseOptions));
} else if (inner[i].classes[pos + 1] === "reset-size" + options.size) {
// This is a nested size change: e.g., inner[i] is the "b" in
// `\Huge a \small b`. Override the old size (the `reset-` class)
// but not the new size.
inner[i].classes[pos + 1] = "reset-size" + baseOptions.size;
}
inner[i].height *= multiplier;
inner[i].depth *= multiplier;
}
return buildCommon.makeFragment(inner);
}
const sizeFuncs = [
"\\tiny", "\\sixptsize", "\\scriptsize", "\\footnotesize", "\\small",
"\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge",
];
defineFunction({
type: "sizing",
names: sizeFuncs,
props: {
numArgs: 0,
allowedInText: true,
},
handler: (context, args) => {
const {breakOnTokenText, funcName, parser} = context;
parser.consumeSpaces();
const body = parser.parseExpression(false, breakOnTokenText);
return {
type: "sizing",
// Figure out what size to use based on the list of functions above
size: utils.indexOf(sizeFuncs, funcName) + 1,
value: body,
};
},
htmlBuilder: (group, options) => {
// Handle sizing operators like \Huge. Real TeX doesn't actually allow
// these functions inside of math expressions, so we do some special
// handling.
const newOptions = options.havingSize(group.value.size);
return sizingGroup(group.value.value, newOptions, options);
},
mathmlBuilder: (group, options) => {
const newOptions = options.havingSize(group.value.size);
const inner = mml.buildExpression(group.value.value, newOptions);
const node = new mathMLTree.MathNode("mstyle", inner);
// TODO(emily): This doesn't produce the correct size for nested size
// changes, because we don't keep state of what style we're currently
// in, so we can't reset the size to normal before changing it. Now
// that we're passing an options parameter we should be able to fix
// this.
node.setAttribute("mathsize", newOptions.sizeMultiplier + "em");
return node;
},
});

79
src/functions/styling.js Normal file
View File

@@ -0,0 +1,79 @@
// @flow
import defineFunction from "../defineFunction";
import mathMLTree from "../mathMLTree";
import Style from "../Style";
import {sizingGroup} from "./sizing";
import * as mml from "../buildMathML";
const styleMap = {
"display": Style.DISPLAY,
"text": Style.TEXT,
"script": Style.SCRIPT,
"scriptscript": Style.SCRIPTSCRIPT,
};
defineFunction({
type: "styling",
names: [
"\\displaystyle", "\\textstyle", "\\scriptstyle",
"\\scriptscriptstyle",
],
props: {
numArgs: 0,
allowedInText: true,
},
handler: (context, args) => {
const {breakOnTokenText, funcName, parser} = context;
// parse out the implicit body
parser.consumeSpaces();
const body = parser.parseExpression(true, breakOnTokenText);
return {
type: "styling",
// Figure out what style to use by pulling out the style from
// the function name
style: funcName.slice(1, funcName.length - 5),
value: body,
};
},
htmlBuilder: (group, options) => {
// Style changes are handled in the TeXbook on pg. 442, Rule 3.
const newStyle = styleMap[group.value.style];
const newOptions = options.havingStyle(newStyle);
return sizingGroup(group.value.value, newOptions, options);
},
mathmlBuilder: (group, options) => {
// Figure out what style we're changing to.
// TODO(kevinb): dedupe this with buildHTML.js
// This will be easier of handling of styling nodes is in the same file.
const styleMap = {
"display": Style.DISPLAY,
"text": Style.TEXT,
"script": Style.SCRIPT,
"scriptscript": Style.SCRIPTSCRIPT,
};
const newStyle = styleMap[group.value.style];
const newOptions = options.havingStyle(newStyle);
const inner = mml.buildExpression(group.value.value, newOptions);
const node = new mathMLTree.MathNode("mstyle", inner);
const styleAttributes = {
"display": ["0", "true"],
"text": ["0", "false"],
"script": ["1", "false"],
"scriptscript": ["2", "false"],
};
const attr = styleAttributes[group.value.style];
node.setAttribute("scriptlevel", attr[0]);
node.setAttribute("displaystyle", attr[1]);
return node;
},
});

View File

@@ -22,3 +22,5 @@ export type ArgType = "color" | "size" | "url" | "original" | Mode;
// LaTeX display style.
export type StyleStr = "text" | "display";
export type BreakToken = "]" | "}" | "$";

View File

@@ -26,6 +26,7 @@ exports[`An implicit group parser within optional groups should work style comma
"type": "styling",
"mode": "math",
"value": {
"type": "styling",
"style": "text",
"value": [
{
@@ -113,6 +114,7 @@ exports[`An implicit group parser within optional groups should work with sizing
"type": "sizing",
"mode": "math",
"value": {
"type": "sizing",
"size": 5,
"value": [
{