diff --git a/src/stretchy.js b/src/stretchy.js index bea4c3ab..95598fa8 100644 --- a/src/stretchy.js +++ b/src/stretchy.js @@ -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; diff --git a/static/katex.less b/static/katex.less index 9d9425d7..721867aa 100644 --- a/static/katex.less +++ b/static/katex.less @@ -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; diff --git a/test/screenshotter/images/ExtensibleArrows-chrome.png b/test/screenshotter/images/ExtensibleArrows-chrome.png index 2f516662..5aab6439 100644 Binary files a/test/screenshotter/images/ExtensibleArrows-chrome.png and b/test/screenshotter/images/ExtensibleArrows-chrome.png differ diff --git a/test/screenshotter/images/ExtensibleArrows-firefox.png b/test/screenshotter/images/ExtensibleArrows-firefox.png index ab8cac64..3050bdd7 100644 Binary files a/test/screenshotter/images/ExtensibleArrows-firefox.png and b/test/screenshotter/images/ExtensibleArrows-firefox.png differ diff --git a/test/screenshotter/images/HorizontalBraces-chrome.png b/test/screenshotter/images/HorizontalBraces-chrome.png index b6103cf9..390fe56b 100644 Binary files a/test/screenshotter/images/HorizontalBraces-chrome.png and b/test/screenshotter/images/HorizontalBraces-chrome.png differ diff --git a/test/screenshotter/images/HorizontalBraces-firefox.png b/test/screenshotter/images/HorizontalBraces-firefox.png index 73ab3090..98308009 100644 Binary files a/test/screenshotter/images/HorizontalBraces-firefox.png and b/test/screenshotter/images/HorizontalBraces-firefox.png differ diff --git a/test/screenshotter/images/LowerAccent-chrome.png b/test/screenshotter/images/LowerAccent-chrome.png index 249f97c3..9371a751 100644 Binary files a/test/screenshotter/images/LowerAccent-chrome.png and b/test/screenshotter/images/LowerAccent-chrome.png differ diff --git a/test/screenshotter/images/LowerAccent-firefox.png b/test/screenshotter/images/LowerAccent-firefox.png index f3319345..5977e740 100644 Binary files a/test/screenshotter/images/LowerAccent-firefox.png and b/test/screenshotter/images/LowerAccent-firefox.png differ diff --git a/test/screenshotter/images/StretchyAccent-chrome.png b/test/screenshotter/images/StretchyAccent-chrome.png index 82640f42..d139bdbd 100644 Binary files a/test/screenshotter/images/StretchyAccent-chrome.png and b/test/screenshotter/images/StretchyAccent-chrome.png differ diff --git a/test/screenshotter/images/StretchyAccent-firefox.png b/test/screenshotter/images/StretchyAccent-firefox.png index ccb75dde..90e5ebbc 100644 Binary files a/test/screenshotter/images/StretchyAccent-firefox.png and b/test/screenshotter/images/StretchyAccent-firefox.png differ