diff --git a/docs/options.md b/docs/options.md index d10458d6..70e9dec6 100644 --- a/docs/options.md +++ b/docs/options.md @@ -14,6 +14,7 @@ You can provide an object of options as the last argument to [`katex.render` and - `throwOnError`: `boolean`. If `true` (the default), KaTeX will throw a `ParseError` when it encounters an unsupported command or invalid LaTeX. If `false`, KaTeX will render unsupported commands as text, and render invalid LaTeX as its source code with hover text giving the error, in the color given by `errorColor`. - `errorColor`: `string`. A color string given in the format `"#XXX"` or `"#XXXXXX"`. This option determines the color that unsupported commands and invalid LaTeX are rendered in when `throwOnError` is set to `false`. (default: `#cc0000`) - `macros`: `object`. A collection of custom macros. Each macro is a property with a name like `\name` (written `"\\name"` in JavaScript) which maps to a string that describes the expansion of the macro, or a function that accepts an instance of `MacroExpander` as first argument and returns the expansion as a string. `MacroExpander` is an internal API and subject to non-backwards compatible changes. See [`src/macros.js`](https://github.com/KaTeX/KaTeX/blob/master/src/macros.js) for its usage. Single-character keys can also be included in which case the character will be redefined as the given macro (similar to TeX active characters). *This object will be modified* if the LaTeX code defines its own macros via `\gdef`, which enables consecutive calls to KaTeX to share state. +- `minRuleThickness`: `number`. Specifies a minimum thickness, in ems, for fraction lines, `\sqrt` top lines, `{array}` vertical lines, `\hline`, `\hdashline`, `\underline`, `\overline`, and the borders of `\fbox`, `\boxed`, and `\fcolorbox`. The usual value for these items is `0.04`, so for `minRuleThickness` to be effective it should probably take a value slightly above `0.04`, say `0.05` or `0.06`. Negative values will be ignored. - `colorIsTextColor`: `boolean`. If `true`, `\color` will work like LaTeX's `\textcolor`, and take two arguments (e.g., `\color{blue}{hello}`), which restores the old behavior of KaTeX (pre-0.8.0). If `false` (the default), `\color` will work like LaTeX's `\color`, and take one argument (e.g., `\color{blue}hello`). In both cases, `\textcolor` works as in LaTeX (e.g., `\textcolor{blue}{hello}`). - `maxSize`: `number`. All user-specified sizes, e.g. in `\rule{500em}{500em}`, will be capped to `maxSize` ems. If set to `Infinity` (the default), users can make elements and spaces arbitrarily large. - `maxExpand`: `number`. Limit the number of macro expansions to the specified number, to prevent e.g. infinite macro loops. If set to `Infinity`, the macro expander will try to fully expand as in LaTeX. (default: 1000) diff --git a/src/Options.js b/src/Options.js index ab92de27..8f20ae02 100644 --- a/src/Options.js +++ b/src/Options.js @@ -52,6 +52,7 @@ export type OptionsData = { fontShape?: FontShape; sizeMultiplier?: number; maxSize: number; + minRuleThickness: number; }; /** @@ -76,6 +77,7 @@ class Options { fontShape: FontShape; sizeMultiplier: number; maxSize: number; + minRuleThickness: number; _fontMetrics: FontMetrics | void; /** @@ -95,6 +97,7 @@ class Options { this.fontShape = data.fontShape || ''; this.sizeMultiplier = sizeMultipliers[this.size - 1]; this.maxSize = data.maxSize; + this.minRuleThickness = data.minRuleThickness; this._fontMetrics = undefined; } @@ -114,6 +117,7 @@ class Options { fontWeight: this.fontWeight, fontShape: this.fontShape, maxSize: this.maxSize, + minRuleThickness: this.minRuleThickness, }; for (const key in extension) { diff --git a/src/Settings.js b/src/Settings.js index 14d5520d..59be4f22 100644 --- a/src/Settings.js +++ b/src/Settings.js @@ -24,6 +24,7 @@ export type SettingsOptions = { throwOnError?: boolean; errorColor?: string; macros?: MacroMap; + minRuleThickness?: number; colorIsTextColor?: boolean; strict?: boolean | "ignore" | "warn" | "error" | StrictFunction; maxSize?: number; @@ -49,6 +50,7 @@ class Settings { throwOnError: boolean; errorColor: string; macros: MacroMap; + minRuleThickness: number; colorIsTextColor: boolean; strict: boolean | "ignore" | "warn" | "error" | StrictFunction; maxSize: number; @@ -65,6 +67,10 @@ class Settings { this.throwOnError = utils.deflt(options.throwOnError, true); this.errorColor = utils.deflt(options.errorColor, "#cc0000"); this.macros = options.macros || {}; + this.minRuleThickness = Math.max( + 0, + utils.deflt(options.minRuleThickness, 0) + ); this.colorIsTextColor = utils.deflt(options.colorIsTextColor, false); this.strict = utils.deflt(options.strict, "warn"); this.maxSize = Math.max(0, utils.deflt(options.maxSize, Infinity)); diff --git a/src/buildCommon.js b/src/buildCommon.js index 06d734d1..bab32096 100644 --- a/src/buildCommon.js +++ b/src/buildCommon.js @@ -423,7 +423,10 @@ const makeLineSpan = function( thickness?: number, ) { const line = makeSpan([className], [], options); - line.height = thickness || options.fontMetrics().defaultRuleThickness; + line.height = Math.max( + thickness || options.fontMetrics().defaultRuleThickness, + options.minRuleThickness, + ); line.style.borderBottomWidth = line.height + "em"; line.maxFontSize = 1.0; return line; diff --git a/src/buildTree.js b/src/buildTree.js index 32ff073f..61d301ac 100644 --- a/src/buildTree.js +++ b/src/buildTree.js @@ -13,6 +13,7 @@ const optionsFromSettings = function(settings: Settings) { return new Options({ style: (settings.displayMode ? Style.DISPLAY : Style.TEXT), maxSize: settings.maxSize, + minRuleThickness: settings.minRuleThickness, }); }; diff --git a/src/delimiter.js b/src/delimiter.js index a150406b..ad688fce 100644 --- a/src/delimiter.js +++ b/src/delimiter.js @@ -25,6 +25,7 @@ import ParseError from "./ParseError"; import Style from "./Style"; import {PathNode, SvgNode, SymbolNode} from "./domTree"; +import {sqrtPath} from "./svgGeometry"; import buildCommon from "./buildCommon"; import {getCharacterMetrics} from "./fontMetrics"; import symbols from "./symbols"; @@ -377,21 +378,11 @@ const sqrtSvg = function( sqrtName: string, height: number, viewBoxHeight: number, + extraViniculum: number, options: Options, ): SvgSpan { - let alternate; - if (sqrtName === "sqrtTall") { - // sqrtTall is from glyph U23B7 in the font KaTeX_Size4-Regular - // One path edge has a variable length. It runs from the viniculumn - // to a point near (14 units) the bottom of the surd. The viniculum - // is 40 units thick. So the length of the line in question is: - const vertSegment = viewBoxHeight - 54 - vbPad; - alternate = `M702 ${vbPad}H400000v40H742v${vertSegment}l-4 4-4 4c-.667.7 --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 661zM702 ${vbPad}H400000v40H742z`; - } - const pathNode = new PathNode(sqrtName, alternate); + const path = sqrtPath(sqrtName, extraViniculum, viewBoxHeight); + const pathNode = new PathNode(sqrtName, path); const svg = new SvgNode([pathNode], { // Note: 1000:1 ratio of viewBox to document em width. @@ -425,6 +416,11 @@ const makeSqrtImage = function( let sizeMultiplier = newOptions.sizeMultiplier; // default + // The standard sqrt SVGs each have a 0.04em thick viniculum. + // If Settings.minRuleThickness is larger than that, we add extraViniculum. + const extraViniculum = Math.max(0, + options.minRuleThickness - options.fontMetrics().sqrtRuleThickness); + // Create a span containing an SVG image of a sqrt symbol. let span; let spanHeight = 0; @@ -440,34 +436,39 @@ const makeSqrtImage = function( if (delim.type === "small") { // Get an SVG that is derived from glyph U+221A in font KaTeX-Main. - viewBoxHeight = 1000 + vbPad; // 1000 unit glyph height. + // 1000 unit normal glyph height. + viewBoxHeight = 1000 + 1000 * extraViniculum + vbPad; if (height < 1.0) { sizeMultiplier = 1.0; // mimic a \textfont radical } else if (height < 1.4) { sizeMultiplier = 0.7; // mimic a \scriptfont radical } - spanHeight = (1.0 + emPad) / sizeMultiplier; - texHeight = 1.00 / sizeMultiplier; - span = sqrtSvg("sqrtMain", spanHeight, viewBoxHeight, options); + spanHeight = (1.0 + extraViniculum + emPad) / sizeMultiplier; + texHeight = (1.00 + extraViniculum) / sizeMultiplier; + span = sqrtSvg("sqrtMain", spanHeight, viewBoxHeight, extraViniculum, + options); span.style.minWidth = "0.853em"; advanceWidth = 0.833 / sizeMultiplier; // from the font. } else if (delim.type === "large") { // These SVGs come from fonts: KaTeX_Size1, _Size2, etc. viewBoxHeight = (1000 + vbPad) * sizeToMaxHeight[delim.size]; - texHeight = sizeToMaxHeight[delim.size] / sizeMultiplier; - spanHeight = (sizeToMaxHeight[delim.size] + emPad) / sizeMultiplier; - span = sqrtSvg("sqrtSize" + delim.size, spanHeight, viewBoxHeight, options); + texHeight = (sizeToMaxHeight[delim.size] + extraViniculum) / sizeMultiplier; + spanHeight = (sizeToMaxHeight[delim.size] + extraViniculum + emPad) + / sizeMultiplier; + span = sqrtSvg("sqrtSize" + delim.size, spanHeight, viewBoxHeight, + extraViniculum, options); span.style.minWidth = "1.02em"; advanceWidth = 1.0 / sizeMultiplier; // 1.0 from the font. } else { // Tall sqrt. In TeX, this would be stacked using multiple glyphs. // We'll use a single SVG to accomplish the same thing. - spanHeight = height + emPad; - texHeight = height; - viewBoxHeight = Math.floor(1000 * height) + vbPad; - span = sqrtSvg("sqrtTall", spanHeight, viewBoxHeight, options); + spanHeight = height + extraViniculum + emPad; + texHeight = height + extraViniculum; + viewBoxHeight = Math.floor(1000 * height + extraViniculum) + vbPad; + span = sqrtSvg("sqrtTall", spanHeight, viewBoxHeight, extraViniculum, + options); span.style.minWidth = "0.742em"; advanceWidth = 1.056; } @@ -482,7 +483,8 @@ const makeSqrtImage = function( // This actually should depend on the chosen font -- e.g. \boldmath // should use the thicker surd symbols from e.g. KaTeX_Main-Bold, and // have thicker rules. - ruleWidth: options.fontMetrics().sqrtRuleThickness * sizeMultiplier, + ruleWidth: (options.fontMetrics().sqrtRuleThickness + extraViniculum) + * sizeMultiplier, }; }; diff --git a/src/domTree.js b/src/domTree.js index 38ffd912..580cf1a7 100644 --- a/src/domTree.js +++ b/src/domTree.js @@ -13,7 +13,7 @@ */ import {scriptFromCodepoint} from "./unicodeScripts"; import utils from "./utils"; -import svgGeometry from "./svgGeometry"; +import {path} from "./svgGeometry"; import type Options from "./Options"; import {DocumentFragment} from "./tree"; @@ -136,12 +136,16 @@ export type CssStyle = $Shape<{ backgroundColor: string, borderBottomWidth: string, borderColor: string, + borderRightStyle: string, borderRightWidth: string, borderTopWidth: string, + borderStyle: string; + borderWidth: string, bottom: string, color: string, height: string, left: string, + margin: string, marginLeft: string, marginRight: string, marginTop: string, @@ -529,7 +533,7 @@ export class PathNode implements VirtualNode { constructor(pathName: string, alternate?: string) { this.pathName = pathName; - this.alternate = alternate; // Used only for tall \sqrt + this.alternate = alternate; // Used only for \sqrt } toNode(): Node { @@ -539,7 +543,7 @@ export class PathNode implements VirtualNode { if (this.alternate) { node.setAttribute("d", this.alternate); } else { - node.setAttribute("d", svgGeometry.path[this.pathName]); + node.setAttribute("d", path[this.pathName]); } return node; @@ -549,7 +553,7 @@ export class PathNode implements VirtualNode { if (this.alternate) { return ``; } else { - return ``; + return ``; } } } diff --git a/src/environments/array.js b/src/environments/array.js index f36f1a3f..134f2d0c 100644 --- a/src/environments/array.js +++ b/src/environments/array.js @@ -174,6 +174,12 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) { let body = new Array(nr); const hlines = []; + const ruleThickness = Math.max( + // From LaTeX \showthe\arrayrulewidth. Equals 0.04 em. + (options.fontMetrics().arrayRuleWidth), + options.minRuleThickness, // User override. + ); + // Horizontal spacing const pt = 1 / options.fontMetrics().ptPerEm; let arraycolsep = 5 * pt; // default value, i.e. \arraycolsep in article.cls @@ -284,20 +290,15 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) { cols.push(colSep); } - if (colDescr.separator === "|") { + if (colDescr.separator === "|" || colDescr.separator === ":") { + const lineType = (colDescr.separator === "|") ? "solid" : "dashed"; const separator = buildCommon.makeSpan( ["vertical-separator"], [], options ); separator.style.height = totalHeight + "em"; - separator.style.verticalAlign = - -(totalHeight - offset) + "em"; - - cols.push(separator); - } else if (colDescr.separator === ":") { - const separator = buildCommon.makeSpan( - ["vertical-separator", "vs-dashed"], [], options - ); - separator.style.height = totalHeight + "em"; + separator.style.borderRightWidth = `${ruleThickness}em`; + separator.style.borderRightStyle = lineType; + separator.style.margin = `0 -${ruleThickness / 2}em`; separator.style.verticalAlign = -(totalHeight - offset) + "em"; @@ -361,8 +362,9 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) { // Add \hline(s), if any. if (hlines.length > 0) { - const line = buildCommon.makeLineSpan("hline", options, 0.05); - const dashes = buildCommon.makeLineSpan("hdashline", options, 0.05); + const line = buildCommon.makeLineSpan("hline", options, ruleThickness); + const dashes = buildCommon.makeLineSpan("hdashline", options, + ruleThickness); const vListElems = [{type: "elem", elem: body, shift: 0}]; while (hlines.length > 0) { const hline = hlines.pop(); diff --git a/src/fontMetrics.js b/src/fontMetrics.js index 868a5263..ead7559a 100644 --- a/src/fontMetrics.js +++ b/src/fontMetrics.js @@ -82,6 +82,14 @@ const sigmasAndXis = { // The space between adjacent `|` columns in an array definition. From // `\showthe\doublerulesep` in LaTeX. Equals 2.0 / ptPerEm. doubleRuleSep: [0.2, 0.2, 0.2], + + // The width of separator lines in {array} environments. From + // `\showthe\arrayrulewidth` in LaTeX. Equals 0.4 / ptPerEm. + arrayRuleWidth: [0.04, 0.04, 0.04], + + // Two values from LaTeX source2e: + fboxsep: [0.3, 0.3, 0.3], // 3 pt / ptPerEm + fboxrule: [0.04, 0.04, 0.04], // 0.4 pt / ptPerEm }; // This map contains a mapping from font name and character code to character diff --git a/src/functions/enclose.js b/src/functions/enclose.js index 9a5285a5..364e05a6 100644 --- a/src/functions/enclose.js +++ b/src/functions/enclose.js @@ -46,15 +46,24 @@ const htmlBuilder = (group, options) => { // Add vertical padding let vertPad = 0; - // ref: LaTeX source2e: \fboxsep = 3pt; \fboxrule = .4pt + let ruleThickness = 0; // ref: cancel package: \advance\totalheight2\p@ % "+2" if (/box/.test(label)) { - vertPad = label === "colorbox" ? 0.3 : 0.34; + ruleThickness = Math.max( + options.fontMetrics().fboxrule, // default + options.minRuleThickness, // User override. + ); + vertPad = options.fontMetrics().fboxsep + + (label === "colorbox" ? 0 : ruleThickness); } else { vertPad = isSingleChar ? 0.2 : 0; } img = stretchy.encloseSpan(inner, label, vertPad, options); + if (/fbox|boxed|fcolorbox/.test(label)) { + img.style.borderStyle = "solid"; + img.style.borderWidth = `${ruleThickness}em`; + } imgShift = inner.depth + vertPad; if (group.backgroundColor) { @@ -111,6 +120,7 @@ const htmlBuilder = (group, options) => { }; const mathmlBuilder = (group, options) => { + let fboxsep = 0; const node = new mathMLTree.MathNode( (group.label.indexOf("colorbox") > -1) ? "mpadded" : "menclose", [mml.buildGroup(group.body, options)] @@ -132,12 +142,17 @@ const mathmlBuilder = (group, options) => { case "\\colorbox": // doesn't have a good notation option. So use // instead. Set some attributes that come included with . - node.setAttribute("width", "+6pt"); - node.setAttribute("height", "+6pt"); - node.setAttribute("lspace", "3pt"); // LaTeX source2e: \fboxsep = 3pt - node.setAttribute("voffset", "3pt"); + fboxsep = options.fontMetrics().fboxsep * + options.fontMetrics().ptPerEm; + node.setAttribute("width", `+${2 * fboxsep}pt`); + node.setAttribute("height", `+${2 * fboxsep}pt`); + node.setAttribute("lspace", `${fboxsep}pt`); // + node.setAttribute("voffset", `${fboxsep}pt`); if (group.label === "\\fcolorbox") { - const thk = options.fontMetrics().defaultRuleThickness; + const thk = Math.max( + options.fontMetrics().fboxrule, // default + options.minRuleThickness, // user override + ); node.setAttribute("style", "border: " + thk + "em solid " + String(group.borderColor)); } diff --git a/src/functions/overline.js b/src/functions/overline.js index 6a76eac0..4c11c14e 100644 --- a/src/functions/overline.js +++ b/src/functions/overline.js @@ -31,13 +31,14 @@ defineFunction({ const line = buildCommon.makeLineSpan("overline-line", options); // Generate the vlist, with the appropriate kerns + const defaultRuleThickness = options.fontMetrics().defaultRuleThickness; const vlist = buildCommon.makeVList({ positionType: "firstBaseline", children: [ {type: "elem", elem: innerGroup}, - {type: "kern", size: 3 * line.height}, + {type: "kern", size: 3 * defaultRuleThickness}, {type: "elem", elem: line}, - {type: "kern", size: line.height}, + {type: "kern", size: defaultRuleThickness}, ], }, options); diff --git a/src/functions/underline.js b/src/functions/underline.js index e0bba1c3..36c1927f 100644 --- a/src/functions/underline.js +++ b/src/functions/underline.js @@ -29,13 +29,14 @@ defineFunction({ const line = buildCommon.makeLineSpan("underline-line", options); // Generate the vlist, with the appropriate kerns + const defaultRuleThickness = options.fontMetrics().defaultRuleThickness; const vlist = buildCommon.makeVList({ positionType: "top", positionData: innerGroup.height, children: [ - {type: "kern", size: line.height}, + {type: "kern", size: defaultRuleThickness}, {type: "elem", elem: line}, - {type: "kern", size: 3 * line.height}, + {type: "kern", size: 3 * defaultRuleThickness}, {type: "elem", elem: innerGroup}, ], }, options); diff --git a/src/katex.less b/src/katex.less index bcfee716..c95430f7 100644 --- a/src/katex.less +++ b/src/katex.less @@ -409,15 +409,10 @@ .mtable { .vertical-separator { display: inline-block; - margin: 0 -0.025em; - border-right: 0.05em solid; + // margin and border-right are set in Javascript min-width: 1px; // Prevent Chrome from omitting a line. } - .vs-dashed { - border-right: 0.05em dashed; - } - .arraycolsep { display: inline-block; } diff --git a/src/svgGeometry.js b/src/svgGeometry.js index f0e5f0a5..25c999ec 100644 --- a/src/svgGeometry.js +++ b/src/svgGeometry.js @@ -1,61 +1,152 @@ // @flow /** - * This file provides support to domTree.js + * This file provides support to domTree.js and delimiter.js. * It's a storehouse of path geometry for SVG images. */ // In all paths below, the viewBox-to-em scale is 1000:1. -const hLinePad = 80; // padding above a sqrt viniculum. +const hLinePad = 80; // padding above a sqrt viniculum. Prevents image cropping. -const path: {[string]: string} = { +// The viniculum of a \sqrt can be made thicker by a KaTeX rendering option. +// Think of variable extraViniculum as two detours in the SVG path. +// The detour begins at the lower left of the area labeled extraViniculum below. +// The detour proceeds one extraViniculum distance up and slightly to the right, +// displacing the radiused corner between surd and viniculum. The radius is +// traversed as usual, then the detour resumes. It goes right, to the end of +// the very long viniculumn, then down one extraViniculum distance, +// after which it resumes regular path geometry for the radical. +/* viniculum + / + /▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒←extraViniculum + / █████████████████████←0.04em (40 unit) std viniculum thickness + / / + / / + / /\ + / / surd +*/ + +const sqrtMain = function(extraViniculum: number, hLinePad: number): string { // sqrtMain path geometry is from glyph U221A in the font KaTeX Main - // All surds have 80 units padding above the viniculumn. - sqrtMain: `M95,${622 + hLinePad}c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5, --10,-9.5,-14c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54c44.2,-33.3,65.8, --50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10s173,378,173,378c0.7,0, -35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429c69,-144,104.5,-217.7,106.5, --221c5.3,-9.3,12,-14,20,-14H400000v40H845.2724s-225.272,467,-225.272,467 -s-235,486,-235,486c-2.7,4.7,-9,7,-19,7c-6,0,-10,-1,-12,-3s-194,-422,-194,-422 -s-65,47,-65,47z M834 ${hLinePad}H400000v40H845z`, + return `M95,${622 + extraViniculum + hLinePad} +c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14 +c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54 +c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10 +s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429 +c69,-144,104.5,-217.7,106.5,-221 +l${extraViniculum / 2.075} -${extraViniculum} +c5.3,-9.3,12,-14,20,-14 +H400000v${40 + extraViniculum}H845.2724 +s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7 +c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z +Ml${834 + extraViniculum} ${hLinePad}h400000v${40 + extraViniculum}h-400000z`; +}; +const sqrtSize1 = function(extraViniculum: number, hLinePad: number): string { // size1 is from glyph U221A in the font KaTeX_Size1-Regular - sqrtSize1: `M263,${601 + hLinePad}c0.7,0,18,39.7,52,119c34,79.3,68.167, -158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120c340,-704.7,510.7,-1060.3,512,-1067 -c4.7,-7.3,11,-11,19,-11H40000v40H1012.3s-271.3,567,-271.3,567c-38.7,80.7,-84, -175,-136,283c-52,108,-89.167,185.3,-111.5,232c-22.3,46.7,-33.8,70.3,-34.5,71 -c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1s-109,-253,-109,-253c-72.7,-168,-109.3, --252,-110,-252c-10.7,8,-22,16.7,-34,26c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26 -s76,-59,76,-59s76,-60,76,-60z M1001 ${hLinePad}H40000v40H1012z`, + return `M263,${601 + extraViniculum + hLinePad}c0.7,0,18,39.7,52,119 +c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120 +c340,-704.7,510.7,-1060.3,512,-1067 +l${extraViniculum / 2.084} -${extraViniculum} +c4.7,-7.3,11,-11,19,-11 +H40000v${40 + extraViniculum}H1012.3 +s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232 +c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1 +s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26 +c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60z +M${1001 + extraViniculum} ${hLinePad}h400000v${40 + extraViniculum}h-400000z`; +}; +const sqrtSize2 = function(extraViniculum: number, hLinePad: number): string { // size2 is from glyph U221A in the font KaTeX_Size2-Regular - // The 80 units padding is most obvious here. Note start node at M1001 80. - sqrtSize2: `M1001,${hLinePad}H400000v40H1013.1s-83.4,268,-264.1,840c-180.7, -572,-277,876.3,-289,913c-4.7,4.7,-12.7,7,-24,7s-12,0,-12,0c-1.3,-3.3,-3.7,-11.7, --7,-25c-35.3,-125.3,-106.7,-373.3,-214,-744c-10,12,-21,25,-33,39s-32,39,-32,39 -c-6,-5.3,-15,-14,-27,-26s25,-30,25,-30c26.7,-32.7,52,-63,76,-91s52,-60,52,-60 -s208,722,208,722c56,-175.3,126.3,-397.3,211,-666c84.7,-268.7,153.8,-488.2,207.5, --658.5c53.7,-170.3,84.5,-266.8,92.5,-289.5c4,-6.7,10,-10,18,-10z -M1001 ${hLinePad}H400000v40H1013z`, + return `M983 ${10 + extraViniculum + hLinePad} +l${extraViniculum / 3.13} -${extraViniculum} +c4,-6.7,10,-10,18,-10 H400000v${40 + extraViniculum} +H1013.1s-83.4,268,-264.1,840c-180.7,572,-277,876.3,-289,913c-4.7,4.7,-12.7,7,-24,7 +s-12,0,-12,0c-1.3,-3.3,-3.7,-11.7,-7,-25c-35.3,-125.3,-106.7,-373.3,-214,-744 +c-10,12,-21,25,-33,39s-32,39,-32,39c-6,-5.3,-15,-14,-27,-26s25,-30,25,-30 +c26.7,-32.7,52,-63,76,-91s52,-60,52,-60s208,722,208,722 +c56,-175.3,126.3,-397.3,211,-666c84.7,-268.7,153.8,-488.2,207.5,-658.5 +c53.7,-170.3,84.5,-266.8,92.5,-289.5z +M${1001 + extraViniculum} ${hLinePad}h400000v${40 + extraViniculum}h-400000z`; +}; +const sqrtSize3 = function(extraViniculum: number, hLinePad: number): string { // size3 is from glyph U221A in the font KaTeX_Size3-Regular - sqrtSize3: `M424,${2398 + hLinePad}c-1.3,-0.7,-38.5,-172,-111.5,-514c-73, --342,-109.8,-513.3,-110.5,-514c0,-2,-10.7,14.3,-32,49c-4.7,7.3,-9.8,15.7,-15.5, -25c-5.7,9.3,-9.8,16,-12.5,20s-5,7,-5,7c-4,-3.3,-8.3,-7.7,-13,-13s-13,-13,-13, --13s76,-122,76,-122s77,-121,77,-121s209,968,209,968c0,-2,84.7,-361.7,254,-1079 -c169.3,-717.3,254.7,-1077.7,256,-1081c4,-6.7,10,-10,18,-10H400000v40H1014.6 -s-87.3,378.7,-272.6,1166c-185.3,787.3,-279.3,1182.3,-282,1185c-2,6,-10,9,-24,9 -c-8,0,-12,-0.7,-12,-2z M1001 ${hLinePad}H400000v40H1014z`, + return `M424,${2398 + extraViniculum + hLinePad} +c-1.3,-0.7,-38.5,-172,-111.5,-514c-73,-342,-109.8,-513.3,-110.5,-514 +c0,-2,-10.7,14.3,-32,49c-4.7,7.3,-9.8,15.7,-15.5,25c-5.7,9.3,-9.8,16,-12.5,20 +s-5,7,-5,7c-4,-3.3,-8.3,-7.7,-13,-13s-13,-13,-13,-13s76,-122,76,-122s77,-121,77,-121 +s209,968,209,968c0,-2,84.7,-361.7,254,-1079c169.3,-717.3,254.7,-1077.7,256,-1081 +l${extraViniculum / 4.223} -${extraViniculum}c4,-6.7,10,-10,18,-10 H400000 +v${40 + extraViniculum}H1014.6 +s-87.3,378.7,-272.6,1166c-185.3,787.3,-279.3,1182.3,-282,1185 +c-2,6,-10,9,-24,9 +c-8,0,-12,-0.7,-12,-2z M${1001 + extraViniculum} ${hLinePad} +h400000v${40 + extraViniculum}h-400000z`; +}; +const sqrtSize4 = function(extraViniculum: number, hLinePad: number): string { // size4 is from glyph U221A in the font KaTeX_Size4-Regular - sqrtSize4: `M473,${2713 + hLinePad}c339.3,-1799.3,509.3,-2700,510,-2702 -c3.3,-7.3,9.3,-11,18,-11H400000v40H1017.7s-90.5,478,-276.2,1466c-185.7,988, --279.5,1483,-281.5,1485c-2,6,-10,9,-24,9c-8,0,-12,-0.7,-12,-2c0,-1.3,-5.3,-32, --16,-92c-50.7,-293.3,-119.7,-693.3,-207,-1200c0,-1.3,-5.3,8.7,-16,30c-10.7, -21.3,-21.3,42.7,-32,64s-16,33,-16,33s-26,-26,-26,-26s76,-153,76,-153s77,-151, -77,-151c0.7,0.7,35.7,202,105,604c67.3,400.7,102,602.7,104,606z -M1001 ${hLinePad}H400000v40H1017z`, + return `M473,${2713 + extraViniculum + hLinePad} +c339.3,-1799.3,509.3,-2700,510,-2702 l${extraViniculum / 5.298} -${extraViniculum} +c3.3,-7.3,9.3,-11,18,-11 H400000v${40 + extraViniculum}H1017.7 +s-90.5,478,-276.2,1466c-185.7,988,-279.5,1483,-281.5,1485c-2,6,-10,9,-24,9 +c-8,0,-12,-0.7,-12,-2c0,-1.3,-5.3,-32,-16,-92c-50.7,-293.3,-119.7,-693.3,-207,-1200 +c0,-1.3,-5.3,8.7,-16,30c-10.7,21.3,-21.3,42.7,-32,64s-16,33,-16,33s-26,-26,-26,-26 +s76,-153,76,-153s77,-151,77,-151c0.7,0.7,35.7,202,105,604c67.3,400.7,102,602.7,104, +606zM${1001 + extraViniculum} ${hLinePad}h400000v${40 + extraViniculum}H1017.7z`; +}; +const sqrtTall = function( + extraViniculum: number, + hLinePad: number, + viewBoxHeight: number +): string { + // sqrtTall is from glyph U23B7 in the font KaTeX_Size4-Regular + // One path edge has a variable length. It runs vertically from the viniculumn + // to a point near (14 units) the bottom of the surd. The viniculum + // is normally 40 units thick. So the length of the line in question is: + const vertSegment = viewBoxHeight - 54 - hLinePad - extraViniculum; + + return `M702 ${extraViniculum + hLinePad}H400000${40 + extraViniculum} +H742v${vertSegment}l-4 4-4 4c-.667.7 -2 1.5-4 2.5s-4.167 1.833-6.5 2.5-5.5 1-9.5 1 +h-12l-28-84c-16.667-52-96.667 -294.333-240-727l-212 -643 -85 170 +c-4-3.333-8.333-7.667-13 -13l-13-13l77-155 77-156c66 199.333 139 419.667 +219 661 l218 661zM702 ${hLinePad}H400000v${40 + extraViniculum}H742z`; +}; + +export const sqrtPath = function( + size: string, + extraViniculum: number, + viewBoxHeight: number +): string { + extraViniculum = 1000 * extraViniculum; // Convert from document ems to viewBox. + let path = ""; + + switch (size) { + case "sqrtMain": + path = sqrtMain(extraViniculum, hLinePad); + break; + case "sqrtSize1": + path = sqrtSize1(extraViniculum, hLinePad); + break; + case "sqrtSize2": + path = sqrtSize2(extraViniculum, hLinePad); + break; + case "sqrtSize3": + path = sqrtSize3(extraViniculum, hLinePad); + break; + case "sqrtSize4": + path = sqrtSize4(extraViniculum, hLinePad); + break; + case "sqrtTall": + path = sqrtTall(extraViniculum, hLinePad, viewBoxHeight); + } + return path; +}; + +export const path: {[string]: string} = { // The doubleleftarrow geometry is from glyph U+21D0 in the font KaTeX Main doubleleftarrow: `M262 157 l10-10c34-36 62.7-77 86-123 3.3-8 5-13.3 5-16 0-5.3-6.7-8-20-8-7.3 @@ -363,6 +454,3 @@ c4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199, c-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5z M500 241 v40 H399408 v-40z M500 435 v40 H400000 v-40z`, }; - - -export default {path}; diff --git a/test/screenshotter/images/Arrays-chrome.png b/test/screenshotter/images/Arrays-chrome.png index ca55d366..98d8909d 100644 Binary files a/test/screenshotter/images/Arrays-chrome.png and b/test/screenshotter/images/Arrays-chrome.png differ diff --git a/test/screenshotter/images/Arrays-firefox.png b/test/screenshotter/images/Arrays-firefox.png index 1bd1d8e0..203ea348 100644 Binary files a/test/screenshotter/images/Arrays-firefox.png and b/test/screenshotter/images/Arrays-firefox.png differ diff --git a/test/screenshotter/images/Sqrt-chrome.png b/test/screenshotter/images/Sqrt-chrome.png index ec5b4f13..2e3c23ed 100644 Binary files a/test/screenshotter/images/Sqrt-chrome.png and b/test/screenshotter/images/Sqrt-chrome.png differ diff --git a/test/screenshotter/images/Sqrt-firefox.png b/test/screenshotter/images/Sqrt-firefox.png index c5de0b3d..81ed3627 100644 Binary files a/test/screenshotter/images/Sqrt-firefox.png and b/test/screenshotter/images/Sqrt-firefox.png differ diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml index 61556742..2bd841b3 100644 --- a/test/screenshotter/ss_data.yaml +++ b/test/screenshotter/ss_data.yaml @@ -323,7 +323,9 @@ Spacing: | \end{matrix} Sqrt: | \sqrt{\sqrt{\sqrt{x}}}_{\sqrt{\sqrt{x}}}^{\sqrt{\sqrt{\sqrt{x}}} - ^{\sqrt{\sqrt{\sqrt{x}}}}} + ^{\sqrt{\sqrt{\sqrt{x}}}}} \\ + \sqrt{\frac{\frac{A}{B}}{\frac{A}{B}}} \; + \sqrt{\frac{\frac{\frac{A}{B}}{\frac{A}{B}}}{\frac{\frac{A}{B}}{\frac{A}{B}}}} SqrtRoot: | \begin{array}{l} 1+\sqrt[3]{2}+\sqrt[1923^234]{2^{2^{2^{2^{2^{2^{2^{2^{2^{2^{2^2}}}}}}}}}}} \\