mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 19:28:39 +00:00
extract sizing and styling implicit functions (#1116)
* extract sizing and styling implicit functions * add BreakToken type
This commit is contained in:
@@ -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) {
|
||||
|
@@ -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));
|
||||
|
@@ -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(
|
||||
|
@@ -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.
|
||||
|
@@ -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
84
src/functions/sizing.js
Normal 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
79
src/functions/styling.js
Normal 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;
|
||||
},
|
||||
});
|
@@ -22,3 +22,5 @@ export type ArgType = "color" | "size" | "url" | "original" | Mode;
|
||||
|
||||
// LaTeX display style.
|
||||
export type StyleStr = "text" | "display";
|
||||
|
||||
export type BreakToken = "]" | "}" | "$";
|
||||
|
@@ -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": [
|
||||
{
|
||||
|
Reference in New Issue
Block a user