From fd18f6979ed315a7165a438962a41dfd544d518e Mon Sep 17 00:00:00 2001 From: Emily Eisenberg Date: Thu, 19 Feb 2015 15:26:57 -0800 Subject: [PATCH] Add an optional settings argument to render calls Summary: Add the ability to pass in options to the render calls which contain information about the parse. This information is passed around to the parser and builder, which parse and render differently depending on the options. Currently, this includes an option to render the math in display mode (i.e. centered, block level, and in displaystyle). Also added some changes to make it easier to add new data to functions (now that new data doesn't need to be copied into the ParseFuncOrArg data structure, it is looked up when it is needed) and has more sane support for the `'original'` argType (as suggested by pull request #93). Test Plan: - Make sure tests and lint pass - Make sure huxley screenshots didn't change, and new screenshot looks correct Reviewers: alpert Reviewed By: alpert Differential Revision: https://phabricator.khanacademy.org/D13810 --- README.md | 18 ++- katex.js | 17 +- src/Parser.js | 57 +++---- src/Settings.js | 26 +++ src/buildTree.js | 15 +- src/functions.js | 13 +- src/parseTree.js | 4 +- static/katex.less | 10 ++ test/huxley/Huxleyfile.json | 6 + .../DisplayMode.hux/firefox-1.png | Bin 0 -> 22294 bytes .../Huxleyfolder/DisplayMode.record.json | 5 + .../Huxleyfolder/Spacing.hux/firefox-1.png | Bin 8787 -> 16091 bytes test/huxley/test.html | 12 +- test/katex-spec.js | 151 +++++++++--------- 14 files changed, 190 insertions(+), 144 deletions(-) create mode 100644 src/Settings.js create mode 100644 test/huxley/Huxleyfolder/DisplayMode.hux/firefox-1.png create mode 100644 test/huxley/Huxleyfolder/DisplayMode.record.json diff --git a/README.md b/README.md index fdc633bd..9a1c4df0 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,17 @@ You can [download KaTeX](https://github.com/khan/katex/releases) and host it on ``` +#### In-browser rendering + Call `katex.render` with a TeX expression and a DOM element to render into: ```js katex.render("c = \\pm\\sqrt{a^2 + b^2}", element); ``` -To generate HTML on the server, you can use `katex.renderToString`: +#### Server side rendering or rendering to a string + +To generate HTML on the server or to generate an HTML string of the rendered math, you can use `katex.renderToString`: ```js var html = katex.renderToString("c = \\pm\\sqrt{a^2 + b^2}"); @@ -33,12 +37,16 @@ var html = katex.renderToString("c = \\pm\\sqrt{a^2 + b^2}"); Make sure to include the CSS and font files, but there is no need to include the JavaScript. -These APIs default to inline math typesetting; for display math you can prepend `\displaystyle` ([#66](https://github.com/Khan/KaTeX/issues/66)): +#### Rendering options + +You can provide an object of options as the last argument to `katex.render` and `katex.renderToString`. Available options are: + +- `displayMode`: `boolean`. If `true` the math will be rendered in display mode, which will put the math in display style (so `\int` and `\sum` are large, for example), and will center the math on the page on its own line. If `false` the math will be rendered in inline mode. (default: `false`) + +For example: ```js -katex.render("\\displaystyle {" + formula + "}", element); -// OR -var html = katex.renderToString("\\displaystyle {" + formula + "}"); +katex.render("c = \\pm\\sqrt{a^2 + b^2}", element, { displayMode: true }); ``` ## Contributing diff --git a/katex.js b/katex.js index b6b96f3c..368a9b92 100644 --- a/katex.js +++ b/katex.js @@ -7,6 +7,7 @@ */ var ParseError = require("./src/ParseError"); +var Settings = require("./src/Settings"); var buildTree = require("./src/buildTree"); var parseTree = require("./src/parseTree"); @@ -16,11 +17,13 @@ var utils = require("./src/utils"); * Parse and build an expression, and place that expression in the DOM node * given. */ -var render = function(toParse, baseNode) { +var render = function(toParse, baseNode, options) { utils.clearNode(baseNode); - var tree = parseTree(toParse); - var node = buildTree(tree).toNode(); + var settings = new Settings(options); + + var tree = parseTree(toParse, settings); + var node = buildTree(tree, settings).toNode(); baseNode.appendChild(node); }; @@ -42,9 +45,11 @@ if (typeof document !== "undefined") { /** * Parse and build an expression, and return the markup for that. */ -var renderToString = function(toParse) { - var tree = parseTree(toParse); - return buildTree(tree).toMarkup(); +var renderToString = function(toParse, options) { + var settings = new Settings(options); + + var tree = parseTree(toParse, settings); + return buildTree(tree, settings).toMarkup(); }; module.exports = { diff --git a/src/Parser.js b/src/Parser.js index f23e7fd9..5a1cbb5e 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -43,9 +43,11 @@ var ParseError = require("./ParseError"); /** * Main Parser class */ -function Parser(input) { +function Parser(input, settings) { // Make a new lexer this.lexer = new Lexer(input); + // Store the settings for use in parsing + this.settings = settings; } /** @@ -69,18 +71,10 @@ function ParseResult(result, newPosition) { * An initial function (without its arguments), or an argument to a function. * The `result` argument should be a ParseResult. */ -function ParseFuncOrArgument(result, isFunction, allowedInText, numArgs, numOptionalArgs, argTypes) { +function ParseFuncOrArgument(result, isFunction) { this.result = result; // Is this a function (i.e. is it something defined in functions.js)? this.isFunction = isFunction; - // Is it allowed in text mode? - this.allowedInText = allowedInText; - // How many arguments? - this.numArgs = numArgs; - // How many optional arguments? - this.numOptionalArgs = numOptionalArgs; - // What types of arguments? - this.argTypes = argTypes; } /** @@ -217,10 +211,10 @@ Parser.prototype.handleSupSubscript = function(pos, mode, symbol, name) { if (!group) { throw new ParseError( "Expected group after '" + symbol + "'", this.lexer, pos); - } else if (group.numArgs > 0) { + } else if (group.isFunction) { // ^ and _ have a greediness, so handle interactions with functions' // greediness - var funcGreediness = functions.getGreediness(group.result.result); + var funcGreediness = functions.funcs[group.result.result].greediness; if (funcGreediness > SUPSUB_GREEDINESS) { return this.parseFunction(pos, mode); } else { @@ -419,7 +413,8 @@ Parser.prototype.parseFunction = function(pos, mode) { if (baseGroup) { if (baseGroup.isFunction) { var func = baseGroup.result.result; - if (mode === "text" && !baseGroup.allowedInText) { + var funcData = functions.funcs[func]; + if (mode === "text" && !funcData.allowedInText) { throw new ParseError( "Can't use function '" + func + "' in text mode", this.lexer, baseGroup.position); @@ -428,17 +423,17 @@ Parser.prototype.parseFunction = function(pos, mode) { var newPos = baseGroup.result.position; var result; - var totalArgs = baseGroup.numArgs + baseGroup.numOptionalArgs; + var totalArgs = funcData.numArgs + funcData.numOptionalArgs; if (totalArgs > 0) { - var baseGreediness = functions.getGreediness(func); + var baseGreediness = funcData.greediness; var args = [func]; var positions = [newPos]; for (var i = 0; i < totalArgs; i++) { - var argType = baseGroup.argTypes && baseGroup.argTypes[i]; + var argType = funcData.argTypes && funcData.argTypes[i]; var arg; - if (i < baseGroup.numOptionalArgs) { + if (i < funcData.numOptionalArgs) { if (argType) { arg = this.parseSpecialGroup(newPos, argType, mode, true); } else { @@ -463,8 +458,9 @@ Parser.prototype.parseFunction = function(pos, mode) { } } var argNode; - if (arg.numArgs > 0) { - var argGreediness = functions.getGreediness(arg.result.result); + if (arg.isFunction) { + var argGreediness = + functions.funcs[arg.result.result].greediness; if (argGreediness > baseGreediness) { argNode = this.parseFunction(newPos, mode); } else { @@ -507,6 +503,11 @@ Parser.prototype.parseFunction = function(pos, mode) { * @return {?ParseFuncOrArgument} */ Parser.prototype.parseSpecialGroup = function(pos, mode, outerMode, optional) { + // Handle `original` argTypes + if (mode === "original") { + mode = outerMode; + } + if (mode === "color" || mode === "size") { // color and size modes are special because they should have braces and // should only lex a single symbol inside @@ -605,23 +606,11 @@ Parser.prototype.parseSymbol = function(pos, mode) { var nucleus = this.lexer.lex(pos, mode); if (functions.funcs[nucleus.text]) { - // If there is a function with this name, we use its data - var func = functions.funcs[nucleus.text]; - - // Here, we replace "original" argTypes with the current mode - var argTypes = func.argTypes; - if (argTypes) { - argTypes = argTypes.slice(); - for (var i = 0; i < argTypes.length; i++) { - if (argTypes[i] === "original") { - argTypes[i] = mode; - } - } - } - + // If there exists a function with this name, we return the function and + // say that it is a function. return new ParseFuncOrArgument( new ParseResult(nucleus.text, nucleus.position), - true, func.allowedInText, func.numArgs, func.numOptionalArgs, argTypes); + true); } else if (symbols[mode][nucleus.text]) { // Otherwise if this is a no-argument function, find the type it // corresponds to in the symbols map diff --git a/src/Settings.js b/src/Settings.js new file mode 100644 index 00000000..49395d9e --- /dev/null +++ b/src/Settings.js @@ -0,0 +1,26 @@ +/** + * This is a module for storing settings passed into KaTeX. It correctly handles + * default settings. + */ + +/** + * Helper function for getting a default value if the value is undefined + */ +function get(option, defaultValue) { + return option === undefined ? defaultValue : option; +} + +/** + * The main Settings object + * + * The current options stored are: + * - displayMode: Whether the expression should be typeset by default in + * textstyle or displaystyle (default false) + */ +function Settings(options) { + // allow null options + options = options || {}; + this.displayMode = get(options.displayMode, false); +} + +module.exports = Settings; diff --git a/src/buildTree.js b/src/buildTree.js index 1939331b..464e6c2f 100644 --- a/src/buildTree.js +++ b/src/buildTree.js @@ -1135,9 +1135,14 @@ var buildGroup = function(group, options, prev) { /** * Take an entire parse tree, and build it into an appropriate set of nodes. */ -var buildTree = function(tree) { +var buildTree = function(tree, settings) { + var startStyle = Style.TEXT; + if (settings.displayMode) { + startStyle = Style.DISPLAY; + } + // Setup the default options - var options = new Options(Style.TEXT, "size5", ""); + var options = new Options(startStyle, "size5", ""); // Build the expression contained in the tree var expression = buildExpression(tree, options); @@ -1161,7 +1166,11 @@ var buildTree = function(tree) { makeSpan(["katex-inner"], [topStrut, bottomStrut, body]) ]); - return katexNode; + if (settings.displayMode) { + return makeSpan(["katex-display"], [katexNode]); + } else { + return katexNode; + } }; module.exports = buildTree; diff --git a/src/functions.js b/src/functions.js index ec702a8d..40600a40 100644 --- a/src/functions.js +++ b/src/functions.js @@ -506,16 +506,6 @@ for (var i = 0; i < duplicatedFunctions.length; i++) { addFuncsWithData(duplicatedFunctions[i].funcs, duplicatedFunctions[i].data); } -// Returns the greediness of a given function. Since greediness is optional, we -// use this function to put in the default value if it is undefined. -var getGreediness = function(func) { - if (functions[func].greediness === undefined) { - return 1; - } else { - return functions[func].greediness; - } -}; - // Set default values of functions for (var f in functions) { if (functions.hasOwnProperty(f)) { @@ -534,6 +524,5 @@ for (var f in functions) { } module.exports = { - funcs: functions, - getGreediness: getGreediness + funcs: functions }; diff --git a/src/parseTree.js b/src/parseTree.js index a778e2c4..3adba824 100644 --- a/src/parseTree.js +++ b/src/parseTree.js @@ -8,8 +8,8 @@ var Parser = require("./Parser"); /** * Parses an expression using a Parser, then returns the parsed result. */ -var parseTree = function(toParse) { - var parser = new Parser(toParse); +var parseTree = function(toParse, settings) { + var parser = new Parser(toParse, settings); return parser.parse(); }; diff --git a/static/katex.less b/static/katex.less index fe18a0af..e394bf73 100644 --- a/static/katex.less +++ b/static/katex.less @@ -1,5 +1,15 @@ @import "fonts.less"; +.katex-display { + display: block; + margin: 1em 0; + text-align: center; + + > .katex { + display: inline-block; + } +} + .katex { font: normal 1.21em KaTeX_Main; line-height: 1.2; diff --git a/test/huxley/Huxleyfile.json b/test/huxley/Huxleyfile.json index 673b5892..06aff0f1 100644 --- a/test/huxley/Huxleyfile.json +++ b/test/huxley/Huxleyfile.json @@ -195,5 +195,11 @@ "name": "Accents", "screenSize": [1024, 768], "url": "http://localhost:7936/test/huxley/test.html?m=\\vec{A}\\vec{x}\\vec x^2\\vec{x}_2^2\\vec{A}^2\\vec{xA}^2" + }, + + { + "name": "DisplayMode", + "screenSize": [1024, 768], + "url": "http://localhost:7936/test/huxley/test.html?m=\\sum_{i=0}^\\infty \\frac{1}{i}&pre=pre&post=post&display=1" } ] diff --git a/test/huxley/Huxleyfolder/DisplayMode.hux/firefox-1.png b/test/huxley/Huxleyfolder/DisplayMode.hux/firefox-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e560a1f0efac97ab0e509e800fe770596fb6b5ed GIT binary patch literal 22294 zcmeAS@N?(olHy`uVBq!ia0y~yU}0cjV0yyA#=yWZQNmr9fx*bb)5S5Qg7M8k?wWI@ zkNtoV&bGEFCP(bnwiS@-wVT2+2p^61f{Cq+9$7P)YSu8j)yooyC+eO+wndE4(V(mFdk zFJ4;ey*2SL+uNI)-7j9e=;h%NurcWDBf1>#wbiUS0O~7C6!r)OCLQ z?`mUa=UY+r_0_30i{1NQZM&T}`TEb#&rPeoWSpI28Qj^~d8%js|G)36e!tybdcXF& zrnX$w3q{@7T_H0KOI`%{&NA`TOiD^J$-86WJJ%|dlaupQkNUg{C;7S`3yreR&N8if z+-tt2?r&A9>(y1Erb$OQT3cIJIy5ptoUl6Pl&k%}AB(NZ->uo4eqKxa+WPqQZM@Q1 zS5^c{S(mNpF+AmadwYKP_Po0#UtR=0Z8J_k_o7pM-i1x6r>DrW^UGa%aIo1l^^^$6 zv0JmRi-A*Dli?N%^~&oiTb(+j4Jjd)de?7vSU5Q*`*++UTo0i__;?m3n2~Ek1Ai z>hkjbS!TJbE?fxMc{gvaWwG0Av)oJR^J|ysMsL&DdUtpE>cYpzW|?MB+g1Kv59}VJ z(@T9;gsq*Gb2}_5F)y+ZvFsy{GG)e0{I_{nZ~IAAdc-%zxr(k*4YP+iO7y z<;#nUx3=ZZz7(~iU|~~JlS%S1p4ijg`g@nS%h!gil(jDNQFiYOsamvX(UqOW=|_(o zIdOWE&eiqt@w(C5LNYQ|I5|0qNJ_2*8TItk)K3*Qr@C~bx2*_W9oE&^8JNY$$T-WQ zaM8S~SDKO2mR`{cTeHHgSL$V}c-)2M^XpbAI=5+PX?bbt#qV2lZ*TSKr$sBXudjo| zn{mu3*28VQ!6_+A_ROpQSGo7^x7(jSe-5tt^XJc%RiWCZ`S)TDxA9)yl6iT`wd3;j zYo_T&uS!2ZZ`Z6gUTLkhbFIs@l+W9I?(v&vqX|ls*{7yxt_oV(bxF#qWW~PU@2t1x z-;ewF@#CqU`}O~0Wv$CXe0+L>K7apy{n63xtIK?)W4-(3Y(v&YnSyK**NX|@+J1X& z-QQm;qqb)4`h3os6%_CXwn{v0Iy>9^boPS-jaAR*mOpv^e0jEJz=DQ*_5W*2-`)y+ zS^4c|dg;46ma+Qwe?Bye>BVTQPdwa~d39AN$T>!r)6i@&0#rO7BYa1B_ zW$yj?YRtH0l?_K!@AoM!j$ zN3y7HR7j?)t83&4yj`=Mz(5(W5mKhqQTKNL2aV z(&=q{va8m-y0de0)b_l!Tk8INbU!=SIvnILhM4X^JxSxVo~EXzDWQsric*G2EtibT z-^I+euaCR#osh6V&41pSGh948A-z(jC8pE$X4N|UfBnmSglH5O}V5Mw4`HK`TKQmvaYV0>NQ<&Ehs~8-CO-V z>+&++tSc)ff}MV%BTz5n;-c0rG2K;fw1SuQyt=x2`InkcC)Ibw$<=-dOuZ^werMsX z($}lx13vCFXQpCUa#}_|Nkq08Zy_abjrN- z`+j+W9Td|WsJG+!yy_{zS67FJSJ}w*Z_By4XwIs@#cf<78Vg>({QZ9a^xJR4szBxE z98lVtI`8lA@7h*YQDFTkM>NEzXove*hHlLYO?|R_eqE5?-MZgzcir2Te}7$1$eM^i z%g|LJnyF8o&##B14@tvQtOpMsJhk%E)6?3@>F4GI`iW`=xm>?JQQ3XUJf~K!l|D0# zPOY4z>aC^B&Mzkdwnws1Q=EmBRdYELGxOAm+~Rr~%bS{;OZS2brk6Wwer^JlmFj+T z1R$ynPq7}8cp8$Bu)wJ3;UU(iQw-g%-#*$cUb@-E%?%Q4JrYj?k4fx|(Odfb)6>(Z zx`l*=OE<^tsR#s{kaA?Hjh3Nd;K`FGz5FjOa-AwJsuL0Ll9QA3;vCE3DYH*a)71tm zGJ+)#+eD?MA(nv@ z6}-5h_!LylO`QlTP4rjA?%rlp^x}ddxJgm4S>kEa-QDHd%eUp;UiPJ>r3GBQbxWD& z=}bSp^vj<=pU;B}u?J~|n&LIz@0Oq9>_5I(fB&DLmz5t5vZqE)`?P40YxkW z;^*fyZT;aO}PCw%x~x2ytA`Rz1{m{E>2W-FL`o8Fmz>*>%V{h zz%EiycH5b9YKrEmb>{bLf?uu-TG|Dw!k(t~+kEQK3R$sW%elGM%df5u*Iq8B8?|J` z3XP@L7Q6F9ylv{XGv!#1(BnjOyVd@nC7bhavaF`?}3t)(3u97{uYm1MHAv%iK4^T}E* zfvVC9T{VSERBOo`4lb^s)6;afW?WPPH#Kr@Uz?=rEg~wq^jNR->VSn#yJoFep<(;= zO0blD-JYP;5gQjNDJh8v2q=KVXx-7JF`uqQ=W7}n1#M0{J1O@z$X(O);N!an zs_hoJa;F}<9$&wfk(o`yc$#kXveeVlG;Klkm6S=wf;qchuiFi3VU@nQ5!lWzAI8;x zeDU}D_3_2e&xIcElfAmonH^LfX~pi^vS-oa#aoM?`_=yadi~d*&*w{jeo9>$wmHq$ z)x~8+#Kt6W7%LdZoKig|(c0d=I%;c{lyO?mx|p4d)@0n+urTfHtfg9^t2FFahpnBp zE${BC=kx31jML9e$-V7W`0x+fHacXwCjp%zY1yCx|qX^PglCiBW#h49<|*`RZJl5k5)%ZtbT_Sb^_ zZI^z0d_4Q-r>9Mb~q(324?=Hhs?eJ^v^0iAcFE7hH zJx%x4-se4q8^13`+AQb7y}i|6Z|CpVG&2kH^z;PPYEDj0A?so+uda)YE`5D1w4F~jWKTsQ z#O_$VyAFEOy?dohgBH8>J~{0*O=o2zGkey91C8L&eBfpPrpf3c?VQ3{SXdafw`%L9 zUvIbHpE9qbgJY^rWY9`bnPHlJ4N@=c_!x7lWwN^etBLM%foZmXzg+gpesN*pER)Ph z;1qVC7o%_HJu_f!lqsy-onSwl`(mU0(E;d_7jrT&Jh}1o^mNnue>H2PwtDR@dm9uI zGKEWAZ_TBp-mBeurLt~rT6$|s=3*gLFAepa+t>Q-|E=ig;K;qP!BO3BPC!Y?7ElY( zYpNEwC$_)tZ^*WsNJa*RC22S7g%}u4Y?Cxj%lPu*VpmVkm8&9JAs)#^lmxXC#+u#P8b! zstn8C-db7t`5CBltrxdv#fcLhxp#I13JMB>>QgRpJsk!HhUp9p4%1XsRaXWt_XAZ! zPoF-2zWmwQ*{kFBR)LD-q~v5!H-ByPc0W%~PcL0V!@$LEy_dw}YZg8|Jss4-K6L`r z1zqYbKG&kqN%Pj$?B(L|HGyYmnFd$w`}-~0_UDtyw|14Tj@pv3kXPDFLq)}ff#JaR z$Zzvm7#KFC{P^&&>igaDr&qV-M%R8g$iD0AwdkqWW*Vn|x#X??aF3X}Y4q!piGG@nd5XBWqoDWoyi?63?|!TQ7aTU%$RxzAmC$ zOjn43;X^JoFfW4|T*u|Aed0iI0qR07a_!c#b#!!mb!lmL*7bF3pPrun`qWfyFI_9E zDDP=Hf$QV;f|`mV)@5%lF!S3im@z}5^6H8}XKAyXfW1{;r%1ALiCj3$Z@-3>TkJ)< zeBFh)<@Z)fnPzb?F#OO{W?-10x_<9BP`CQ;udiR<*Z+^6mijWvs_e~*UTO1HB`+^| zy}q$A`PG$`lZAwYo|OLkQweI+7#bRW+V%V0?yqmR-(Tg_%5|!1!UO@|*=D|Ri`{yI zii$QdFf_b3Vq##JS@z{cpsS0^iA_4SUoN_X`nPFU*F*-d4qF@K;n4wZ8220}Nv#xr5 zzf;`5X;s+TsHlxet$+Uhoq7#4knsEc{`l45>!(fMzkk1&e%zW17XqFhS>V|GWNX4f zCPoH^`>8Ap3`V(kca?s6b#Za~rq!O4)j%zwOPdo9vn~Dc-2VScHeM-><+Dt))z)^RI)c>+R{fyyHCIV^Yb$k z1H-8zq-);MTJmQY!zj*QDC#}FmEes3|`QUg7 z+gDRr`S;iB^}FQsVsm-!SH7J_PSuW34(aDQA~8yy}u z+dTi;*AHim&%gNc^71KUyivS0^^Nw745!zst*)0ac^&L7heJzWN~Wc z(u&-)#n}LyL)OZi1GQn{dy5lDz5taYU$B#acujoR z@IV_JQ=)n?5r^A&C+FS<#ptHTxAXUhF87-|WzHO(^<{5wz1(^|ZgTkEs;?&L=VT(M zEd}?i_tpM>b!w{ish;@yzoF-Czt7q8>&wfDKD+d21s#yMc7a7>>Z(vF z+o~;X{POFPkN0V=^_y$85!s78K)1q1-6M|#+)kGQ^8g}*eUfq~{ zd`t0jzqdCwI%i*76IfERg@NI~|Hq)ZX_H&8ROY)oJ3&K*ZoN`V&)ffxIdAuS4XEsn z&fDo4C$1lNWts16llp%(wZC32_ww)v=sGT6A2V6qKg_|Q!RXTZc>C72wyXmUjG$-^ z+FkUt3)CN48N6J}!Xn~mm+0l4#m}cqn6Ti=y_(OyUteGM_cKX3ApjbJWnfS!j$vY8 zxOr)Bb-Ac^m{0BRZ&yJz$o~5JN)SJ;{?EtuoZDepPft(3emj5v+IhCsTE=oU9~|G_ z+Z(Oy-go89Oyj9@=B(NB=+Pq)aq;C>KsE8p%ga|+e}DI>bXw}kZMlpL4_-r)!nTlA zA)bkeiJxw5PVWZ|A^iN53JNAoozp>Uqqdr4UC{t_3qWN?7^t3}YgHPwzwU3!?QNj; z`HmeHOVu?rJp64wwt#X~8?W@Kll%UB^8WkfvcH%A!$YlC_tjSS%36not_r!>#w$H# z9$GRedNM6K?_vw5@RwuK`5B*|oxQa^fBvGyi$#Tm78N}`wbHSf4K&PD1RC4ET>XCU za?q$;_4jw6ELi&OP2|zz$5*>_3ayIUyGzO>L!rLD{%2G5`@P|Jca;V=GO>zCNUR82 z>a{ZIXxFai^QueU@BM!2Y0=5-_xJWPF)&Q>W?*=*OCw-G187KineS|^?V$S0H0#O) z{pvRxkAJz5+|S9tkkU6Orb8O~H|0h%F#O;GP0Glp-rmN*@Ie$bF=KZM9HpQ_grR0i z?rkQ91K|>k3=BUsZf|2{Xjoss$YO<`{l}Fd==QeM+uL$( zPs_dislsMcZuGX?)2FxP-rT0Ee|y{0)Z5#FZh)Cn4{gg$J+dvg^5(X+XExQ zyxr>P{Jo(+=R$1U!NTBR_wCv2{8yjP+l%MvY`?v>{C;gX1B1eU111Iq`P~&C7Zp5i zZEt`5=xFz;_u$dieTO(08ftV*OhR^i1kFQaU(jlN zGmI227AFqu0QAYwBwH_=H4#aeRorC zbl&!8xqf#DdGh4RqTP3kcHi~#@&dIHKwaV^M_g1@R5bMT{3cGEXjT4h4rp}d@^b&z z+wa!}CnYT^x;#ZQ_{*2e{--zTJPk*3R76I`ibIDFU)`AOo_ckfZZxR(4l0?7q;78m zH84RV`@g@xzdp~ldQ19wxwp5rdQY4tHW3O&Ne%>9_oEn28K^N`DCp! zUR+pM^?vX7DWS@4Jpq@O`IdgaTYlP8=XB7`O{t;Vaw1K0ZUn59wX2B$O;@z>N{a{z zE}X3HueBRARBV=eOJhGYaZG1mC}`jmR?GPN>nnIP>CvaBr$OxuP>26eEB9&fkaaPU zQ?}!-uC%nX+U}M^{%+O3D(e(pM_+ABovK`SW79J2Ll5;CpUNS`nbJQWSiOfuU%a1 z&bl&WWssD4-W)!8yEO-!*-uL>?m2SwsHTlg%+HCC6e_{U@W22xwFR0hSp%9xd4F#& zC^P)APdzo|;+L0~zuqmsztU@})~OR)GB2mx-u6ivl7kGG7!D+MiD)kR`T6;3heoDT ztIp0ge=Yz2NBf8SvrIA<#qKW4ys;tC>-<9J_Ag(r$7}E32+yiAFZTU@cX>zQ<0-Pj zYCfQLy6T_p4Gj!?f4|#3%eHzOxZQYVW$@JdH$X$F;Kl}@Y0(o8&~SKU>g{d6zP?_5 z;)KV;^}oKoU2U3uZAHb$M_#!{yF{(({_J32aJZMk!qDIxIc@39&FSDC$13CWb1Ulq z|J#%s{R1?~@e(v}xua)0VD@+L|?0&2Nsz zlJ#k4XMvhcpx%xMC_1Lhn6YBdqf1M@Z|y8zZnP=+IG?JjDklR&`~x8d2EOade5HBi z?PC7_-v58~nyRN$!%P1E{SN9Q9g4U8@t_$ri}>Z!Y5mjMT3TM9I{*K_zf*668h=x@ z!?jwEKMo8Engp7tx^W{y*1j&rd%E7$mzS4Moyf{9_5#!jdVOte=-#SQ&}1niBjY9= z@0DL)Ty$pT5)oiv_&L*?f#F93Xqf5lu2Rs9+a@JVP0zhmUoZ8^T8qfY=={0bt-o)D zY4)`jv-9^&y#D0mz{7BU-#R$_V>3?KhRX-=JfN|u9m#G;ON>dCJHiDkb%K& zfkPux)P{sc@JtP8*mttOo#vABn{+_s5F;bwr(64KcklcEubPpeVb4(x1_t@Sokgjj z-pEbE{ChT_X-$v&x!dn96I6E7I9mOFuQ~(6gzY<67#jFr-rl}`->+BNwVzI^p90O+ ztw=oFwkm4tst@7%d%sMw`}4sWG~e{`(NU|SCmmX$tCrjeTIwYV>Y^U)5)CdW*uW*K zrBYvCzcWVf=g*%fC6w*$?IWkDp5F8%HF8?c?JzEIv3MtMyNz7`q{)+?mhCQmdP)>p zff8L*g!D8{$=?}wE6%(IC+ zJzZa%fx)3JiG`t|U&^j#2WYI}jf6o$18B;TfuZ5gAy8naMot5bX&R-T(pbG{@#3q? z{pBldFiHD<>u@PnAtR z-gos_uQV$I!-VUgv9w-kbHDF*%ja)8^|oxa|9rb$1rM7T7#KboDKjvDMx`_U{rL&% zoossbLqQ41elD39^Ba zd5Y}ec79OZz{0>VK^&~hDD{*GXc0l=)Cm(7fSPlPilBLy%3eVRh9CNkjg6or$R(z; zV$H4Hi7c3 z<}bJN_k*O?gN8FDL9?Eq`I}HsGN}6gZtasNDWFhebz@*qa8~u2a$$~TaY|2MSnjPY zp6mDj+XeDIsK_n|{eHiGzgxfD+BXvRb$cM)qlVdMIT#pXF27#Cf8DWOY3FLVl^Ma=9#X{;EjY ztNnH2Z5cNM17w)^X4skt!?Ux^{p;%LPOW;s|9@Qa@jg+Iu?Z{;3`I*kC#!+R<2OC- zHNSV^*VosebaNZj^XmiEbi4ll`#m)@Z+mpV-LDmTF*_6(7#i+?+*5Q$V)4%6=dY$k z=LLrS{rCI5lugA31_p+r$>8QI2QP1EMa2%#xXGHREg2I*)788(76Cu*Q8G&p7Ic?qQ`oFK^R|PNc+qA0w|39se6%#<_H>ZL| z*&gh&s`;_u)s>ZtZ`_DTbzAN?_sdc7_!AP!aNWC4P1Vl+_2uQMp5O0w`+t6Ze!3y3 zS-KMxVo$o}*;Z#=Sm3y8R+p%D*3VB*wPJQaTo3A0o4()secro0pZ%6Dcj*-B>gWhq z8MG9XGr-}pj+28U0Mr8xTjGH78`9-A@)L&05}&u5H5KFI?0c{Zhps|6kYlgC@M2#LdBR;nUO8p(}$_L1{JzlxE${a&9cx zu)*NwwzVS1BtQx6>+9>FaxxASiJRUYXk@N>v9KLfS6@-{owcN0uF8XffuRySd@=3B z36HJW*ZrQKpC6tDS`e`Lyqz|v9616S^|`s!d-|1y&g`Js!j-n4PAG#`46%YrM39$t zK33Sgy1IIL-LIGGORp{Q6b5w(r=@}m6i`_c`RPva`OKS}QoYt@U0oHms|1wkKyk6> z1jz7PM>>T~OJ9X74ck?c37V^_`1lA^4zC7vbZ2fVc<8kE$D?kqwFM6kfdudGf+om2m zf8HO|u(-RcwDnKw(^FHco=kKH^{_>_#dI#5n`^zb_IDX83kwGW!*Y;|!%IIra4day zC$h7%(<}GX6wOyRHZGp9U&g*J#2LZEDc-cBMA!hjEjq$j$K<5x%lz%{?ni_$5ml#XMLK*#w(R^b5m;7*Q?=M>;Kn* zR){1fCRROc6%W~v;K<1EAOsZF|MSnyu>>{jrbGnjinHUZn2Mwv`-rg3Pld~qM`svhglZ*=r4{w1+oPPYMs00nC-Q86x%)oFW z8dT#a8X6jc4D*|3v+@e4EnWEd80YnEDJO-_&bN=(1PvrUc#r^Z$$?r+Aa_)MdlQ%i zN`5_(#x2>mcbBhcM=4rXa-fJpxN@R7cKnR4mX?d|J9LHPvKtV{X%>8X@aN=H!f&KOYLkaca1X3X6yD}zmIeiX#+EOO1f zTm617XbO5tc+Acs*Uf2XFYT%Pye0d(9%x`C<>aJQ(c9NmeR;9)-QC@gM&1vu=?n}X z1nvKRnOybljpp{-Yp>Y-dZ7#&4nJ|@<>lpH&lsP7(XGEPzzr0{dy=lNi+y!>_jb_w ziYuUypRDem_43kE(9$(Ay_gkVQ?+2-2)hT@S39 z&?{#fWoT%4s$>5A`Jgt-o`j#Do`OoHM~@#*U6gRJ2~^a3c?EU7y}f;P<>zO=J|33` z^_&?Q9+ZGWy8u-3-QJcfDk-`0%ArGtzWjQhpS*F>`K0ZFa+Oe7K*W-Tskkw(jpni&G-T%Mky)u?T#m~+NGBEgo zn$mpJX3SV|ZmuI?Hb)b+h@#>sQvTt_$;H;u0A>2RcBt^-@iX^o3eW! zsHgU+!sZjGR=4}{fLSv6N$8pgL(|+_A)qw?S={1!SGHtc1`V<^GCW8Dc_m<5?&|pc zb)fPuXm#Y~Wp@An6z`1Da|F$Sy}G*EH25&Da->(<96a>AZk}!Rs)U10yJmuBJ3tHeR;Qet1gepH482}o zSsCo*4<24EumX)uEO>c&dG?_ePA^>*6&FE4L9Ot0bLQwNDuU(>FLVm4gBFT{CI>*p zFDTJCxAUERTlTt&lbieU*(V<>zVz4san`(7@t7Ah8kz}O2lM;eTQC2ONk>^28ZPf- zVQ4tKCTgn}Xno=(P+fcQ$o1<5Xa1LIyWeiRy`|z~Qta7xcXxxT>80x6 zG9En6`Dr7lU;pjR%|pFcH>dl9*0B9_tf<&=DR^g5s%hSx2+(*a*l^J3%(JtzwJj|p z^R`0=|K$4JVbR-vTv;F7AqEe1Zpw|0gA8{%Ay*oA1D=~7~9-Y&Z zrjyV7_~UWEw!Md!*Co(eyLtcq{sxU`J_3(uPEd3PjrB9W1hrm4LylW?P6yrJSIf%K z0PeRHte9z>u4N1AXXWkv8pg=*z#G)Il+iFU3epZ=7gBZT&>KUMf#HV~Xw10%=C<6~-`?C@{N>C0`|%76 zKa{33Ffhod+1SLq-1&Cf?JH{{jhCi>e022Jqi+2tMLWN|TD^W%($Ov^h6A3Urdq*- zBS&07b>+*K*VoV2IUQ6LSNT*_)}p{6ZmxBC*wHRgR)&Vjppj>b6Q7=*2F=(mO^uxP z>-+olU*6o_?$6FAGa)=7VS!xD2T;Sp1k_33GphcUvvj%VWHnG5;F59vJ)5N!PbRvH zh=`~#Fw6maM)BW|$NX!fwtBsMd4K=@OT|x5iGqq!ua93Y`*SlesDow|3pzlnIyE&t zGexyRJnrr;53d3(JiQE>`7C{XZB_XCc?=8%O5k7sEqAN}O=v%Okgyb#l?{`Roj5JA z7}OqMU|0?@?$Ob1ZClfe^au28KDHF8u+;RiUew9XQ~y zR8&G@#g&}f*G^1SHm(0xQwi#(fSP;^3O6Griz;cUw(*gPK&-c~rT*Kwm)wpBf!VtZsW>QVnk;+0d6Sb_CoefFO3R9o; z?0HaBmY?a=sY^Ap(()|cJ%1wgE^48>$oJmR)|C72?f*S5IA{AkW{PXtlc$yQ-t7H; zkFkN*vHkLYMFs}ZmzS6O`&m3}$=P-@ZKipCT-y10bC-HgfA;r%eLWk$+?>n)_O|wa zU;1C(o`0X=K-(fgh6b+Ex3^5y=avNdS-o7sz~HIOaNvLbuP-luUR_@|)pN4i(+AD` z=RQ0<9K70Hu2LmDt}^vluk>{BxC+JM=jVLi-`l(SYFPBs24=n!&+Y&J*3-lDf# zuP-Zqe=qXg`}+UYe70XM=)~=@U^wt=H4DQD)#)*l9GlsedQaE0d@{k=DDTdWoLw)M ztvsk-_p#e&hJm7X&132NC(Lf=Ox}KZef<6&Ia@1jpE(wW`+pqOXJS~hUy6}o)AM=N z&rYh(_sJF0jha$?&Qg7?Z1EYxNKsH&?)$nnKlSFO)W~<$_kYLj`}0(PdGK;SMuuzl zstgWW*X{fD%4e>XsdmoJr_*j0oz~r~Wp=;jbI$(1-y#!_^+@jgcubm^VaCGYRq=GuO_;0$tUFT(-0w0nChEx+F>{`~v?e>?rXUxLhTrcCCMwK7@k z-hXc0?sv1IY`)(q&Rp$1T`yAh?yger=<~MUV;CB~`YSVRV61$(bo!^2{Y{d&#v#e!y|#6vAQaeFM{|2z`keBSPN%I33Xr+?-B`tmY!^~H>o zQWL4(Eni=)Ue5*&hXZU0H#en9S{9`&^P4-Xna@h2{7#{}-M^3h%j5Ug-AtVxyXnvz z%i^NLyyi>T^7noXyOm`k6)D^6rks0rhM_XUfnUo78E!EA`S*SQ`5lFiFD>_<|K}lp zy+?6zao7yQWVfhlkWc-dot-`X+2xC3)ner+O#>9&t^uxJ7awQjN0s+NutyAVrPL$gtfVwPO4R^|9-bSU#{-Q!#(0T z`+h#FeAp^}X{q=0E!!$SKH3uMb@G+Q(j{wuetyml_MyYmcYD9zv-$mI^UJHN!`*tN zre6E^Wx4&+v*!1GvYp%c&RXA0>el@fcX4(2dQjMG&AGX0&Dv?xrfu1paIlGi;q`ns zh6P2SJyx6Ve0X>mG_k*>>}KlprCS@B+0(w?EkAF)^V_ZL&vDo9+?i9laOX}-P@u3d zXqngj`Jq#OLa}|x)|;EtpZ|Kj-amV>Td$Yyu9BCNwnarnZL*4rin=ps={QvE=2YEmyZ@ zUx&Hx?Af!Kd3k=()8nc#qol1$GPd0*I(=nP_4jv~*)K0Gjg#ZgbFwy_HM_lJEi1QJO6i&T_V&5Cd%s=-Fs_qX~(rm!NWtKuH{=vtC9@4suv6Qh=U3*a01y|{e8=~hy3*xd#k>l>X9_wbO=;e zSS|CNJ?$DOxbK$Vw*{HZ!eFMrEv|P){{N5TTgr|Ix`S4fXXc)_{qA#pU2O5=Uh`$P zwZFcctahDmS9@t?@bWEJuUwg;=-jqsYj}LE>FVT5OFT1k*YEx2wfG{a9LWS3XnwaO z_}{sTn+b?fSKK^Fg?YzwMyykZ%d_8z#qOx(>n;UCB{P}!7 z{pO}r&7~=!pfRD$w;jrT91I)kv=|$BPduF-Kku5|kAwWrl;wYVMD6}`N_+F2bo<{o z_x%6+{`{|;BOQV_Yd)XdeCW^5&!4yN|Es(D(aiLDCqaI8S|7L9sOZTF28P$mg&1x$ z+^PM3H}fqhZDoIce*V0E{m;`n<&Qem(`v78Og=uR>u^}q<`Hc&EREECeQzq0;=+^Ja{tM|6Btj^QJ>b z#pCCEe0==*TW~e;=c)c?r~m){etw%@Zw;w*%oP6q{_dY0ox3$OY{&C?)kU{5moIhi zm;3oPzkYU9$-6t2;jyKmpb7k^&(6=EFR1MH} zd^Ril(%R_l3=FT=^DY1H%#grrSo!HmkF>d-HmL0aYOv_-c+ezMey1>UOGaSb@7wp& zK0G+M()0el+Rex1s-Nt7z3%j*qus{E&(6e@-2{a{sJJRFE>`oMHRZJ4ZXI{o(vY~i zpHFqRpH!Ruq)j^S#Djy)%nWPp&x-n|$grVthHZ7(w|mv^K{a-6>5B`B&*#_Iff_qk zUcFpC|J=vp^7DVc+pYip$%eyxr++@5KVNtI9V3PVzt$gq`CowHhC|7#E1DoXrp>l0 z&3blb=Hz>OtDl1!MWA|FP}yxs+WYGJzl-h^9#4I7Vd2X=JBz33L{9qo`FZ-uNvba| zE_N@wmASk}%2Z1{t|D>Yr>X0gxcAF3GQ7HQf+H%$KYAOQ*1H)Bq&;t5Z;iDRcaVVJ{V#_`^$MWaJ{<3#hpRYHffT!k&}WQop^qnQ0>R^6u_(Ha?jdd%xeaR(9(-;b;AJN@?z^D=T-t zTs9lj9tE{R_4ofNN;^Ai>d|iT<Mu|yqGx8wmR*0?)I}FOXil}vji1p+ioOv zGcYh5Se23WvwctV^7(aE*6(&Chk@1@%vihqp4Dkk%jf>jbLB-RRi~#sJk%N~>fFY2 zvhw-d>9*f)D8JtZ%HsAv4)PbhUb{UF)Yc8H{r&B1r~14Z>+8O*zL~MO_fsCIuqt}D z^ZB!h?s6wz{rb>uKc|`B&LaNDA@NP0&sjfzYxDQZ<(q}aWk2Uxzuj`V=-12T>w@}t`@%E`#tTv?e{Y;E-o&9HZy(Mlu28^KAWBY?1-@c8BkBe z6kG_(*;?8EzIp!B=lTC_=KnmCzR4`E{_j^%QL+7QS@g9l!u~cB)#p_xJ)c*d2WsBT zDLSPoX_^%RDje3jU7N@*Ut{q5{r-ATOHQ=v>#M2#bzc^PN>r&{H#Of`Q$T%}%gcPd zqfbxMJsp`ocWV0lS~F0ST&maY+Lg!s_VW@Cw><@w+tJavTSNP7KAm{8{eIoc>+9op zmA{_{Y8--UsnGD)skifX>v~VqImtCy-Tz#NpmNcxmCK*)`FzehI5;@$`jwTzoA1~C z22J65WykI+IoTno{HfEwMyZ*N*X#Ygy`To#nOUaV>-T)}dbj`oKTttl`EKX)OV{rI z`?fu%;2rXNAci(RI`0n}2tCe`caXZ?0d&2#Jf zo|>xK#)*eoY94aOpZM|d@#Sk9kIT*O*57C0>+2gCs;jFjX_Vpt>f>JfcEA3AkEF30 zD5c!a%*;HQU-x--&EMDY=kM$+zI^Mr-M5W4KOQt^o_}+5bMf!D+m~+zHAyWWwg_Lk z_HOt4dHJ=kqa#I|*?3PaaBOB`U^q}6t^E4HN%i@2US3|FzBAa*QuX=V@;K1$xo;&e zE-13gmIQpeTYi5|&8L$)A2e}ax(2GJnfYxDl->KzysG$mHT*NEr)ivi&L?~6l$i3n zrJxSsCqFh`sVSfY2=Xu3<$61xOv>4KRP5)y@B5~QmV)vGsFRy}VS(e$pU-ArUKzZ6 z&*WvxmesuUjz7g`{bquH%_HH+4GE56N8O}zryS<9)>-R2*UI$m%iG)Y`K;f7=8gmU zCzU=p$S!}T=(uco%)i(B|7puwm7K7Cd1GU8-M`QC(~oorW}XM-sO$0dwyReyU8)M| zT!Q*A+s;|NUL#X^@;ehlOCPq1FIx)=-Qb{{KgLMh1q4(7;W7;C7UM{jbYw`j#zQ7E|-_=*yd%({F*+I2l!ad9h{{ zxBec3`hTD2U%pm$J9m11&131v*x7lzGSAt3KC|U{i?H8`MXueS{6N_g6jHf=FWdhO zzIFr@8RzC&vokO--19tX1#ae4e}5MlYg_f@#LDIKW_^n(JSsZVFxlifl$+-Z{`P$(ogq`|a&~eSI@QTfu+ceP1`d{8PLAAI0EhKAzE_FiLEfoo0G9 z!vxec-12<6{ol-}sHjh6AoqU*wLc?+t>115uKNCN?!CR$%hS)#1BKK#lTCc!9DH+g z`n4<1X6MfXHKFYu^Y-&W zy$?`<8@6ocPRqC8F(6R-%YL-y^SRTN&*zFWFfdeDtG+(qZvX4znzbOgx0R1N)s3pZ zy;*bS%uM6r`?cSf+WvmKJ^tD?x#~9)^X~3iY71&>-#fSMcHZo7;6(d+&1O)Dy!JY2 zwc)a#b!zV09gq7m&u>aS{prJD{&esN(^Bv0%WSRRY;e97rl~6b;{f}mdDZXtn&+pt z%a(ck|M&Ouy!UnAci$|(U;Ft0Ge4*XyqBx`+F^g)-%Z;98sQT^J@Ur{0-~YS{-+$`$`u%bsPnw%-;>+1` z(JeAMI%lJ6SlX*ADjMeR+TV|2@*bzQ3RUO($lD!DKbxQ(@7$Q>XsCb$#Eo z?Dc!i_-#Hoggv|HE`N4$zum0*|KICDX}~Dw#s(f~Go9OcyK@;B7!rI%qEnj7pES$=NdUDAWD1W6{=Cp`2ioiy5!${)+&f?8Rw$BBx=gyu2$GGs>vuA&vNw=GPzUGh;>~n~Y}XZ1N0S7Qep^oUV=X@9imhd&@LC z``w+Lku&T5{z}a)eSJ+ARIr1(nAz9X#MJ-&DrubN^KQ@Qb9?4)Og{dsiCgbP`2Js4 zVWxw|dahmDeBN&M)Sm~~?Iui>G)Qm=dp0#Z?j)!T-}?2G_WC8ZbFIt$_SgOWly?g> z%6P``_>&pQeJ77}3KxI7nf|;>d)6bPp9|tw9xKv*sFg|ZH zd8=vRqa$n1%&UGkbL!6%?sk)=&a@~@y2mT5?&nht>dJs>s@JCuG%|x~=x6(Wzw-v? zMo>?)kzEe7cXUhGCO(^wM}#Az`)$8P+`7Ejz5mUQ$9<7A`|JNa&NPwAJRj_DJ9R5) zR0lK$lzINbLg&x>A12HHj>y~ncAJU0`EuKSyI&cgcFxvGr4Qy+zdLy=Vp~omc)a*| z12dlosM)bB{ro&oU45(g`P}lfT+o_VP-NZueE$ER^Oe>h6a4J|ewkDE>!s!MImMgI zK-Hvv%|q_Y^UzG5u{K3^hC!m!yoyJiYgQIMKKAtM^?3j4e{b*q(|!NsMsmMz^}C(V z=bf2nYn}ag+3Y;8YS5@IsNV`P{+z|*o;iibBqeRDN+3T8K|NzFP|p3P6SYMnKmF5_lah8d z6>a?T^FUqiA0Hl8{(L%}8PwhgKWSBT(N+BE`ue}wVb|yX`?4I==FdD|zW?`K&=A&K zQ0F#%PNCba;%kxVsUII5jTBBk)^n0uf6s(%Y&;SZK-JXh*TRkAe!R%<~7?<>v%0c1sQYdwTyL?{ByB_iy?7s9XQpoZ@pQUse76dL2}7 zEML29*|I;+%=deOhA^+6ulu$c93`(n<9E@wbGOIZ{kqtn7Mi>N@3&0Qu{f31{`P-M zqHd>5_Pw;b{5=B$!-spJoA~AwpREp`PYczJ*$nYeCblvwXzit8h?K5ub*>uW6sS@F|}W> zX0BbjR5dzpXKLB)-0fSwemKnk{7SIDueY~%n8o8B<4*_q>kL2@N4e>yzCRz@?azF9 zc^OoYEL*x%)%;#XGN`Cs)7NWuOXGTMc`PVFtXVm??3QNv{o3+2Ug>F|=9RWi2BfxozLa-ux$biqHE72M1rf z7F&LIDu2xbX3)@l(T9WV>DOb+&w^S@AZxqD_0L7;@15EytPU#NPG1d=pL;#7S~r(h z*2?61Ty<{P^$m%KH$9ye4Jvp&vzN~)@=7~1VGv)TRQ}&}%*{ z+b`1V0CqML4_2k0h)Sok!z%E^}C(v zdV9ZI0?o5jnxC0tX&nFi)^$*#OwR??f1t@7qnaNd)~o|HO78#v_r3V*)$sIpcXn2q zgNo?*zpuiJ&sjcSQvCc}q;UD&(r`Yj7Yjfkw|cRAzZ~e;md%IxtXaSvSkTbiwF@^k zCL7oN`LU%G6us%^=Yf*LgM-b*M+Dtd4mPn?{`>iSbHT$yYkY!(gF&;2pU>Od+yA=Q z56WYEqCrFGpm9LZaN{!n`SU=|($&?C1b4JwTwH9Nc4o$#J)h4-3WJ7wKRi5K3>wuu z(7*`lSv@&9d2`0aMQhf92DCu~ai9^^0Jvndvy(YP!rHwbMUa!4;ZIY@ts7?92Q+?iv?Dc!6 zSr$J7jUI#M9YC$bgU#%qNw}Z)4RUX7`E&mNAAeB)Nqv5e(fU1~yg(@sEe?pplAQQ-~i$l!{~pwPxOKzdvsqXn1wY z^J~%hXP3>+n{^GehXWLopuxz0-%U4hfd(5ulbeyD@jp+6U%KY0K6lFeKTp619{&4| zJal>egw~D3cG*jidNJW|2&f-0I`%m_;OoV}z%V+>y=ru5e{=!_)ZL@@tk1pw?Ec3l VY9?1d`UR>2JYD@<);T3K0RUiohH(G@ literal 0 HcmV?d00001 diff --git a/test/huxley/Huxleyfolder/DisplayMode.record.json b/test/huxley/Huxleyfolder/DisplayMode.record.json new file mode 100644 index 00000000..3cae6ac6 --- /dev/null +++ b/test/huxley/Huxleyfolder/DisplayMode.record.json @@ -0,0 +1,5 @@ +[ + { + "action": "screenshot" + } +] diff --git a/test/huxley/Huxleyfolder/Spacing.hux/firefox-1.png b/test/huxley/Huxleyfolder/Spacing.hux/firefox-1.png index 7d33e06bcdb2d612843f7b8aa99268ed4d88cab0..1017f997504d181eecb4cef2b2f98c3c531b0297 100644 GIT binary patch literal 16091 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 a3Ybkc|K5|R`f>uOLFwu0=d#Wzp$P!voK2hn literal 8787 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 AkpKVy diff --git a/test/huxley/test.html b/test/huxley/test.html index 6ff6a228..2b8b4c9f 100644 --- a/test/huxley/test.html +++ b/test/huxley/test.html @@ -16,13 +16,15 @@ diff --git a/test/katex-spec.js b/test/katex-spec.js index 64d30294..310cfbef 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -1,17 +1,26 @@ -var katex = require("../katex"); var buildTree = require("../src/buildTree"); -var parseTree = require("../src/parseTree"); +var katex = require("../katex"); var ParseError = require("../src/ParseError"); +var parseTree = require("../src/parseTree"); +var Settings = require("../src/Settings"); + +var defaultSettings = new Settings({}); var getBuilt = function(expr) { expect(expr).toBuild(); - var built = buildTree(parseTree(expr)); + var built = buildTree(parseTree(expr), defaultSettings); // Remove the outer .katex and .katex-inner layers return built.children[0].children[2].children; }; +var getParsed = function(expr) { + expect(expr).toParse(); + + return parseTree(expr, defaultSettings); +}; + beforeEach(function() { jasmine.addMatchers({ toParse: function() { @@ -23,7 +32,7 @@ beforeEach(function() { }; try { - parseTree(actual); + parseTree(actual, defaultSettings); } catch (e) { result.pass = false; if (e instanceof ParseError) { @@ -50,7 +59,7 @@ beforeEach(function() { }; try { - parseTree(actual); + parseTree(actual, defaultSettings); } catch (e) { if (e instanceof ParseError) { result.pass = true; @@ -78,7 +87,7 @@ beforeEach(function() { expect(actual).toParse(); try { - buildTree(parseTree(actual)); + buildTree(parseTree(actual), defaultSettings); } catch (e) { result.pass = false; if (e instanceof ParseError) { @@ -103,8 +112,8 @@ describe("A parser", function() { }); it("should ignore whitespace", function() { - var parseA = parseTree(" x y "); - var parseB = parseTree("xy"); + var parseA = getParsed(" x y "); + var parseB = getParsed("xy"); expect(parseA).toEqual(parseB); }); }); @@ -117,7 +126,7 @@ describe("An ord parser", function() { }); it("should build a list of ords", function() { - var parse = parseTree(expression); + var parse = getParsed(expression); expect(parse).toBeTruthy(); @@ -128,7 +137,7 @@ describe("An ord parser", function() { }); it("should parse the right number of ords", function() { - var parse = parseTree(expression); + var parse = getParsed(expression); expect(parse.length).toBe(expression.length); }); @@ -142,7 +151,7 @@ describe("A bin parser", function() { }); it("should build a list of bins", function() { - var parse = parseTree(expression); + var parse = getParsed(expression); expect(parse).toBeTruthy(); for (var i = 0; i < parse.length; i++) { @@ -160,7 +169,7 @@ describe("A rel parser", function() { }); it("should build a list of rels", function() { - var parse = parseTree(expression); + var parse = getParsed(expression); expect(parse).toBeTruthy(); for (var i = 0; i < parse.length; i++) { @@ -178,7 +187,7 @@ describe("A punct parser", function() { }); it("should build a list of puncts", function() { - var parse = parseTree(expression); + var parse = getParsed(expression); expect(parse).toBeTruthy(); for (var i = 0; i < parse.length; i++) { @@ -196,7 +205,7 @@ describe("An open parser", function() { }); it("should build a list of opens", function() { - var parse = parseTree(expression); + var parse = getParsed(expression); expect(parse).toBeTruthy(); for (var i = 0; i < parse.length; i++) { @@ -214,7 +223,7 @@ describe("A close parser", function() { }); it("should build a list of closes", function() { - var parse = parseTree(expression); + var parse = getParsed(expression); expect(parse).toBeTruthy(); for (var i = 0; i < parse.length; i++) { @@ -253,7 +262,7 @@ describe("A subscript and superscript parser", function() { }); it("should produce supsubs for superscript", function() { - var parse = parseTree("x^2")[0]; + var parse = getParsed("x^2")[0]; expect(parse.type).toBe("supsub"); expect(parse.value.base).toBeDefined(); @@ -262,7 +271,7 @@ describe("A subscript and superscript parser", function() { }); it("should produce supsubs for subscript", function() { - var parse = parseTree("x_3")[0]; + var parse = getParsed("x_3")[0]; expect(parse.type).toBe("supsub"); expect(parse.value.base).toBeDefined(); @@ -271,7 +280,7 @@ describe("A subscript and superscript parser", function() { }); it("should produce supsubs for ^_", function() { - var parse = parseTree("x^2_3")[0]; + var parse = getParsed("x^2_3")[0]; expect(parse.type).toBe("supsub"); expect(parse.value.base).toBeDefined(); @@ -280,7 +289,7 @@ describe("A subscript and superscript parser", function() { }); it("should produce supsubs for _^", function() { - var parse = parseTree("x_3^2")[0]; + var parse = getParsed("x_3^2")[0]; expect(parse.type).toBe("supsub"); expect(parse.value.base).toBeDefined(); @@ -289,8 +298,8 @@ describe("A subscript and superscript parser", function() { }); it("should produce the same thing regardless of order", function() { - var parseA = parseTree("x^2_3"); - var parseB = parseTree("x_3^2"); + var parseA = getParsed("x^2_3"); + var parseB = getParsed("x_3^2"); expect(parseA).toEqual(parseB); }); @@ -350,7 +359,7 @@ describe("A group parser", function() { }); it("should produce a single ord", function() { - var parse = parseTree("{xy}"); + var parse = getParsed("{xy}"); expect(parse.length).toBe(1); @@ -368,7 +377,7 @@ describe("An implicit group parser", function() { }); it("should produce a single object", function() { - var parse = parseTree("\\Large abc"); + var parse = getParsed("\\Large abc"); expect(parse.length).toBe(1); @@ -379,7 +388,7 @@ describe("An implicit group parser", function() { }); it("should apply only after the function", function() { - var parse = parseTree("a \\Large abc"); + var parse = getParsed("a \\Large abc"); expect(parse.length).toBe(2); @@ -390,7 +399,7 @@ describe("An implicit group parser", function() { }); it("should stop at the ends of groups", function() { - var parse = parseTree("a { b \\Large c } d"); + var parse = getParsed("a { b \\Large c } d"); var group = parse[1]; var sizing = group.value[1]; @@ -446,7 +455,7 @@ describe("A frac parser", function() { }); it("should produce a frac", function() { - var parse = parseTree(expression)[0]; + var parse = getParsed(expression)[0]; expect(parse.type).toMatch("frac"); expect(parse.value.numer).toBeDefined(); @@ -460,13 +469,13 @@ describe("A frac parser", function() { }); it("should parse dfrac and tfrac as fracs", function() { - var dfracParse = parseTree(dfracExpression)[0]; + var dfracParse = getParsed(dfracExpression)[0]; expect(dfracParse.type).toMatch("frac"); expect(dfracParse.value.numer).toBeDefined(); expect(dfracParse.value.denom).toBeDefined(); - var tfracParse = parseTree(tfracExpression)[0]; + var tfracParse = getParsed(tfracExpression)[0]; expect(tfracParse.type).toMatch("frac"); expect(tfracParse.value.numer).toBeDefined(); @@ -486,13 +495,13 @@ describe("An over parser", function() { it("should produce a frac", function() { var parse; - parse = parseTree(simpleOver)[0]; + parse = getParsed(simpleOver)[0]; expect(parse.type).toMatch("frac"); expect(parse.value.numer).toBeDefined(); expect(parse.value.denom).toBeDefined(); - parse = parseTree(complexOver)[0]; + parse = getParsed(complexOver)[0]; expect(parse.type).toMatch("frac"); expect(parse.value.numer).toBeDefined(); @@ -500,14 +509,14 @@ describe("An over parser", function() { }); it("should create a numerator from the atoms before \\over", function () { - var parse = parseTree(complexOver)[0]; + var parse = getParsed(complexOver)[0]; var numer = parse.value.numer; expect(numer.value.length).toEqual(4); }); it("should create a demonimator from the atoms after \\over", function () { - var parse = parseTree(complexOver)[0]; + var parse = getParsed(complexOver)[0]; var denom = parse.value.numer; expect(denom.value.length).toEqual(4); @@ -515,9 +524,7 @@ describe("An over parser", function() { it("should handle empty numerators", function () { var emptyNumerator = "\\over x"; - expect(emptyNumerator).toParse(); - - var parse = parseTree(emptyNumerator)[0]; + var parse = getParsed(emptyNumerator)[0]; expect(parse.type).toMatch("frac"); expect(parse.value.numer).toBeDefined(); expect(parse.value.denom).toBeDefined(); @@ -525,9 +532,7 @@ describe("An over parser", function() { it("should handle empty denominators", function () { var emptyDenominator = "1 \\over"; - expect(emptyDenominator).toParse(); - - var parse = parseTree(emptyDenominator)[0]; + var parse = getParsed(emptyDenominator)[0]; expect(parse.type).toMatch("frac"); expect(parse.value.numer).toBeDefined(); expect(parse.value.denom).toBeDefined(); @@ -535,9 +540,7 @@ describe("An over parser", function() { it("should handle \\displaystyle correctly", function () { var displaystyleExpression = "\\displaystyle 1 \\over 2"; - expect(displaystyleExpression).toParse(); - - var parse = parseTree(displaystyleExpression)[0]; + var parse = getParsed(displaystyleExpression)[0]; expect(parse.type).toMatch("frac"); expect(parse.value.numer.value[0].type).toMatch("styling"); expect(parse.value.denom).toBeDefined(); @@ -545,9 +548,7 @@ describe("An over parser", function() { it("should handle nested factions", function () { var nestedOverExpression = "{1 \\over 2} \\over 3"; - expect(nestedOverExpression).toParse(); - - var parse = parseTree(nestedOverExpression)[0]; + var parse = getParsed(nestedOverExpression)[0]; expect(parse.type).toMatch("frac"); expect(parse.value.numer.value[0].type).toMatch("frac"); expect(parse.value.numer.value[0].value.numer.value[0].value).toMatch(1); @@ -574,7 +575,7 @@ describe("A sizing parser", function() { }); it("should produce a sizing node", function() { - var parse = parseTree(sizeExpression)[0]; + var parse = getParsed(sizeExpression)[0]; expect(parse.type).toMatch("sizing"); expect(parse.value).toBeDefined(); @@ -596,14 +597,14 @@ describe("A text parser", function() { }); it("should produce a text", function() { - var parse = parseTree(textExpression)[0]; + var parse = getParsed(textExpression)[0]; expect(parse.type).toMatch("text"); expect(parse.value).toBeDefined(); }); it("should produce textords instead of mathords", function() { - var parse = parseTree(textExpression)[0]; + var parse = getParsed(textExpression)[0]; var group = parse.value.body; expect(group[0].type).toMatch("textord"); @@ -626,7 +627,7 @@ describe("A text parser", function() { }); it("should contract spaces", function() { - var parse = parseTree(spaceTextExpression)[0]; + var parse = getParsed(spaceTextExpression)[0]; var group = parse.value.body; expect(group[0].type).toMatch("spacing"); @@ -636,7 +637,7 @@ describe("A text parser", function() { }); it("should ignore a space before the text group", function() { - var parse = parseTree(leadingSpaceTextExpression)[0]; + var parse = getParsed(leadingSpaceTextExpression)[0]; // [m, o, o] expect(parse.value.body.length).toBe(3); expect( @@ -655,7 +656,7 @@ describe("A color parser", function() { }); it("should build a color node", function() { - var parse = parseTree(colorExpression)[0]; + var parse = getParsed(colorExpression)[0]; expect(parse.type).toMatch("color"); expect(parse.value.color).toBeDefined(); @@ -667,7 +668,7 @@ describe("A color parser", function() { }); it("should correctly extract the custom color", function() { - var parse = parseTree(customColorExpression)[0]; + var parse = getParsed(customColorExpression)[0]; expect(parse.value.color).toMatch("#fA6"); }); @@ -690,20 +691,20 @@ describe("A tie parser", function() { }); it("should produce spacing in math mode", function() { - var parse = parseTree(mathTie); + var parse = getParsed(mathTie); expect(parse[1].type).toMatch("spacing"); }); it("should produce spacing in text mode", function() { - var text = parseTree(textTie)[0]; + var text = getParsed(textTie)[0]; var parse = text.value.body; expect(parse[1].type).toMatch("spacing"); }); it("should not contract with spaces in text mode", function() { - var text = parseTree(textTie)[0]; + var text = getParsed(textTie)[0]; var parse = text.value.body; expect(parse[2].type).toMatch("spacing"); @@ -725,22 +726,22 @@ describe("A delimiter sizing parser", function() { }); it("should produce a delimsizing", function() { - var parse = parseTree(normalDelim)[0]; + var parse = getParsed(normalDelim)[0]; expect(parse.type).toMatch("delimsizing"); }); it("should produce the correct direction delimiter", function() { - var leftParse = parseTree(normalDelim)[0]; - var rightParse = parseTree(bigDelim)[0]; + var leftParse = getParsed(normalDelim)[0]; + var rightParse = getParsed(bigDelim)[0]; expect(leftParse.value.delimType).toMatch("open"); expect(rightParse.value.delimType).toMatch("close"); }); it("should parse the correct size delimiter", function() { - var smallParse = parseTree(normalDelim)[0]; - var bigParse = parseTree(bigDelim)[0]; + var smallParse = getParsed(normalDelim)[0]; + var bigParse = getParsed(bigDelim)[0]; expect(smallParse.value.size).toEqual(1); expect(bigParse.value.size).toEqual(4); @@ -755,7 +756,7 @@ describe("An overline parser", function() { }); it("should produce an overline", function() { - var parse = parseTree(overline)[0]; + var parse = getParsed(overline)[0]; expect(parse.type).toMatch("overline"); }); @@ -785,14 +786,14 @@ describe("A rule parser", function() { }); it("should produce a rule", function() { - var parse = parseTree(emRule)[0]; + var parse = getParsed(emRule)[0]; expect(parse.type).toMatch("rule"); }); it("should list the correct units", function() { - var emParse = parseTree(emRule)[0]; - var exParse = parseTree(exRule)[0]; + var emParse = getParsed(emRule)[0]; + var exParse = getParsed(exRule)[0]; expect(emParse.value.width.unit).toMatch("em"); expect(emParse.value.height.unit).toMatch("em"); @@ -802,16 +803,14 @@ describe("A rule parser", function() { }); it("should parse the number correctly", function() { - var hardNumberParse = parseTree(hardNumberRule)[0]; + var hardNumberParse = getParsed(hardNumberRule)[0]; expect(hardNumberParse.value.width.number).toBeCloseTo(1.24); expect(hardNumberParse.value.height.number).toBeCloseTo(2.45); }); it("should parse negative sizes", function() { - expect("\\rule{-1em}{- 0.2em}").toParse(); - - var parse = parseTree("\\rule{-1em}{- 0.2em}")[0]; + var parse = getParsed("\\rule{-1em}{- 0.2em}")[0]; expect(parse.value.width.number).toBeCloseTo(-1); expect(parse.value.height.number).toBeCloseTo(-0.2); @@ -827,7 +826,7 @@ describe("A left/right parser", function() { }); it("should produce a leftright", function() { - var parse = parseTree(normalLeftRight)[0]; + var parse = getParsed(normalLeftRight)[0]; expect(parse.type).toMatch("leftright"); expect(parse.value.left).toMatch("\\("); @@ -876,7 +875,7 @@ describe("A sqrt parser", function() { }); it("should produce sqrts", function() { - var parse = parseTree(sqrt)[0]; + var parse = getParsed(sqrt)[0]; expect(parse.type).toMatch("sqrt"); }); @@ -1036,18 +1035,16 @@ describe("A style change parser", function() { }); it("should produce the correct style", function() { - var displayParse = parseTree("\\displaystyle x")[0]; + var displayParse = getParsed("\\displaystyle x")[0]; expect(displayParse.value.style).toMatch("display"); - var scriptscriptParse = parseTree("\\scriptscriptstyle x")[0]; + var scriptscriptParse = getParsed("\\scriptscriptstyle x")[0]; expect(scriptscriptParse.value.style).toMatch("scriptscript"); }); it("should only change the style within its group", function() { var text = "a b { c d \\displaystyle e f } g h"; - expect(text).toParse(); - - var parse = parseTree(text); + var parse = getParsed(text); var displayNode = parse[2].value[2]; @@ -1108,13 +1105,13 @@ describe("An accent parser", function() { }); it("should produce accents", function() { - var parse = parseTree("\\vec x")[0]; + var parse = getParsed("\\vec x")[0]; expect(parse.type).toMatch("accent"); }); it("should be grouped more tightly than supsubs", function() { - var parse = parseTree("\\vec x^2")[0]; + var parse = getParsed("\\vec x^2")[0]; expect(parse.type).toMatch("supsub"); }); @@ -1144,7 +1141,7 @@ describe("An accent builder", function() { describe("A parser error", function () { it("should report the position of an error", function () { try { - parseTree("\\sqrt}"); + parseTree("\\sqrt}", defaultSettings); } catch (e) { expect(e.position).toEqual(5); }