完成世界书、骰子、apiconfig页面处理

This commit is contained in:
2026-04-30 01:35:10 +08:00
parent a3e3711b2b
commit ba9b925c32
4602 changed files with 785225 additions and 23 deletions

314
frontend/node_modules/katex/src/Options.ts generated vendored Normal file
View File

@@ -0,0 +1,314 @@
/**
* This file contains information about the options that the Parser carries
* around with it while parsing. Data is held in an `Options` object, and when
* recursing, a new `Options` object can be created with the `.with*` and
* `.reset` functions.
*/
import {getGlobalMetrics} from "./fontMetrics";
import type {FontMetrics} from "./fontMetrics";
import type {StyleInterface} from "./Style";
const sizeStyleMap = [
// Each element contains [textsize, scriptsize, scriptscriptsize].
// The size mappings are taken from TeX with \normalsize=10pt.
[1, 1, 1], // size1: [5, 5, 5] \tiny
[2, 1, 1], // size2: [6, 5, 5]
[3, 1, 1], // size3: [7, 5, 5] \scriptsize
[4, 2, 1], // size4: [8, 6, 5] \footnotesize
[5, 2, 1], // size5: [9, 6, 5] \small
[6, 3, 1], // size6: [10, 7, 5] \normalsize
[7, 4, 2], // size7: [12, 8, 6] \large
[8, 6, 3], // size8: [14.4, 10, 7] \Large
[9, 7, 6], // size9: [17.28, 12, 10] \LARGE
[10, 8, 7], // size10: [20.74, 14.4, 12] \huge
[11, 10, 9], // size11: [24.88, 20.74, 17.28] \HUGE
];
const sizeMultipliers = [
// fontMetrics.js:getGlobalMetrics also uses size indexes, so if
// you change size indexes, change that function.
0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2, 1.44, 1.728, 2.074, 2.488,
];
const sizeAtStyle = function(size: number, style: StyleInterface): number {
return style.size < 2 ? size : sizeStyleMap[size - 1][style.size - 1];
};
// In these types, "" (empty string) means "no change".
export type FontWeight = "textbf" | "textmd" | "";
export type FontShape = "textit" | "textup" | "";
export type OptionsData = {
style: StyleInterface;
color?: string | undefined;
size?: number;
textSize?: number;
phantom?: boolean;
font?: string;
fontFamily?: string;
fontWeight?: FontWeight;
fontShape?: FontShape;
sizeMultiplier?: number;
maxSize: number;
minRuleThickness: number;
};
/**
* This is the main options class. It contains the current style, size, color,
* and font.
*
* Options objects should not be modified. To create a new Options with
* different properties, call a `.having*` method.
*/
class Options {
style: StyleInterface;
color: string | undefined;
size: number;
textSize: number;
phantom: boolean;
// A font family applies to a group of fonts (i.e. SansSerif), while a font
// represents a specific font (i.e. SansSerif Bold).
// See: https://tex.stackexchange.com/questions/22350/difference-between-textrm-and-mathrm
font: string;
fontFamily: string;
fontWeight: FontWeight;
fontShape: FontShape;
sizeMultiplier: number;
maxSize: number;
minRuleThickness: number;
_fontMetrics: FontMetrics | undefined;
/**
* The base size index.
*/
static BASESIZE: number = 6;
constructor(data: OptionsData) {
this.style = data.style;
this.color = data.color;
this.size = data.size || Options.BASESIZE;
this.textSize = data.textSize || this.size;
this.phantom = !!data.phantom;
this.font = data.font || "";
this.fontFamily = data.fontFamily || "";
this.fontWeight = data.fontWeight || '';
this.fontShape = data.fontShape || '';
this.sizeMultiplier = sizeMultipliers[this.size - 1];
this.maxSize = data.maxSize;
this.minRuleThickness = data.minRuleThickness;
this._fontMetrics = undefined;
}
/**
* Returns a new options object with the same properties as "this". Properties
* from "extension" will be copied to the new options object.
*/
extend(extension: Partial<OptionsData>): Options {
const data: OptionsData = {
style: this.style,
size: this.size,
textSize: this.textSize,
color: this.color,
phantom: this.phantom,
font: this.font,
fontFamily: this.fontFamily,
fontWeight: this.fontWeight,
fontShape: this.fontShape,
maxSize: this.maxSize,
minRuleThickness: this.minRuleThickness,
};
Object.assign(data, extension);
return new Options(data);
}
/**
* Return an options object with the given style. If `this.style === style`,
* returns `this`.
*/
havingStyle(style: StyleInterface): Options {
if (this.style === style) {
return this;
} else {
return this.extend({
style: style,
size: sizeAtStyle(this.textSize, style),
});
}
}
/**
* Return an options object with a cramped version of the current style. If
* the current style is cramped, returns `this`.
*/
havingCrampedStyle(): Options {
return this.havingStyle(this.style.cramp());
}
/**
* Return an options object with the given size and in at least `\textstyle`.
* Returns `this` if appropriate.
*/
havingSize(size: number): Options {
if (this.size === size && this.textSize === size) {
return this;
} else {
return this.extend({
style: this.style.text(),
size: size,
textSize: size,
sizeMultiplier: sizeMultipliers[size - 1],
});
}
}
/**
* Like `this.havingSize(BASESIZE).havingStyle(style)`. If `style` is omitted,
* changes to at least `\textstyle`.
*/
havingBaseStyle(style: StyleInterface): Options {
style = style || this.style.text();
const wantSize = sizeAtStyle(Options.BASESIZE, style);
if (this.size === wantSize && this.textSize === Options.BASESIZE
&& this.style === style) {
return this;
} else {
return this.extend({
style: style,
size: wantSize,
});
}
}
/**
* Remove the effect of sizing changes such as \Huge.
* Keep the effect of the current style, such as \scriptstyle.
*/
havingBaseSizing(): Options {
let size;
switch (this.style.id) {
case 4:
case 5:
size = 3; // normalsize in scriptstyle
break;
case 6:
case 7:
size = 1; // normalsize in scriptscriptstyle
break;
default:
size = 6; // normalsize in textstyle or displaystyle
}
return this.extend({
style: this.style.text(),
size: size,
});
}
/**
* Create a new options object with the given color.
*/
withColor(color: string): Options {
return this.extend({
color: color,
});
}
/**
* Create a new options object with "phantom" set to true.
*/
withPhantom(): Options {
return this.extend({
phantom: true,
});
}
/**
* Creates a new options object with the given math font or old text font.
* @type {[type]}
*/
withFont(font: string): Options {
return this.extend({
font,
});
}
/**
* Create a new options objects with the given fontFamily.
*/
withTextFontFamily(fontFamily: string): Options {
return this.extend({
fontFamily,
font: "",
});
}
/**
* Creates a new options object with the given font weight
*/
withTextFontWeight(fontWeight: FontWeight): Options {
return this.extend({
fontWeight,
font: "",
});
}
/**
* Creates a new options object with the given font weight
*/
withTextFontShape(fontShape: FontShape): Options {
return this.extend({
fontShape,
font: "",
});
}
/**
* Return the CSS sizing classes required to switch from enclosing options
* `oldOptions` to `this`. Returns an array of classes.
*/
sizingClasses(oldOptions: Options): Array<string> {
if (oldOptions.size !== this.size) {
return ["sizing", "reset-size" + oldOptions.size, "size" + this.size];
} else {
return [];
}
}
/**
* Return the CSS sizing classes required to switch to the base size. Like
* `this.havingSize(BASESIZE).sizingClasses(this)`.
*/
baseSizingClasses(): Array<string> {
if (this.size !== Options.BASESIZE) {
return ["sizing", "reset-size" + this.size, "size" + Options.BASESIZE];
} else {
return [];
}
}
/**
* Return the font metrics for this size.
*/
fontMetrics(): FontMetrics {
if (!this._fontMetrics) {
this._fontMetrics = getGlobalMetrics(this.size);
}
return this._fontMetrics;
}
/**
* Gets the CSS color of the current options object
*/
getColor(): string | undefined {
if (this.phantom) {
return "transparent";
} else {
return this.color;
}
}
}
export default Options;

775
frontend/node_modules/katex/src/buildCommon.ts generated vendored Normal file
View File

