diff --git a/docs/support_table.md b/docs/support_table.md
index 93af131066..dce53dcf33 100644
--- a/docs/support_table.md
+++ b/docs/support_table.md
@@ -360,6 +360,7 @@ use `\ce` instead|
|\downharpoonleft|$\downharpoonleft$||
|\downharpoonright|$\downharpoonright$||
|{drcases}|$\begin{drcases}a&\text{if }b\\c&\text{if }d\end{drcases}$|`\begin{drcases}`
`a &\text{if } b \\`
`c &\text{if } d`
`\end{drcases}`|
+|\dv|$\dv{f}{t} \dv[2]{y}{x}$|`\dv{f}{t} \dv[2]{y}{x}`|
## E
@@ -791,6 +792,7 @@ use `\ce` instead|
|\O|$\text{\O}$|`\text{\O}`|
|\o|$\text{\o}$|`\text{\o}`|
|\odot|$\odot$||
+|\odv|$\odv{f}{t} \odv[2]{y}{x}$|`\odv{f}{t} \odv[2]{y}{x}`|
|\OE|$\text{\OE}$|`\text{\OE}`|
|\oe|$\text{\oe}$|`\text{\oe}`|
|\officialeuro|Not supported||
@@ -837,6 +839,7 @@ use `\ce` instead|
|\parallel|$\parallel$||
|\part|Not supported|[Deprecated](https://en.wikipedia.org/wiki/Help:Displaying_a_formula#Deprecated_syntax)|
|\partial|$\partial$||
+|\pdv|$\pdv{f}{t} \pdv[2,2][4]{\psi}{x, y}$|`\pdv{f}{t} \pdv[2,2][4]{\psi}{x, y}`|
|\perp|$\perp$||
|\phantom|$\Gamma^{\phantom{i}j}_{i\phantom{j}k}$|`\Gamma^{\phantom{i}j}_{i\phantom{j}k}`|
|\phase|$\phase{-78^\circ}$|`\phase{-78^\circ}`|
diff --git a/docs/supported.md b/docs/supported.md
index 6e2fbac68f..04f9bfd783 100644
--- a/docs/supported.md
+++ b/docs/supported.md
@@ -559,6 +559,40 @@ Direct Input: $← ↑ → ↓ ↔ ↕ ↖ ↗ ↘ ↙ ↚ ↛ ↞ ↠ ↢ ↣
Extensible arrows all can take an optional argument in the same manner
as `\xrightarrow[under]{over}`.
+## Derivatives
+
+The syntax closely emulates popular $LaTeX$ packages that provide derivatives,
+such as `physics`, `diffcoef`, and `derivative`.
+
+|||
+|:----------|:----------|
+|$\odv{}{t}$ `\odv{}{t}` |$\odv[2]{}{x}$ `\odv[2]{}{x}` |
+|$\odv{f}{t}$ `\odv{f}{t}` |$\odv[2]{y}{x}$ `\odv[2]{y}{x}`|
+|$\pdv{}{t}$ `\pdv{}{t}` |$\pdv[2]{}{x}$ `\pdv[2]{}{x}` |
+|$\pdv{f}{t}$ `\pdv{f}{t}` |$\pdv[2]{y}{x}$ `\pdv[2]{y}{x}`|
+|$\pdv[1,1][2]{}{x,y}$ `\pdv[1,1][2]{}{x,y}` |$\pdv[1,n][1+n]{}{x,y}$ `\pdv[1,n][1+n]{}{x,y}` |
+|$\pdv[1,1][2]{z}{x,y}$ `\pdv[1,1][2]{z}{x,y}`|$\pdv[1,n][1+n]{z}{x,y}$ `\pdv[1,n][1+n]{z}{x,y}`|
+|$\pdv[1,N-1][N]{z}{x_{\mu,\sigma},y_\xi}$ `\pdv[1,N-1][N]{z}{x_{\mu,\sigma},y_\xi}`||
+
+Ordinary derivative `\odv[order]{f}{x}`:
+* The first required brace `{f}` holds the function to take derivative of. This
+ brace can be left empty.
+* The second required brace `{x}` holds the variable.
+* `[order]` holds the derivative order. If the order is 1, you may omit this
+ bracket.
+
+Partial derivative `\pdv[var_orders][total_order]{f}{vars}`:
+* The first required brace `{f}` holds the function to take derivative of. This
+ brace can be left empty.
+* The second required brace `{vars}` holds the comma-separated variables, e.g.
+ `{x}`, `{x, y, z_k}`.
+* The first optional braket `[var_orders]` holds the list of derivative orders
+ corresponding to the variables, e.g. `[2]`, `[1, n, m+1]`. If there is only
+ one variable and its order is 1, you may omit this bracket.
+* The second optional braket `[total_order]` holds the total order of the
+ derivative. If there is only one variable order and that is the same as the
+ total order, you may omit this bracket.
+
## Special Notation
**Bra-ket Notation**
diff --git a/src/macros.js b/src/macros.js
index feb7400b8f..1e6075cc1f 100644
--- a/src/macros.js
+++ b/src/macros.js
@@ -16,6 +16,7 @@ import utils from "./utils";
import {makeEm} from "./units";
import ParseError from "./ParseError";
+import {assembleDerivativeExpr} from "./macros/derivatives";
//////////////////////////////////////////////////////////////////////
// macro tools
@@ -895,6 +896,17 @@ defineMacro("\\argmin", "\\DOTSB\\operatorname*{arg\\,min}");
defineMacro("\\argmax", "\\DOTSB\\operatorname*{arg\\,max}");
defineMacro("\\plim", "\\DOTSB\\mathop{\\operatorname{plim}}\\limits");
+//////////////////////////////////////////////////////////////////////
+// derivative.sty
+// https://ctan.math.illinois.edu/macros/latex/contrib/derivative/derivative.pdf
+
+defineMacro(
+ "\\dv", (context) => assembleDerivativeExpr("\\textnormal{d}", context));
+defineMacro(
+ "\\odv", (context) => assembleDerivativeExpr("\\textnormal{d}", context));
+defineMacro(
+ "\\pdv", (context) => assembleDerivativeExpr("\\partial", context));
+
//////////////////////////////////////////////////////////////////////
// braket.sty
// http://ctan.math.washington.edu/tex-archive/macros/latex/contrib/braket/braket.pdf
diff --git a/src/macros/derivatives.js b/src/macros/derivatives.js
new file mode 100644
index 0000000000..9f873ce05c
--- /dev/null
+++ b/src/macros/derivatives.js
@@ -0,0 +1,102 @@
+// @flow
+/**
+ * Macro utilities for assembling derivatives.
+ */
+
+import type {MacroContextInterface} from "../defineMacro";
+
+function splitByTopLevelComma(s: string): string[] {
+ if (!s || s.length === 0) {
+ return [];
+ }
+
+ // "1+a,n" => ["1+a", "n"]
+ // "1+a,{n,m}" => ["1+a", "{n,m}"]
+ // "1+a,{n,{p,q}}" => ["1+a", "{n,{p,q}}"]
+ // "1+a" => ["1+a"]
+ // "1+a,,n" => ["1+a", "", "n"]
+ // "1+a,," => ["1+a", ""]
+ // "1+a," => ["1+a"]
+ // "," => [""]
+ // "" => []
+ const res = [];
+ let currentStr = "";
+ let braceDepth = 0;
+ const isTopLevelDelimiter = (c) => c === "," && braceDepth <= 0;
+ for (let i = 0; i < s.length; ++i) {
+ const c = s[i];
+ if (c === "{") {
+ ++braceDepth;
+ } else if (c === "}") {
+ --braceDepth;
+ }
+ if (!isTopLevelDelimiter(c)) {
+ currentStr += c;
+ }
+ if (isTopLevelDelimiter(c) || i === s.length - 1) {
+ res.push(currentStr);
+ currentStr = "";
+ }
+ }
+
+ return res;
+}
+
+function addTokenProtector(s: string): string {
+ return s.length > 1 ? ` ${s} ` : s;
+}
+
+function extractArgs(context: MacroContextInterface): {
+ func: string,
+ vars: string[],
+ totalOrders: string[],
+ varOrders: string[],
+} {
+ const optionalArgGroups = [];
+ let arg = context.consumeArg().tokens;
+ while (arg.length === 1 && arg[0].text === "[") {
+ let bracketContent = '';
+ let token = context.expandNextToken();
+ while (token.text !== "]" && token.text !== "EOF") {
+ bracketContent += token.text;
+ token = context.expandNextToken();
+ }
+ optionalArgGroups.push(splitByTopLevelComma(bracketContent).reverse());
+ arg = context.consumeArg().tokens;
+ }
+
+ const func = arg.reverse().map(
+ e => addTokenProtector(e.text)).join("");
+ arg = context.consumeArg().tokens;
+ const vars = splitByTopLevelComma(arg.reverse().map(
+ e => addTokenProtector(e.text)).join(""));
+ const varOrders =
+ optionalArgGroups[0] ? optionalArgGroups[0].reverse() : ["1"];
+ const totalOrders =
+ optionalArgGroups[1] ? optionalArgGroups[1].reverse() : varOrders;
+
+ return {func, vars, totalOrders, varOrders};
+}
+
+const PLACEHOLDER = "\\square";
+
+export function assembleDerivativeExpr(
+ d: string, context: MacroContextInterface): string {
+ const {func, vars, totalOrders, varOrders} = extractArgs(context);
+ const numVars = vars.length;
+ const numVarOrders = varOrders.length;
+ if (numVars > numVarOrders) {
+ varOrders.push(...Array(numVars - numVarOrders).fill(PLACEHOLDER));
+ } else if (numVarOrders > numVars) {
+ vars.push(...Array(numVarOrders - numVars).fill(PLACEHOLDER));
+ }
+
+ const makeIndex = (s) => s === "1" ? "" : `^{${s}}`;
+ const numer = `{${d}}${makeIndex(totalOrders[0] ?? PLACEHOLDER)}{${func}}`;
+ const denom = vars.map((variable, i) => {
+ const order = varOrders[i];
+ return `{${d}}{${variable}}${makeIndex(order)}`;
+ }).join("");
+
+ return `\\frac{${numer}}{${denom}}`;
+}
diff --git a/test/screenshotter/images/Derivatives-chrome.png b/test/screenshotter/images/Derivatives-chrome.png
new file mode 100644
index 0000000000..5b279cc635
Binary files /dev/null and b/test/screenshotter/images/Derivatives-chrome.png differ
diff --git a/test/screenshotter/images/Derivatives-firefox.png b/test/screenshotter/images/Derivatives-firefox.png
new file mode 100644
index 0000000000..c8275c097b
Binary files /dev/null and b/test/screenshotter/images/Derivatives-firefox.png differ
diff --git a/test/screenshotter/images/DerivativesFallbacks-chrome.png b/test/screenshotter/images/DerivativesFallbacks-chrome.png
new file mode 100644
index 0000000000..13ed2eca11
Binary files /dev/null and b/test/screenshotter/images/DerivativesFallbacks-chrome.png differ
diff --git a/test/screenshotter/images/DerivativesFallbacks-firefox.png b/test/screenshotter/images/DerivativesFallbacks-firefox.png
new file mode 100644
index 0000000000..9ed3264bf7
Binary files /dev/null and b/test/screenshotter/images/DerivativesFallbacks-firefox.png differ
diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml
index f0fb33a7e3..62f57c533a 100644
--- a/test/screenshotter/ss_data.yaml
+++ b/test/screenshotter/ss_data.yaml
@@ -125,6 +125,17 @@ DelimiterSizing: |
a & b & c\\
a & b & c\\
\end{pmatrix}\; \Braket{ ϕ | \frac{∂^2}{∂ t^2} | ψ } \\
+Derivatives: |
+ \dv{}{x},\odv{y}{x},\odv[2]{}{x},\odv[n+1]{y}{x} \\
+ \pdv{}{x},\pdv{y}{x},\pdv[2]{}{x},\pdv[n+1]{y}{x} \\
+ \pdv[1,1][2]{z}{x,y},\pdv[2,2][4]{z}{x,y}, \pdv[1,a][1+a]{z}{x,y} \\
+ \pdv[2][2]{\tiny\begin{pmatrix}\phi & 0 \\0 & 1\end{pmatrix}}{x},
+ \pdv[1+2^n,{(1,2)},k,\Lambda][N_k+1]{\langle\psi_{1,ûnïcoδé}|\phi_\alpha\rangle} {x,y,z_{\mu,\sigma},\tau} \\
+DerivativesFallbacks: |
+ \square = \text{placeholder} \\
+ \odv[]{y}{x}, \pdv[]{y}{x} \\
+ \pdv[2][4]{z}{x,y}, \pdv[2,2][]{z}{x,y}, \pdv[2,2][4]{z}{x} \\
+ \pdv[2][6]{\psi}{x,y,z}, \pdv[2,2][]{\psi}{x,y,z}, \pdv[2,2,2][6]{\psi}{x} \\
DisplayMode:
tex: \sum_{i=0}^\infty \frac{1}{i}
pre: pre