mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 19:28:39 +00:00
Implement \verb (#614)
* Implement \verb * Implement @gagern's comments * \verb: look up characters one at a time. * Add screenshot test for \verb * Add error tests for \verb * Include space symbol in typewriter font, and fix single quotes This is based on https://github.com/Khan/MathJax-dev/pull/2 which hasn't been accepted yet at the time this commit is made. * Add \verb* tests * \verb should use Typewriter-Regular font! * Switch \verb to use text mode and no-break space. * Screenshot update with Typewriter-Regular * \verb test: fix *, add commas to make spaces clear * Fix spaces and style handling * Implement @kevinbarabash's comments * Make error clearly an assertion failure * verb screenshot for Chrome
This commit is contained in:
committed by
Kevin Barabash
parent
c47655cc0e
commit
f10ea4cbeb
@@ -986,11 +986,12 @@ $map{cmtt10} = {
|
||||
0x15 => [0x306,-525,0], # \breve (combining)
|
||||
0x16 => [0x304,-525,0], # \bar (combining)
|
||||
0x17 => [0x30A,-525,0], # ring above (combining)
|
||||
0x20 => 0x2423, # graphic representation of space
|
||||
|
||||
[0x21,0x7F] => 0x21,
|
||||
|
||||
0x27 => 2018, # left quote
|
||||
0x60 => 2019, # right quote
|
||||
0x27 => 0x2018, # left quote
|
||||
0x60 => 0x2019, # right quote
|
||||
0x5E => [0x302,-525,0], # \hat (combining)
|
||||
0x7E => [0x303,-525,0], # \tilde (combining)
|
||||
0x7F => [0x308,-525,0], # \ddot (combining)
|
||||
|
@@ -35,6 +35,8 @@ const tokenRegex = new RegExp(
|
||||
"([ \r\n\t]+)|" + // whitespace
|
||||
"([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + // single codepoint
|
||||
"|[\uD800-\uDBFF][\uDC00-\uDFFF]" + // surrogate pair
|
||||
"|\\\\verb\\*([^]).*?\\3" + // \verb*
|
||||
"|\\\\verb([^*a-zA-Z]).*?\\4" + // \verb unstarred
|
||||
"|\\\\(?:[a-zA-Z@]+|[^\uD800-\uDFFF])" + // function name
|
||||
")"
|
||||
);
|
||||
|
@@ -916,6 +916,25 @@ class Parser {
|
||||
return new ParseFuncOrArgument(
|
||||
nucleus.text,
|
||||
false, nucleus);
|
||||
} else if (/^\\verb[^a-zA-Z]/.test(nucleus.text)) {
|
||||
this.consume();
|
||||
let arg = nucleus.text.slice(5);
|
||||
const star = (arg.charAt(0) === "*");
|
||||
if (star) {
|
||||
arg = arg.slice(1);
|
||||
}
|
||||
// Lexer's tokenRegex is constructed to always have matching
|
||||
// first/last characters.
|
||||
if (arg.length < 2 || arg.charAt(0) !== arg.slice(-1)) {
|
||||
throw new ParseError(`\\verb assertion failed --
|
||||
please report what input caused this bug`);
|
||||
}
|
||||
arg = arg.slice(1, -1); // remove first and last char
|
||||
return new ParseFuncOrArgument(
|
||||
new ParseNode("verb", {
|
||||
body: arg,
|
||||
star: star,
|
||||
}, "text"), false, nucleus);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@@ -168,6 +168,20 @@ const makeOrd = function(group, options, type) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Combine as many characters as possible in the given array of characters
|
||||
* via their tryCombine method.
|
||||
*/
|
||||
const tryCombineChars = function(chars) {
|
||||
for (let i = 0; i < chars.length - 1; i++) {
|
||||
if (chars[i].tryCombine(chars[i + 1])) {
|
||||
chars.splice(i + 1, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return chars;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate the height, depth, and maxFontSize of an element based on its
|
||||
* children.
|
||||
@@ -394,6 +408,18 @@ const makeVList = function(children, positionType, positionData, options) {
|
||||
return vtable;
|
||||
};
|
||||
|
||||
// Converts verb group into body string, dealing with \verb* form
|
||||
const makeVerb = function(group, options) {
|
||||
let text = group.value.body;
|
||||
if (group.value.star) {
|
||||
text = text.replace(/ /g, '\u2423'); // Open Box
|
||||
} else {
|
||||
text = text.replace(/ /g, '\xA0'); // No-Break Space
|
||||
// (so that, in particular, spaces don't coalesce)
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
// A map of spacing functions to their attributes, like size and corresponding
|
||||
// CSS class
|
||||
const spacingFunctions = {
|
||||
@@ -487,6 +513,8 @@ module.exports = {
|
||||
makeFragment: makeFragment,
|
||||
makeVList: makeVList,
|
||||
makeOrd: makeOrd,
|
||||
makeVerb: makeVerb,
|
||||
tryCombineChars: tryCombineChars,
|
||||
prependChildren: prependChildren,
|
||||
spacingFunctions: spacingFunctions,
|
||||
};
|
||||
|
@@ -306,12 +306,7 @@ groupTypes.ordgroup = function(group, options) {
|
||||
groupTypes.text = function(group, options) {
|
||||
const newOptions = options.withFont(group.value.font);
|
||||
const inner = buildExpression(group.value.body, newOptions, true);
|
||||
for (let i = 0; i < inner.length - 1; i++) {
|
||||
if (inner[i].tryCombine(inner[i + 1])) {
|
||||
inner.splice(i + 1, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
buildCommon.tryCombineChars(inner);
|
||||
return makeSpan(["mord", "text"],
|
||||
inner, newOptions);
|
||||
};
|
||||
@@ -1094,6 +1089,30 @@ groupTypes.font = function(group, options) {
|
||||
return buildGroup(group.value.body, options.withFont(font));
|
||||
};
|
||||
|
||||
groupTypes.verb = function(group, options) {
|
||||
const text = buildCommon.makeVerb(group, options);
|
||||
const body = [];
|
||||
// \verb enters text mode and therefore is sized like \textstyle
|
||||
const newOptions = options.havingStyle(options.style.text());
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if (text[i] === '\xA0') { // spaces appear as nonbreaking space
|
||||
// The space character isn't in the Typewriter-Regular font,
|
||||
// so we implement it as a kern of the same size as a character.
|
||||
// 0.525 is the width of a texttt character in LaTeX.
|
||||
// It automatically gets scaled by the font size.
|
||||
const rule = makeSpan(["mord", "rule"], [], newOptions);
|
||||
rule.style.marginLeft = "0.525em";
|
||||
body.push(rule);
|
||||
} else {
|
||||
body.push(buildCommon.makeSymbol(text[i], "Typewriter-Regular",
|
||||
group.mode, newOptions, ["mathtt"]));
|
||||
}
|
||||
}
|
||||
buildCommon.tryCombineChars(body);
|
||||
return makeSpan(["mord", "text"].concat(newOptions.sizingClasses(options)),
|
||||
body, newOptions);
|
||||
};
|
||||
|
||||
groupTypes.rule = function(group, options) {
|
||||
// Make an empty span for the rule
|
||||
const rule = makeSpan(["mord", "rule"], [], options);
|
||||
|
@@ -455,6 +455,13 @@ groupTypes.sizing = function(group, options) {
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.verb = function(group, options) {
|
||||
const text = new mathMLTree.TextNode(buildCommon.makeVerb(group, options));
|
||||
const node = new mathMLTree.MathNode("mtext", [text]);
|
||||
node.setAttribute("mathvariant", fontMap["mathtt"].variant);
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.overline = function(group, options) {
|
||||
const operator = new mathMLTree.MathNode(
|
||||
"mo", [new mathMLTree.TextNode("\u203e")]);
|
||||
|
@@ -1748,7 +1748,10 @@ const fontMetricsData = {
|
||||
"937": [0, 0.61111, 0, 0],
|
||||
"2018": [0, 0.61111, 0, 0],
|
||||
"2019": [0, 0.61111, 0, 0],
|
||||
"8216": [0, 0.61111, 0, 0],
|
||||
"8217": [0, 0.61111, 0, 0],
|
||||
"8242": [0, 0.61111, 0, 0],
|
||||
"9251": [0.11111, 0.21944, 0, 0],
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -713,3 +713,15 @@ defineFunction(["\\raisebox"], {
|
||||
value: ordargument(body),
|
||||
};
|
||||
});
|
||||
|
||||
// \verb and \verb* are dealt with directly in Parser.js.
|
||||
// If we end up here, it's because of a failure to match the two delimiters
|
||||
// in the regex in Lexer.js. LaTeX raises the following error when \verb is
|
||||
// terminated by end of line (or file).
|
||||
defineFunction(["\\verb"], {
|
||||
numArgs: 0,
|
||||
allowedInText: true,
|
||||
}, function(context) {
|
||||
throw new ParseError(
|
||||
"\\verb ended by end of line instead of matching delimiter");
|
||||
});
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -183,6 +183,17 @@ describe("Parser:", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#verb", function() {
|
||||
it("complains about mismatched \\verb with end of string", function() {
|
||||
expect("\\verb|hello").toFailWithParseError(
|
||||
"\\verb ended by end of line instead of matching delimiter");
|
||||
});
|
||||
it("complains about mismatched \\verb with end of line", function() {
|
||||
expect("\\verb|hello\nworld|").toFailWithParseError(
|
||||
"\\verb ended by end of line instead of matching delimiter");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Parser.expect calls:", function() {
|
||||
|
BIN
test/screenshotter/images/Verb-chrome.png
Normal file
BIN
test/screenshotter/images/Verb-chrome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
test/screenshotter/images/Verb-firefox.png
Normal file
BIN
test/screenshotter/images/Verb-firefox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@@ -279,6 +279,13 @@ UnsupportedCmds:
|
||||
noThrow: 1
|
||||
errorColor: "#dd4c4c"
|
||||
nolatex: deliberately does not compile
|
||||
Verb: |
|
||||
\begin{array}{ll}
|
||||
\verb \verb , & \verb|\verb |, \\
|
||||
\verb* \verb* , & \verb*|\verb* |, \\
|
||||
\verb!<x> & </y>! & \scriptstyle\verb|ss verb| \\
|
||||
\verb*!<x> & </y>! & \small\verb|sm verb| \\
|
||||
\end{array}
|
||||
VerticalSpacing:
|
||||
pre: potato<br>blah
|
||||
tex: x^{\Huge y}z
|
||||
|
Reference in New Issue
Block a user