mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-11 22:18:41 +00:00
* Initial webpack config. Moving to ES6 modules. Some module cleanup. * WIP * WIP * Removing commented out code. * Removing old deps. * Removing the build script (used for testing). * Working tests. * Switching to node api over cli. * Updating per comments. Still need to fix server.js to properly run the selenium tests. * Cleaning up the config. * More cleanup. * Bringing back server.js for selenium tests. * Bringing back old dependencies. * Adding back eslint rules for webpack config. Final cleanup for webpack config. * Pointing to correct pre-existing module versions. Adding some extra logic to server.js to ensure it gets transpiled properly. * Getting make build to work again. Updating package.json with some shortcut scripts. * Resolving conflict. * Reverting back to commonjs modules. * Removing extra spaces in babelrc
319 lines
12 KiB
JavaScript
319 lines
12 KiB
JavaScript
/**
|
|
* This file provides support to buildMathML.js and buildHTML.js
|
|
* for stretchy wide elements rendered from SVG files
|
|
* and other CSS trickery.
|
|
*/
|
|
|
|
import domTree from "./domTree";
|
|
import buildCommon from "./buildCommon";
|
|
import mathMLTree from "./mathMLTree";
|
|
import utils from "./utils";
|
|
|
|
const stretchyCodePoint = {
|
|
widehat: "^",
|
|
widetilde: "~",
|
|
undertilde: "~",
|
|
overleftarrow: "\u2190",
|
|
underleftarrow: "\u2190",
|
|
xleftarrow: "\u2190",
|
|
overrightarrow: "\u2192",
|
|
underrightarrow: "\u2192",
|
|
xrightarrow: "\u2192",
|
|
underbrace: "\u23b5",
|
|
overbrace: "\u23de",
|
|
overleftrightarrow: "\u2194",
|
|
underleftrightarrow: "\u2194",
|
|
xleftrightarrow: "\u2194",
|
|
Overrightarrow: "\u21d2",
|
|
xRightarrow: "\u21d2",
|
|
overleftharpoon: "\u21bc",
|
|
xleftharpoonup: "\u21bc",
|
|
overrightharpoon: "\u21c0",
|
|
xrightharpoonup: "\u21c0",
|
|
xLeftarrow: "\u21d0",
|
|
xLeftrightarrow: "\u21d4",
|
|
xhookleftarrow: "\u21a9",
|
|
xhookrightarrow: "\u21aa",
|
|
xmapsto: "\u21a6",
|
|
xrightharpoondown: "\u21c1",
|
|
xleftharpoondown: "\u21bd",
|
|
xrightleftharpoons: "\u21cc",
|
|
xleftrightharpoons: "\u21cb",
|
|
xtwoheadleftarrow: "\u219e",
|
|
xtwoheadrightarrow: "\u21a0",
|
|
xLongequal: "=",
|
|
xtofrom: "\u21c4",
|
|
};
|
|
|
|
const mathMLnode = function(label) {
|
|
const node = new mathMLTree.MathNode(
|
|
"mo", [new mathMLTree.TextNode(stretchyCodePoint[label.substr(1)])]);
|
|
node.setAttribute("stretchy", "true");
|
|
return node;
|
|
};
|
|
|
|
// Many of the KaTeX SVG images have been adapted from glyphs in KaTeX fonts.
|
|
// Copyright (c) 2009-2010, Design Science, Inc. (<www.mathjax.org>)
|
|
// Copyright (c) 2014-2017 Khan Academy (<www.khanacademy.org>)
|
|
// Licensed under the SIL Open Font License, Version 1.1.
|
|
// See \nhttp://scripts.sil.org/OFL
|
|
|
|
// Nested SVGs
|
|
// Many of the KaTeX SVG images contain a nested SVG. This is done to
|
|
// achieve a stretchy image while avoiding distortion of arrowheads or
|
|
// brace corners.
|
|
|
|
// The inner SVG typically contains a very long (400 em) arrow.
|
|
|
|
// The outer SVG acts like a window that exposes only part of the inner SVG.
|
|
// The outer SVG will grow or shrink to match the dimensions set by CSS.
|
|
|
|
// The inner SVG always has a longer, thinner aspect ratio than the outer
|
|
// SVG. After the inner SVG fills 100% of the height of the outer SVG,
|
|
// there is a long arrow shaft left over. That left-over shaft is not shown.
|
|
// Instead, it is sliced off because the inner SVG is set to
|
|
// "preserveAspectRatio='... slice'".
|
|
|
|
// Thus, the reader sees an arrow that matches the subject matter width
|
|
// without distortion.
|
|
|
|
// Some functions, such as \cancel, need to vary their aspect ratio. These
|
|
// functions do not get the nested SVG treatment.
|
|
|
|
// Second Brush Stroke
|
|
// Low resolution monitors struggle to display images in fine detail.
|
|
// So browsers apply anti-aliasing. A long straight arrow shaft therefore
|
|
// will sometimes appear as if it has a blurred edge.
|
|
|
|
// To mitigate this, these SVG files contain a second "brush-stroke" on the
|
|
// arrow shafts. That is, a second long thin rectangular SVG path has been
|
|
// written directly on top of each arrow shaft. This reinforcement causes
|
|
// some of the screen pixels to display as black instead of the anti-aliased
|
|
// gray pixel that a single path would generate. So we get arrow shafts
|
|
// whose edges appear to be sharper.
|
|
|
|
// In the katexImagesData object just below, the dimensions all
|
|
// correspond to path geometry inside the relevant SVG.
|
|
// For example, \overrightarrow uses the same arrowhead as glyph U+2192
|
|
// from the KaTeX Main font. The scaling factor is 1000.
|
|
// That is, inside the font, that arrowhead is 522 units tall, which
|
|
// corresponds to 0.522 em inside the document.
|
|
|
|
const katexImagesData = {
|
|
// path(s), minWidth, height, align
|
|
overrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"],
|
|
overleftarrow: [["leftarrow"], 0.888, 522, "xMinYMin"],
|
|
underrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"],
|
|
underleftarrow: [["leftarrow"], 0.888, 522, "xMinYMin"],
|
|
xrightarrow: [["rightarrow"], 1.469, 522, "xMaxYMin"],
|
|
xleftarrow: [["leftarrow"], 1.469, 522, "xMinYMin"],
|
|
Overrightarrow: [["doublerightarrow"], 0.888, 560, "xMaxYMin"],
|
|
xRightarrow: [["doublerightarrow"], 1.526, 560, "xMaxYMin"],
|
|
xLeftarrow: [["doubleleftarrow"], 1.526, 560, "xMinYMin"],
|
|
overleftharpoon: [["leftharpoon"], 0.888, 522, "xMinYMin"],
|
|
xleftharpoonup: [["leftharpoon"], 0.888, 522, "xMinYMin"],
|
|
xleftharpoondown: [["leftharpoondown"], 0.888, 522, "xMinYMin"],
|
|
overrightharpoon: [["rightharpoon"], 0.888, 522, "xMaxYMin"],
|
|
xrightharpoonup: [["rightharpoon"], 0.888, 522, "xMaxYMin"],
|
|
xrightharpoondown: [["rightharpoondown"], 0.888, 522, "xMaxYMin"],
|
|
xLongequal: [["longequal"], 0.888, 334, "xMinYMin"],
|
|
xtwoheadleftarrow: [["twoheadleftarrow"], 0.888, 334, "xMinYMin"],
|
|
xtwoheadrightarrow: [["twoheadrightarrow"], 0.888, 334, "xMaxYMin"],
|
|
|
|
overleftrightarrow: [["leftarrow", "rightarrow"], 0.888, 522],
|
|
overbrace: [["leftbrace", "midbrace", "rightbrace"], 1.6, 548],
|
|
underbrace: [["leftbraceunder", "midbraceunder", "rightbraceunder"],
|
|
1.6, 548],
|
|
underleftrightarrow: [["leftarrow", "rightarrow"], 0.888, 522],
|
|
xleftrightarrow: [["leftarrow", "rightarrow"], 1.75, 522],
|
|
xLeftrightarrow: [["doubleleftarrow", "doublerightarrow"], 1.75, 560],
|
|
xrightleftharpoons: [["leftharpoondownplus", "rightharpoonplus"], 1.75, 716],
|
|
xleftrightharpoons: [["leftharpoonplus", "rightharpoondownplus"],
|
|
1.75, 716],
|
|
xhookleftarrow: [["leftarrow", "righthook"], 1.08, 522],
|
|
xhookrightarrow: [["lefthook", "rightarrow"], 1.08, 522],
|
|
overlinesegment: [["leftlinesegment", "rightlinesegment"], 0.888, 522],
|
|
underlinesegment: [["leftlinesegment", "rightlinesegment"], 0.888, 522],
|
|
overgroup: [["leftgroup", "rightgroup"], 0.888, 342],
|
|
undergroup: [["leftgroupunder", "rightgroupunder"], 0.888, 342],
|
|
xmapsto: [["leftmapsto", "rightarrow"], 1.5, 522],
|
|
xtofrom: [["leftToFrom", "rightToFrom"], 1.75, 528],
|
|
};
|
|
|
|
const groupLength = function(arg) {
|
|
if (arg.type === "ordgroup") {
|
|
return arg.value.length;
|
|
} else {
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
const svgSpan = function(group, options) {
|
|
// Create a span with inline SVG for the element.
|
|
const label = group.value.label.substr(1);
|
|
let attributes = [];
|
|
let height;
|
|
let viewBoxWidth = 400000; // default
|
|
let minWidth = 0;
|
|
let path;
|
|
let pathName;
|
|
let svgNode;
|
|
const classNames = [];
|
|
|
|
if (utils.contains(["widehat", "widetilde", "undertilde"], label)) {
|
|
// There are four SVG images available for each function.
|
|
// Choose a taller image when there are more characters.
|
|
const numChars = groupLength(group.value.base);
|
|
let viewBoxHeight;
|
|
|
|
if (numChars > 5) {
|
|
viewBoxHeight = (label === "widehat" ? 420 : 312);
|
|
viewBoxWidth = (label === "widehat" ? 2364 : 2340);
|
|
// Next get the span height, in 1000 ems
|
|
height = (label === "widehat" ? 0.42 : 0.34);
|
|
pathName = (label === "widehat" ? "widehat" : "tilde") + "4";
|
|
} else {
|
|
const imgIndex = [1, 1, 2, 2, 3, 3][numChars];
|
|
if (label === "widehat") {
|
|
viewBoxWidth = [0, 1062, 2364, 2364, 2364][imgIndex];
|
|
viewBoxHeight = [0, 239, 300, 360, 420][imgIndex];
|
|
height = [0, 0.24, 0.3, 0.3, 0.36, 0.42][imgIndex];
|
|
pathName = "widehat" + imgIndex;
|
|
} else {
|
|
viewBoxWidth = [0, 600, 1033, 2339, 2340][imgIndex];
|
|
viewBoxHeight = [0, 260, 286, 306, 312][imgIndex];
|
|
height = [0, 0.26, 0.286, 0.3, 0.306, 0.34][imgIndex];
|
|
pathName = "tilde" + imgIndex;
|
|
}
|
|
}
|
|
path = new domTree.pathNode(pathName);
|
|
attributes.push(["width", "100%"]);
|
|
attributes.push(["height", height + "em"]);
|
|
attributes.push(["viewBox", `0 0 ${viewBoxWidth} ${viewBoxHeight}`]);
|
|
attributes.push(["preserveAspectRatio", "none"]);
|
|
|
|
svgNode = new domTree.svgNode([path], attributes);
|
|
|
|
} else {
|
|
let width;
|
|
let align;
|
|
|
|
const [paths, gWidth, vbHeight, alignOne] = katexImagesData[label];
|
|
const numSvgChildren = paths.length;
|
|
const innerSVGs = [];
|
|
height = vbHeight / 1000;
|
|
minWidth = gWidth;
|
|
|
|
for (let i = 0; i < numSvgChildren; i++) {
|
|
path = new domTree.pathNode(paths[i]);
|
|
attributes = [];
|
|
|
|
if (numSvgChildren === 1) {
|
|
width = "400em";
|
|
align = alignOne;
|
|
} else if (numSvgChildren === 2) {
|
|
// small overlap to prevent a 1 pixel gap.
|
|
if (i > 0) {
|
|
attributes.push(["x", "50%"]);
|
|
}
|
|
width = ["50.1%", "50%"][i];
|
|
align = ["xMinYMin", "xMaxYMin"][i];
|
|
} else {
|
|
// 3 inner SVGs, as in a brace
|
|
if (i > 0) {
|
|
attributes.push(["x", [null, "25%", "74.9%"][i]]);
|
|
}
|
|
width = ["25.5%", "50%", "25.1%"][i];
|
|
align = ["xMinYMin", "xMidYMin", "xMaxYMin"][i];
|
|
}
|
|
|
|
attributes.push(["width", width]);
|
|
attributes.push(["height", height + "em"]);
|
|
attributes.push(["viewBox", `0 0 ${viewBoxWidth} ${vbHeight}`]);
|
|
attributes.push(["preserveAspectRatio", align + " slice"]);
|
|
|
|
if (numSvgChildren > 1) {
|
|
innerSVGs.push(new domTree.svgNode([path], attributes));
|
|
} else {
|
|
// The single svgChild is a child of a hide-tail span, not the
|
|
// child of another svg.
|
|
svgNode = new domTree.svgNode([path], attributes);
|
|
classNames.push("hide-tail");
|
|
}
|
|
}
|
|
if (numSvgChildren > 1) {
|
|
attributes = [["width", "100%"], ["height", height + "em"]];
|
|
svgNode = new domTree.svgNode(innerSVGs, attributes);
|
|
}
|
|
}
|
|
|
|
const span = buildCommon.makeSpan(classNames, [svgNode], options);
|
|
// Note that we are returning span.depth = 0.
|
|
// Any adjustments relative to the baseline must be done in buildHTML.
|
|
span.height = height;
|
|
span.style.height = height + "em";
|
|
if (minWidth > 0) {
|
|
span.style.minWidth = minWidth + "em";
|
|
}
|
|
|
|
return span;
|
|
};
|
|
|
|
const encloseSpan = function(inner, label, pad, options) {
|
|
// Return an image span for \cancel, \bcancel, \xcancel, or \fbox
|
|
let img;
|
|
const totalHeight = inner.height + inner.depth + 2 * pad;
|
|
|
|
if (/(fbox)|(color)/.test(label)) {
|
|
img = buildCommon.makeSpan(["stretchy", label], [], options);
|
|
|
|
if (label === "fbox" && options.color) {
|
|
img.style.borderColor = options.getColor();
|
|
}
|
|
|
|
} else {
|
|
// \cancel, \bcancel, or \xcancel
|
|
// Since \cancel's SVG is inline and it omits the viewBox attribute,
|
|
// its stroke-width will not vary with span area.
|
|
|
|
let attributes = [["x1", "0"]];
|
|
const lines = [];
|
|
|
|
if (label !== "cancel") {
|
|
attributes.push(["y1", "0"]);
|
|
attributes.push(["x2", "100%"]);
|
|
attributes.push(["y2", "100%"]);
|
|
attributes.push(["stroke-width", "0.046em"]);
|
|
lines.push(new domTree.lineNode(attributes));
|
|
}
|
|
|
|
if (label === "xcancel") {
|
|
attributes = [["x1", "0"]]; // start a second line.
|
|
}
|
|
|
|
if (label !== "bcancel") {
|
|
attributes.push(["y1", "100%"]);
|
|
attributes.push(["x2", "100%"]);
|
|
attributes.push(["y2", "0"]);
|
|
attributes.push(["stroke-width", "0.046em"]);
|
|
lines.push(new domTree.lineNode(attributes));
|
|
}
|
|
|
|
attributes = [["width", "100%"], ["height", totalHeight + "em"]];
|
|
const svgNode = new domTree.svgNode(lines, attributes);
|
|
|
|
img = buildCommon.makeSpan([], [svgNode], options);
|
|
}
|
|
|
|
img.height = totalHeight;
|
|
img.style.height = totalHeight + "em";
|
|
|
|
return img;
|
|
};
|
|
|
|
export default {
|
|
encloseSpan: encloseSpan,
|
|
mathMLnode: mathMLnode,
|
|
svgSpan: svgSpan,
|
|
};
|