@@ -0,0 +1,775 @@
/* eslint no-console:0 */
/**
* This module contains general functions that can be used for building
* different kinds of domTree nodes in a consistent manner.
*/
import {SymbolNode, Anchor, Span, PathNode, SvgNode, createClass} from "./domTree";
import {getCharacterMetrics} from "./fontMetrics";
import symbols, {ligatures} from "./symbols";
import {wideCharacterFont} from "./wide-character";
import {calculateSize, makeEm} from "./units";
import {DocumentFragment} from "./tree";
import type Options from "./Options";
import type {ParseNode} from "./parseNode";
import type {CharacterMetrics} from "./fontMetrics";
import type {FontVariant, Mode} from "./types";
import type {documentFragment as HtmlDocumentFragment} from "./domTree";
import type {HtmlDomNode, DomSpan, SvgSpan, CssStyle} from "./domTree";
import type {Measurement} from "./units";
/**
* Looks up the given symbol in fontMetrics, after applying any symbol
* replacements defined in symbol.js
*/
const lookupSymbol = function(
value: string,
// TODO(#963): Use a union type for this.
fontName: string,
mode: Mode,
): {
value: string;
metrics: CharacterMetrics | null | undefined;
} {
// Replace the value with its replaced value from symbol.js
if (symbols[mode][value]) {
const replacement = symbols[mode][value].replace;
if (replacement) {
value = replacement;
}
}
return {
value,
metrics: getCharacterMetrics(value, fontName, mode),
};
};
/**
* Makes a symbolNode after translation via the list of symbols in symbols.js.
* Correctly pulls out metrics for the character, and optionally takes a list of
* classes to be attached to the node.
*
* TODO: make argument order closer to makeSpan
* TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which
* should if present come first in `classes`.
* TODO(#953): Make `options` mandatory and always pass it in.
*/
export const makeSymbol = function(
value: string,
fontName: string,
mode: Mode,
options?: Options,
classes?: string[],
): SymbolNode {
const lookup = lookupSymbol(value, fontName, mode);
const metrics = lookup.metrics;
value = lookup.value;
let symbolNode;
if (metrics) {
let italic = metrics.italic;
if (mode === "text" || (options && options.font === "mathit")) {
italic = 0;
}
symbolNode = new SymbolNode(
value, metrics.height, metrics.depth, italic, metrics.skew,
metrics.width, classes);
} else {
// TODO(emily): Figure out a good way to only print this in development
typeof console !== "undefined" && console.warn("No character metrics " +
`for '${value}' in style '${fontName}' and mode '${mode}'`);
symbolNode = new SymbolNode(value, 0, 0, 0, 0, 0, classes);
}
if (options) {
symbolNode.maxFontSize = options.sizeMultiplier;
if (options.style.isTight()) {
symbolNode.classes.push("mtight");
}
const color = options.getColor();
if (color) {
symbolNode.style.color = color;
}
}
return symbolNode;
};
/**
* Makes a symbol in Main-Regular or AMS-Regular.
* Used for rel, bin, open, close, inner, and punct.
*/
export const mathsym = function(
value: string,
mode: Mode,
options: Options,
classes: string[] = [],
): SymbolNode {
// Decide what font to render the symbol in by its entry in the symbols
// table.
// Have a special case for when the value = \ because the \ is used as a
// textord in unsupported command errors but cannot be parsed as a regular
// text ordinal and is therefore not present as a symbol in the symbols
// table for text, as well as a special case for boldsymbol because it
// can be used for bold + and -
if (options.font === "boldsymbol" &&
lookupSymbol(value, "Main-Bold", mode).metrics) {
return makeSymbol(value, "Main-Bold", mode, options,
classes.concat(["mathbf"]));
} else if (value === "\\" || symbols[mode][value].font === "main") {
return makeSymbol(value, "Main-Regular", mode, options, classes);
} else {
return makeSymbol(
value, "AMS-Regular", mode, options, classes.concat(["amsrm"]));
}
};
/**
* Determines which of the two font names (Main-Bold and Math-BoldItalic) and
* corresponding style tags (mathbf or boldsymbol) to use for font "boldsymbol",
* depending on the symbol. Use this function instead of fontMap for font
* "boldsymbol".
*/
const boldsymbol = function(
value: string,
mode: Mode,
options: Options,
classes: string[],
type: "mathord" | "textord",
): {
fontName: string;
fontClass: string;
} {
if (type !== "textord" &&
lookupSymbol(value, "Math-BoldItalic", mode).metrics) {
return {
fontName: "Math-BoldItalic",
fontClass: "boldsymbol",
};
} else {
// Some glyphs do not exist in Math-BoldItalic so we need to use
// Main-Bold instead.
return {
fontName: "Main-Bold",
fontClass: "mathbf",
};
}
};
/**
* Makes either a mathord or textord in the correct font and color.
*/
export const makeOrd = function<NODETYPE extends "spacing" | "mathord" | "textord">(
group: ParseNode<NODETYPE>,
options: Options,
type: "mathord" | "textord",
): HtmlDocumentFragment | SymbolNode {
const mode = group.mode;
const text = group.text;
const classes = ["mord"];
// Math mode or Old font (i.e. \rm)
const isFont = mode === "math" || (mode === "text" && options.font);
const fontOrFamily = isFont ? options.font : options.fontFamily;
let wideFontName = "";
let wideFontClass = "";
if (text.charCodeAt(0) === 0xD835) {
[wideFontName, wideFontClass] = wideCharacterFont(text, mode);
}
if (wideFontName.length > 0) {
// surrogate pairs get special treatment
return makeSymbol(text, wideFontName, mode, options,
classes.concat(wideFontClass));
} else if (fontOrFamily) {
let fontName;
let fontClasses;
if (fontOrFamily === "boldsymbol") {
const fontData = boldsymbol(text, mode, options, classes, type);
fontName = fontData.fontName;
fontClasses = [fontData.fontClass];
} else if (isFont) {
fontName = fontMap[fontOrFamily].fontName;
fontClasses = [fontOrFamily];
} else {
fontName = retrieveTextFontName(fontOrFamily, options.fontWeight,
options.fontShape);
fontClasses = [fontOrFamily, options.fontWeight, options.fontShape];
}
if (lookupSymbol(text, fontName, mode).metrics) {
return makeSymbol(text, fontName, mode, options,
classes.concat(fontClasses));
} else if (ligatures.hasOwnProperty(text) &&
fontName.slice(0, 10) === "Typewriter") {
// Deconstruct ligatures in monospace fonts (\texttt, \tt).
const parts = [];
for (let i = 0; i < text.length; i++) {
parts.push(makeSymbol(text[i], fontName, mode, options,
classes.concat(fontClasses)));
}
return makeFragment(parts);
}
}
// Makes a symbol in the default font for mathords and textords.
if (type === "mathord") {
return makeSymbol(text, "Math-Italic", mode, options,
classes.concat(["mathnormal"]));
} else if (type === "textord") {
const font = symbols[mode][text] && symbols[mode][text].font;
if (font === "ams") {
const fontName = retrieveTextFontName("amsrm", options.fontWeight,
options.fontShape);
return makeSymbol(
text, fontName, mode, options,
classes.concat("amsrm", options.fontWeight, options.fontShape));
} else if (font === "main" || !font) {
const fontName = retrieveTextFontName("textrm", options.fontWeight,
options.fontShape);
return makeSymbol(
text, fontName, mode, options,
classes.concat(options.fontWeight, options.fontShape));
} else { // fonts added by plugins
const fontName = retrieveTextFontName(font, options.fontWeight,
options.fontShape);
// We add font name as a css class
return makeSymbol(
text, fontName, mode, options,
classes.concat(fontName, options.fontWeight, options.fontShape));
}
} else {
throw new Error("unexpected type: " + type + " in makeOrd");
}
};
/**
* Returns true if subsequent symbolNodes have the same classes, skew, maxFont,
* and styles. For mathnormal text, the left node must also have zero italic
* correction so we don't lose spacing between combined glyphs.
*/
const canCombine = (prev: SymbolNode, next: SymbolNode) => {
if (createClass(prev.classes) !== createClass(next.classes)
|| prev.skew !== next.skew
|| prev.maxFontSize !== next.maxFontSize
|| (prev.italic !== 0 && prev.hasClass("mathnormal"))) {
return false;
}
// If prev and next both are just "mbin"s or "mord"s we don't combine them
// so that the proper spacing can be preserved.
if (prev.classes.length === 1) {
const cls = prev.classes[0];
if (cls === "mbin" || cls === "mord") {
return false;
}
}
for (const key of Object.keys(prev.style) as Array<keyof CssStyle>) {
if (prev.style[key] !== next.style[key]) {
return false;
}
}
for (const key of Object.keys(next.style) as Array<keyof CssStyle>) {
if (prev.style[key] !== next.style[key]) {
return false;
}
}
return true;
};
/**
* Combine consecutive domTree.symbolNodes into a single symbolNode.
* Note: this function mutates the argument.
*/
export const tryCombineChars = (chars: HtmlDomNode[]): HtmlDomNode[] => {
for (let i = 0; i < chars.length - 1; i++) {
const prev = chars[i];
const next = chars[i + 1];
if (prev instanceof SymbolNode
&& next instanceof SymbolNode
&& canCombine(prev, next)) {
prev.text += next.text;
prev.height = Math.max(prev.height, next.height);
prev.depth = Math.max(prev.depth, next.depth);
// Use the last character's italic correction since we use
// it to add padding to the right of the span created from
// the combined characters.
prev.italic = next.italic;
chars.splice(i + 1, 1);
i--;
}
}
return chars;
};
/**
* Calculate the height, depth, and maxFontSize of an element based on its
* children.
*/
const sizeElementFromChildren = function(
elem: DomSpan | Anchor | HtmlDocumentFragment,
) {
let height = 0;
let depth = 0;
let maxFontSize = 0;
for (let i = 0; i < elem.children.length; i++) {
const child = elem.children[i];
if (child.height > height) {
height = child.height;
}
if (child.depth > depth) {
depth = child.depth;
}
if (child.maxFontSize > maxFontSize) {
maxFontSize = child.maxFontSize;
}
}
elem.height = height;
elem.depth = depth;
elem.maxFontSize = maxFontSize;
};
/**
* Makes a span with the given list of classes, list of children, and options.
*
* TODO(#953): Ensure that `options` is always provided (currently some call
* sites don't pass it) and make the type below mandatory.
* TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which
* should if present come first in `classes`.
*/
export const makeSpan = function(
classes?: string[],
children?: HtmlDomNode[],
options?: Options,
style?: CssStyle,
): DomSpan {
const span = new Span(classes, children, options, style);
sizeElementFromChildren(span);
return span;
};
// SVG one is simpler -- doesn't require height, depth, max-font setting.
// This is also a separate method for typesafety.
export const makeSvgSpan = (
classes?: string[],
children?: SvgNode[],
options?: Options,
style?: CssStyle,
): SvgSpan => new Span(classes, children, options, style);
export const makeLineSpan = function(
className: string,
options: Options,
thickness?: number,
): DomSpan {
const line = makeSpan([className], [], options);
line.height = Math.max(
thickness || options.fontMetrics().defaultRuleThickness,
options.minRuleThickness,
);
line.style.borderBottomWidth = makeEm(line.height);
line.maxFontSize = 1.0;
return line;
};
/**
* Makes an anchor with the given href, list of classes, list of children,
* and options.
*/
export const makeAnchor = function(
href: string,
classes: string[],
children: HtmlDomNode[],
options: Options,
): Anchor {
const anchor = new Anchor(href, classes, children, options);
sizeElementFromChildren(anchor);
return anchor;
};
/**
* Makes a document fragment with the given list of children.
*/
export const makeFragment = function(
children: HtmlDomNode[],
): HtmlDocumentFragment {
const fragment = new DocumentFragment(children);
sizeElementFromChildren(fragment);
return fragment;
};
/**
* Wraps group in a span if it's a document fragment, allowing to apply classes
* and styles
*/
export const wrapFragment = function(
group: HtmlDomNode,
options: Options,
): HtmlDomNode {
if (group instanceof DocumentFragment) {
return makeSpan([], [group], options);
}
return group;
};
export type VListElem = {
type: "elem";
elem: HtmlDomNode;
marginLeft?: string | null | undefined;
marginRight?: string;
wrapperClasses?: string[];
wrapperStyle?: CssStyle;
};
type VListElemAndShift = {
type: "elem";
elem: HtmlDomNode;
shift: number;
marginLeft?: string | null | undefined;
marginRight?: string;
wrapperClasses?: string[];
wrapperStyle?: CssStyle;
};
type VListKern = {
type: "kern";
size: number;
};
// A list of child or kern nodes to be stacked on top of each other (i.e. the
// first element will be at the bottom, and the last at the top).
type VListChild = VListElem | VListKern;
type VListParam = {
// Each child contains how much it should be shifted downward.
positionType: "individualShift";
children: VListElemAndShift[];
} | {
// "top": The positionData specifies the topmost point of the vlist (note this
// is expected to be a height, so positive values move up).
// "bottom": The positionData specifies the bottommost point of the vlist (note
// this is expected to be a depth, so positive values move down).
// "shift": The vlist will be positioned such that its baseline is positionData
// away from the baseline of the first child which MUST be an
// "elem". Positive values move downwards.
positionType: "top" | "bottom" | "shift";
positionData: number;
children: VListChild[];
} | {
// The vlist is positioned so that its baseline is aligned with the baseline
// of the first child which MUST be an "elem". This is equivalent to "shift"
// with positionData=0.
positionType: "firstBaseline";
children: VListChild[];
};
// Computes the updated `children` list and the overall depth.
//
// This helper function for makeVList makes it easier to enforce type safety by
// allowing early exits (returns) in the logic.
const getVListChildrenAndDepth = function(params: VListParam): {
children: (VListChild | VListElemAndShift)[] | VListChild[];
depth: number;
} {
if (params.positionType === "individualShift") {
const oldChildren = params.children;
const children: (VListChild | VListElemAndShift)[] = [oldChildren[0]];
// Add in kerns to the list of params.children to get each element to be
// shifted to the correct specified shift
const depth = -oldChildren[0].shift - oldChildren[0].elem.depth;
let currPos = depth;
for (let i = 1; i < oldChildren.length; i++) {
const diff = -oldChildren[i].shift - currPos -
oldChildren[i].elem.depth;
const size = diff -
(oldChildren[i - 1].elem.height +
oldChildren[i - 1].elem.depth);
currPos = currPos + diff;
children.push({type: "kern", size});
children.push(oldChildren[i]);
}
return {children, depth};
}
let depth;
if (params.positionType === "top") {
// We always start at the bottom, so calculate the bottom by adding up
// all the sizes
let bottom = params.positionData;
for (let i = 0; i < params.children.length; i++) {
const child = params.children[i];
bottom -= child.type === "kern"
? child.size
: child.elem.height + child.elem.depth;
}
depth = bottom;
} else if (params.positionType === "bottom") {
depth = -params.positionData;
} else {
const firstChild = params.children[0];
if (firstChild.type !== "elem") {
throw new Error('First child must have type "elem".');
}
if (params.positionType === "shift") {
depth = -firstChild.elem.depth - params.positionData;
} else if (params.positionType === "firstBaseline") {
depth = -firstChild.elem.depth;
} else {
throw new Error(`Invalid positionType ${params.positionType}.`);
}
}
return {children: params.children, depth};
};
/**
* Makes a vertical list by stacking elements and kerns on top of each other.
* Allows for many different ways of specifying the positioning method.
*
* See VListParam documentation above.
*/
export const makeVList = function(params: VListParam, options: Options): DomSpan {
const {children, depth} = getVListChildrenAndDepth(params);
// Create a strut that is taller than any list item. The strut is added to
// each item, where it will determine the item's baseline. Since it has
// `overflow:hidden`, the strut's top edge will sit on the item's line box's
// top edge and the strut's bottom edge will sit on the item's baseline,
// with no additional line-height spacing. This allows the item baseline to
// be positioned precisely without worrying about font ascent and
// line-height.
let pstrutSize = 0;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.type === "elem") {
const elem = child.elem;
pstrutSize = Math.max(pstrutSize, elem.maxFontSize, elem.height);
}
}
pstrutSize += 2;
const pstrut = makeSpan(["pstrut"], []);
pstrut.style.height = makeEm(pstrutSize);
// Create a new list of actual children at the correct offsets
const realChildren = [];
let minPos = depth;
let maxPos = depth;
let currPos = depth;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.type === "kern") {
currPos += child.size;
} else {
const elem = child.elem;
const classes = child.wrapperClasses || [];
const style = child.wrapperStyle || {};
const childWrap = makeSpan(classes, [pstrut, elem], undefined, style);
childWrap.style.top = makeEm(-pstrutSize - currPos - elem.depth);
if (child.marginLeft) {
childWrap.style.marginLeft = child.marginLeft;
}
if (child.marginRight) {
childWrap.style.marginRight = child.marginRight;
}
realChildren.push(childWrap);
currPos += elem.height + elem.depth;
}
minPos = Math.min(minPos, currPos);
maxPos = Math.max(maxPos, currPos);
}
// The vlist contents go in a table-cell with `vertical-align:bottom`.
// This cell's bottom edge will determine the containing table's baseline
// without overly expanding the containing line-box.
const vlist = makeSpan(["vlist"], realChildren);
vlist.style.height = makeEm(maxPos);
// A second row is used if necessary to represent the vlist's depth.
let rows;
if (minPos < 0) {
// We will define depth in an empty span with display: table-cell.
// It should render with the height that we define. But Chrome, in
// contenteditable mode only, treats that span as if it contains some
// text content. And that min-height over-rides our desired height.
// So we put another empty span inside the depth strut span.
const emptySpan = makeSpan([], []);
const depthStrut = makeSpan(["vlist"], [emptySpan]);
depthStrut.style.height = makeEm(-minPos);
// Safari wants the first row to have inline content; otherwise it
// puts the bottom of the *second* row on the baseline.
const topStrut = makeSpan(["vlist-s"], [new SymbolNode("\u200b")]);
rows = [makeSpan(["vlist-r"], [vlist, topStrut]),
makeSpan(["vlist-r"], [depthStrut])];
} else {
rows = [makeSpan(["vlist-r"], [vlist])];
}
const vtable = makeSpan(["vlist-t"], rows);
if (rows.length === 2) {
vtable.classes.push("vlist-t2");
}
vtable.height = maxPos;
vtable.depth = -minPos;
return vtable;
};
// Glue is a concept from TeX which is a flexible space between elements in
// either a vertical or horizontal list. In KaTeX, at least for now, it's
// static space between elements in a horizontal layout.
export const makeGlue = (measurement: Measurement, options: Options): DomSpan => {
// Make an empty span for the space
const rule = makeSpan(["mspace"], [], options);
const size = calculateSize(measurement, options);
rule.style.marginRight = makeEm(size);
return rule;
};
// Takes font options, and returns the appropriate fontLookup name
const retrieveTextFontName = function(
fontFamily: string,
fontWeight: string,
fontShape: string,
): string {
let baseFontName = "";
switch (fontFamily) {
case "amsrm":
baseFontName = "AMS";
break;
case "textrm":
baseFontName = "Main";
break;
case "textsf":
baseFontName = "SansSerif";
break;
case "texttt":
baseFontName = "Typewriter";
break;
default:
baseFontName = fontFamily; // use fonts added by a plugin
}
let fontStylesName;
if (fontWeight === "textbf" && fontShape === "textit") {
fontStylesName = "BoldItalic";
} else if (fontWeight === "textbf") {
fontStylesName = "Bold";
} else if (fontWeight === "textit") {
fontStylesName = "Italic";
} else {
fontStylesName = "Regular";
}
return `${baseFontName}-${fontStylesName}`;
};
/**
* Maps TeX font commands to objects containing:
* - variant: string used for "mathvariant" attribute in buildMathML.js
* - fontName: the "style" parameter to fontMetrics.getCharacterMetrics
*/
// A map between tex font commands an MathML mathvariant attribute values
export const fontMap: Record<string, {
variant: FontVariant;
fontName: string;
}> = {
// styles
"mathbf": {
variant: "bold",
fontName: "Main-Bold",
},
"mathrm": {
variant: "normal",
fontName: "Main-Regular",
},
"textit": {
variant: "italic",
fontName: "Main-Italic",
},
"mathit": {
variant: "italic",
fontName: "Main-Italic",
},
"mathnormal": {
variant: "italic",
fontName: "Math-Italic",
},
"mathsfit": {
variant: "sans-serif-italic",
fontName: "SansSerif-Italic",
},
// "boldsymbol" is missing because they require the use of multiple fonts:
// Math-BoldItalic and Main-Bold. This is handled by a special case in
// makeOrd which ends up calling boldsymbol.
// families
"mathbb": {
variant: "double-struck",
fontName: "AMS-Regular",
},
"mathcal": {
variant: "script",
fontName: "Caligraphic-Regular",
},
"mathfrak": {
variant: "fraktur",
fontName: "Fraktur-Regular",
},
"mathscr": {
variant: "script",
fontName: "Script-Regular",
},
"mathsf": {
variant: "sans-serif",
fontName: "SansSerif-Regular",
},
"mathtt": {
variant: "monospace",
fontName: "Typewriter-Regular",
},
};
export const svgData: Record<string, [string, number, number]> = {
// path, width, height
vec: ["vec", 0.471, 0.714], // values from the font glyph
oiintSize1: ["oiintSize1", 0.957, 0.499], // oval to overlay the integrand
oiintSize2: ["oiintSize2", 1.472, 0.659],
oiiintSize1: ["oiiintSize1", 1.304, 0.499],
oiiintSize2: ["oiiintSize2", 1.98, 0.659],
};
export const staticSvg = function(value: string, options: Options): SvgSpan {
// Create a span with inline SVG for the element.
const [pathName, width, height] = svgData[value];
const path = new PathNode(pathName);
const svgNode = new SvgNode([path], {
"width": makeEm(width),
"height": makeEm(height),
// Override CSS rule `.katex svg { width: 100% }`
"style": "width:" + makeEm(width),
"viewBox": "0 0 " + 1000 * width + " " + 1000 * height,
"preserveAspectRatio": "xMinYMin",
});
const span = makeSvgSpan(["overlay"], [svgNode], options);
span.height = height;
span.style.height = makeEm(height);
span.style.width = makeEm(width);
return span;
};

