Convert nested SVGs to single-level SVGs (#909)
* Convert nested SVGs to single-level SVGs This PR evades a Safari bug which causes nested SVGs to zoom improperly. Fixes the remainder of issue #883. * Add omitted word * Fix lint errors * update screenshots * Pick up review comments * Clean up variable names Remove two more redundant variables.
@@ -58,27 +58,26 @@ const mathMLnode = function(label) {
|
||||
// Licensed under the SIL Open Font License, Version 1.1.
|
||||
// See \nhttp://scripts.sil.org/OFL
|
||||
|
||||
// Nested SVGs
|
||||
// Many of the KaTeX SVG images contain a nested SVG. This is done to
|
||||
// achieve a stretchy image while avoiding distortion of arrowheads or
|
||||
// brace corners.
|
||||
// Very Long SVGs
|
||||
// Many of the KaTeX stretchy wide elements use a long SVG image and an
|
||||
// overflow: hidden tactic to achieve a stretchy image while avoiding
|
||||
// distortion of arrowheads or brace corners.
|
||||
|
||||
// The inner SVG typically contains a very long (400 em) arrow.
|
||||
// The SVG typically contains a very long (400 em) arrow.
|
||||
|
||||
// The outer SVG acts like a window that exposes only part of the inner SVG.
|
||||
// The outer SVG will grow or shrink to match the dimensions set by CSS.
|
||||
// The SVG is in a container span that has overflow: hidden, so the span
|
||||
// acts like a window that exposes only part of the SVG.
|
||||
|
||||
// The inner SVG always has a longer, thinner aspect ratio than the outer
|
||||
// SVG. After the inner SVG fills 100% of the height of the outer SVG,
|
||||
// The SVG always has a longer, thinner aspect ratio than the container span.
|
||||
// After the SVG fills 100% of the height of the container span,
|
||||
// there is a long arrow shaft left over. That left-over shaft is not shown.
|
||||
// Instead, it is sliced off because the inner SVG is set to
|
||||
// "preserveAspectRatio='... slice'".
|
||||
// Instead, it is sliced off because the span's CSS has overflow: hidden.
|
||||
|
||||
// Thus, the reader sees an arrow that matches the subject matter width
|
||||
// without distortion.
|
||||
|
||||
// Some functions, such as \cancel, need to vary their aspect ratio. These
|
||||
// functions do not get the nested SVG treatment.
|
||||
// functions do not get the overflow SVG treatment.
|
||||
|
||||
// Second Brush Stroke
|
||||
// Low resolution monitors struggle to display images in fine detail.
|
||||
@@ -154,11 +153,13 @@ const svgSpan = function(group, options) {
|
||||
let attributes = [];
|
||||
let height;
|
||||
let viewBoxWidth = 400000; // default
|
||||
let viewBoxHeight = 0;
|
||||
let minWidth = 0;
|
||||
let path;
|
||||
let paths;
|
||||
let pathName;
|
||||
let svgNode;
|
||||
const classNames = [];
|
||||
let span;
|
||||
|
||||
if (utils.contains(["widehat", "widetilde", "undertilde"], label)) {
|
||||
// There are four SVG images available for each function.
|
||||
@@ -193,61 +194,48 @@ const svgSpan = function(group, options) {
|
||||
attributes.push(["preserveAspectRatio", "none"]);
|
||||
|
||||
svgNode = new domTree.svgNode([path], attributes);
|
||||
span = buildCommon.makeSpan([], [svgNode], options);
|
||||
|
||||
} else {
|
||||
let width;
|
||||
let widthClass;
|
||||
let align;
|
||||
const spans = [];
|
||||
|
||||
const [paths, gWidth, vbHeight, alignOne] = katexImagesData[label];
|
||||
[paths, minWidth, viewBoxHeight, align] = katexImagesData[label];
|
||||
const numSvgChildren = paths.length;
|
||||
const innerSVGs = [];
|
||||
height = vbHeight / 1000;
|
||||
minWidth = gWidth;
|
||||
height = viewBoxHeight / 1000;
|
||||
|
||||
for (let i = 0; i < numSvgChildren; i++) {
|
||||
path = new domTree.pathNode(paths[i]);
|
||||
attributes = [];
|
||||
|
||||
if (numSvgChildren === 1) {
|
||||
width = "400em";
|
||||
align = alignOne;
|
||||
} else if (numSvgChildren === 2) {
|
||||
// small overlap to prevent a 1 pixel gap.
|
||||
if (i > 0) {
|
||||
attributes.push(["x", "50%"]);
|
||||
}
|
||||
width = ["50.1%", "50%"][i];
|
||||
attributes = [["width", "400em"], ["height", height + "em"]];
|
||||
attributes.push(["viewBox", `0 0 ${viewBoxWidth} ${viewBoxHeight}`]);
|
||||
|
||||
if (numSvgChildren === 2) {
|
||||
widthClass = ["halfarrow-left", "halfarrow-right"][i];
|
||||
align = ["xMinYMin", "xMaxYMin"][i];
|
||||
} else {
|
||||
// 3 inner SVGs, as in a brace
|
||||
if (i > 0) {
|
||||
attributes.push(["x", [null, "25%", "74.9%"][i]]);
|
||||
}
|
||||
width = ["25.5%", "50%", "25.1%"][i];
|
||||
} else if (numSvgChildren === 3) {
|
||||
widthClass = ["brace-left", "brace-center", "brace-right"][i];
|
||||
align = ["xMinYMin", "xMidYMin", "xMaxYMin"][i];
|
||||
}
|
||||
|
||||
attributes.push(["width", width]);
|
||||
attributes.push(["height", height + "em"]);
|
||||
attributes.push(["viewBox", `0 0 ${viewBoxWidth} ${vbHeight}`]);
|
||||
attributes.push(["preserveAspectRatio", align + " slice"]);
|
||||
svgNode = new domTree.svgNode([path], attributes);
|
||||
|
||||
if (numSvgChildren > 1) {
|
||||
innerSVGs.push(new domTree.svgNode([path], attributes));
|
||||
if (numSvgChildren === 1) {
|
||||
span = buildCommon.makeSpan(["hide-tail"], [svgNode], options);
|
||||
} 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");
|
||||
span = buildCommon.makeSpan([widthClass], [svgNode], options);
|
||||
span.style.height = height + "em";
|
||||
spans.push(span);
|
||||
}
|
||||
}
|
||||
|
||||
if (numSvgChildren > 1) {
|
||||
attributes = [["width", "100%"], ["height", height + "em"]];
|
||||
svgNode = new domTree.svgNode(innerSVGs, attributes);
|
||||
span = buildCommon.makeSpan(["stretchy"], spans, options);
|
||||
}
|
||||
}
|
||||
|
||||
const span = buildCommon.makeSpan(classNames, [svgNode], options);
|
||||
// Note that we are returning span.depth = 0.
|
||||
// Any adjustments relative to the baseline must be done in buildHTML.
|
||||
span.height = height;
|
||||
|
@@ -582,6 +582,41 @@
|
||||
overflow: hidden; // This line applies to all browsers.
|
||||
}
|
||||
|
||||
.halfarrow-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 50.1%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.halfarrow-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.brace-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 25.1%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.brace-center {
|
||||
position: absolute;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.brace-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 25.1%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Lengthen the extensible arrows via padding.
|
||||
.x-arrow-pad {
|
||||
padding: 0 0.5em;
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 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: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |