Skip to content

Commit 30fe6a6

Browse files
authored
Use LaTeX fonts for math symbols in circuit diagrams (#2327)
Circuit rendering now appears as per the below in VS Code (and have tested in JupyterLabs in both Safari and Edge browsers, and in light and dark themes). <img width="596" alt="image" src="https://github.com/user-attachments/assets/955d0304-9107-41a0-a874-765447c914a9" />
1 parent a075d69 commit 30fe6a6

File tree

7 files changed

+77
-37
lines changed

7 files changed

+77
-37
lines changed

npm/qsharp/src/shared/legacyCircuitUpdate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ function isCircuit(circuit: any): circuit is Circuit {
189189
*/
190190
function getKetLabel(ket: string): string {
191191
// Check that the ket conforms to the format |{label}> or |{label}⟩
192+
// Be overly permissive with the ket format, allowing for various closing characters
192193
const ketRegex = /^\|([^\s>]+)(?:[>])$/;
193194

194195
// Match the ket string against the regex

npm/qsharp/ux/circuit-vis/formatters/formatUtils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,12 @@ export const text = (
146146
x: number,
147147
y: number,
148148
fs: number = labelFontSize,
149-
): SVGElement => {
150-
const el: SVGElement = createSvgElement("text", {
149+
): SVGTextElement => {
150+
const el = createSvgElement("text", {
151151
"font-size": fs.toString(),
152152
x: x.toString(),
153153
y: y.toString(),
154-
});
154+
}) as SVGTextElement;
155155
el.textContent = text;
156156
return el;
157157
};

npm/qsharp/ux/circuit-vis/formatters/gateFormatter.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
dashedBox,
2727
} from "./formatUtils";
2828

29+
import { mathChars } from "../utils";
30+
2931
/**
3032
* Given an array of operations render data, return the SVG representation.
3133
*
@@ -234,6 +236,31 @@ const _measure = (x: number, y: number): SVGElement => {
234236
return group([mBox, mArc, meter]);
235237
};
236238

239+
const use_katex = true;
240+
241+
function _style_gate_text(gate: SVGTextElement) {
242+
if (!use_katex) return;
243+
let label = gate.textContent || "";
244+
245+
// In general, use the regular math font
246+
gate.classList.add("qs-maintext");
247+
248+
// Wrap any latin or greek letters in tspan with KaTeX_Math font
249+
// Style the entire Greek + Coptic block (https://unicodeplus.com/block/0370)
250+
// Note this deliberately leaves ASCII digits [0-9] non-italic
251+
const italicChars = /[a-zA-Z\u{0370}-\u{03ff}]+/gu;
252+
253+
label = label.replace(italicChars, `<tspan class='qs-mathtext'>$&</tspan>`);
254+
255+
// Replace a trailing ' with the proper unicode dagger symbol
256+
label = label.replace(
257+
/'$/,
258+
`<tspan dx="2" dy="-3" style="font-size: 0.8em;">${mathChars.dagger}</tspan>`,
259+
);
260+
261+
gate.innerHTML = label;
262+
}
263+
237264
/**
238265
* Creates the SVG for a unitary gate on an arbitrary number of qubits.
239266
*
@@ -311,12 +338,15 @@ const _unitaryBox = (
311338
uBox.setAttribute("class", cssClass);
312339
}
313340
const labelY = y + height / 2 - (displayArgs == null ? 0 : 7);
314-
const labelText: SVGElement = text(label, x, labelY);
341+
const labelText = text(label, x, labelY);
342+
_style_gate_text(labelText);
343+
315344
const elems = [uBox, labelText];
316345
if (displayArgs != null) {
317346
const argStrY = y + height / 2 + 8;
318347

319-
const argButton: SVGElement = text(displayArgs, x, argStrY, argsFontSize);
348+
const argButton = text(displayArgs, x, argStrY, argsFontSize);
349+
_style_gate_text(argButton);
320350
argButton.setAttribute("class", "arg-button");
321351
elems.push(argButton);
322352
}
@@ -369,15 +399,15 @@ const _x = (renderData: GateRenderData): SVGElement => {
369399
const _ket = (label: string, renderData: GateRenderData): SVGElement => {
370400
const { x, targetsY, width } = renderData;
371401
const gate = _unitary(
372-
`|${label}`,
402+
`|${label}${mathChars.rangle}`,
373403
x,
374404
targetsY as number[][],
375405
width,
376406
undefined,
377407
false,
378408
"gate-ket",
379409
);
380-
gate.querySelector("text")!.setAttribute("class", "ket-text");
410+
gate.querySelector("text")!.classList.add("ket-text");
381411
return gate;
382412
};
383413

npm/qsharp/ux/circuit-vis/formatters/inputFormatter.ts

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
classicalRegHeight,
1111
} from "../constants";
1212
import { group, text } from "./formatUtils";
13+
import { mathChars } from "../utils";
1314

1415
/**
1516
* `formatInputs` takes in an array of Qubits and outputs the SVG string of formatted
@@ -71,38 +72,15 @@ const formatInputs = (
7172
const _qubitInput = (y: number, subscript?: string): SVGElement => {
7273
const el: SVGElement = text("", leftPadding, y, 16);
7374

74-
// Create the main text node
75-
const mainText = document.createElementNS(
76-
"http://www.w3.org/2000/svg",
77-
"tspan",
78-
);
79-
mainText.textContent = "|𝜓";
75+
const subtext = subscript
76+
? `<tspan baseline-shift="sub" font-size="65%">${subscript}</tspan>`
77+
: "";
8078

81-
// Create the subscript node if provided
82-
if (subscript) {
83-
const subscriptText = document.createElementNS(
84-
"http://www.w3.org/2000/svg",
85-
"tspan",
86-
);
87-
subscriptText.textContent = subscript;
88-
subscriptText.setAttribute("baseline-shift", "sub");
89-
subscriptText.setAttribute("font-size", "65%");
90-
mainText.appendChild(subscriptText);
91-
}
92-
93-
// Add the closing part of the text
94-
const closingText = document.createElementNS(
95-
"http://www.w3.org/2000/svg",
96-
"tspan",
97-
);
98-
closingText.textContent = "⟩";
99-
100-
// Append all parts to the main SVG text element
101-
el.appendChild(mainText);
102-
el.appendChild(closingText);
79+
el.innerHTML = `|<tspan class="qs-mathtext">${mathChars.psi}</tspan>${subtext}${mathChars.rangle}</tspan>`;
10380

10481
el.setAttribute("text-anchor", "start");
10582
el.setAttribute("dominant-baseline", "middle");
83+
el.classList.add("qs-maintext");
10684
return el;
10785
};
10886

npm/qsharp/ux/circuit-vis/gateRenderData.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export enum GateType {
1515
Swap,
1616
/** X gate. */
1717
X,
18-
/** |0or |1 gate. */
18+
/** |0or |1 gate. */
1919
Ket,
2020
/** Single/multi qubit unitary gate. */
2121
Unitary,

npm/qsharp/ux/circuit-vis/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,19 @@ const getGateElems = (container: HTMLElement): SVGGraphicsElement[] => {
365365
: [];
366366
};
367367

368+
// Non-ASCII chars are fraught with danger. Copy/paste these when possible.
369+
// Use the following regex in VS Code to find invalid unicode chars
370+
// [^\x20-\x7e\u{03b8}-\u{03c8}\u{2020}\u{27e8}\u{27e9}]
371+
372+
const mathChars = {
373+
theta: "θ", // \u{03b8}
374+
pi: "π", // \u{03c0}
375+
psi: "ψ", // \u{03c8}
376+
dagger: "†", // \u{2020}
377+
langle: "⟨", // \u{27e8}
378+
rangle: "⟩", // \u{27e9}
379+
};
380+
368381
export {
369382
createUUID,
370383
getGateWidth,
@@ -380,4 +393,5 @@ export {
380393
getToolboxElems,
381394
getHostElems,
382395
getGateElems,
396+
mathChars,
383397
};

npm/qsharp/ux/qsharp-circuit.css

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,23 @@
4040
fill: var(--main-color);
4141
dominant-baseline: middle;
4242
text-anchor: middle;
43-
font-family: Arial;
43+
}
44+
45+
/* When run in VS Code, the "KaTeX_Main" and "KaTeX_Math" fonts are already present */
46+
/* When run in Jupyter in the browser, the MJX* (MathJax) fonts are used and present */
47+
.qs-maintext {
48+
font-family: "KaTeX_Main", "MJXZERO", "MJXTEX", sans-serif;
49+
font-style: normal;
50+
}
51+
52+
.qs-mathtext {
53+
font-family: "KaTeX_Math", "MJXZERO", "MJXTEX-I", serif;
54+
}
55+
56+
/* The math font used in KaTex (thus in VS Code) needs italic applied also, whereas
57+
the font used in MathJax (thus Jupyter web) doesn't. So apply if in VS Code */
58+
[data-vscode-theme-kind] .qs-mathtext {
59+
font-style: italic;
4460
}
4561

4662
/* Background for gates */
@@ -59,6 +75,7 @@
5975
text-decoration: none;
6076
cursor: default;
6177
pointer-events: none;
78+
word-spacing: -0.2em;
6279
}
6380

6481
/* Edit mode style for arg-button (hyperlink appearance) */

0 commit comments

Comments
 (0)