124
frontend/node_modules/katex/src/defineMacro.ts generated vendored Normal file
View File

@@ -0,0 +1,124 @@
import {Token} from "./Token";
import type Namespace from "./Namespace";
import type {Mode} from "./types";
/**
* Provides context to macros defined by functions. Implemented by
* MacroExpander.
*/
export interface MacroContextInterface {
mode: Mode;
/**
* Object mapping macros to their expansions.
*/
macros: Namespace<MacroDefinition>;
/**
* Returns the topmost token on the stack, without expanding it.
* Similar in behavior to TeX's `\futurelet`.
*/
future(): Token;
/**
* Remove and return the next unexpanded token.
*/
popToken(): Token;
/**
* Consume all following space tokens, without expansion.
*/
consumeSpaces(): void;
/**
* Expand the next token only once if possible.
*/
expandOnce(expandableOnly?: boolean): number | boolean;
/**
* Expand the next token only once (if possible), and return the resulting
* top token on the stack (without removing anything from the stack).
* Similar in behavior to TeX's `\expandafter\futurelet`.
*/
expandAfterFuture(): Token;
/**
* Recursively expand first token, then return first non-expandable token.
*/
expandNextToken(): Token;
/**
* Fully expand the given macro name and return the resulting list of
* tokens, or return `undefined` if no such macro is defined.
*/
expandMacro(name: string): Token[] | undefined;
/**
* Fully expand the given macro name and return the result as a string,
* or return `undefined` if no such macro is defined.
*/
expandMacroAsText(name: string): string | undefined;
/**
* Fully expand the given token stream and return the resulting list of
* tokens. Note that the input tokens are in reverse order, but the
* output tokens are in forward order.
*/
expandTokens(tokens: Token[]): Token[];
/**
* Consume an argument from the token stream, and return the resulting array
* of tokens and start/end token.
*/
consumeArg(delims?: string[] | null | undefined): MacroArg;
/**
* Consume the specified number of arguments from the token stream,
* and return the resulting array of arguments.
*/
consumeArgs(numArgs: number): Token[][];
/**
* Determine whether a command is currently "defined" (has some
* functionality), meaning that it's a macro (in the current group),
* a function, a symbol, or one of the special commands listed in
* `implicitCommands`.
*/
isDefined(name: string): boolean;
/**
* Determine whether a command is expandable.
*/
isExpandable(name: string): boolean;
}
export type MacroArg = {
tokens: Token[];
start: Token;
end: Token;
};
/** Macro tokens (in reverse order). */
export type MacroExpansion = {
tokens: Token[];
numArgs: number;
delimiters?: string[][];
unexpandable?: boolean; // used in \let
};
export type MacroDefinition = string | MacroExpansion |
((arg0: MacroContextInterface) => string | MacroExpansion);
export type MacroMap = Record<string, MacroDefinition>;
/**
* All registered global/built-in macros.
* `macros.js` exports this same dictionary again and makes it public.
* `Parser.js` requires this dictionary via `macros.js`.
*/
export const _macros: MacroMap = {};
// This function might one day accept an additional argument and do more things.
export default function defineMacro(name: string, body: MacroDefinition) {
_macros[name] = body;
}

280
frontend/node_modules/katex/src/fontMetrics.ts generated vendored Normal file
View File

