text: // @flow
  /**
   * This file provides support to buildMathML.js and buildHTML.js
   * for stretchy wide elements rendered from SVG files
   * and other CSS trickery.
   */
  
  import {LineNode, PathNode, SvgNode} from "./domTree";
  import buildCommon from "./buildCommon";
  import mathMLTree from "./mathMLTree";
  import utils from "./utils";
  
  import type Options from "./Options";
  import type {ParseNode, AnyParseNode} from "./parseNode";
  import type {DomSpan, HtmlDomNode, SvgSpan} from "./domTree";
  
  const stretchyCodePoint: {[string]: string} = {
      widehat: "^",
      widecheck: "ˇ",
      widetilde: "~",
      utilde: "~",
      overleftarrow: "\u2190",
      underleftarrow: "\u2190",
      xleftarrow: "\u2190",
      overrightarrow: "\u2192",
      underrightarrow: "\u2192",
      xrightarrow: "\u2192",
      underbrace: "\u23df",
      overbrace: "\u23de",
      overgroup: "\u23e0",
      undergroup: "\u23e1",
      overleftrightarrow: "\u2194",
      underleftrightarrow: "\u2194",
      xleftrightarrow: "\u2194",
      Overrightarrow: "\u21d2",
      xRightarrow: "\u21d2",
      overleftharpoon: "\u21bc",
      xleftharpoonup: "\u21bc",
      overrightharpoon: "\u21c0",
      xrightharpoonup: "\u21c0",
      xLeftarrow: "\u21d0",
      xLeftrightarrow: "\u21d4",
      xhookleftarrow: "\u21a9",
      xhookrightarrow: "\u21aa",
      xmapsto: "\u21a6",
      xrightharpoondown: "\u21c1",
      xleftharpoondown: "\u21bd",
      xrightleftharpoons: "\u21cc",
      xleftrightharpoons: "\u21cb",
      xtwoheadleftarrow: "\u219e",
      xtwoheadrightarrow: "\u21a0",
      xlongequal: "=",
      xtofrom: "\u21c4",
      xrightleftarrows: "\u21c4",
      xrightequilibrium: "\u21cc",  // Not a perfect match.
      xleftequilibrium: "\u21cb",   // None better available.
      "\\\\cdrightarrow": "\u2192",
      "\\\\cdleftarrow": "\u2190",
      "\\\\cdlongequal": "=",
  };
  
  const mathMLnode = function(label: string): mathMLTree.MathNode {
      const node = new mathMLTree.MathNode(
          "mo", [new mathMLTree.TextNode(stretchyCodePoint[label])]);
      node.setAttribute("stretchy", "true");
      return node;
  };
  
  // Many of the KaTeX SVG images have been adapted from glyphs in KaTeX fonts.
  // Copyright (c) 2009-2010, Design Science, Inc. (<www.mathjax.org>)
  // Copyright (c) 2014-2017 Khan Academy (<www.khanacademy.org>)
  // Licensed under the SIL Open Font License, Version 1.1.
  // See \nhttp://scripts.sil.org/OFL
  
  // 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 SVG typically contains a very long (400 em) arrow.
  
  //    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 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 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 overflow SVG treatment.
  
  // Second Brush Stroke
  //    Low resolution monitors struggle to display images in fine detail.
  //    So browsers apply anti-aliasing. A long straight arrow shaft therefore
  //    will sometimes appear as if it has a blurred edge.
  
  //    To mitigate this, these SVG files contain a second "brush-stroke" on the
  //    arrow shafts. That is, a second long thin rectangular SVG path has been
  //    written directly on top of each arrow shaft. This reinforcement causes
  //    some of the screen pixels to display as black instead of the anti-aliased
  //    gray pixel that a  single path would generate. So we get arrow shafts
  //    whose edges appear to be sharper.
  
  // In the katexImagesData object just below, the dimensions all
  // correspond to path geometry inside the relevant SVG.
  // For example, \overrightarrow uses the same arrowhead as glyph U+2192
  // from the KaTeX Main font. The scaling factor is 1000.
  // That is, inside the font, that arrowhead is 522 units tall, which
  // corresponds to 0.522 em inside the document.
  
  const katexImagesData: {
      [string]: ([string[], number, number] | [[string], number, number, string])
  } = {
                     //   path(s), minWidth, height, align
      overrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"],
      overleftarrow: [["leftarrow"], 0.888, 522, "xMinYMin"],
      underrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"],
      underleftarrow: [["leftarrow"], 0.888, 522, "xMinYMin"],
      xrightarrow: [["rightarrow"], 1.469, 522, "xMaxYMin"],
      "\\cdrightarrow": [["rightarrow"], 3.0, 522, "xMaxYMin"], // CD minwwidth2.5pc
      xleftarrow: [["leftarrow"], 1.469, 522, "xMinYMin"],
      "\\cdleftarrow": [["leftarrow"], 3.0, 522, "xMinYMin"],
      Overrightarrow: [["doublerightarrow"], 0.888, 560, "xMaxYMin"],
      xRightarrow: [["doublerightarrow"], 1.526, 560, "xMaxYMin"],
      xLeftarrow: [["doubleleftarrow"], 1.526, 560, "xMinYMin"],
      overleftharpoon: [["leftharpoon"], 0.888, 522, "xMinYMin"],
      xleftharpoonup: [["leftharpoon"], 0.888, 522, "xMinYMin"],
      xleftharpoondown: [["leftharpoondown"], 0.888, 522, "xMinYMin"],
      overrightharpoon: [["rightharpoon"], 0.888, 522, "xMaxYMin"],
      xrightharpoonup: [["rightharpoon"], 0.888, 522, "xMaxYMin"],
      xrightharpoondown: [["rightharpoondown"], 0.888, 522, "xMaxYMin"],
      xlongequal: [["longequal"], 0.888, 334, "xMinYMin"],
      "\\cdlongequal": [["longequal"], 3.0, 334, "xMinYMin"],
      xtwoheadleftarrow: [["twoheadleftarrow"], 0.888, 334, "xMinYMin"],
      xtwoheadrightarrow: [["twoheadrightarrow"], 0.888, 334, "xMaxYMin"],
  
      overleftrightarrow: [["leftarrow", "rightarrow"], 0.888, 522],
      overbrace: [["leftbrace", "midbrace", "rightbrace"], 1.6, 548],
      underbrace: [["leftbraceunder", "midbraceunder", "rightbraceunder"],
          1.6, 548],
      underleftrightarrow: [["leftarrow", "rightarrow"], 0.888, 522],
      xleftrightarrow: [["leftarrow", "rightarrow"], 1.75, 522],
      xLeftrightarrow: [["doubleleftarrow", "doublerightarrow"], 1.75, 560],
      xrightleftharpoons: [["leftharpoondownplus", "rightharpoonplus"], 1.75, 716],
      xleftrightharpoons: [["leftharpoonplus", "rightharpoondownplus"],
          1.75, 716],
      xhookleftarrow: [["leftarrow", "righthook"], 1.08, 522],
      xhookrightarrow: [["lefthook", "rightarrow"], 1.08, 522],
      overlinesegment: [["leftlinesegment", "rightlinesegment"], 0.888, 522],
      underlinesegment: [["leftlinesegment", "rightlinesegment"], 0.888, 522],
      overgroup: [["leftgroup", "rightgroup"], 0.888, 342],
      undergroup: [["leftgroupunder", "rightgroupunder"], 0.888, 342],
      xmapsto: [["leftmapsto", "rightarrow"], 1.5, 522],
      xtofrom: [["leftToFrom", "rightToFrom"], 1.75, 528],
  
      // The next three arrows are from the mhchem package.
      // In mhchem.sty, min-length is 2.0em. But these arrows might appear in the
      // document as \xrightarrow or \xrightleftharpoons. Those have
      // min-length = 1.75em, so we set min-length on these next three to match.
      xrightleftarrows: [["baraboveleftarrow", "rightarrowabovebar"], 1.75, 901],
      xrightequilibrium: [["baraboveshortleftharpoon",
          "rightharpoonaboveshortbar"], 1.75, 716],
      xleftequilibrium: [["shortbaraboveleftharpoon",
          "shortrightharpoonabovebar"], 1.75, 716],
  };
  
  const groupLength = function(arg: AnyParseNode): number {
      if (arg.type === "ordgroup") {
          return arg.body.length;
      } else {
          return 1;
      }
  };
  
  const svgSpan = function(
      group: ParseNode<"accent"> | ParseNode<"accentUnder"> | ParseNode<"xArrow">
           | ParseNode<"horizBrace">,
      options: Options,
  ): DomSpan | SvgSpan {
      // Create a span with inline SVG for the element.
      function buildSvgSpan_(): {
          span: DomSpan | SvgSpan,
          minWidth: number,
          height: number,
      } {
          let viewBoxWidth = 400000;  // default
          const label = group.label.substr(1);
          if (utils.contains(["widehat", "widecheck", "widetilde", "utilde"],
              label)) {
              // Each type in the `if` statement corresponds to one of the ParseNode
              // types below. This narrowing is required to access `grp.base`.
              // $FlowFixMe
              const grp: ParseNode<"accent"> | ParseNode<"accentUnder"> = group;
              // There are four SVG images available for each function.
              // Choose a taller image when there are more characters.
              const numChars = groupLength(grp.base);
              let viewBoxHeight;
              let pathName;
              let height;
  
              if (numChars > 5) {
                  if (label === "widehat" || label === "widecheck") {
                      viewBoxHeight = 420;
                      viewBoxWidth = 2364;
                      height = 0.42;
                      pathName = label + "4";
                  } else {
                      viewBoxHeight = 312;
                      viewBoxWidth = 2340;
                      height = 0.34;
                      pathName = "tilde4";
                  }
              } else {
                  const imgIndex = [1, 1, 2, 2, 3, 3][numChars];
                  if (label === "widehat" || label === "widecheck") {
                      viewBoxWidth = [0, 1062, 2364, 2364, 2364][imgIndex];
                      viewBoxHeight = [0, 239, 300, 360, 420][imgIndex];
                      height = [0, 0.24, 0.3, 0.3, 0.36, 0.42][imgIndex];
                      pathName = label + imgIndex;
                  } else {
                      viewBoxWidth = [0, 600, 1033, 2339, 2340][imgIndex];
                      viewBoxHeight = [0, 260, 286, 306, 312][imgIndex];
                      height = [0, 0.26, 0.286, 0.3, 0.306, 0.34][imgIndex];
                      pathName = "tilde" + imgIndex;
                  }
              }
              const path = new PathNode(pathName);
              const svgNode = new SvgNode([path], {
                  "width": "100%",
                  "height": height + "em",
                  "viewBox": `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
                  "preserveAspectRatio": "none",
              });
              return {
                  span: buildCommon.makeSvgSpan([], [svgNode], options),
                  minWidth: 0,
                  height,
              };
          } else {
              const spans = [];
  
              const data = katexImagesData[label];
              const [paths, minWidth, viewBoxHeight] = data;
              const height = viewBoxHeight / 1000;
  
              const numSvgChildren = paths.length;
              let widthClasses;
              let aligns;
              if (numSvgChildren === 1) {
                  // $FlowFixMe: All these cases must be of the 4-tuple type.
                  const align1: string = data[3];
                  widthClasses = ["hide-tail"];
                  aligns = [align1];
              } else if (numSvgChildren === 2) {
                  widthClasses = ["halfarrow-left", "halfarrow-right"];
                  aligns = ["xMinYMin", "xMaxYMin"];
              } else if (numSvgChildren === 3) {
                  widthClasses = ["brace-left", "brace-center", "brace-right"];
                  aligns = ["xMinYMin", "xMidYMin", "xMaxYMin"];
              } else {
                  throw new Error(
                      `Correct katexImagesData or update code here to support
                      ${numSvgChildren} children.`);
              }
  
              for (let i = 0; i < numSvgChildren; i++) {
                  const path = new PathNode(paths[i]);
  
                  const svgNode = new SvgNode([path], {
                      "width": "400em",
                      "height": height + "em",
                      "viewBox": `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
                      "preserveAspectRatio": aligns[i] + " slice",
                  });
  
                  const span = buildCommon.makeSvgSpan(
                      [widthClasses[i]], [svgNode], options);
                  if (numSvgChildren === 1) {
                      return {span, minWidth, height};
                  } else {
                      span.style.height = height + "em";
                      spans.push(span);
                  }
              }
  
              return {
                  span: buildCommon.makeSpan(["stretchy"], spans, options),
                  minWidth,
                  height,
              };
          }
      } // buildSvgSpan_()
      const {span, minWidth, height} = buildSvgSpan_();
  
      // Note that we are returning span.depth = 0.
      // Any adjustments relative to the baseline must be done in buildHTML.
      span.height = height;
      span.style.height = height + "em";
      if (minWidth > 0) {
          span.style.minWidth = minWidth + "em";
      }
  
      return span;
  };
  
  const encloseSpan = function(
      inner: HtmlDomNode,
      label: string,
      topPad: number,
      bottomPad: number,
      options: Options,
  ): DomSpan | SvgSpan {
      // Return an image span for \cancel, \bcancel, \xcancel, \fbox, or \angl
      let img;
      const totalHeight = inner.height + inner.depth + topPad + bottomPad;
  
      if (/fbox|color|angl/.test(label)) {
          img = buildCommon.makeSpan(["stretchy", label], [], options);
  
          if (label === "fbox") {
              const color = options.color && options.getColor();
              if (color) {
                  img.style.borderColor = color;
              }
          }
  
      } else {
          // \cancel, \bcancel, or \xcancel
          // Since \cancel's SVG is inline and it omits the viewBox attribute,
          // its stroke-width will not vary with span area.
  
          const lines = [];
          if (/^[bx]cancel$/.test(label)) {
              lines.push(new LineNode({
                  "x1": "0",
                  "y1": "0",
                  "x2": "100%",
                  "y2": "100%",
                  "stroke-width": "0.046em",
              }));
          }
  
          if (/^x?cancel$/.test(label)) {
              lines.push(new LineNode({
                  "x1": "0",
                  "y1": "100%",
                  "x2": "100%",
                  "y2": "0",
                  "stroke-width": "0.046em",
              }));
          }
  
          const svgNode = new SvgNode(lines, {
              "width": "100%",
              "height": totalHeight + "em",
          });
  
          img = buildCommon.makeSvgSpan([], [svgNode], options);
      }
  
      img.height = totalHeight;
      img.style.height = totalHeight + "em";
  
      return img;
  };
  
  export default {
      encloseSpan,
      mathMLnode,
      svgSpan,
  };
  