diff --git a/src/buildHTML.js b/src/buildHTML.js index d64e2bb2..8255b017 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -915,6 +915,59 @@ groupTypes.op = function(group, options) { } }; +groupTypes.mod = function(group, options) { + var inner = []; + + if (group.value.modType === "bmod") { + // “\nonscript\mskip-\medmuskip\mkern5mu” + if (!options.style.isTight()) { + inner.push(makeSpan( + ["mspace", "negativemediumspace"], [], options)); + } + inner.push(makeSpan(["mspace", "thickspace"], [], options)); + } else if (options.style.size === Style.DISPLAY.size) { + inner.push(makeSpan(["mspace", "quad"], [], options)); + } else if (group.value.modType === "mod") { + inner.push(makeSpan(["mspace", "twelvemuspace"], [], options)); + } else { + inner.push(makeSpan(["mspace", "eightmuspace"], [], options)); + } + + if (group.value.modType === "pod" || group.value.modType === "pmod") { + inner.push(buildCommon.mathsym("(", group.mode)); + } + + if (group.value.modType !== "pod") { + var modInner = [ + buildCommon.mathsym("m", group.mode), + buildCommon.mathsym("o", group.mode), + buildCommon.mathsym("d", group.mode)]; + if (group.value.modType === "bmod") { + inner.push(makeSpan(["mbin"], modInner, options)); + // “\mkern5mu\nonscript\mskip-\medmuskip” + inner.push(makeSpan(["mspace", "thickspace"], [], options)); + if (!options.style.isTight()) { + inner.push(makeSpan( + ["mspace", "negativemediumspace"], [], options)); + } + } else { + Array.prototype.push.apply(inner, modInner); + inner.push(makeSpan(["mspace", "sixmuspace"], [], options)); + } + } + + if (group.value.value) { + Array.prototype.push.apply(inner, + buildExpression(group.value.value, options, false)); + } + + if (group.value.modType === "pod" || group.value.modType === "pmod") { + inner.push(buildCommon.mathsym(")", group.mode)); + } + + return buildCommon.makeFragment(inner); +}; + groupTypes.katex = function(group, options) { // The KaTeX logo. The offsets for the K and a were chosen to look // good, but the offsets for the T, E, and X were taken from the diff --git a/src/buildMathML.js b/src/buildMathML.js index 795f402c..6c17d418 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -348,6 +348,31 @@ groupTypes.op = function(group, options) { return node; }; +groupTypes.mod = function(group, options) { + var inner = []; + + if (group.value.modType === "pod" || group.value.modType === "pmod") { + inner.push(new mathMLTree.MathNode( + "mo", [makeText("(", group.mode)])); + } + if (group.value.modType !== "pod") { + inner.push(new mathMLTree.MathNode( + "mo", [makeText("mod", group.mode)])); + } + if (group.value.value) { + var space = new mathMLTree.MathNode("mspace"); + space.setAttribute("width", "0.333333em"); + inner.push(space); + inner = inner.concat(buildExpression(group.value.value, options)); + } + if (group.value.modType === "pod" || group.value.modType === "pmod") { + inner.push(new mathMLTree.MathNode( + "mo", [makeText(")", group.mode)])); + } + + return new mathMLTree.MathNode("mo", inner); +}; + groupTypes.katex = function(group) { var node = new mathMLTree.MathNode( "mtext", [new mathMLTree.TextNode("KaTeX")]); diff --git a/src/functions.js b/src/functions.js index 7e50cccf..20e7134e 100644 --- a/src/functions.js +++ b/src/functions.js @@ -279,6 +279,28 @@ defineFunction("\\stackrel", { }; }); +// \mod-type functions +defineFunction("\\bmod", { + numArgs: 0, +}, function(context, args) { + return { + type: "mod", + modType: "bmod", + value: null, + }; +}); + +defineFunction(["\\pod", "\\pmod", "\\mod"], { + numArgs: 1, +}, function(context, args) { + var body = args[0]; + return { + type: "mod", + modType: context.funcName.substr(1), + value: ordargument(body), + }; +}); + // Extra data needed for the delimiter handler down below var delimiterSizes = { "\\bigl" : {mclass: "mopen", size: 1}, diff --git a/static/katex.less b/static/katex.less index 764e4463..b935d296 100644 --- a/static/katex.less +++ b/static/katex.less @@ -105,9 +105,9 @@ @ptperem: 10.0; @nulldelimiterspace: 1.2em / @ptperem; - @thinspace: 0.16667em; - @mediumspace: 0.22222em; - @thickspace: 0.27778em; + @thinspace: 0.16667em; // 3mu + @mediumspace: 0.22222em; // 4mu + @thickspace: 0.27778em; // 5mu // These spacings apply in textstyle and displaystyle. .mord { @@ -300,6 +300,10 @@ width: @thinspace; } + &.negativemediumspace { + margin-left: -@mediumspace; + } + &.mediumspace { width: @mediumspace; } @@ -308,10 +312,22 @@ width: @thickspace; } + &.sixmuspace { + width: 0.333333em; + } + + &.eightmuspace { + width: 0.444444em; + } + &.enspace { width: 0.5em; } + &.twelvemuspace { + width: 0.666667em; + } + &.quad { width: 1em; } diff --git a/test/screenshotter/images/Mod-chrome.png b/test/screenshotter/images/Mod-chrome.png new file mode 100644 index 00000000..c5b72d28 Binary files /dev/null and b/test/screenshotter/images/Mod-chrome.png differ diff --git a/test/screenshotter/images/Mod-firefox.png b/test/screenshotter/images/Mod-firefox.png new file mode 100644 index 00000000..7787a882 Binary files /dev/null and b/test/screenshotter/images/Mod-firefox.png differ diff --git a/test/screenshotter/images/ModScript-chrome.png b/test/screenshotter/images/ModScript-chrome.png new file mode 100644 index 00000000..accb05bd Binary files /dev/null and b/test/screenshotter/images/ModScript-chrome.png differ diff --git a/test/screenshotter/images/ModScript-firefox.png b/test/screenshotter/images/ModScript-firefox.png new file mode 100644 index 00000000..27ecd68c Binary files /dev/null and b/test/screenshotter/images/ModScript-firefox.png differ diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml index cd5fbb29..1b986633 100644 --- a/test/screenshotter/ss_data.yaml +++ b/test/screenshotter/ss_data.yaml @@ -92,6 +92,18 @@ MathRm: \mathrm{Ax2k\breve{a}\omega\Omega\imath+\KaTeX} MathSf: \mathsf{Ax2k\breve{a}\omega\Omega\imath+\KaTeX} MathScr: \mathscr{Ax2k\breve{a}\omega\Omega\imath+\KaTeX} MathTt: \mathtt{Ax2k\breve{a}\omega\Omega\imath+\KaTeX} +Mod: | + \begin{array}{cc} + a \bmod 2 & b \pod 3 \\ + c \pmod{4} & d \mod{56} \\ + \displaystyle a\bmod 2 & \displaystyle b \pod 3 \\ + \displaystyle c\pmod{4} & \displaystyle d \mod{56} + \end{array} +ModScript: | + \begin{array}{cc} + \scriptstyle a\bmod 2 & \scriptstyle b \pod 3 \\ + \scriptstyle c\pmod{4} & \scriptstyle d \mod{56} + \end{array} NegativeSpaceBetweenRel: A =\!= B NestedFractions: | \dfrac{\frac{a}{b}}{\frac{c}{d}}\dfrac{\dfrac{a}{b}}