@@ -0,0 +1,280 @@
import {supportedCodepoint} from "./unicodeScripts";
import type {Mode} from "./types";
/**
* This file contains metrics regarding fonts and individual symbols. The sigma
* and xi variables, as well as the metricMap map contain data extracted from
* TeX, TeX font metrics, and the TTF files. These data are then exposed via the
* `metrics` variable and the getCharacterMetrics function.
*/
// In TeX, there are actually three sets of dimensions, one for each of
// textstyle (size index 5 and higher: >=9pt), scriptstyle (size index 3 and 4:
// 7-8pt), and scriptscriptstyle (size index 1 and 2: 5-6pt). These are
// provided in the arrays below, in that order.
//
// The font metrics are stored in fonts cmsy10, cmsy7, and cmsy5 respectively.
// This was determined by running the following script:
//
// latex -interaction=nonstopmode \
// '\documentclass{article}\usepackage{amsmath}\begin{document}' \
// '$a$ \expandafter\show\the\textfont2' \
// '\expandafter\show\the\scriptfont2' \
// '\expandafter\show\the\scriptscriptfont2' \
// '\stop'
//
// The metrics themselves were retrieved using the following commands:
//
// tftopl cmsy10
// tftopl cmsy7
// tftopl cmsy5
//
// The output of each of these commands is quite lengthy. The only part we
// care about is the FONTDIMEN section. Each value is measured in EMs.
const sigmasAndXis: Record<string, [number, number, number]> = {
slant: [0.250, 0.250, 0.250], // sigma1
space: [0.000, 0.000, 0.000], // sigma2
stretch: [0.000, 0.000, 0.000], // sigma3
shrink: [0.000, 0.000, 0.000], // sigma4
xHeight: [0.431, 0.431, 0.431], // sigma5
quad: [1.000, 1.171, 1.472], // sigma6
extraSpace: [0.000, 0.000, 0.000], // sigma7
num1: [0.677, 0.732, 0.925], // sigma8
num2: [0.394, 0.384, 0.387], // sigma9
num3: [0.444, 0.471, 0.504], // sigma10
denom1: [0.686, 0.752, 1.025], // sigma11
denom2: [0.345, 0.344, 0.532], // sigma12
sup1: [0.413, 0.503, 0.504], // sigma13
sup2: [0.363, 0.431, 0.404], // sigma14
sup3: [0.289, 0.286, 0.294], // sigma15
sub1: [0.150, 0.143, 0.200], // sigma16
sub2: [0.247, 0.286, 0.400], // sigma17
supDrop: [0.386, 0.353, 0.494], // sigma18
subDrop: [0.050, 0.071, 0.100], // sigma19
delim1: [2.390, 1.700, 1.980], // sigma20
delim2: [1.010, 1.157, 1.420], // sigma21
axisHeight: [0.250, 0.250, 0.250], // sigma22
// These font metrics are extracted from TeX by using tftopl on cmex10.tfm;
// they correspond to the font parameters of the extension fonts (family 3).
// See the TeXbook, page 441. In AMSTeX, the extension fonts scale; to
// match cmex7, we'd use cmex7.tfm values for script and scriptscript
// values.
defaultRuleThickness: [0.04, 0.049, 0.049], // xi8; cmex7: 0.049
bigOpSpacing1: [0.111, 0.111, 0.111], // xi9
bigOpSpacing2: [0.166, 0.166, 0.166], // xi10
bigOpSpacing3: [0.2, 0.2, 0.2], // xi11
bigOpSpacing4: [0.6, 0.611, 0.611], // xi12; cmex7: 0.611
bigOpSpacing5: [0.1, 0.143, 0.143], // xi13; cmex7: 0.143
// The \sqrt rule width is taken from the height of the surd character.
// Since we use the same font at all sizes, this thickness doesn't scale.
sqrtRuleThickness: [0.04, 0.04, 0.04],
// This value determines how large a pt is, for metrics which are defined
// in terms of pts.
// This value is also used in katex.scss; if you change it make sure the
// values match.
ptPerEm: [10.0, 10.0, 10.0],
// The space between adjacent `|` columns in an array definition. From
// `\showthe\doublerulesep` in LaTeX. Equals 2.0 / ptPerEm.
doubleRuleSep: [0.2, 0.2, 0.2],
// The width of separator lines in {array} environments. From
// `\showthe\arrayrulewidth` in LaTeX. Equals 0.4 / ptPerEm.
arrayRuleWidth: [0.04, 0.04, 0.04],
// Two values from LaTeX source2e:
fboxsep: [0.3, 0.3, 0.3], // 3 pt / ptPerEm
fboxrule: [0.04, 0.04, 0.04], // 0.4 pt / ptPerEm
};
// This map contains a mapping from font name and character code to character
// metrics, including height, depth, italic correction, and skew (kern from the
// character to the corresponding \skewchar)
// This map is generated via `make metrics`. It should not be changed manually.
import metricMap from "./fontMetricsData";
// These are very rough approximations. We default to Times New Roman which
// should have Latin-1 and Cyrillic characters, but may not depending on the
// operating system. The metrics do not account for extra height from the
// accents. In the case of Cyrillic characters which have both ascenders and
// descenders we prefer approximations with ascenders, primarily to prevent
// the fraction bar or root line from intersecting the glyph.
// TODO(kevinb) allow union of multiple glyph metrics for better accuracy.
const extraCharacterMap: Record<string, string> = {
// Latin-1
'Å': 'A',
'Ð': 'D',
'Þ': 'o',
'å': 'a',
'ð': 'd',
'þ': 'o',
// Cyrillic
'А': 'A',
'Б': 'B',
'В': 'B',
'Г': 'F',
'Д': 'A',
'Е': 'E',
'Ж': 'K',
'З': '3',
'И': 'N',
'Й': 'N',
'К': 'K',
'Л': 'N',
'М': 'M',
'Н': 'H',
'О': 'O',
'П': 'N',
'Р': 'P',
'С': 'C',
'Т': 'T',
'У': 'y',
'Ф': 'O',
'Х': 'X',
'Ц': 'U',
'Ч': 'h',
'Ш': 'W',
'Щ': 'W',
'Ъ': 'B',
'Ы': 'X',
'Ь': 'B',
'Э': '3',
'Ю': 'X',
'Я': 'R',
'а': 'a',
'б': 'b',
'в': 'a',
'г': 'r',
'д': 'y',
'е': 'e',
'ж': 'm',
'з': 'e',
'и': 'n',
'й': 'n',
'к': 'n',
'л': 'n',
'м': 'm',
'н': 'n',
'о': 'o',
'п': 'n',
'р': 'p',
'с': 'c',
'т': 'o',
'у': 'y',
'ф': 'b',
'х': 'x',
'ц': 'n',
'ч': 'n',
'ш': 'w',
'щ': 'w',
'ъ': 'a',
'ы': 'm',
'ь': 'a',
'э': 'e',
'ю': 'm',
'я': 'r',
};
export type CharacterMetrics = {
depth: number;
height: number;
italic: number;
skew: number;
width: number;
};
export type MetricMap = {
[key: string]: [number, number, number, number, number];
};
/**
* This function adds new font metrics to default metricMap
* It can also override existing metrics
*/
export function setFontMetrics(fontName: string, metrics: MetricMap) {
metricMap[fontName] = metrics;
}
/**
* This function is a convenience function for looking up information in the
* metricMap table. It takes a character as a string, and a font.
*
* Note: the `width` property may be undefined if fontMetricsData.js wasn't
* built using `Make extended_metrics`.
*/
export function getCharacterMetrics(
character: string,
font: string,
mode: Mode,
): CharacterMetrics | null | undefined {
if (!metricMap[font]) {
throw new Error(`Font metrics not found for font: ${font}.`);
}
let ch = character.charCodeAt(0);
let metrics = metricMap[font][ch];
if (!metrics && character[0] in extraCharacterMap) {
ch = extraCharacterMap[character[0]].charCodeAt(0);
metrics = metricMap[font][ch];
}
if (!metrics && mode === 'text') {
// We don't typically have font metrics for Asian scripts.
// But since we support them in text mode, we need to return
// some sort of metrics.
// So if the character is in a script we support but we
// don't have metrics for it, just use the metrics for
// the Latin capital letter M. This is close enough because
// we (currently) only care about the height of the glyph
// not its width.
if (supportedCodepoint(ch)) {
metrics = metricMap[font][77]; // 77 is the charcode for 'M'
}
}
if (metrics) {
return {
depth: metrics[0],
height: metrics[1],
italic: metrics[2],
skew: metrics[3],
width: metrics[4],
};
}
}
type FontSizeIndex = 0 | 1 | 2;
export type FontMetrics = {
cssEmPerMu: number;
[key: string]: number;
};
const fontMetricsBySizeIndex: Partial<Record<FontSizeIndex, FontMetrics>> = {};
/**
* Get the font metrics for a given size.
*/
export function getGlobalMetrics(size: number): FontMetrics {
let sizeIndex: FontSizeIndex;
if (size >= 5) {
sizeIndex = 0;
} else if (size >= 3) {
sizeIndex = 1;
} else {
sizeIndex = 2;
}
if (!fontMetricsBySizeIndex[sizeIndex]) {
const metrics: FontMetrics = fontMetricsBySizeIndex[sizeIndex] = {
cssEmPerMu: sigmasAndXis.quad[sizeIndex] / 18,
};
for (const key in sigmasAndXis) {
if (sigmasAndXis.hasOwnProperty(key)) {
metrics[key] = sigmasAndXis[key][sizeIndex];
}
}
}
return fontMetricsBySizeIndex[sizeIndex]!;
}

2077
frontend/node_modules/katex/src/fontMetricsData.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

139
frontend/node_modules/katex/src/fonts/Makefile generated vendored Normal file
View File

