Improve \sqrt (#810)
* Improve \sqrt Make \sqrt out of inline SVGs to ensure a perfect match at the junction between surd and viniculum. * Tweak for kern clarity * Fix lint error * regenerate screenshot tests with sqrts * Correct advance Edit the SVG paths so that they have the correct left bearing and advance width values. This will correct the spacing on the left side of each surd and it will also improve the placment of a root indice. * Revise scriptstyle surds In the `main` size, delimiters *do* scale with scriptstyle and scriptscriptstyle. * update screenshot images containing sqrts
@@ -1042,7 +1042,13 @@ groupTypes.sqrt = function(group, options) {
|
|||||||
|
|
||||||
// First, we do the same steps as in overline to build the inner group
|
// First, we do the same steps as in overline to build the inner group
|
||||||
// and line
|
// and line
|
||||||
const inner = buildGroup(group.value.body, options.havingCrampedStyle());
|
let inner = buildGroup(group.value.body, options.havingCrampedStyle());
|
||||||
|
|
||||||
|
// Some groups can return document fragments. Handle those by wrapping
|
||||||
|
// them in a span.
|
||||||
|
if (inner instanceof domTree.documentFragment) {
|
||||||
|
inner = makeSpan([], [inner], options);
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate the minimum size for the \surd delimiter
|
// Calculate the minimum size for the \surd delimiter
|
||||||
const metrics = options.fontMetrics();
|
const metrics = options.fontMetrics();
|
||||||
@@ -1059,20 +1065,18 @@ groupTypes.sqrt = function(group, options) {
|
|||||||
const minDelimiterHeight = (inner.height + inner.depth +
|
const minDelimiterHeight = (inner.height + inner.depth +
|
||||||
lineClearance + theta) * options.sizeMultiplier;
|
lineClearance + theta) * options.sizeMultiplier;
|
||||||
|
|
||||||
// Create a \surd delimiter of the required minimum size
|
// Create a sqrt SVG of the required minimum size
|
||||||
const delimChar = delimiter.customSizedDelim("\\surd", minDelimiterHeight,
|
const img = delimiter.customSizedDelim("\\surd", minDelimiterHeight,
|
||||||
false, options, group.mode);
|
false, options, group.mode);
|
||||||
const delim = makeSpan(["sqrt-sign"], [delimChar], options);
|
|
||||||
|
|
||||||
// Calculate the actual line width.
|
// Calculate the actual line width.
|
||||||
// This actually should depend on the chosen font -- e.g. \boldmath
|
// This actually should depend on the chosen font -- e.g. \boldmath
|
||||||
// should use the thicker surd symbols from e.g. KaTeX_Main-Bold, and
|
// should use the thicker surd symbols from e.g. KaTeX_Main-Bold, and
|
||||||
// have thicker rules.
|
// have thicker rules.
|
||||||
const ruleWidth = options.fontMetrics().sqrtRuleThickness *
|
const ruleWidth = options.fontMetrics().sqrtRuleThickness *
|
||||||
delimChar.delimSizeMultiplier;
|
img.sizeMultiplier;
|
||||||
const line = makeLineSpan("sqrt-line", options, ruleWidth);
|
|
||||||
|
|
||||||
const delimDepth = (delim.height + delim.depth) - ruleWidth;
|
const delimDepth = img.height - ruleWidth;
|
||||||
|
|
||||||
// Adjust the clearance based on the delimiter size
|
// Adjust the clearance based on the delimiter size
|
||||||
if (delimDepth > inner.height + inner.depth + lineClearance) {
|
if (delimDepth > inner.height + inner.depth + lineClearance) {
|
||||||
@@ -1080,12 +1084,8 @@ groupTypes.sqrt = function(group, options) {
|
|||||||
(lineClearance + delimDepth - inner.height - inner.depth) / 2;
|
(lineClearance + delimDepth - inner.height - inner.depth) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift the delimiter so that its top lines up with the top of the line
|
// Shift the sqrt image
|
||||||
const delimShift = -(inner.height + lineClearance + ruleWidth) +
|
const imgShift = img.height - inner.height - lineClearance - ruleWidth;
|
||||||
delim.height;
|
|
||||||
delim.style.top = delimShift + "em";
|
|
||||||
delim.height -= delimShift;
|
|
||||||
delim.depth += delimShift;
|
|
||||||
|
|
||||||
// We add a special case here, because even when `inner` is empty, we
|
// We add a special case here, because even when `inner` is empty, we
|
||||||
// still get a line. So, we use a simple heuristic to decide if we
|
// still get a line. So, we use a simple heuristic to decide if we
|
||||||
@@ -1096,16 +1096,20 @@ groupTypes.sqrt = function(group, options) {
|
|||||||
if (inner.height === 0 && inner.depth === 0) {
|
if (inner.height === 0 && inner.depth === 0) {
|
||||||
body = makeSpan();
|
body = makeSpan();
|
||||||
} else {
|
} else {
|
||||||
|
inner.style.paddingLeft = img.surdWidth + "em";
|
||||||
|
|
||||||
|
// Overlay the image and the argument.
|
||||||
body = buildCommon.makeVList([
|
body = buildCommon.makeVList([
|
||||||
{type: "elem", elem: inner},
|
{type: "elem", elem: inner},
|
||||||
{type: "kern", size: lineClearance},
|
{type: "kern", size: -(inner.height + imgShift)},
|
||||||
{type: "elem", elem: line},
|
{type: "elem", elem: img},
|
||||||
{type: "kern", size: ruleWidth},
|
{type: "kern", size: ruleWidth},
|
||||||
], "firstBaseline", null, options);
|
], "firstBaseline", null, options);
|
||||||
|
body.children[0].children[0].classes.push("svg-align");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!group.value.index) {
|
if (!group.value.index) {
|
||||||
return makeSpan(["mord", "sqrt"], [delim, body], options);
|
return makeSpan(["mord", "sqrt"], [body], options);
|
||||||
} else {
|
} else {
|
||||||
// Handle the optional root index
|
// Handle the optional root index
|
||||||
|
|
||||||
@@ -1113,13 +1117,9 @@ groupTypes.sqrt = function(group, options) {
|
|||||||
const newOptions = options.havingStyle(Style.SCRIPTSCRIPT);
|
const newOptions = options.havingStyle(Style.SCRIPTSCRIPT);
|
||||||
const rootm = buildGroup(group.value.index, newOptions, options);
|
const rootm = buildGroup(group.value.index, newOptions, options);
|
||||||
|
|
||||||
// Figure out the height and depth of the inner part
|
|
||||||
const innerRootHeight = Math.max(delim.height, body.height);
|
|
||||||
const innerRootDepth = Math.max(delim.depth, body.depth);
|
|
||||||
|
|
||||||
// The amount the index is shifted by. This is taken from the TeX
|
// The amount the index is shifted by. This is taken from the TeX
|
||||||
// source, in the definition of `\r@@t`.
|
// source, in the definition of `\r@@t`.
|
||||||
const toShift = 0.6 * (innerRootHeight - innerRootDepth);
|
const toShift = 0.6 * (body.height - body.depth);
|
||||||
|
|
||||||
// Build a VList with the superscript shifted up correctly
|
// Build a VList with the superscript shifted up correctly
|
||||||
const rootVList = buildCommon.makeVList(
|
const rootVList = buildCommon.makeVList(
|
||||||
@@ -1130,7 +1130,7 @@ groupTypes.sqrt = function(group, options) {
|
|||||||
const rootVListWrap = makeSpan(["root"], [rootVList]);
|
const rootVListWrap = makeSpan(["root"], [rootVList]);
|
||||||
|
|
||||||
return makeSpan(["mord", "sqrt"],
|
return makeSpan(["mord", "sqrt"],
|
||||||
[rootVListWrap, delim, body], options);
|
[rootVListWrap, body], options);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
138
src/delimiter.js
@@ -236,11 +236,6 @@ const makeStackedDelim = function(delim, heightTotal, center, options, mode,
|
|||||||
bottom = "\u23a9";
|
bottom = "\u23a9";
|
||||||
repeat = "\u23aa";
|
repeat = "\u23aa";
|
||||||
font = "Size4-Regular";
|
font = "Size4-Regular";
|
||||||
} else if (delim === "\\surd") {
|
|
||||||
top = "\ue001";
|
|
||||||
bottom = "\u23b7";
|
|
||||||
repeat = "\ue000";
|
|
||||||
font = "Size4-Regular";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the metrics of the four sections
|
// Get the metrics of the four sections
|
||||||
@@ -318,6 +313,112 @@ const makeStackedDelim = function(delim, heightTotal, center, options, mode,
|
|||||||
Style.TEXT, options, classes);
|
Style.TEXT, options, classes);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sqrtInnerSVG = {
|
||||||
|
// The main path geometry is from glyph U221A in the font KaTeX Main
|
||||||
|
main: `<svg viewBox='0 0 400000 1000' preserveAspectRatio='xMinYMin
|
||||||
|
slice'><path fill='currentColor' d='M95 622c-2.667 0-7.167-2.667-13.5
|
||||||
|
-8S72 604 72 600c0-2 .333-3.333 1-4 1.333-2.667 23.833-20.667 67.5-54s
|
||||||
|
65.833-50.333 66.5-51c1.333-1.333 3-2 5-2 4.667 0 8.667 3.333 12 10l173
|
||||||
|
378c.667 0 35.333-71 104-213s137.5-285 206.5-429S812 17.333 812 14c5.333
|
||||||
|
-9.333 12-14 20-14h399166v40H845.272L620 507 385 993c-2.667 4.667-9 7-19
|
||||||
|
7-6 0-10-1-12-3L160 575l-65 47zM834 0h399166v40H845z'/></svg>`,
|
||||||
|
|
||||||
|
// size1 is from glyph U221A in the font KaTeX_Size1-Regular
|
||||||
|
1: `<svg viewBox='0 0 400000 1200' preserveAspectRatio='xMinYMin
|
||||||
|
slice'><path fill='currentColor' d='M263 601c.667 0 18 39.667 52 119s68.167
|
||||||
|
158.667 102.5 238 51.833 119.333 52.5 120C810 373.333 980.667 17.667 982 11
|
||||||
|
c4.667-7.333 11-11 19-11h398999v40H1012.333L741 607c-38.667 80.667-84 175-136
|
||||||
|
283s-89.167 185.333-111.5 232-33.833 70.333-34.5 71c-4.667 4.667-12.333 7-23
|
||||||
|
7l-12-1-109-253c-72.667-168-109.333-252-110-252-10.667 8-22 16.667-34 26-22
|
||||||
|
17.333-33.333 26-34 26l-26-26 76-59 76-60zM1001 0h398999v40H1012z'/></svg>`,
|
||||||
|
|
||||||
|
// size2 is from glyph U221A in the font KaTeX_Size2-Regular
|
||||||
|
2: `<svg viewBox='0 0 400000 1800' preserveAspectRatio='xMinYMin
|
||||||
|
slice'><path fill='currentColor' d='M1001 0h398999v40H1013.084S929.667 308 749
|
||||||
|
880s-277 876.333-289 913c-4.667 4.667-12.667 7-24 7h-12c-1.333-3.333-3.667
|
||||||
|
-11.667-7-25-35.333-125.333-106.667-373.333-214-744-10 12-21 25-33 39l-32 39
|
||||||
|
c-6-5.333-15-14-27-26l25-30c26.667-32.667 52-63 76-91l52-60 208 722c56-175.333
|
||||||
|
126.333-397.333 211-666s153.833-488.167 207.5-658.5C944.167 129.167 975 32.667
|
||||||
|
983 10c4-6.667 10-10 18-10zm0 0h398999v40H1013z'/></svg>`,
|
||||||
|
|
||||||
|
// size3 is from glyph U221A in the font KaTeX_Size3-Regular
|
||||||
|
3: `<svg viewBox='0 0 400000 2400' preserveAspectRatio='xMinYMin
|
||||||
|
slice'><path fill='currentColor' d='M424 2398c-1.333-.667-38.5-172-111.5-514
|
||||||
|
S202.667 1370.667 202 1370c0-2-10.667 14.333-32 49-4.667 7.333-9.833 15.667
|
||||||
|
-15.5 25s-9.833 16-12.5 20l-5 7c-4-3.333-8.333-7.667-13-13l-13-13 76-122 77-121
|
||||||
|
209 968c0-2 84.667-361.667 254-1079C896.333 373.667 981.667 13.333 983 10
|
||||||
|
c4-6.667 10-10 18-10h398999v40H1014.622S927.332 418.667 742 1206c-185.333
|
||||||
|
787.333-279.333 1182.333-282 1185-2 6-10 9-24 9-8 0-12-.667-12-2z
|
||||||
|
M1001 0h398999v40H1014z'/></svg>`,
|
||||||
|
|
||||||
|
// size4 is from glyph U221A in the font KaTeX_Size4-Regular
|
||||||
|
4: `<svg viewBox='0 0 400000 3000' preserveAspectRatio='xMinYMin
|
||||||
|
slice'><path fill='currentColor' d='M473 2713C812.333 913.667 982.333 13 983 11
|
||||||
|
c3.333-7.333 9.333-11 18-11h399110v40H1017.698S927.168 518 741.5 1506C555.833
|
||||||
|
2494 462 2989 460 2991c-2 6-10 9-24 9-8 0-12-.667-12-2s-5.333-32-16-92c-50.667
|
||||||
|
-293.333-119.667-693.333-207-1200 0-1.333-5.333 8.667-16 30l-32 64-16 33-26-26
|
||||||
|
76-153 77-151c.667.667 35.667 202 105 604 67.333 400.667 102 602.667 104 606z
|
||||||
|
M1001 0h398999v40H1017z'/></svg>`,
|
||||||
|
|
||||||
|
// tall is from glyph U23B7 in the font KaTeX_Size4-Regular
|
||||||
|
tall: `l-4 4-4 4c-.667.667-2 1.5-4 2.5s-4.167 1.833-6.5 2.5-5.5 1-9.5 1h
|
||||||
|
-12l-28-84c-16.667-52-96.667 -294.333-240-727l-212 -643 -85 170c-4-3.333-8.333
|
||||||
|
-7.667-13 -13l-13-13l77-155 77-156c66 199.333 139 419.667 219 661 l218 661z
|
||||||
|
M702 0H400000v40H742z'/></svg>`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sqrtSpan = function(height, delim, options) {
|
||||||
|
// Create a span containing an SVG image of a sqrt symbol.
|
||||||
|
const span = buildCommon.makeSpan([], [], options);
|
||||||
|
let sizeMultiplier = options.sizeMultiplier; // default
|
||||||
|
|
||||||
|
if (delim.type === "small") {
|
||||||
|
// Get an SVG that is derived from glyph U+221A in font KaTeX-Main.
|
||||||
|
const newOptions = options.havingBaseStyle(delim.style);
|
||||||
|
sizeMultiplier = newOptions.sizeMultiplier / options.sizeMultiplier;
|
||||||
|
|
||||||
|
span.height = 1 * sizeMultiplier;
|
||||||
|
span.style.height = span.height + "em";
|
||||||
|
span.surdWidth = 0.833 * sizeMultiplier; // from the font.
|
||||||
|
//In the font, the glyph is 1000 units tall. The font scale is 1:1000.
|
||||||
|
|
||||||
|
span.innerHTML = `<svg width='100%' height='${span.height}em'>
|
||||||
|
${sqrtInnerSVG['main']}</svg>`;
|
||||||
|
|
||||||
|
} else if (delim.type === "large") {
|
||||||
|
// These SVGs come from fonts: KaTeX_Size1, _Size2, etc.
|
||||||
|
// Get sqrt height from font data
|
||||||
|
span.height = sizeToMaxHeight[delim.size] / sizeMultiplier;
|
||||||
|
span.style.height = span.height + "em";
|
||||||
|
span.surdWidth = 1.0 / sizeMultiplier; // from the font
|
||||||
|
|
||||||
|
span.innerHTML = `<svg width="100%" height="${span.height}em">
|
||||||
|
${sqrtInnerSVG[delim.size]}</svg>`;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Tall sqrt. In TeX, this would be stacked using multiple glyphs.
|
||||||
|
// We'll use a single SVG to accomplish the same thing.
|
||||||
|
span.height = height / sizeMultiplier;
|
||||||
|
span.style.height = span.height + "em";
|
||||||
|
span.surdWidth = 1.056 / sizeMultiplier;
|
||||||
|
const viewBoxHeight = Math.floor(span.height * 1000); // scale = 1:1000
|
||||||
|
const vertSegment = viewBoxHeight - 54;
|
||||||
|
|
||||||
|
// This \sqrt is customized in both height and width. We set the
|
||||||
|
// height now. Then CSS will stretch the image to the correct width.
|
||||||
|
// This SVG path comes from glyph U+23B7, font KaTeX_Size4-Regular.
|
||||||
|
span.innerHTML = `<svg width='100%' height='${span.height}em'>
|
||||||
|
<svg viewBox='0 0 400000 ${viewBoxHeight}'
|
||||||
|
preserveAspectRatio='xMinYMax slice'>
|
||||||
|
<path fill='currentColor' d='M702 0H400000v40H742v${vertSegment}
|
||||||
|
${sqrtInnerSVG['tall']}</svg>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.sizeMultiplier = sizeMultiplier;
|
||||||
|
|
||||||
|
return span;
|
||||||
|
};
|
||||||
|
|
||||||
// There are three kinds of delimiters, delimiters that stack when they become
|
// There are three kinds of delimiters, delimiters that stack when they become
|
||||||
// too large
|
// too large
|
||||||
const stackLargeDelimiters = [
|
const stackLargeDelimiters = [
|
||||||
@@ -488,16 +589,23 @@ const makeCustomSizedDelim = function(delim, height, center, options, mode,
|
|||||||
// Look through the sequence
|
// Look through the sequence
|
||||||
const delimType = traverseSequence(delim, height, sequence, options);
|
const delimType = traverseSequence(delim, height, sequence, options);
|
||||||
|
|
||||||
// Depending on the sequence element we decided on, call the appropriate
|
if (delim === "\\surd") {
|
||||||
// function.
|
// Get an SVG image for
|
||||||
if (delimType.type === "small") {
|
return sqrtSpan(height, delimType, options);
|
||||||
return makeSmallDelim(delim, delimType.style, center, options, mode,
|
} else {
|
||||||
classes);
|
// Get the delimiter from font glyphs.
|
||||||
} else if (delimType.type === "large") {
|
// Depending on the sequence element we decided on, call the
|
||||||
return makeLargeDelim(delim, delimType.size, center, options, mode,
|
// appropriate function.
|
||||||
classes);
|
if (delimType.type === "small") {
|
||||||
} else if (delimType.type === "stack") {
|
return makeSmallDelim(delim, delimType.style, center, options,
|
||||||
return makeStackedDelim(delim, height, center, options, mode, classes);
|
mode, classes);
|
||||||
|
} else if (delimType.type === "large") {
|
||||||
|
return makeLargeDelim(delim, delimType.size, center, options, mode,
|
||||||
|
classes);
|
||||||
|
} else if (delimType.type === "stack") {
|
||||||
|
return makeStackedDelim(delim, height, center, options, mode,
|
||||||
|
classes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -399,17 +399,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sqrt {
|
.sqrt {
|
||||||
> .sqrt-sign {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sqrt-line {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
border-bottom-style: solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .root {
|
> .root {
|
||||||
// These values are taken from the definition of `\r@@t`,
|
// These values are taken from the definition of `\r@@t`,
|
||||||
// `\mkern 5mu` and `\mkern -10mu`.
|
// `\mkern 5mu` and `\mkern -10mu`.
|
||||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |