mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-09 21:18:40 +00:00
feat: Support \phase (#2406)
* Support \phase * Update screenshots * Update documentation * Add a11y test * Edit MathML notation to phasorangle * Move code to enclose.js Co-authored-by: Kevin Barabash <kevinb@khanacademy.org>
This commit is contained in:
@@ -534,6 +534,13 @@ const handleObject = (
|
|||||||
regionStrings.push("end strikeout");
|
regionStrings.push("end strikeout");
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
} else if (/phase/.test(tree.label)) {
|
||||||
|
buildRegion(a11yStrings, function(regionStrings) {
|
||||||
|
regionStrings.push("start phase angle");
|
||||||
|
buildA11yStrings(tree.body, regionStrings, atomType);
|
||||||
|
regionStrings.push("end phase angle");
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`KaTeX-a11y: enclose node with ${tree.label} not supported yet`);
|
`KaTeX-a11y: enclose node with ${tree.label} not supported yet`);
|
||||||
|
@@ -151,6 +151,15 @@ describe("renderA11yString", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("phase", () => {
|
||||||
|
test("\\phase", () => {
|
||||||
|
const result = renderA11yString("\\phase{a}");
|
||||||
|
expect(result).toMatchInlineSnapshot(
|
||||||
|
`"start phase angle, a, end phase angle"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("exponents", () => {
|
describe("exponents", () => {
|
||||||
test("simple exponent", () => {
|
test("simple exponent", () => {
|
||||||
const result = renderA11yString("e^x");
|
const result = renderA11yString("e^x");
|
||||||
|
@@ -816,7 +816,7 @@ use `\ce` instead|
|
|||||||
|\partial|$\partial$||
|
|\partial|$\partial$||
|
||||||
|\perp|$\perp$||
|
|\perp|$\perp$||
|
||||||
|\phantom|$\Gamma^{\phantom{i}j}_{i\phantom{j}k}$|`\Gamma^{\phantom{i}j}_{i\phantom{j}k}`|
|
|\phantom|$\Gamma^{\phantom{i}j}_{i\phantom{j}k}$|`\Gamma^{\phantom{i}j}_{i\phantom{j}k}`|
|
||||||
|\phase|<span style="color:firebrick;">Not supported</span>||
|
|\phase|$\phase{-78^\circ}$|`\phase{-78^\circ}`|
|
||||||
|\Phi|$\Phi$||
|
|\Phi|$\Phi$||
|
||||||
|\phi|$\phi$||
|
|\phi|$\phi$||
|
||||||
|\Pi|$\Pi$||
|
|\Pi|$\Pi$||
|
||||||
|
@@ -212,6 +212,7 @@ For Persian composite characters, a user-supplied [plug-in](https://github.com/
|
|||||||
|$\xcancel{ABC}$ `\xcancel{ABC}`|$\not =$ `\not =`
|
|$\xcancel{ABC}$ `\xcancel{ABC}`|$\not =$ `\not =`
|
||||||
|$\sout{abc}$ `\sout{abc}`|$\boxed{\pi=\frac c d}$ `\boxed{\pi=\frac c d}`
|
|$\sout{abc}$ `\sout{abc}`|$\boxed{\pi=\frac c d}$ `\boxed{\pi=\frac c d}`
|
||||||
|$a_{\angl n}$ `$a_{\angl n}`|$a_\angln$ `a_\angln`
|
|$a_{\angl n}$ `$a_{\angl n}`|$a_\angln$ `a_\angln`
|
||||||
|
|$\phase{-78^\circ}$`\phase{-78^\circ}` |
|
||||||
|
|
||||||
`\tag{hi} x+y^{2x}`
|
`\tag{hi} x+y^{2x}`
|
||||||
$$\tag{hi} x+y^{2x}$$
|
$$\tag{hi} x+y^{2x}$$
|
||||||
|
@@ -533,7 +533,7 @@ export class PathNode implements VirtualNode {
|
|||||||
|
|
||||||
constructor(pathName: string, alternate?: string) {
|
constructor(pathName: string, alternate?: string) {
|
||||||
this.pathName = pathName;
|
this.pathName = pathName;
|
||||||
this.alternate = alternate; // Used only for \sqrt
|
this.alternate = alternate; // Used only for \sqrt and \phase
|
||||||
}
|
}
|
||||||
|
|
||||||
toNode(): Node {
|
toNode(): Node {
|
||||||
|
@@ -4,6 +4,9 @@ import buildCommon from "../buildCommon";
|
|||||||
import mathMLTree from "../mathMLTree";
|
import mathMLTree from "../mathMLTree";
|
||||||
import utils from "../utils";
|
import utils from "../utils";
|
||||||
import stretchy from "../stretchy";
|
import stretchy from "../stretchy";
|
||||||
|
import {phasePath} from "../svgGeometry";
|
||||||
|
import {PathNode, SvgNode} from "../domTree";
|
||||||
|
import {calculateSize} from "../units";
|
||||||
import {assertNodeType} from "../parseNode";
|
import {assertNodeType} from "../parseNode";
|
||||||
|
|
||||||
import * as html from "../buildHTML";
|
import * as html from "../buildHTML";
|
||||||
@@ -11,14 +14,14 @@ import * as mml from "../buildMathML";
|
|||||||
|
|
||||||
|
|
||||||
const htmlBuilder = (group, options) => {
|
const htmlBuilder = (group, options) => {
|
||||||
// \cancel, \bcancel, \xcancel, \sout, \fbox, \colorbox, \fcolorbox
|
// \cancel, \bcancel, \xcancel, \sout, \fbox, \colorbox, \fcolorbox, \phase
|
||||||
// Some groups can return document fragments. Handle those by wrapping
|
// Some groups can return document fragments. Handle those by wrapping
|
||||||
// them in a span.
|
// them in a span.
|
||||||
const inner = buildCommon.wrapFragment(
|
const inner = buildCommon.wrapFragment(
|
||||||
html.buildGroup(group.body, options), options);
|
html.buildGroup(group.body, options), options);
|
||||||
|
|
||||||
const label = group.label.substr(1);
|
const label = group.label.substr(1);
|
||||||
const scale = options.sizeMultiplier;
|
let scale = options.sizeMultiplier;
|
||||||
let img;
|
let img;
|
||||||
let imgShift = 0;
|
let imgShift = 0;
|
||||||
|
|
||||||
@@ -34,6 +37,33 @@ const htmlBuilder = (group, options) => {
|
|||||||
img.height = options.fontMetrics().defaultRuleThickness / scale;
|
img.height = options.fontMetrics().defaultRuleThickness / scale;
|
||||||
imgShift = -0.5 * options.fontMetrics().xHeight;
|
imgShift = -0.5 * options.fontMetrics().xHeight;
|
||||||
|
|
||||||
|
} else if (label === "phase") {
|
||||||
|
// Set a couple of dimensions from the steinmetz package.
|
||||||
|
const lineWeight = calculateSize({number: 0.6, unit: "pt"}, options);
|
||||||
|
const clearance = calculateSize({number: 0.35, unit: "ex"}, options);
|
||||||
|
|
||||||
|
// Prevent size changes like \Huge from affecting line thickness
|
||||||
|
const newOptions = options.havingBaseSizing();
|
||||||
|
scale = scale / newOptions.sizeMultiplier;
|
||||||
|
|
||||||
|
const angleHeight = inner.height + inner.depth + lineWeight + clearance;
|
||||||
|
// Reserve a left pad for the angle.
|
||||||
|
inner.style.paddingLeft = (angleHeight / 2 + lineWeight) + "em";
|
||||||
|
|
||||||
|
// Create an SVG
|
||||||
|
const viewBoxHeight = Math.floor(1000 * angleHeight * scale);
|
||||||
|
const path = phasePath(viewBoxHeight);
|
||||||
|
const svgNode = new SvgNode([new PathNode("phase", path)], {
|
||||||
|
"width": "400em",
|
||||||
|
"height": `${viewBoxHeight / 1000}em`,
|
||||||
|
"viewBox": `0 0 400000 ${viewBoxHeight}`,
|
||||||
|
"preserveAspectRatio": "xMinYMin slice",
|
||||||
|
});
|
||||||
|
// Wrap it in a span with overflow: hidden.
|
||||||
|
img = buildCommon.makeSvgSpan(["hide-tail"], [svgNode], options);
|
||||||
|
img.style.height = angleHeight + "em";
|
||||||
|
imgShift = inner.depth + lineWeight + clearance;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Add horizontal padding
|
// Add horizontal padding
|
||||||
if (/cancel/.test(label)) {
|
if (/cancel/.test(label)) {
|
||||||
@@ -100,6 +130,7 @@ const htmlBuilder = (group, options) => {
|
|||||||
],
|
],
|
||||||
}, options);
|
}, options);
|
||||||
} else {
|
} else {
|
||||||
|
const classes = /cancel|phase/.test(label) ? ["svg-align"] : [];
|
||||||
vlist = buildCommon.makeVList({
|
vlist = buildCommon.makeVList({
|
||||||
positionType: "individualShift",
|
positionType: "individualShift",
|
||||||
children: [
|
children: [
|
||||||
@@ -113,7 +144,7 @@ const htmlBuilder = (group, options) => {
|
|||||||
type: "elem",
|
type: "elem",
|
||||||
elem: img,
|
elem: img,
|
||||||
shift: imgShift,
|
shift: imgShift,
|
||||||
wrapperClasses: /cancel/.test(label) ? ["svg-align"] : [],
|
wrapperClasses: classes,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}, options);
|
}, options);
|
||||||
@@ -147,6 +178,9 @@ const mathmlBuilder = (group, options) => {
|
|||||||
case "\\bcancel":
|
case "\\bcancel":
|
||||||
node.setAttribute("notation", "downdiagonalstrike");
|
node.setAttribute("notation", "downdiagonalstrike");
|
||||||
break;
|
break;
|
||||||
|
case "\\phase":
|
||||||
|
node.setAttribute("notation", "phasorangle");
|
||||||
|
break;
|
||||||
case "\\sout":
|
case "\\sout":
|
||||||
node.setAttribute("notation", "horizontalstrike");
|
node.setAttribute("notation", "horizontalstrike");
|
||||||
break;
|
break;
|
||||||
@@ -255,11 +289,11 @@ defineFunction({
|
|||||||
|
|
||||||
defineFunction({
|
defineFunction({
|
||||||
type: "enclose",
|
type: "enclose",
|
||||||
names: ["\\cancel", "\\bcancel", "\\xcancel", "\\sout"],
|
names: ["\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\phase"],
|
||||||
props: {
|
props: {
|
||||||
numArgs: 1,
|
numArgs: 1,
|
||||||
},
|
},
|
||||||
handler({parser, funcName}, args, optArgs) {
|
handler({parser, funcName}, args) {
|
||||||
const body = args[0];
|
const body = args[0];
|
||||||
return {
|
return {
|
||||||
type: "enclose",
|
type: "enclose",
|
||||||
|
@@ -98,6 +98,11 @@ s76,-153,76,-153s77,-151,77,-151c0.7,0.7,35.7,202,105,604c67.3,400.7,102,602.7,1
|
|||||||
606zM${1001 + extraViniculum} ${hLinePad}h400000v${40 + extraViniculum}H1017.7z`;
|
606zM${1001 + extraViniculum} ${hLinePad}h400000v${40 + extraViniculum}H1017.7z`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const phasePath = function(y: number): string {
|
||||||
|
const x = y / 2; // x coordinate at top of angle
|
||||||
|
return `M400000 ${y} H0 L${x} 0 l65 45 L145 ${y - 80} H400000z`;
|
||||||
|
};
|
||||||
|
|
||||||
const sqrtTall = function(
|
const sqrtTall = function(
|
||||||
extraViniculum: number,
|
extraViniculum: number,
|
||||||
hLinePad: number,
|
hLinePad: number,
|
||||||
|
@@ -2479,6 +2479,15 @@ describe("A actuarial angle builder", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("\\phase", function() {
|
||||||
|
it("should fail in text mode", function() {
|
||||||
|
expect`\text{\phase{-78.2^\circ}}`.not.toParse();
|
||||||
|
});
|
||||||
|
it("should not fail in math mode", function() {
|
||||||
|
expect`\phase{-78.2^\circ}`.toBuild();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("A phantom parser", function() {
|
describe("A phantom parser", function() {
|
||||||
it("should not fail", function() {
|
it("should not fail", function() {
|
||||||
expect`\phantom{x}`.toParse();
|
expect`\phantom{x}`.toParse();
|
||||||
|
BIN
test/screenshotter/images/Phase-chrome.png
Normal file
BIN
test/screenshotter/images/Phase-chrome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
test/screenshotter/images/Phase-firefox.png
Normal file
BIN
test/screenshotter/images/Phase-firefox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@@ -318,6 +318,7 @@ Phantom: \begin{array}{l}
|
|||||||
\text{a \phantom{123}} b \hphantom{\frac{1}{2}}=c \vphantom{101112} d \\
|
\text{a \phantom{123}} b \hphantom{\frac{1}{2}}=c \vphantom{101112} d \\
|
||||||
\sqrt{\mathstrut a} + \sqrt{\mathstrut d}
|
\sqrt{\mathstrut a} + \sqrt{\mathstrut d}
|
||||||
\end{array}
|
\end{array}
|
||||||
|
Phase: 120\text{V}\phase{-78.2^\circ}\;\Large\phase{78.2^\circ}
|
||||||
Pmb: \mu\pmb{\mu}\pmb{=}\mu\pmb{+}\mu
|
Pmb: \mu\pmb{\mu}\pmb{=}\mu\pmb{+}\mu
|
||||||
PrimeSpacing: f'+f_2'+f^{f'}
|
PrimeSpacing: f'+f_2'+f^{f'}
|
||||||
PrimeSuper: x'^2+x'''^2+x'^2_3+x_3'^2
|
PrimeSuper: x'^2+x'''^2+x'^2_3+x_3'^2
|
||||||
|
Reference in New Issue
Block a user