@@ -0,0 +1,139 @@
#!gmake
#
# Version: Apache License 2.0
#
# Copyright (c) 2013 MathJax Project
# Copyright (c) 2013 The MathJax Consortium
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
CUSTOM=custom.cfg
-include $(CUSTOM)
MFTRACE_MODIFIED=lib/mftrace-modified
all: config fonts
$(CUSTOM):
@cp default.cfg $(CUSTOM);
$(CUSTOM).pl: $(CUSTOM)
@echo "Creating Perl config file..."
@cp $(CUSTOM) $(CUSTOM).pl
@echo >> $(CUSTOM).pl # ensure that the config file ends by a new line
@echo "MFTRACE_PATH=`$(WHICH) $(MFTRACE)`" >> $(CUSTOM).pl
@$(SED) -i "s|^\([A-Z_0-9]*\)=\(.*\)|$$\1='\2';|" $(CUSTOM).pl
@echo "1;" >> $(CUSTOM).pl
.PHONY: config
config: $(CUSTOM).pl
blacker: $(MFTRACE_MODIFIED)
$(MFTRACE_MODIFIED):
$(PERL) -I. makeBlacker 15 # values between 10 and 30 seem best
pfa: $(MFTRACE_MODIFIED)
@echo "cmr10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmr10
@echo "cmmi10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --encoding $(TETEXENCODING)/aae443f0.enc --simplify cmmi10
@echo "cmsy10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --encoding $(TETEXENCODING)/10037936.enc --simplify cmsy10
@echo "cmex10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmex10
@echo "cmbx10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmbx10
@echo "cmbxti10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmbxti10
@echo "cmti10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmti10
@echo "msam10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify --encoding $(TETEXENCODING)/10037936.enc msam10
@echo "msbm10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify --encoding $(TETEXENCODING)/10037936.enc msbm10
@echo "cmmib10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --encoding $(TETEXENCODING)/aae443f0.enc --simplify cmmib10
@echo "cmbsy10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --encoding $(TETEXENCODING)/10037936.enc --simplify cmbsy10
@echo "cmtt10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmtt10
@echo "cmss10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmss10
@echo "cmssi10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmssi10
@echo "cmssbx10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify cmssbx10
@echo "eufm10"
cp "`$(KPSEWHICH) eufm10.pfb`" eufm10.pfb
@echo "eufb10"
cp "`$(KPSEWHICH) eufb10.pfb`" eufb10.pfb
# echo "eusm10"
# $(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify eusm10
# echo "eusb10"
# $(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify eusb10
@echo "rsfs10"
$(PYTHON) $(MFTRACE_MODIFIED) --magnification 1000 --simplify --encoding $(BASEENCODING)/tex256.enc rsfs10
mkdir -p pfa
rm -f pfa/*
mv *.pfa pfa
mv *.pfb pfa
ff: pfa
mkdir -p ff otf
rm -f ff/* otf/*
$(PERL) -I. makeFF
.PHONY: fonts
fonts: ff
mkdir -p ttf woff woff2
rm -f ttf/* woff/* woff2/*
@for file in `ls ff/*.ff | $(SED) 's|ff/\(.*\)\.ff|\1|'`; do \
echo ""; \
echo $$file; \
$(FONTFORGE) -lang=ff -script ff/$$file.ff; \
\
echo "Hinting $$file"; \
if echo "$$file" | $(GREP) -q -e "Size[1-4]" -e "Typewriter"; then \
$(TTFAUTOHINT) -f none -S --windows-compatibility --symbol ttf/$$file.ttf ttf/$$file.ttf.hinted; \
else \
$(TTFAUTOHINT) -f none -S --windows-compatibility ttf/$$file.ttf ttf/$$file.ttf.hinted; \
fi; \
mv ttf/$$file.ttf.hinted ttf/$$file.ttf; \
\
echo "Generating $$file..."; \
$(PYTHON) generate_fonts.py ttf/$$file.ttf; \
done
clean:
rm -f $(CUSTOM).pl
rm -f $(MFTRACE_MODIFIED) lib/blacker.mf
rm -rf pfa ff otf ttf woff woff2

20
frontend/node_modules/katex/src/fonts/default.cfg generated vendored Normal file
View File

@@ -0,0 +1,20 @@
# Note: paths should be absolute, unless they point to programs in your $PATH.
##### Standard programs #####
GREP=grep
PERL=perl
PYTHON=python3
SED=sed
WHICH=which
KPSEWHICH=kpsewhich
##### Font tools #####
# Most of the tools below are standard and should be available from your package manager.
FONTFORGE=fontforge
MFTRACE=mftrace
TTX=ttx
TTFAUTOHINT=ttfautohint
##### TeXLive Encoding
TETEXENCODING=/usr/share/texlive/texmf-texlive/fonts/enc/dvips/tetex/
BASEENCODING=/usr/share/texlive/texmf-texlive/fonts/enc/dvips/base/

234
frontend/node_modules/katex/src/fonts/lib/Space.ttx generated vendored Normal file
View File

@@ -0,0 +1,234 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="OTTO" ttLibVersion="3.28">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="space"/>
<GlyphID id="2" name="uni00A0"/>
</GlyphOrder>
<head>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="1.0"/>
<checkSumAdjustment value="0xa98c8795"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Sun Jan 24 23:04:46 2010"/>
<modified value="Sat May 14 12:26:22 2011"/>
<xMin value="50"/>
<yMin value="0"/>
<xMax value="200"/>
<yMax value="533"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="8"/>
<fontDirectionHint value="2"/>
<indexToLocFormat value="0"/>
<glyphDataFormat value="0"/>
</head>
<hhea>
<tableVersion value="0x00010000"/>
<ascent value="800"/>
<descent value="-200"/>
<lineGap value="90"/>
<advanceWidthMax value="250"/>
<minLeftSideBearing value="50"/>
<minRightSideBearing value="50"/>
<xMaxExtent value="200"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
<numberOfHMetrics value="1"/>
</hhea>
<maxp>
<tableVersion value="0x5000"/>
<numGlyphs value="3"/>
</maxp>
<OS_2>
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="2"/>
<xAvgCharWidth value="225"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000000"/>
<ySubscriptXSize value="650"/>
<ySubscriptYSize value="700"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="140"/>
<ySuperscriptXSize value="650"/>
<ySuperscriptYSize value="700"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="480"/>
<yStrikeoutSize value="49"/>
<yStrikeoutPosition value="258"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="0"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="10000000 00000000 00000000 11101111"/>
<ulUnicodeRange2 value="00010000 00000000 11101100 11101101"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="PfEd"/>
<fsSelection value="00000000 01000000"/>
<usFirstCharIndex value="32"/>
<usLastCharIndex value="160"/>
<sTypoAscender value="800"/>
<sTypoDescender value="-200"/>
<sTypoLineGap value="90"/>
<usWinAscent value="533"/>
<usWinDescent value="0"/>
<ulCodePageRange1 value="00100000 00000000 00000000 10001111"/>
<ulCodePageRange2 value="01011110 00000011 00000000 00000000"/>
<sxHeight value="0"/>
<sCapHeight value="0"/>
<usDefaultChar value="32"/>
<usBreakChar value="32"/>
<usMaxContext value="1"/>
</OS_2>
<name>
<namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
Copyright (c) 2009-2010 Design Science, Inc.
Copyright (c) 2014-2018 Khan Academy
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
*NAME*
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
*WEIGHT_S*
</namerecord>
<namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
FontForge 2.0 : *NAME*-*WEIGHT*
</namerecord>
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
*NAME*-*WEIGHT*
</namerecord>
<namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
Version 1.1
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
*NAME*-*WEIGHT*
</namerecord>
<namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
Copyright (c) 2009-2010, Design Science, Inc. (&lt;www.mathjax.org&gt;)
Copyright (c) 2014-2018 Khan Academy (&lt;www.khanacademy.org&gt;),
with Reserved Font Name *NAME*.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license available with a FAQ at:
http://scripts.sil.org/OFL
</namerecord>
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
http://scripts.sil.org/OFL
</namerecord>
</name>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="0" platEncID="3" language="0">
<map code="0x20" name="space"/><!-- SPACE -->
<map code="0xa0" name="uni00A0"/><!-- NO-BREAK SPACE -->
</cmap_format_4>
<cmap_format_4 platformID="3" platEncID="1" language="0">
<map code="0x20" name="space"/><!-- SPACE -->
<map code="0xa0" name="uni00A0"/><!-- NO-BREAK SPACE -->
</cmap_format_4>
</cmap>
<post>
<formatType value="3.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="-125"/>
<underlineThickness value="50"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
</post>
<CFF>
<major value="1"/>
<minor value="0"/>
<CFFFont name="*NAME*-*WEIGHT*">
<version value="001.001"/>
<Notice value="Copyright (c) 2009-2010 Design Science, Inc., Copyright (c) 2014-2018 Khan Academy"/>
<FullName value="*NAME*-*WEIGHT*"/>
<FamilyName value="*NAME*"/>
<Weight value="*NORMAL*"/>
<isFixedPitch value="0"/>
<ItalicAngle value="0"/>
<UnderlinePosition value="-150"/>
<UnderlineThickness value="50"/>
<PaintType value="0"/>
<CharstringType value="2"/>
<FontMatrix value="0.001 0 0 0.001 0 0"/>
<FontBBox value="50 0 200 533"/>
<StrokeWidth value="0"/>
<!-- charset is dumped separately as the 'GlyphOrder' element -->
<Encoding name="StandardEncoding"/>
<Private>
<BlueScale value="0.03963"/>
<BlueShift value="0"/>
<BlueFuzz value="1"/>
<StdHW value="50"/>
<StdVW value="50"/>
<ForceBold value="0"/>
<LanguageGroup value="0"/>
<ExpansionFactor value="0.06"/>
<initialRandomSeed value="0"/>
<defaultWidthX value="250"/>
<nominalWidthX value="193"/>
</Private>
<CharStrings>
<CharString name=".notdef">
0 50 433 50 hstem
50 50 50 50 vstem
50 hmoveto
150 533 -150 hlineto
50 -483 rmoveto
433 50 -433 vlineto
endchar
</CharString>
<CharString name="space">
endchar
</CharString>
<CharString name="uni00A0">
endchar
</CharString>
</CharStrings>
</CFFFont>
<GlobalSubrs>
<!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
</GlobalSubrs>
</CFF>
<hmtx>
<mtx name=".notdef" width="250" lsb="50"/>
<mtx name="space" width="250" lsb="0"/>
<mtx name="uni00A0" width="250" lsb="0"/>
</hmtx>
</ttFont>

54
frontend/node_modules/katex/src/functions.ts generated vendored Normal file
View File

@@ -0,0 +1,54 @@
/** Include this to ensure that all functions are defined. */
import {_functions} from "./defineFunction";
const functions = _functions;
export default functions;
// TODO(kevinb): have functions return an object and call defineFunction with
// that object in this file instead of relying on side-effects.
import "./functions/accent";
import "./functions/accentunder";
import "./functions/arrow";
import "./functions/pmb";
import "./environments/cd";
import "./functions/char";
import "./functions/color";
import "./functions/cr";
import "./functions/def";
import "./functions/delimsizing";
import "./functions/enclose";
import "./functions/environment";
import "./functions/font";
import "./functions/genfrac";
import "./functions/horizBrace";
import "./functions/href";
import "./functions/hbox";
import "./functions/html";
import "./functions/htmlmathml";
import "./functions/includegraphics";
import "./functions/kern";
import "./functions/lap";
import "./functions/math";
import "./functions/mathchoice";
import "./functions/mclass";
import "./functions/op";
import "./functions/operatorname";
import "./functions/ordgroup";
import "./functions/overline";
import "./functions/phantom";
import "./functions/raisebox";
import "./functions/relax";
import "./functions/rule";
import "./functions/sizing";
import "./functions/smash";
import "./functions/sqrt";
import "./functions/styling";
import "./functions/supsub";
import "./functions/symbolsOp";
import "./functions/symbolsOrd";
import "./functions/symbolsSpacing";
import "./functions/tag";
import "./functions/text";
import "./functions/underline";
import "./functions/vcenter";
import "./functions/verb";

119
frontend/node_modules/katex/src/functions/font.ts generated vendored Normal file
View File

