Upgrade \sqrt zoom and width (#890)
* Fix \sqrt zoom in Safari This PR evades a Safari bug which causes nested SVGs to zoom improperly. `\sqrt` and single-ended arrow SVGs have been modified. These have been converted from nested SVGs to single level. Their long tails are now sliced off using CSS `overflow: hidden`. Safari will still improperly zoom any double-ended stretchy arrows and horizontal braces. * Fix \sqrt{} Even if the function argument is empty, still render an SVG whose width equals the surd glyph. * Fix tall \sqrt when scaled * update screenshots affected by sqrt fixes * more screenshots after changes to fix sqrt * Pick up review comments
@@ -1115,6 +1115,10 @@ 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
|
||||||
let inner = buildGroup(group.value.body, options.havingCrampedStyle());
|
let inner = buildGroup(group.value.body, options.havingCrampedStyle());
|
||||||
|
if (inner.height === 0) {
|
||||||
|
// Render a small surd.
|
||||||
|
inner.height = options.fontMetrics().xHeight;
|
||||||
|
}
|
||||||
|
|
||||||
// Some groups can return document fragments. Handle those by wrapping
|
// Some groups can return document fragments. Handle those by wrapping
|
||||||
// them in a span.
|
// them in a span.
|
||||||
@@ -1159,26 +1163,16 @@ groupTypes.sqrt = function(group, options) {
|
|||||||
// Shift the sqrt image
|
// Shift the sqrt image
|
||||||
const imgShift = img.height - inner.height - lineClearance - ruleWidth;
|
const imgShift = img.height - inner.height - lineClearance - ruleWidth;
|
||||||
|
|
||||||
// We add a special case here, because even when `inner` is empty, we
|
inner.style.paddingLeft = img.advanceWidth + "em";
|
||||||
// still get a line. So, we use a simple heuristic to decide if we
|
|
||||||
// should omit the body entirely. (note this doesn't work for something
|
|
||||||
// like `\sqrt{\rlap{x}}`, but if someone is doing that they deserve for
|
|
||||||
// it not to work.
|
|
||||||
let body;
|
|
||||||
if (inner.height === 0 && inner.depth === 0) {
|
|
||||||
body = makeSpan();
|
|
||||||
} else {
|
|
||||||
inner.style.paddingLeft = img.surdWidth + "em";
|
|
||||||
|
|
||||||
// Overlay the image and the argument.
|
// Overlay the image and the argument.
|
||||||
body = buildCommon.makeVList([
|
const body = buildCommon.makeVList([
|
||||||
{type: "elem", elem: inner},
|
{type: "elem", elem: inner},
|
||||||
{type: "kern", size: -(inner.height + imgShift)},
|
{type: "kern", size: -(inner.height + imgShift)},
|
||||||
{type: "elem", elem: img},
|
{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");
|
body.children[0].children[0].classes.push("svg-align");
|
||||||
}
|
|
||||||
|
|
||||||
if (!group.value.index) {
|
if (!group.value.index) {
|
||||||
return makeSpan(["mord", "sqrt"], [body], options);
|
return makeSpan(["mord", "sqrt"], [body], options);
|
||||||
|
@@ -329,14 +329,13 @@ const sqrtSvg = function(sqrtName, height, viewBoxHeight, options) {
|
|||||||
}
|
}
|
||||||
const pathNode = new domTree.pathNode(sqrtName, alternate);
|
const pathNode = new domTree.pathNode(sqrtName, alternate);
|
||||||
|
|
||||||
let attributes = [["width", "100%"], ["height", height + "em"]];
|
// Note: 1000:1 ratio of viewBox to document em width.
|
||||||
|
const attributes = [["width", "400em"], ["height", height + "em"]];
|
||||||
attributes.push(["viewBox", "0 0 400000 " + viewBoxHeight]);
|
attributes.push(["viewBox", "0 0 400000 " + viewBoxHeight]);
|
||||||
attributes.push(["preserveAspectRatio", "xMinYMin slice"]);
|
attributes.push(["preserveAspectRatio", "xMinYMin slice"]);
|
||||||
const innerSVG = new domTree.svgNode([pathNode], attributes);
|
const svg = new domTree.svgNode([pathNode], attributes);
|
||||||
|
|
||||||
attributes = [["width", "100%"], ["height", height + "em"]];
|
return buildCommon.makeSpan(["hide-tail"], [svg], options);
|
||||||
const svg = new domTree.svgNode([innerSVG], attributes);
|
|
||||||
return buildCommon.makeSpan([], [svg], options);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sqrtSpan = function(height, delim, options) {
|
const sqrtSpan = function(height, delim, options) {
|
||||||
@@ -353,22 +352,25 @@ const sqrtSpan = function(height, delim, options) {
|
|||||||
sizeMultiplier = newOptions.sizeMultiplier / options.sizeMultiplier;
|
sizeMultiplier = newOptions.sizeMultiplier / options.sizeMultiplier;
|
||||||
spanHeight = 1 * sizeMultiplier;
|
spanHeight = 1 * sizeMultiplier;
|
||||||
span = sqrtSvg("sqrtMain", spanHeight, viewBoxHeight, options);
|
span = sqrtSvg("sqrtMain", spanHeight, viewBoxHeight, options);
|
||||||
span.surdWidth = 0.833 * sizeMultiplier; // from the font.
|
span.style.minWidth = "0.853em";
|
||||||
|
span.advanceWidth = 0.833 * sizeMultiplier; // from the font.
|
||||||
|
|
||||||
} else if (delim.type === "large") {
|
} else if (delim.type === "large") {
|
||||||
// These SVGs come from fonts: KaTeX_Size1, _Size2, etc.
|
// These SVGs come from fonts: KaTeX_Size1, _Size2, etc.
|
||||||
viewBoxHeight = 1000 * sizeToMaxHeight[delim.size];
|
viewBoxHeight = 1000 * sizeToMaxHeight[delim.size];
|
||||||
spanHeight = sizeToMaxHeight[delim.size] / sizeMultiplier;
|
spanHeight = sizeToMaxHeight[delim.size] / sizeMultiplier;
|
||||||
span = sqrtSvg("sqrtSize" + delim.size, spanHeight, viewBoxHeight, options);
|
span = sqrtSvg("sqrtSize" + delim.size, spanHeight, viewBoxHeight, options);
|
||||||
span.surdWidth = 1.0 / sizeMultiplier; // from the font
|
span.style.minWidth = "1.02em";
|
||||||
|
span.advanceWidth = 1.0 / sizeMultiplier; // from the font
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Tall sqrt. In TeX, this would be stacked using multiple glyphs.
|
// Tall sqrt. In TeX, this would be stacked using multiple glyphs.
|
||||||
// We'll use a single SVG to accomplish the same thing.
|
// We'll use a single SVG to accomplish the same thing.
|
||||||
spanHeight = height / sizeMultiplier;
|
spanHeight = height / sizeMultiplier;
|
||||||
viewBoxHeight = Math.floor(1000 * spanHeight);
|
viewBoxHeight = Math.floor(1000 * height);
|
||||||
span = sqrtSvg("sqrtTall", spanHeight, viewBoxHeight, options);
|
span = sqrtSvg("sqrtTall", spanHeight, viewBoxHeight, options);
|
||||||
span.surdWidth = 1.056 / sizeMultiplier;
|
span.style.minWidth = "0.742em";
|
||||||
|
span.advanceWidth = 1.056 / sizeMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.height = spanHeight;
|
span.height = spanHeight;
|
||||||
|
@@ -158,6 +158,7 @@ const svgSpan = function(group, options) {
|
|||||||
let path;
|
let path;
|
||||||
let pathName;
|
let pathName;
|
||||||
let svgNode;
|
let svgNode;
|
||||||
|
const classNames = [];
|
||||||
|
|
||||||
if (utils.contains(["widehat", "widetilde", "undertilde"], label)) {
|
if (utils.contains(["widehat", "widetilde", "undertilde"], label)) {
|
||||||
// There are four SVG images available for each function.
|
// There are four SVG images available for each function.
|
||||||
@@ -208,7 +209,7 @@ const svgSpan = function(group, options) {
|
|||||||
attributes = [];
|
attributes = [];
|
||||||
|
|
||||||
if (numSvgChildren === 1) {
|
if (numSvgChildren === 1) {
|
||||||
width = "100%";
|
width = "400em";
|
||||||
align = alignOne;
|
align = alignOne;
|
||||||
} else if (numSvgChildren === 2) {
|
} else if (numSvgChildren === 2) {
|
||||||
// small overlap to prevent a 1 pixel gap.
|
// small overlap to prevent a 1 pixel gap.
|
||||||
@@ -231,13 +232,22 @@ const svgSpan = function(group, options) {
|
|||||||
attributes.push(["viewBox", `0 0 ${viewBoxWidth} ${vbHeight}`]);
|
attributes.push(["viewBox", `0 0 ${viewBoxWidth} ${vbHeight}`]);
|
||||||
attributes.push(["preserveAspectRatio", align + " slice"]);
|
attributes.push(["preserveAspectRatio", align + " slice"]);
|
||||||
|
|
||||||
innerSVGs.push(new domTree.svgNode([path], attributes));
|
if (numSvgChildren > 1) {
|
||||||
|
innerSVGs.push(new domTree.svgNode([path], attributes));
|
||||||
|
} else {
|
||||||
|
// The single svgChild is a child of a hide-tail span, not the
|
||||||
|
// child of another svg.
|
||||||
|
svgNode = new domTree.svgNode([path], attributes);
|
||||||
|
classNames.push("hide-tail");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (numSvgChildren > 1) {
|
||||||
|
attributes = [["width", "100%"], ["height", height + "em"]];
|
||||||
|
svgNode = new domTree.svgNode(innerSVGs, attributes);
|
||||||
}
|
}
|
||||||
attributes = [["width", "100%"], ["height", height + "em"]];
|
|
||||||
svgNode = new domTree.svgNode(innerSVGs, attributes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const span = buildCommon.makeSpan([], [svgNode], options);
|
const span = buildCommon.makeSpan(classNames, [svgNode], options);
|
||||||
// Note that we are returning span.depth = 0.
|
// Note that we are returning span.depth = 0.
|
||||||
// Any adjustments relative to the baseline must be done in buildHTML.
|
// Any adjustments relative to the baseline must be done in buildHTML.
|
||||||
span.height = height;
|
span.height = height;
|
||||||
|
@@ -575,6 +575,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide the long tail of a stretchy SVG.
|
||||||
|
.hide-tail {
|
||||||
|
width: 100%; // necessary only to get IE to work properly
|
||||||
|
position: relative; // ditto
|
||||||
|
overflow: hidden; // This line applies to all browsers.
|
||||||
|
}
|
||||||
|
|
||||||
// Lengthen the extensible arrows via padding.
|
// Lengthen the extensible arrows via padding.
|
||||||
.x-arrow-pad {
|
.x-arrow-pad {
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 49 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: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 11 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: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |