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:
Ron Kok
2020-08-12 21:08:36 -07:00
committed by GitHub
parent 26e0a05221
commit 42cc5e507e
11 changed files with 73 additions and 7 deletions

View File

@@ -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`);

View File

@@ -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");

View File

@@ -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$||

View File

@@ -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}$$

View File

@@ -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 {

View File

@@ -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",

View File

@@ -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,

View File

@@ -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();

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -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