@@ -0,0 +1,119 @@
// TODO(kevinb): implement \\sl and \\sc
import {binrelClass} from "./mclass";
import defineFunction, {normalizeArgument} from "../defineFunction";
import {isCharacterBox} from "../utils";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import type Options from "../Options";
import type {ParseNode} from "../parseNode";
const htmlBuilder = (group: ParseNode<"font">, options: Options) => {
const font = group.font;
const newOptions = options.withFont(font);
return html.buildGroup(group.body, newOptions);
};
const mathmlBuilder = (group: ParseNode<"font">, options: Options) => {
const font = group.font;
const newOptions = options.withFont(font);
return mml.buildGroup(group.body, newOptions);
};
const fontAliases: Record<string, string> = {
"\\Bbb": "\\mathbb",
"\\bold": "\\mathbf",
"\\frak": "\\mathfrak",
"\\bm": "\\boldsymbol",
};
defineFunction({
type: "font",
names: [
// styles, except \boldsymbol defined below
"\\mathrm", "\\mathit", "\\mathbf", "\\mathnormal", "\\mathsfit",
// families
"\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf",
"\\mathtt",
// aliases, except \bm defined below
"\\Bbb", "\\bold", "\\frak",
],
props: {
numArgs: 1,
allowedInArgument: true,
},
handler: ({parser, funcName}, args) => {
const body = normalizeArgument(args[0]);
let func = funcName;
if (func in fontAliases) {
func = fontAliases[func];
}
return {
type: "font",
mode: parser.mode,
font: func.slice(1),
body,
};
},
htmlBuilder,
mathmlBuilder,
});
defineFunction({
type: "mclass",
names: ["\\boldsymbol", "\\bm"],
props: {
numArgs: 1,
},
handler: ({parser}, args) => {
const body = args[0];
// amsbsy.sty's \boldsymbol uses \binrel spacing to inherit the
// argument's bin|rel|ord status
return {
type: "mclass",
mode: parser.mode,
mclass: binrelClass(body),
body: [
{
type: "font",
mode: parser.mode,
font: "boldsymbol",
body,
},
],
isCharacterBox: isCharacterBox(body),
};
},
});
// Old font changing functions
defineFunction({
type: "font",
names: ["\\rm", "\\sf", "\\tt", "\\bf", "\\it", "\\cal"],
props: {
numArgs: 0,
allowedInText: true,
},
handler: ({parser, funcName, breakOnTokenText}, args) => {
const {mode} = parser;
const body = parser.parseExpression(true, breakOnTokenText);
const style = `math${funcName.slice(1)}`;
return {
type: "font",
mode: mode,
font: style,
body: {
type: "ordgroup",
mode: parser.mode,
body,
},
};
},
htmlBuilder,
mathmlBuilder,
});

105
frontend/node_modules/katex/src/functions/html.ts generated vendored Normal file
View File

@@ -0,0 +1,105 @@
import defineFunction, {ordargument} from "../defineFunction";
import {makeSpan} from "../buildCommon";
import {assertNodeType} from "../parseNode";
import ParseError from "../ParseError";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import type {AnyTrustContext} from "../Settings";
defineFunction({
type: "html",
names: ["\\htmlClass", "\\htmlId", "\\htmlStyle", "\\htmlData"],
props: {
numArgs: 2,
argTypes: ["raw", "original"],
allowedInText: true,
},
handler: ({parser, funcName, token}, args) => {
const value = assertNodeType(args[0], "raw").string;
const body = args[1];
if (parser.settings.strict) {
parser.settings.reportNonstrict("htmlExtension",
"HTML extension is disabled on strict mode");
}
let trustContext: AnyTrustContext;
const attributes: Record<string, string> = {};
switch (funcName) {
case "\\htmlClass":
attributes.class = value;
trustContext = {
command: "\\htmlClass",
class: value,
};
break;
case "\\htmlId":
attributes.id = value;
trustContext = {
command: "\\htmlId",
id: value,
};
break;
case "\\htmlStyle":
attributes.style = value;
trustContext = {
command: "\\htmlStyle",
style: value,
};
break;
case "\\htmlData": {
const data = value.split(",");
for (let i = 0; i < data.length; i++) {
const item = data[i];
const firstEquals = item.indexOf("=");
if (firstEquals < 0) {
throw new ParseError(`\\htmlData key/value '${item}'` +
` missing equals sign`);
}
const key = item.slice(0, firstEquals);
const value = item.slice(firstEquals + 1);
attributes["data-" + key.trim()] = value;
}
trustContext = {
command: "\\htmlData",
attributes,
};
break;
}
default:
throw new Error("Unrecognized html command");
}
if (!parser.settings.isTrusted(trustContext)) {
return parser.formatUnsupportedCmd(funcName);
}
return {
type: "html",
mode: parser.mode,
attributes,
body: ordargument(body),
};
},
htmlBuilder: (group, options) => {
const elements = html.buildExpression(group.body, options, false);
const classes = ["enclosing"];
if (group.attributes.class) {
classes.push(...group.attributes.class.trim().split(/\s+/));
}
const span = makeSpan(classes, elements, options);
for (const attr in group.attributes) {
if (attr !== "class" && group.attributes.hasOwnProperty(attr)) {
span.setAttribute(attr, group.attributes[attr]);
}
}
return span;
},
mathmlBuilder: (group, options) => {
return mml.buildExpressionRow(group.body, options);
},
});

55
frontend/node_modules/katex/src/functions/kern.ts generated vendored Normal file
View File

@@ -0,0 +1,55 @@
// Horizontal spacing commands
import defineFunction from "../defineFunction";
import {makeGlue} from "../buildCommon";
import {SpaceNode} from "../mathMLTree";
import {calculateSize} from "../units";
import {assertNodeType} from "../parseNode";
// TODO: \hskip and \mskip should support plus and minus in lengths
defineFunction({
type: "kern",
names: ["\\kern", "\\mkern", "\\hskip", "\\mskip"],
props: {
numArgs: 1,
argTypes: ["size"],
primitive: true,
allowedInText: true,
},
handler({parser, funcName}, args) {
const size = assertNodeType(args[0], "size");
if (parser.settings.strict) {
const mathFunction = (funcName[1] === 'm'); // \mkern, \mskip
const muUnit = (size.value.unit === 'mu');
if (mathFunction) {
if (!muUnit) {
parser.settings.reportNonstrict("mathVsTextUnits",
`LaTeX's ${funcName} supports only mu units, ` +
`not ${size.value.unit} units`);
}
if (parser.mode !== "math") {
parser.settings.reportNonstrict("mathVsTextUnits",
`LaTeX's ${funcName} works only in math mode`);
}
} else { // !mathFunction
if (muUnit) {
parser.settings.reportNonstrict("mathVsTextUnits",
`LaTeX's ${funcName} doesn't support mu units`);
}
}
}
return {
type: "kern",
mode: parser.mode,
dimension: size.value,
};
},
htmlBuilder(group, options) {
return makeGlue(group.dimension, options);
},
mathmlBuilder(group, options) {
const dimension = calculateSize(group.dimension, options);
return new SpaceNode(dimension);
},
});

41
frontend/node_modules/katex/src/functions/math.ts generated vendored Normal file
View File

@@ -0,0 +1,41 @@
import defineFunction from "../defineFunction";
import ParseError from "../ParseError";
// Switching from text mode back to math mode
defineFunction({
type: "styling",
names: ["\\(", "$"],
props: {
numArgs: 0,
allowedInText: true,
allowedInMath: false,
},
handler({funcName, parser}, args) {
const outerMode = parser.mode;
parser.switchMode("math");
const close = (funcName === "\\(" ? "\\)" : "$");
const body = parser.parseExpression(false, close);
parser.expect(close);
parser.switchMode(outerMode);
return {
type: "styling",
mode: parser.mode,
style: "text",
body,
};
},
});
// Check for extra closing math delimiters
defineFunction({
type: "text", // Doesn't matter what this is.
names: ["\\)", "\\]"],
props: {
numArgs: 0,
allowedInText: true,
allowedInMath: false,
},
handler(context, args) {
throw new ParseError(`Mismatched ${context.funcName}`);
},
});

165
frontend/node_modules/katex/src/functions/mclass.ts generated vendored Normal file
View File

@@ -0,0 +1,165 @@
import defineFunction, {ordargument} from "../defineFunction";
import {makeSpan} from "../buildCommon";
import {isCharacterBox} from "../utils";
import {MathNode} from "../mathMLTree";
import type {AnyParseNode} from "../parseNode";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import type Options from "../Options";
import type {ParseNode} from "../parseNode";
function htmlBuilder(group: ParseNode<"mclass">, options: Options) {
const elements = html.buildExpression(group.body, options, true);
return makeSpan([group.mclass], elements, options);
}
function mathmlBuilder(group: ParseNode<"mclass">, options: Options) {
let node: MathNode;
const inner = mml.buildExpression(group.body, options);
if (group.mclass === "minner") {
node = new MathNode("mpadded", inner);
} else if (group.mclass === "mord") {
if (group.isCharacterBox) {
node = inner[0];
node.type = "mi";
} else {
node = new MathNode("mi", inner);
}
} else {
if (group.isCharacterBox) {
node = inner[0];
node.type = "mo";
} else {
node = new 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";
} else if (group.mclass === "minner") {
node.attributes.lspace = "0.0556em"; // 1 mu is the most likely option
node.attributes.width = "+0.1111em";
}
// 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
defineFunction({
type: "mclass",
names: [
"\\mathord", "\\mathbin", "\\mathrel", "\\mathopen",
"\\mathclose", "\\mathpunct", "\\mathinner",
],
props: {
numArgs: 1,
primitive: true,
},
handler({parser, funcName}, args) {
const body = args[0];
return {
type: "mclass",
mode: parser.mode,
mclass: "m" + funcName.slice(5), // TODO(kevinb): don't prefix with 'm'
body: ordargument(body),
isCharacterBox: isCharacterBox(body),
};
},
htmlBuilder,
mathmlBuilder,
});
export const binrelClass = (arg: AnyParseNode): string => {
// \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 atom = (arg.type === "ordgroup" && arg.body.length ? arg.body[0] : arg);
if (atom.type === "atom" && (atom.family === "bin" || atom.family === "rel")) {
return "m" + atom.family;
} 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 {
type: "mclass",
mode: parser.mode,
mclass: binrelClass(args[0]),
body: ordargument(args[1]),
isCharacterBox: isCharacterBox(args[1]),
};
},
});
// Build a relation or stacked op by placing one symbol on top of another
defineFunction({
type: "mclass",
names: ["\\stackrel", "\\overset", "\\underset"],
props: {
numArgs: 2,
},
handler({parser, funcName}, args) {
const baseArg = args[1];
const shiftedArg = args[0];
let mclass;
if (funcName !== "\\stackrel") {
// LaTeX applies \binrel spacing to \overset and \underset.
mclass = binrelClass(baseArg);
} else {
mclass = "mrel"; // for \stackrel
}
const baseOp: ParseNode<"op"> = {
type: "op",
mode: baseArg.mode,
limits: true,
alwaysHandleSupSub: true,
parentIsSupSub: false,
symbol: false,
suppressBaseShift: funcName !== "\\stackrel",
body: ordargument(baseArg),
};
const supsub: ParseNode<"supsub"> = {
type: "supsub",
mode: shiftedArg.mode,
base: baseOp,
sup: funcName === "\\underset" ? null : shiftedArg,
sub: funcName === "\\underset" ? shiftedArg : null,
};
return {
type: "mclass",
mode: parser.mode,
mclass,
body: [supsub],
isCharacterBox: isCharacterBox(supsub),
};
},
htmlBuilder,
mathmlBuilder,
});

View File

