mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-06 03:38:39 +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");
|
||||
});
|
||||
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(
|
||||
`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", () => {
|
||||
test("simple exponent", () => {
|
||||
const result = renderA11yString("e^x");
|
||||
|
@@ -816,7 +816,7 @@ use `\ce` instead|
|
||||
|\partial|$\partial$||
|
||||
|\perp|$\perp$||
|
||||
|\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$||
|
||||
|\Pi|$\Pi$||
|
||||
|
@@ -212,6 +212,7 @@ For Persian composite characters, a user-supplied [plug-in](https://github.com/
|
||||
|$\xcancel{ABC}$ `\xcancel{ABC}`|$\not =$ `\not =`
|
||||
|$\sout{abc}$ `\sout{abc}`|$\boxed{\pi=\frac c d}$ `\boxed{\pi=\frac c d}`
|
||||
|$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}$$
|
||||
|
@@ -533,7 +533,7 @@ export class PathNode implements VirtualNode {
|
||||
|
||||
constructor(pathName: string, alternate?: string) {
|
||||
this.pathName = pathName;
|
||||
this.alternate = alternate; // Used only for \sqrt
|
||||
this.alternate = alternate; // Used only for \sqrt and \phase
|
||||
}
|
||||
|
||||
toNode(): Node {
|
||||
|
@@ -4,6 +4,9 @@ import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import utils from "../utils";
|
||||
import stretchy from "../stretchy";
|
||||
import {phasePath} from "../svgGeometry";
|
||||
import {PathNode, SvgNode} from "../domTree";
|
||||
import {calculateSize} from "../units";
|
||||
import {assertNodeType} from "../parseNode";
|
||||
|
||||
import * as html from "../buildHTML";
|
||||
@@ -11,14 +14,14 @@ import * as mml from "../buildMathML";
|
||||
|
||||
|
||||
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
|
||||
// them in a span.
|
||||
const inner = buildCommon.wrapFragment(
|
||||
html.buildGroup(group.body, options), options);
|
||||
|
||||
const label = group.label.substr(1);
|
||||
const scale = options.sizeMultiplier;
|
||||
let scale = options.sizeMultiplier;
|
||||
let img;
|
||||
let imgShift = 0;
|
||||
|
||||
@@ -34,6 +37,33 @@ const htmlBuilder = (group, options) => {
|
||||
img.height = options.fontMetrics().defaultRuleThickness / scale;
|
||||
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 {
|
||||
// Add horizontal padding
|
||||
if (/cancel/.test(label)) {
|
||||
@@ -100,6 +130,7 @@ const htmlBuilder = (group, options) => {
|
||||
],
|
||||
}, options);
|
||||
} else {
|
||||
const classes = /cancel|phase/.test(label) ? ["svg-align"] : [];
|
||||
vlist = buildCommon.makeVList({
|
||||
positionType: "individualShift",
|
||||
children: [
|
||||
@@ -113,7 +144,7 @@ const htmlBuilder = (group, options) => {
|
||||
type: "elem",
|
||||
elem: img,
|
||||
shift: imgShift,
|
||||
wrapperClasses: /cancel/.test(label) ? ["svg-align"] : [],
|
||||
wrapperClasses: classes,
|
||||
},
|
||||
],
|
||||
}, options);
|
||||
@@ -147,6 +178,9 @@ const mathmlBuilder = (group, options) => {
|
||||
case "\\bcancel":
|
||||
node.setAttribute("notation", "downdiagonalstrike");
|
||||
break;
|
||||
case "\\phase":
|
||||
node.setAttribute("notation", "phasorangle");
|
||||
break;
|
||||
case "\\sout":
|
||||
node.setAttribute("notation", "horizontalstrike");
|
||||
break;
|
||||
@@ -255,11 +289,11 @@ defineFunction({
|
||||
|
||||
defineFunction({
|
||||
type: "enclose",
|
||||
names: ["\\cancel", "\\bcancel", "\\xcancel", "\\sout"],
|
||||
names: ["\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\phase"],
|
||||
props: {
|
||||
numArgs: 1,
|
||||
},
|
||||
handler({parser, funcName}, args, optArgs) {
|
||||
handler({parser, funcName}, args) {
|
||||
const body = args[0];
|
||||
return {
|
||||
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`;
|
||||
};
|
||||
|
||||
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(
|
||||
extraViniculum: 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() {
|
||||
it("should not fail", function() {
|
||||
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 \\
|
||||
\sqrt{\mathstrut a} + \sqrt{\mathstrut d}
|
||||
\end{array}
|
||||
Phase: 120\text{V}\phase{-78.2^\circ}\;\Large\phase{78.2^\circ}
|
||||
Pmb: \mu\pmb{\mu}\pmb{=}\mu\pmb{+}\mu
|
||||
PrimeSpacing: f'+f_2'+f^{f'}
|
||||
PrimeSuper: x'^2+x'''^2+x'^2_3+x_3'^2
|
||||
|
Reference in New Issue
Block a user