@@ -0,0 +1,161 @@
import defineFunction, {ordargument} from "../defineFunction";
import defineMacro from "../defineMacro";
import {makeSpan} from "../buildCommon";
import {MathNode, newDocumentFragment, SpaceNode, TextNode} from "../mathMLTree";
import {SymbolNode} from "../domTree";
import {assembleSupSub} from "./utils/assembleSupSub";
import {assertNodeType} from "../parseNode";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction";
import type {AnyParseNode, ParseNode} from "../parseNode";
// NOTE: Unlike most `htmlBuilder`s, this one handles not only
// "operatorname", but also "supsub" since \operatorname* can
// affect super/subscripting.
export const htmlBuilder: HtmlBuilderSupSub<"operatorname"> = (grp, options) => {
// Operators are handled in the TeXbook pg. 443-444, rule 13(a).
let supGroup;
let subGroup;
let hasLimits = false;
let group: ParseNode<"operatorname">;
if (grp.type === "supsub") {
// If we have limits, supsub will pass us its group to handle. Pull
// out the superscript and subscript and set the group to the op in
// its base.
supGroup = grp.sup;
subGroup = grp.sub;
group = assertNodeType(grp.base, "operatorname");
hasLimits = true;
} else {
group = assertNodeType(grp, "operatorname");
}
let base;
if (group.body.length > 0) {
const body = group.body.map((child): AnyParseNode => {
const childText = "text" in child ? child.text : undefined;
if (typeof childText === "string") {
return {
type: "textord",
mode: child.mode,
text: childText,
};
} else {
return child;
}
});
// Consolidate function names into symbol characters.
const expression = html.buildExpression(
body, options.withFont("mathrm"), true);
for (let i = 0; i < expression.length; i++) {
const child = expression[i];
if (child instanceof SymbolNode) {
// Per amsopn package,
// change minus to hyphen and \ast to asterisk
child.text = child.text.replace(/\u2212/, "-")
.replace(/\u2217/, "*");
}
}
base = makeSpan(["mop"], expression, options);
} else {
base = makeSpan(["mop"], [], options);
}
if (hasLimits) {
return assembleSupSub(base, supGroup, subGroup, options,
options.style, 0, 0);
} else {
return base;
}
};
const mathmlBuilder: MathMLBuilder<"operatorname"> = (group, options) => {
// The steps taken here are similar to the html version.
let expression: Array<MathNode | TextNode> = mml.buildExpression(
group.body, options.withFont("mathrm"));
// Is expression a string or has it something like a fraction?
let isAllString = true; // default
for (let i = 0; i < expression.length; i++) {
const node = expression[i];
if (node instanceof SpaceNode) {
// Do nothing
} else if (node instanceof MathNode) {
switch (node.type) {
case "mi":
case "mn":
case "mspace":
case "mtext":
break; // Do nothing yet.
case "mo": {
const child = node.children[0];
if (node.children.length === 1 &&
child instanceof TextNode) {
child.text =
child.text.replace(/\u2212/, "-")
.replace(/\u2217/, "*");
} else {
isAllString = false;
}
break;
}
default:
isAllString = false;
}
} else {
isAllString = false;
}
}
if (isAllString) {
// Write a single TextNode instead of multiple nested tags.
const word = expression.map(node => node.toText()).join("");
expression = [new TextNode(word)];
}
const identifier = new MathNode("mi", expression);
identifier.setAttribute("mathvariant", "normal");
// \u2061 is the same as &ApplyFunction;
// ref: https://www.w3schools.com/charsets/ref_html_entities_a.asp
const operator = new MathNode("mo",
[mml.makeText("\u2061", "text")]);
if (group.parentIsSupSub) {
return new MathNode("mrow", [identifier, operator]);
} else {
return newDocumentFragment([identifier, operator]);
}
};
// \operatorname
// amsopn.dtx: \mathop{#1\kern\z@\operator@font#3}\newmcodes@
defineFunction({
type: "operatorname",
names: ["\\operatorname@", "\\operatornamewithlimits"],
props: {
numArgs: 1,
},
handler: ({parser, funcName}, args) => {
const body = args[0];
return {
type: "operatorname",
mode: parser.mode,
body: ordargument(body),
alwaysHandleSupSub: (funcName === "\\operatornamewithlimits"),
limits: false,
parentIsSupSub: false,
};
},
htmlBuilder,
mathmlBuilder,
});
defineMacro("\\operatorname",
"\\@ifstar\\operatornamewithlimits\\operatorname@");

124
frontend/node_modules/katex/src/functions/sqrt.ts generated vendored Normal file
View File

@@ -0,0 +1,124 @@
import defineFunction from "../defineFunction";
import {makeSpan, makeVList, wrapFragment} from "../buildCommon";
import {MathNode} from "../mathMLTree";
import {makeSqrtImage} from "../delimiter";
import Style from "../Style";
import {makeEm} from "../units";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
defineFunction({
type: "sqrt",
names: ["\\sqrt"],
props: {
numArgs: 1,
numOptionalArgs: 1,
},
handler({parser}, args, optArgs) {
const index = optArgs[0];
const body = args[0];
return {
type: "sqrt",
mode: parser.mode,
body,
index,
};
},
htmlBuilder(group, options) {
// Square roots are handled in the TeXbook pg. 443, Rule 11.
// First, we do the same steps as in overline to build the inner group
// and line
let inner = html.buildGroup(group.body, options.havingCrampedStyle());
if (inner.height === 0) {
// Render a small surd.
inner.height = options.fontMetrics().xHeight;
}
// Some groups can return document fragments. Handle those by wrapping
// them in a span.
inner = wrapFragment(inner, options);
// Calculate the minimum size for the \surd delimiter
const metrics = options.fontMetrics();
const theta = metrics.defaultRuleThickness;
let phi = theta;
if (options.style.id < Style.TEXT.id) {
phi = options.fontMetrics().xHeight;
}
// Calculate the clearance between the body and line
let lineClearance = theta + phi / 4;
const minDelimiterHeight = (inner.height + inner.depth +
lineClearance + theta);
// Create a sqrt SVG of the required minimum size
const {span: img, ruleWidth, advanceWidth} =
makeSqrtImage(minDelimiterHeight, options);
const delimDepth = img.height - ruleWidth;
// Adjust the clearance based on the delimiter size
if (delimDepth > inner.height + inner.depth + lineClearance) {
lineClearance =
(lineClearance + delimDepth - inner.height - inner.depth) / 2;
}
// Shift the sqrt image
const imgShift = img.height - inner.height - lineClearance - ruleWidth;
inner.style.paddingLeft = makeEm(advanceWidth);
// Overlay the image and the argument.
const body = makeVList({
positionType: "firstBaseline",
children: [
{type: "elem", elem: inner, wrapperClasses: ["svg-align"]},
{type: "kern", size: -(inner.height + imgShift)},
{type: "elem", elem: img},
{type: "kern", size: ruleWidth},
],
}, options);
if (!group.index) {
return makeSpan(["mord", "sqrt"], [body], options);
} else {
// Handle the optional root index
// The index is always in scriptscript style
const newOptions = options.havingStyle(Style.SCRIPTSCRIPT);
const rootm = html.buildGroup(group.index, newOptions, options);
// The amount the index is shifted by. This is taken from the TeX
// source, in the definition of `\r@@t`.
const toShift = 0.6 * (body.height - body.depth);
// Build a VList with the superscript shifted up correctly
const rootVList = makeVList({
positionType: "shift",
positionData: -toShift,
children: [{type: "elem", elem: rootm}],
}, options);
// Add a class surrounding it so we can add on the appropriate
// kerning
const rootVListWrap = makeSpan(["root"], [rootVList]);
return makeSpan(["mord", "sqrt"],
[rootVListWrap, body], options);
}
},
mathmlBuilder(group, options) {
const {body, index} = group;
return index ?
new MathNode(
"mroot", [
mml.buildGroup(body, options),
mml.buildGroup(index, options),
]) :
new MathNode(
"msqrt", [mml.buildGroup(body, options)]);
},
});

View File

@@ -0,0 +1,72 @@
import {defineFunctionBuilders} from "../defineFunction";
import {mathsym, makeOrd, makeSpan} from "../buildCommon";
import {MathNode, TextNode} from "../mathMLTree";
import ParseError from "../ParseError";
// A map of CSS-based spacing functions to their CSS class.
const cssSpace: Record<string, string> = {
"\\nobreak": "nobreak",
"\\allowbreak": "allowbreak",
};
// A lookup table to determine whether a spacing function/symbol should be
// treated like a regular space character. If a symbol or command is a key
// in this table, then it should be a regular space character. Furthermore,
// the associated value may have a `className` specifying an extra CSS class
// to add to the created `span`.
const regularSpace: Record<string, {className?: string}> = {
" ": {},
"\\ ": {},
"~": {
className: "nobreak",
},
"\\space": {},
"\\nobreakspace": {
className: "nobreak",
},
};
// ParseNode<"spacing"> created in Parser.js from the "spacing" symbol Groups in
// src/symbols.js.
defineFunctionBuilders({
type: "spacing",
htmlBuilder(group, options) {
if (regularSpace.hasOwnProperty(group.text)) {
const className = regularSpace[group.text].className || "";
// Spaces are generated by adding an actual space. Each of these
// things has an entry in the symbols table, so these will be turned
// into appropriate outputs.
if (group.mode === "text") {
const ord = makeOrd(group, options, "textord");
ord.classes.push(className);
return ord;
} else {
return makeSpan(["mspace", className],
[mathsym(group.text, group.mode, options)],
options);
}
} else if (cssSpace.hasOwnProperty(group.text)) {
// Spaces based on just a CSS class.
return makeSpan(
["mspace", cssSpace[group.text]],
[], options);
} else {
throw new ParseError(`Unknown type of space "${group.text}"`);
}
},
mathmlBuilder(group, options) {
let node;
if (regularSpace.hasOwnProperty(group.text)) {
node = new MathNode(
"mtext", [new TextNode("\u00a0")]);
} else if (cssSpace.hasOwnProperty(group.text)) {
// CSS-based MathML spaces (\nobreak, \allowbreak) are ignored
return new MathNode("mspace");
} else {
throw new ParseError(`Unknown type of space "${group.text}"`);
}
return node;
},
});

77
frontend/node_modules/katex/src/functions/text.ts generated vendored Normal file
View File

@@ -0,0 +1,77 @@
import defineFunction, {ordargument} from "../defineFunction";
import {makeSpan} from "../buildCommon";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import type Options from "../Options";
import type {ParseNode} from "../parseNode";
// Non-mathy text, possibly in a font
const textFontFamilies: Record<string, string | undefined> = {
"\\text": undefined, "\\textrm": "textrm", "\\textsf": "textsf",
"\\texttt": "texttt", "\\textnormal": "textrm",
};
const textFontWeights: Record<string, "textbf" | "textmd"> = {
"\\textbf": "textbf",
"\\textmd": "textmd",
};
const textFontShapes: Record<string, "textit" | "textup"> = {
"\\textit": "textit",
"\\textup": "textup",
};
const optionsWithFont = (group: ParseNode<"text">, options: Options): Options => {
const font = group.font;
// Checks if the argument is a font family or a font style.
if (!font) {
return options;
} else if (textFontFamilies[font]) {
return options.withTextFontFamily(textFontFamilies[font]);
} else if (textFontWeights[font]) {
return options.withTextFontWeight(textFontWeights[font]);
} else if (font === "\\emph") {
return options.fontShape === "textit" ?
options.withTextFontShape("textup") :
options.withTextFontShape("textit");
}
return options.withTextFontShape(textFontShapes[font] as "textit" | "textup");
};
defineFunction({
type: "text",
names: [
// Font families
"\\text", "\\textrm", "\\textsf", "\\texttt", "\\textnormal",
// Font weights
"\\textbf", "\\textmd",
// Font Shapes
"\\textit", "\\textup", "\\emph",
],
props: {
numArgs: 1,
argTypes: ["text"],
allowedInArgument: true,
allowedInText: true,
},
handler({parser, funcName}, args) {
const body = args[0];
return {
type: "text",
mode: parser.mode,
body: ordargument(body),
font: funcName,
};
},
htmlBuilder(group, options) {
const newOptions = optionsWithFont(group, options);
const inner = html.buildExpression(group.body, newOptions, true);
return makeSpan(["mord", "text"], inner, newOptions);
},
mathmlBuilder(group, options) {
const newOptions = optionsWithFont(group, options);
return mml.buildExpressionRow(group.body, newOptions);
},
});

259
frontend/node_modules/katex/src/mathMLTree.ts generated vendored Normal file
View File

@@ -0,0 +1,259 @@
/**
* These objects store data about MathML nodes. This is the MathML equivalent
* of the types in domTree.js. Since MathML handles its own rendering, and
* since we're mainly using MathML to improve accessibility, we don't manage
* any of the styling state that the plain DOM nodes do.
*
* The `toNode` and `toMarkup` functions work similarly to how they do in
* domTree.js, creating namespaced DOM nodes and HTML text markup respectively.
*/
import {escape} from "./utils";
import {DocumentFragment} from "./tree";
import {createClass} from "./domTree";
import {makeEm} from "./units";
import type {VirtualNode} from "./tree";
/**
* MathML node types used in KaTeX. For a complete list of MathML nodes, see
* https://developer.mozilla.org/en-US/docs/Web/MathML/Element.
*/
export type MathNodeType =
"math" | "annotation" | "semantics" |
"mtext" | "mn" | "mo" | "mi" | "mspace" |
"mover" | "munder" | "munderover" | "msup" | "msub" | "msubsup" |
"mfrac" | "mroot" | "msqrt" |
"mtable" | "mtr" | "mtd" | "mlabeledtr" |
"mrow" | "menclose" |
"mstyle" | "mpadded" | "mphantom" | "mglyph";
export interface MathDomNode extends VirtualNode {
toText(): string;
}
export type documentFragment = DocumentFragment<MathDomNode>;
export function newDocumentFragment(
children: ReadonlyArray<MathDomNode>
): documentFragment {
return new DocumentFragment(children);
}
/**
* This node represents a general purpose MathML node of any type. The
* constructor requires the type of node to create (for example, `"mo"` or
* `"mspace"`, corresponding to `<mo>` and `<mspace>` tags).
*/
export class MathNode implements MathDomNode {
type: MathNodeType;
attributes: Record<string, string>;
children: MathDomNode[];
classes: string[];
constructor(
type: MathNodeType,
children?: MathDomNode[],
classes?: string[]
) {
this.type = type;
this.attributes = {};
this.children = children || [];
this.classes = classes || [];
}
/**
* Sets an attribute on a MathML node. MathML depends on attributes to convey a
* semantic content, so this is used heavily.
*/
setAttribute(name: string, value: string) {
this.attributes[name] = value;
}
/**
* Gets an attribute on a MathML node.
*/
getAttribute(name: string): string {
return this.attributes[name];
}
/**
* Converts the math node into a MathML-namespaced DOM element.
*/
toNode(): Node {
const node = document.createElementNS(
"http://www.w3.org/1998/Math/MathML", this.type);
for (const attr in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
node.setAttribute(attr, this.attributes[attr]);
}
}
if (this.classes.length > 0) {
node.className = createClass(this.classes);
}
for (let i = 0; i < this.children.length; i++) {
// Combine multiple TextNodes into one TextNode, to prevent
// screen readers from reading each as a separate word [#3995]
if (this.children[i] instanceof TextNode &&
this.children[i + 1] instanceof TextNode) {
let text = this.children[i].toText() + this.children[++i].toText();
while (this.children[i + 1] instanceof TextNode) {
text += this.children[++i].toText();
}
node.appendChild(new TextNode(text).toNode());
} else {
node.appendChild(this.children[i].toNode());
}
}
return node;
}
/**
* Converts the math node into an HTML markup string.
*/
toMarkup(): string {
let markup = "<" + this.type;
// Add the attributes
for (const attr in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
markup += " " + attr + "=\"";
markup += escape(this.attributes[attr]);
markup += "\"";
}
}
if (this.classes.length > 0) {
markup += ` class ="${escape(createClass(this.classes))}"`;
}
markup += ">";
for (let i = 0; i < this.children.length; i++) {
markup += this.children[i].toMarkup();
}
markup += "</" + this.type + ">";
return markup;
}
/**
* Converts the math node into a string, similar to innerText, but escaped.
*/
toText(): string {
return this.children.map(child => child.toText()).join("");
}
}
/**
* This node represents a piece of text.
*/
export class TextNode implements MathDomNode {
text: string;
constructor(text: string) {
this.text = text;
}
/**
* Converts the text node into a DOM text node.
*/
toNode(): Node {
return document.createTextNode(this.text);
}
/**
* Converts the text node into escaped HTML markup
* (representing the text itself).
*/
toMarkup(): string {
return escape(this.toText());
}
/**
* Converts the text node into a string
* (representing the text itself).
*/
toText(): string {
return this.text;
}
}
/**
* This node represents a space, but may render as <mspace.../> or as text,
* depending on the width.
*/
export class SpaceNode implements MathDomNode {
width: number;
character: string | null | undefined;
/**
* Create a Space node with width given in CSS ems.
*/
constructor(width: number) {
this.width = width;
// See https://www.w3.org/TR/2000/WD-MathML2-20000328/chapter6.html
// for a table of space-like characters. We use Unicode
// representations instead of &LongNames; as it's not clear how to
// make the latter via document.createTextNode.
if (width >= 0.05555 && width <= 0.05556) {
this.character = "\u200a"; // &VeryThinSpace;
} else if (width >= 0.1666 && width <= 0.1667) {
this.character = "\u2009"; // &ThinSpace;
} else if (width >= 0.2222 && width <= 0.2223) {
this.character = "\u2005"; // &MediumSpace;
} else if (width >= 0.2777 && width <= 0.2778) {
this.character = "\u2005\u200a"; // &ThickSpace;
} else if (width >= -0.05556 && width <= -0.05555) {
this.character = "\u200a\u2063"; // &NegativeVeryThinSpace;
} else if (width >= -0.1667 && width <= -0.1666) {
this.character = "\u2009\u2063"; // &NegativeThinSpace;
} else if (width >= -0.2223 && width <= -0.2222) {
this.character = "\u205f\u2063"; // &NegativeMediumSpace;
} else if (width >= -0.2778 && width <= -0.2777) {
this.character = "\u2005\u2063"; // &NegativeThickSpace;
} else {
this.character = null;
}
}
/**
* Converts the math node into a MathML-namespaced DOM element.
*/
toNode(): Node {
if (this.character) {
return document.createTextNode(this.character);
} else {
const node = document.createElementNS(
"http://www.w3.org/1998/Math/MathML", "mspace");
node.setAttribute("width", makeEm(this.width));
return node;
}
}
/**
* Converts the math node into an HTML markup string.
*/
toMarkup(): string {
if (this.character) {
return `<mtext>${this.character}</mtext>`;
} else {
return `<mspace width="${makeEm(this.width)}"/>`;
}
}
/**
* Converts the math node into a string, similar to innerText.
*/
toText(): string {
if (this.character) {
return this.character;
} else {
return " ";
}
}
}

28
frontend/node_modules/katex/src/metrics/format_json.py generated vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
import sys
import json
props = ['depth', 'height', 'italic', 'skew']
if len(sys.argv) > 1:
if sys.argv[1] == '--width':
props.append('width')
data = json.load(sys.stdin)
sys.stdout.write(
"// This file is GENERATED by buildMetrics.sh. DO NOT MODIFY.\n")
sep = "export default {\n "
for font in sorted(data):
sys.stdout.write(sep + json.dumps(font))
sep = ": {\n "
for glyph in sorted(data[font], key=int):
sys.stdout.write(sep + json.dumps(glyph) + ": ")
values = [value if value != 0.0 else 0 for value in
[data[font][glyph][key] for key in props]]
sys.stdout.write(json.dumps(values))
sep = ",\n "
sep = ",\n },\n "
sys.stdout.write(",\n },\n};\n")

108
frontend/node_modules/katex/src/unicodeSupOrSub.ts generated vendored Normal file
View File

@@ -0,0 +1,108 @@
// Helpers for Parser.js handling of Unicode (sub|super)script characters.
export const unicodeSubRegEx = /^[₊₋₌₍₎₀₁₂₃₄₅₆₇₈₉ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ]/;
export const uSubsAndSups: Readonly<Record<string, string>> = Object.freeze({
'₊': '+',
'₋': '-',
'₌': '=',
'₍': '(',
'₎': ')',
'₀': '0',
'₁': '1',
'₂': '2',
'₃': '3',
'₄': '4',
'₅': '5',
'₆': '6',
'₇': '7',
'₈': '8',
'₉': '9',
'\u2090': 'a',
'\u2091': 'e',
'\u2095': 'h',
'\u1D62': 'i',
'\u2C7C': 'j',
'\u2096': 'k',
'\u2097': 'l',
'\u2098': 'm',
'\u2099': 'n',
'\u2092': 'o',
'\u209A': 'p',
'\u1D63': 'r',
'\u209B': 's',
'\u209C': 't',
'\u1D64': 'u',
'\u1D65': 'v',
'\u2093': 'x',
'\u1D66': 'β',
'\u1D67': 'γ',
'\u1D68': 'ρ',
'\u1D69': '\u03d5',
'\u1D6A': 'χ',
'⁺': '+',
'⁻': '-',
'⁼': '=',
'⁽': '(',
'⁾': ')',
'⁰': '0',
'¹': '1',
'²': '2',
'³': '3',
'⁴': '4',
'⁵': '5',
'⁶': '6',
'⁷': '7',
'⁸': '8',
'⁹': '9',
'\u1D2C': 'A',
'\u1D2E': 'B',
'\u1D30': 'D',
'\u1D31': 'E',
'\u1D33': 'G',
'\u1D34': 'H',
'\u1D35': 'I',
'\u1D36': 'J',
'\u1D37': 'K',
'\u1D38': 'L',
'\u1D39': 'M',
'\u1D3A': 'N',
'\u1D3C': 'O',
'\u1D3E': 'P',
'\u1D3F': 'R',
'\u1D40': 'T',
'\u1D41': 'U',
'\u2C7D': 'V',
'\u1D42': 'W',
'\u1D43': 'a',
'\u1D47': 'b',
'\u1D9C': 'c',
'\u1D48': 'd',
'\u1D49': 'e',
'\u1DA0': 'f',
'\u1D4D': 'g',
'\u02B0': 'h',
'\u2071': 'i',
'\u02B2': 'j',
'\u1D4F': 'k',
'\u02E1': 'l',
'\u1D50': 'm',
'\u207F': 'n',
'\u1D52': 'o',
'\u1D56': 'p',
'\u02B3': 'r',
'\u02E2': 's',
'\u1D57': 't',
'\u1D58': 'u',
'\u1D5B': 'v',
'\u02B7': 'w',
'\u02E3': 'x',
'\u02B8': 'y',
'\u1DBB': 'z',
'\u1D5D': 'β',
'\u1D5E': 'γ',
'\u1D5F': 'δ',
'\u1D60': '\u03d5',
'\u1D61': 'χ',
'\u1DBF': 'θ',
});