From 2ef44c1b27f38b27f4caac2940c6a1b7afc20f77 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Tue, 30 Sep 2025 21:22:09 -0600 Subject: [PATCH] Work on character centering --- fonts/KaTeX_Main-Italic.ttf | Bin 0 -> 33580 bytes src/consts.rs | 8 +- src/element/element_parser.rs | 29 +++++-- src/element/functions.rs | 10 +-- src/element/mod.rs | 6 ++ src/element/rasterizer.rs | 147 +++++++++++++++---------------- src/element/symbols.rs | 46 ++++++++++ src/element/text_parser.rs | 159 ++++++++++++++++++++++++++++++++++ src/fonts.rs | 2 + src/main.rs | 19 +++- 10 files changed, 330 insertions(+), 96 deletions(-) create mode 100644 fonts/KaTeX_Main-Italic.ttf create mode 100644 src/element/symbols.rs diff --git a/fonts/KaTeX_Main-Italic.ttf b/fonts/KaTeX_Main-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0e9b0f354ad460202bba554359f5adcc8da666b7 GIT binary patch literal 33580 zcmdSC33waVeJ?ua%nl3&g8?wucM>46a03YLi%5}_MD06e$)YUDi{#yoW5;%4J6>Yh zN!%t)oHpCdp0wW$WGBtizTRv%ZQ7)5)|)hI^V%k@)1=Mb)FR*SoB=7ymXqeb_ucP% zqCfyMXJ*cSU(caH1VJ!``vg%~-nFlHVD3HVULgqAAI8z;lQ*2W`NUgJW5o3z76h{S z5*7d5bpU?oIi5)&DY#;RnLWc1mT8faTDX36Sv)rvKZWmqf$`ZJPuy_&`0MX61mWJOAV{~~eA8{W z|2!MmB?$LFj`6R*`PS1npZ)}W81vOI{waY919<-n8V0ngAk+i(K&c)m47~2tzyJ+j zcxHf{$C$IY_8$6KAtYo4?SrX^CeVQj8F0d3RZ*2fzF`kFhbC#gDK?vR91h`Fk!%a6 zwr>+@D7;nlBt%W2+SkOcR3z1t3Mc7jqmK8x-xU>`{FxdviAd>%Yf{|_C*G3)phAn> zN#pd7g_JOcC-xQsGJ9gaIN0D12#45%Ri%Ujcyx0RpX-d;}jArBF~!S!Gye zur`3^p`qs-8Y+~Eo&2Jq=55ydi^bDdi-sZIa`VnTimn-C#>z*C{w{peRFf=j+qu)^ zqxQsPMUj(BH|^4Gzu&8gln-!+zw^?AxN3)=eDj^fBL2U@xlYMT(V<37+3h6)xQvT z(H{yU!fxS&aJ%4}_S}5(@SZIbF(QEx82TbkeSp!P37%zvwLoSJMDAFk8fqXWUkHaByBod28t>%?c_2i6CQmE=9 zbmjcY2dS)R1{FzyIG+B#`k23~cTV+NO1_JzyIz&@`clzQ$ToGK$FJ!#HYH}cylEht z8%^qsQvFa_5kp0vVOox16+61QB-~E9&6=47Mj~0FU1y;x#<^`>h~Tday6G3 zna=eXisIkA6Wsc;)qfH5kn4Md$H1-k-`gxODTDL^TO2RVp&=l`mAVL%ITd(fOqnmX zMQ_N(Ynuozm*UcTeXv-}bNt~VcHkn0f!J`UO}s(SqsUMV`DJ4|2bzN%_2IDNgdKWo zMs7(Sxq!3s&1fQ&PCJBJq#DiB?n12? zE2f7^UgDUMT;A(FIVUd6CG5xFG1OZdPM2q!Mj;aQ9(vSEHw_f~!huj)rfQL}!$SkUUvgwl>os6_Ju5xz|BBzJLLr#u~y z$#2qgq}tY5_DDAE(G|nhWE*tWu7$3k;?qT@nc_Nh7Sy-!#HvDNNK{Htp4J6CCE|(% z1PFNNWOcb6f)x%G{=KJe4?Mhev!SV?^P+`aHce)w(64&laAfDShB+ZZU{xUx;~5z|BgmhTUw;VbS}c_sLqh}g`ugJv?%j6>*xhAWk`4aY z&0Bx9cK=UAbGW~zhy;(`vGmh-ALwUK`XrgATkxd4c+#j1XoB1@)SXBK%!dDvY8_k@mNkim&@1dl1vh&rtB7d7-a;g*X*PBDr%fk*%_ISrW%q=uk|Mg=`QEGCmdCi z2o1?QIyyt4ZX#M<$7@-ZpJvjM?DI#Yo!iD7)$n=9Yo)zn(ktn{c-9}tN-Cg!eD%-i zztJ9{45%ZNaZ0nvsHLc(`T#c@_MCnkCM#< z+SthD9(srwi*x7ldD(N{3s&#g`h-xux@ z(wK$Ds5~^_s#zspOk`Y4H}yA7d?Kj9q$fGRP-B>L`RHx?wYlYD;EMC zK~48%`o;}dl&F*JQs7<@VZZPJazOY`_)f~xfPJ9S$pK}AYpWizQ#B|}9J@u5K*s-6 zMX67ei71J3G{&$6uSNJC=IO;9WOj#(9zsZYV>nq&hf}pgBvnp0sS3v1kdW)ZQ|yUe z!m;bJjRM1xFAg;YIwyHyLCJ+L!LIozp;h`%8n<}QZc;RuLGgb;tKPW!3>~0vfVWhG zR_%!BuD9gkh~WiNz&NxyB(AAJU<}exEVvp|RmhuPo9y1)*cy$!LN*dz<>AKE|0-w9 zKqZrEbf=?oDAlmi^(<`d8{R*C{W}h93M%g$8hiNAky=H&@R`mY#ot@~;ybIEvPwE4 z-GBYk!3f~H5orAq{e7Vy@FjhKFPL||CpH(ReM6MlI=CQgsvsYxgAwc`Gqk~ms{Asf zkzg)1*VKq4nXgg3{g|=rwU6X;2lFbS@ex~9@6f8`8Lw%97T(lW54A3QKvW?~I<-uh zQZJ1$Q>K&86FJT(y>0bhXbJN=i+S~W`MipQYcp$8fT<=fy^PPQ`e~Z(<~uDVF9ADc(l!^_5Uxr>5kZ3z?vt z?D0ynHv&MS^=f~ln5#q#-KY7z5)E6L52G zhwRSBwMcH?NX`r%-xf`b4)spO%-n#77$(sMat7qhcc8D^3a4dJ^W_aVVAhsJj{CJJ&UO5~p6U{5yW>sE4#O-(W_CW%A| zi9Df&w|b+~$wt!l`Mh4eXy%7H(w4Gmu&=Kibt1XSHb?2+d&SFiGSsVt4&p(gR4A8o zyXx_N6L$a}dR8aNw}6K&z{7k9c!)HIFlh#Vi5VZp3Bfk_Va`LGg&=}BvBq7%(@?XG z5$1k#d|b%O(XpPl_7eYUp71Y}1Th{k8zzGP&#oVx4NCO1;(6k-3gc z*PKS7AMXTggV2u+VH@=0%urW?d7X`+fnsr8h#8P;49cNg(+6-?z?^LbrLIf&MUN7e z?(+jgl&L?~*E7`_&>E9li($na>7SSxEmWw-?zwegt2ZgV2#c60@8=acC?RTjqb zXJXl|SaSQ+D8hUqyaEuu1Uxwl>Ww)}Heo*)Y>fW^&LCH-fDx*eImeu&%r60@CMD(| zF#ttAmKlp+ll;h+b^La4lL;EI6u&5SHxw_NUfC;pdS;#q+K! zG@l(UQMvwZFEwO;C~Lptc0TJ`(nMP(@hl%pJ0!B`!At()3z#@4bKY?VC zh|^Zcgjqen%zgh-i97$~trOd?jT@w-6$$kpoyv6#?=V%Pc%#qPM`i2UDcM&)pvpbE zqsXFC&xN{HzTNB|y}}@xVi)_Q__nxCe}8)T%FRKEYG? zEFK+S_K@zay*}Gl(rj5KQo2!2m8vUprMbPXSdoC&&UV`ai#!8y7Bpm#4|6WvJlfaA zB6&B8VNT?lP{CUS?A)xc`?}2cdIfP`%o0V;aK~gW!y6v zc)@$R=mFkfoWLsJh9a0rS$~X^sHA(8(72YDbWI~xUm{4TsHz*E%Th7pPICViPl3t? zFyQYSF8T=j$KbnNcu3U9O@bjrF&D@0p@2^askyAnrn#Kd7|Lb2-<->dnt$b+E8mjf z3zGsVT)5lM-k7h9!`~!VJGz_i_re|PvTdJeomN29HM=2*VZoY8U}}42PD## z63!uEZe?7lXO`DB#>nTeu}m0T{Q%<&6TC5#_DmomSqs&^qGSzDDt1jjIH}rxhd~sE zq+8>KM7m5OC3`ZZC#W@&EJS+JvK*H>`qvX~!gCnZYfhNID`E`3v<8c9YR zUrJF#kDcl2&Wu-yoih8PW~tLM8kSw0flHE%7giqJkehpQ^=D$3eq2}L0U?;ZEhsF;j5sU+V_>({1i)rt+NFxVQE0e>TsF+mI*Tq;5!k^U-E<6> zJ%+O)y^ctl;mgHiGC8ot3iLNmJvld)Dy1^Vj(K8^hY(#pduOTV_)EYS_K8CHs0Bmt06F90m_1Qq_6J4R4^B~z<39C1?fS#J^)@S!7B(e)r*lpTd;@XK8Oj@z2lN4WOZ)m4&A;LjCrOMNy{FQ88DjUeGh{`hr z0x9m*FjQ6=vaY%gZ`n=guinfR_4&oxzPlLl?fs_0mHH82{6BuDGsm!V9uBN z2I1KvqcBisB4h*Z!D69MEXuAhYb2LR9fD zk9oa~dBwhNHq!O>tB*t3$Vf<-Y9S)i$A~gy1DH3DDckJM2!a1UPPgyy^dq{tS2}Wt z6bc7slh)wY5`EGK#0C#)p`_Kn6W*V`WiT8bKW+ih3K9JQsCh+~Xcb8ShNDy4n7-1a zpTPiA-_E-JF`)ESk0V;5a^UKz?qPTnuUmaatkO>ko8e6~Q{2<%T)~8i1#H}yaxvnV zVLoATgM=~p!XWs!VU`+T;n5%>1^V%^scVk)=gJ;Z8`!q{Wie6lhbi3K2lmDC31#PX zA)N-63(0G4v+~+Phe)@}{u#^}iA^eze_gy{slM&xHJWpzf8e2gNQcVNeGi2d;=O7| zS1}wtcYC*U&>-fpvIBNWTltj=v#lzI15O_T$yHFh0==nyaI$7Gz04?$iMGuqW9iF~ zcMv)fQ$dlTFza=|tw>pe2f; z9gqmzB#$W*%Muk?^JT}XbK8$(b{Eqn4;(=dbXk@}&`mJJ=1&KP82%EzEFU-*8b*2rV z54ost1|tW9SLCvL{3gQ`vW}cPIPywKNGY-vKXGxV!YQjX>1fEGbBIqAv)R5;D-!oY zFS1&RU!9`Bu>g8uH}4J0jK5xoTs_kQOW>~%!Y2VjJm#SLJ&=7JeNa7^1mJa-fu1My0Wuq9AK3}f(9fV?)M z+V+L95k}Dg=Q+dHByi1CFdMpgQ~!wO;rOtdhN4GROOhUVV8S&fmyVEA9T7=~VsLZV|Nshw39mmHZ>){y|vHBbfUmnEQz|Fh9gRK0fyi4$un-0fR$yrwtKU zOX_k+xI%Q51T;V-O~Wh$yuqntj{sr@8zLJOa`_($)*bR^kzL4yZ&0Jpk$_Q+YXtDO zBa7R~+wU2aC^DsRL#-{5ddQE;PDq(-qDZ`aTr5l`e3c+G9g30KVs8(!J0knGm3g*d={ zfcdEkCZxt?5@<9e@_sKAFX8$Jah_}2ZwKxt{|lOjG0~+G(B>DYrS|c{WweD^SdJYH z2jD!G#=IyIJwClTvUxMgJ~`zu^6IINu1V&lLQYiXe!~F==l>0rjCz$y)nE7OEjcb_QV8-%NtqC!W1Vc7=mfPgze6dy)cn(htwb?Gn%kCusxJfm6opG!P^pbND)Xcn=|y_s>mmt0b`E(~euN;S2^9yH zVRSZNo7b6!mM^@OK*kTQ3M;=LKr}zkaE8tsZf8!eQQM8ecvXR6Em$lSxI4mQT00V& zl$eLc!!K@p7z;pT+Ngl&;W3ogB}zTCB<8sv$K$e)N%IN`T3bTf!@4d@svr1zMC{FR zCO^CdHU6hiCPrD&7~Eum@4ekkPlx77`l-f-$W})Pxq`(l)Cs5!-3WWmkb6giKp&6- zOd;0SZ4-FXXqts;V09wXl>izu%y{2S{};R&k8Jo*U4@pHnWtxXEM?_uE8ixn`a~i- zmL!E0P0UEpwG;(5+?2(tzXx9=L_a2kg+6%j#i+q6Tuje%ZNil%i$H*^$a;#F`yj6N zbiEduG*af_>*9qsU)9@tY--CxSMK`ft+SRhd^+D}(b0xtJKFT{LRUs3^kdZb%4@f5 zyXL)FYii3q_kMGt@%ddRW%Ky_$@`qt;xZgEb9muJ$9AGKiNzQ6Yg}3nV|Lp&@ksij zH@+cG|9%*`5G4|~^S~Pn65_wZR6YI?98mhldY>oOJKO0tG_T4^N zDVJoDk0iain*Lpb97^?sM!Pmw%3gKm;az)*_TovM>{|ZgM)O2(Kr7_C<2fF4BAfd~ z@WirkH+bTg`!C`(Or;xCkZkusbQw+o2;tu$K z8K!y5d;C=UXP}C4Ju@4C^SCh z8w*hK^825|YCRn}7V;7;mE69wiRi^2sP`3^&SW**yXdEdqtKbNS!QP(K+Az;@do>0 zy#at7oW)X}%oHNH&AT~srac*s-CR#w8o_(4K@Q{cu5N`!WvAXNE=<{K_kxFZ%adD! z@k}6gX58}Z?>1vT>Wqk@r9t5m>4xDW@$e%3k{b<_(KR@k-ihP66NVWw{FL){kefiI1*w&0{|9e0U2(^pIZke zN9(2&s9^9iR$*Rp5LUVX0b>Kzr(`-StK|I<`cC+}SvZ`M9*o+9HN=keaZ#P`;_j1O zz9W4d6agVdb|_)Y&5KIys)Cb6KJtSIc>l^$k&=VH_$o`;iA2&vqvKJS6!`lWzJL}D z=96;2i1`opJt8B1Ea zRuc{qcQF{8%=dPG$V>KIBazeVKE!jwDqqEyFp1pkpPw8}kY(3Oj@I{jHW*0xI1N{_ z)?g#*+^{=}O7?5p>9;k1(Wj_l-+Kpc7#DM)##p1iUm`Ct)7&&N=b~*GJu8o>)6IfW zHqa_dgwFv3r+@*Lmn#HVEaceX5aV&?V{^}(1r--VB6LVFg<7O6AHfE&RK)-v(*`|H z?jz+#9~H$euP7lxG*X-EL7RmLqCQ<%d6OCrNwi65ml@(V9#$UI>}}j+xpAg3O|KDG z(h~XJGf)K)+#<-FTMXk7(!hmxA!|clLQL3m5p@if&Sg`fx=E^y0C6$o9N7j4-IfqlxHyQW8UU{iJ`buc!VijuLv|SCMuCuJYB$`o&Pb1v z=a5&|$-w-4osd|};7IUg7aBG?tVGXfN|ILoOkr+v&>y`bGmCt#r4jUmme6j+0x@5H zaWWm}QDmgYOY9eAngKwc_xk}T+^5p;jR81XXcD6L2A+9_kRuPg{7M#280)y=F+xp~ zF8q-K@%g$GslIpQ?nz{7G>P6pQC4e*Zfi&-Xyu5m9JanRdMcam5Pisd!J!g$%g?i1 zeb5NQ!0V&HYf@yI5s2e}gHbpt;1j}zxE>`l5%|xXM>qIXA|Z!a4Q6!Rs~VFO$p_9@ zE**e3=t$SIQ{y^>M{YxFSM&R^3?kE^YN7c0Vp&x=R{Y!R^tzLh*i#ero;Y_s zz2#AF*<%!f)B|iOUXNO!)AV6c4f(t%0swZPTl8;@4JLy+RK;k?s^2FK4@r7Y%8l8R zf$fQ%ejdOn2C$OeMXHsu z7BpOWP2k+es|d{V5mn~-$Gs!3m*4i^Bo8|m0*fP4Ct7|40|XJ?Ka%>ub)u?9sVl?a z?@xmcXFvz$)yFQ)&$1p7R&?HJj?BNTUkwt7qO4CY=obJ`B{3gEyj$s z)qiK6VP{0vOeNMBq*_lJAw9aLdWLTaAg3b-;~vjJ`WcCMvp#@@9yZ^!(;IawGvg)e z-n-(ePt}cXR33P;XbS11wng`nd~Ob zacj3mBX(MEdi^yqv?w7ybmJwKh1~;h`D4J#DDVJmTdY(o4Ra}Re zZ$-E1|3JT90y9U78J$0jwV6_3tuoxRWsJ@mB182*vy71uQTmB2;tl3n*^dd8zTgcQ zK$=JV8`d{rB7IGty@jxtfA!yS_iHxZop;Nvp@Kh#*{vZws!{myW$#Wu-M;;^{p+_E zUya)%{GvW;(GT-(+^EU0j;?FWyj0@C3<+NEpkPipwGy%h}bj;feJ%MmR}BVTV(&zyjM- z^jmk_z^p&j?9Y4?>9RONXd(tt>VcQ%MO1Zq^=C9kK8iZWE_l$JhkIl2pwR`w((H`) zxiWKkh4BR^L&43T0T^cFp~V>bB2Bjv$IkI4d^eopI*xqK2rr1#=QDyqR0?!C0Q(lj zvW-5h5g`ivM%(Tl@C5cXyONzz-4bJCRw-V_hoYS?2r!`1xv*=W$?l$_EP^q;fk##N3F`%s6Kl=^GdE+{WP`5`5QPFH3j%A|y@Fv-Ra&tcF zxx%Kocp#D|FAIu_vv2f>7Tg-3zEHmrLUm{=C{ypjnM8E)u9%5SQpI4P6N{l-ZW;n^ zrv(4Fb9jOT{HrTLjxi4zs_C}j_Zwl1rmS;WKSw*(>fZSU2JP)e!+Mx z`AuvVx_MTcU*j(%oqWE~php}a9|?u!&natkkQM7fO9^Jg7v{CS_n36|8k?p3X+>*< zR(Y_c?da1Gjc|%|WB_<(ek@vxrj^LfgolVsJO1-(gM0!#Agup>E9bJt5Jx_5GT?qZG%sev zezc*VL`=r2Rg4nVS`X7=kw|I?E=yl75pd{oI-*AOO^>mZAUux8K016= zf3HD{*+emHXB;X=tkO3B6`J=|4^j+4mQYq4{hqD{8=U@Ey?~=^B`c6+W>;^bKMq-ls%8 zTqjzlbasyElR4=9JS24yl+LiOakrI?dy_70OBWTNE3p(V{*|{a#NZ;o-fsz{5+7*+~3=YSoI2Sqp)0Yqf^Ma?Q!ts2~eW5Yy0 z5XzppF{_#ZA3bnUF71`?QKd4}pX?di(-SZx+e&US;3NfZjnZl{Tz+X&XS7{}1RkVM zpbu^iv>nU9|A3xfYk~tSs>p9~(t(v-j)ma|pW+z)ueEhO4?K|lUkZ^xq|*sFKGhqv z(eEb_8!2ol+JIBpQGwgKL=-FdJitVkqj~MO$eo)#bOk z>*}QJlO+-j>(AkAX4tKjv!dgx4hAB{OuiDdB3YTJ8YsB8l(v`!f+O87Mh% zB$=Sv0{xq8Ww@euYdvAOWheJ1F6DQ~>LGY6!@yJxn2K+})a5NG;{o7h-Q03;UrWvB zIbYp%BB#f5Ym&+5f1zdgo@4R`%6=tu*NT$hK?xyuomGUd&05m*cGE{o9c!)lU@U7sU?F3su3n2H(jda!oD9H;YW ztTLTMwASI#-6OluB;(Tq49VzAf%Y|04cVNrB=7JONupP`TAnkyVWp7}wFXmIAH@|= zv_FyS>;Y>_YdYv)Fnji8S|X*9IC?hXM%d?UvU1nChekBhn=`}vkgri>Pw5C&0t|YL z;Q?Uhf;)QvWY#ud^ea8BQdlX!7vT!ekJhExCW>*tVnZ3rR<}C8F1VvRn^$EFBx@iH3+eR)sIi ztb?*TiPnOT3R{IMF@uAPXm`R4*7PTWvkkUxErMfN7B?5blr5;jtp=!p_~kitLehXG z7dA)%#9)sCR7dj}bi)ia5iJZJ4##XQ?a**IuS#fjMcU3|ZLv28d6H8J8V6qu7LhIp#o+nNl^E4OJ8Z4q%%A)uZm-g+KG?bCipG0}86A_FaN{HyyQDP)|JG)J$7F7Jl+N!i8|Gsi^pbtNChB}5A=E!C!|+z zf4-;gJvtK$Z+lrP*r$7pfOX-o(GLztd0#8CMV72e2Y0JEa{Fa{b1rqr5`2{BMoKEP zJZp*w@NqC41|&2A2m(D9b9gO zKtL`@&Y|%YV@SehlVFI{dJt`k>WK~o^+TEX3@h3bK-nEW zCGw1!?o7I?-dP~E8q0#hAe4H87VI~@HDGAlVJVO;H`L#8`a049q6D)j&8wpo^p46_ zEY=?jG`f7Rc_quY!E=|%?wJG@bG5biIAU^E6`E&V80)=z7gct9!VK+PdATNyFZx0) zmU0?`ModwxrM8p;qZY_Ze4|r7D;)8MgBN|wV0Wfa?jeB=P3f=Y0^6ANTO^|GMXN+W z2{4FbeU?*-_(bBZcqWHCYJ;N_)!>?^IakV5x}DlpwNzxT8bMyRq{6tZd`0!!DWaOC zs)dA%6gid9#Hion?c#9$ocw_N3Gc5m`1<5Vb1g^GR@MOE`7l2RXa*!8BQG!XM1$!{ zES@P%%?2~hCE#UtsFs%RCIPI0AJdHTpraBvq7Q^RE2-k{7j5ncIh)3EbNi>t>k!=> zqrII@ZBKVGxt$s}A4tZE8N}eOb^S2r&8nA|n>Uk*!Wwh^#_10YaZeObfH8T{2N#qJ zmkxMy zx?yl@K#7=!JUBI3fB5-OpYFA-z0}+ESYkvDd{2S?*zrKW=@_ZbY~|h(M+Ff^VQ&d^ z$Z=p|j@Pu>;IhbvNfIkhL^x;{RUdNaOv$KRi?`$2HkWdJ!$@B$x%k87%qv$pK5!or zESK_eAA+)gCi`~|poMQfsd+lOItFqHE7GB8o`6?P*dqE#s~)YM_9+1*^lUv-$z&$e zQmN5d&mkF*^XQ4Lh}cBZ_*8#)x~5ajZ0u9Wu*b+(3MnYhvWbF#X!ht|wEX^l>fe+_ z-xOjht>*7{tO2;z7#6nkSd=xZf7QSq3GiO))C0NQ0c;HX7IF*22GBr!QDIPT(6%nz zOo)N5-zc`3a;VtJEW??^y_>s+HNqe*i;;9CUkZCAsqpKiwvc`DwcbREt{mQZO(`eq z>8`Nd?W2*-?oDwY)L@A5eiPdE3!wNxz}UyK!_2#4)*|9?6W1K0Y#$o_!;4JZUtwfq zbi{FH&zXF14p6}+-0x(KhL- zJy9|L7SIIyHFRM`BQ-&Me#TtdIO9T zL4bs*aFIcKYdKYxyHla$cgu1%>XAZ;8eNK2s;P23UW<1qG#1Vjao1s*M(yOY@R^hF z&7$`DZcXdtV)otPg3YW&U~s}+d{>y3!+%cms|tutSI6g9jLqE_{4 z;Z1<-_g@>|xJHT>++pzXyrCd=M8aFnVLiJHlWh+ToQl#MI1`Mka+l}1%LQ;54J1op#Z0?Lf>g6mtbU~`J{94B-4;enW<(BSK=&`Dk&8bXWfRAj=3Bph4YDaz(# z&VKyKR8m77n*3ap(nPYs?g%a{ySLhb8V&;ge7T37h z<~?^^cShE*?TIAYtnNr}Ku<3Qt^AOXN6?M-;OfW4Zu+l6uP}q0zvn}Xvs3-00J_oG z4npfF9%9ZK^X9^N$RATMoPMQ}ii#U2ViQ#`1Q9sQet>#hF{Ds@!Ub(|F$xw-pPzX6 z$}Kyz#LlKpq-g~jM_$Bgyg}l5&y6F~J4cm{BI?;xoFkvDZj%hUXMVB+dcxfHx3h

sao)W)RDhP<$lKtvB5J&Be8B^s^2^vpe>NHvGX3*^v$xZ0pI$f4f^8M>O~ z(9v=iayYxO#+brdZhRd%?Q{oP&{>-{H!a+aXRUf`!4qR&C@F4oSayefbyYKUF~-rz zRLY)mSPFa1#3p3UnNQqPqpBU~d-%m+A_lhP6fEya1^k9`cCs{>a1v4!{(M;XWE@$? z64_`l?nm}LR>;({0iW+GJ6TDj5YBY4%>4Cv6eOvUEj~zgp!=Tr%!JmKI!_&!jF2&N zsO(26rQk$L(XCkZ5zQu3@IkQ!f@JQq{UHa-=5n>pYRYdV!wdOrSIFq>zVNF}Cn`yu z7BcA)a-5=Y0GL*)uL zW3{%TWh}=#8eRV4xkKW5HOTNcI7gCC7>P(Spv(`bDrK1?Pj1uVcvX}^6l~T zNGe49n>`LPexXFOE}Lb)A==O%-3zwk*ZXTX?;%RmGN_?+^mNzqnV@)oG$lv&CD7Vy zbtG1P(kBKMm3wX{)Z6{i-RMdag^|@)(;CeoFF1-7mYxr7o*eG&@?DfYD7Od{r>o(Z z9J!pvv4qqn{&RK8B(E)W$iVW2x*J!*zqad^{kcRbtM5gj%{Wki!Z9Yh_YboM-Z1_t zo`52Dk!G|b8s449j()d3Dor$RGXqYcFc^1qlIYuZ<=gY#pm>wa9&@wgn=&;gs$IFG zr-{X>$z(DiDY~vS$=jm7%DGL&sQJd-MY3nx8)e-aFYTP3;JZ-qd_KIk5y(|73nQ2Y#n}a=H3hLziP%F;@%LrAiP9n* zE^J`3vp-+hbR<8DzEVTurbngHhu)blS2Ky|?yDZWa&Z?Lq_Dx0Y>E1K zsScUfPM3`?en!JdRal15t? zYt6iSVw_U7)Mu(U=2LQD;jRyu-OEFbbaKfi)1e{A#;MgGK#zP1YdiJ`S3x$8?cch| z%XYZ9n9YJkf_(?BnA&U$2lo#g*FRX-GHtyA1S7AthE_L_XKJR?J%p1u#&G`+qw^m7 z;BYKU{o67`R8mJvU8v5^HC~Jjb&4IPvAN#XFG{evsKFDR>VSE5hHpwHN}ZWRc)$)d zQ!#|B(4ii=I2klk+mCo@=*rnlZOiRRG$vQBL5AK$ zEU=-9v?1P!xEKOEt=l2yK^A)j z2fN8nA;TZRS`Fkj+SxR&-dQIuGlO93p(d3~L@nFx?kFl6Xjv2K?}|S9k&uZPX4K4L zvqat0>5~*uG0Bl1lMmvK0d~iBmWR2=%s*z90??9N#twMpnJ9c+=@h#59Up|ZwyEmDg;o2%H2ifZGh80LOTJIKw5tk~Xfs1Y;FG2z7w;s5+}eOC;6=;FQ> z8&<*p;0NuM+@c)I+S@0h=#s}e z?G(C3os;pa;_ZgY%dKxmrOxhkx;iy9m8ufj;#F*rh2B$&{y>WCv_O9dZV7^awGYN4 zFht;BFj<6kffp3E%M7**#Bwp9H7|s+bWje2W%`InS6VCQStl#`u1L2I)XB;l$zRgX zh>CP!4>nn3jkWY?NxJar)m5^BZb7z^XKlSzH{Ty)Q@wySSFllNbLDae0da+y`v8%P zrpJl2ZIp`bAQ4kQbMSo3q&$W43?nkjs>3*OF*9X91uieMFzW|fHXmQwku@6q!SLSg zN4v}iDZTDGgq{8fI;+`gNkr_}Xy^rwK7aYvSNEJfbJZJ1`WKE_%J!pAkH3a`yu>sW zU7Rr-Y%9m|JR<5-&p!y>83FDe0`B*KcO0fU>WuzemoXle9K?#d1QspUNCkQxx4rN>bbU%XH7K@c zKz;zNt|H6IKl6-8zVSH+8jS_@Xe0;qw*lH8V@*jHdXFC99!gKQpYM)>?Tb(ZXIc%> z;96yEaInyBSAmTHlwb)oEJR%XKzALOV9hHmF(eJ~b*bF{{x1tbIqCu=z(8z-i zVc{nYvdt%OfQVUyg+zYnL0MK;Of>_KO_U6z?2ztY{G^JW4}aPO5>0Hk%6f@w7$C0D zH=@k~pR|WXWG+yGC>SiqA~$T{UL}wIJr=~iNm0c->zc^J5K^)OVJL2g?Xo1Bgo5hI zHtcrydX|>KV>G+0vk)H+hfxl*?ql*KDvPWOm}x2T3TUa*ze!8wa$I8;m}~u;cM@Pd z!yp4x*z_CLh0Un*h2nPab%Hyy|jeUO?HcZ}GA6eRM`cl%KmBfJ~Ak z3T)B~pTRa`8wLPFJAfe*IXSl1-30Rr34&y>S-lCAY>YpUlVLgGuw!!vMvb_2Z^luK zp|}deUM4y02PTkvUxj1}cmqxSJW<912{{(uYut32z8Iu>;&*PiX-ld%S;`=349-AX zFxEebEC0FjpIG8lCH>?e!J2Q#8qaSbDkz2qV=-hU$jZ0SQ>Q-i7?{kLDjh=cjv^I! zn+Zo(g{zPOOkV~})5y%QdJyVbYx^)2+8G%p20@-C1U$?aV{sZ&CrsmToeH4XH~t;o z3slZU2e@swn3_?TLc;31>(V!%uEDZ+kxeF&cMI4es#LVKp`fHmfP&EIOulP=6qv`{ z1EH;_WpAw?=@LJ{nksMgIXwW+U6}zB#h3Mde&$45w&$aPJfk#@peh0VxPi3gzVE+X+FImEIZBbsllXgdLA z@fI(wR$dC83~nDDsg^xosFgh{e@&F|Jlpr_v*1Fua_FVt!tH)=VQ0f;WtA63x`^^z z8;Q`;V6ZjzL|4Lp0rTi!Xk(ovP;nzy)4O>A^76CbK4t9k2tNY@MsKOcI*o8g#>@64 z#5U(ajTQFGRpVe*tN~ z5_leFE8m7-EjhaC2wFKtm?SgVzR1lyhF@dZ`r6hGu7<$q^=KD7%cCuY1#H|qtN*UV z49`Tt$;jT1->G1Sv}1Q+BNKhA)jJuD+WCs6htb+%`PF$U?nez%iY)er%d_KtW2AbE zhKeN(&+fxkh&231s4XFTpz4YJCxX@Doljv6`M&M`xTWkq;f!qU?7)J4e+~QEW0#{p zz;DgJ{lP;$S8SWuO1+&&Kb3lUuHma=(G)f=f1I#AvzLTl(lSZGt8LeHg52JR*jf?P zK3FT>#>FCYxqI1S5ts*ewFq{dV5>!laGUT;$&dT^ai4bNij5XgLV&JhD!%zrDn1NT zg6faBGO;q@-h}x^*gK!P_xPXWZgQ4>6uURJcK~18uR7u`TwzLp>s#eN8rupP9RZ0p zRlqhjk`HNw9&-2Gfh)_7>hox7|F$Z$d<^?nVs8H@_S>hzPsrW$IQ@_iM~@VGa1$!u zBLQVFCZG{GWE>Y4VW_|GDN1DgK&(wDS;(n+o&-f1_Q3yt1w$_$*nf)-GcWP zYI7B1)v)96Z~`M(5`t~aUK;T9`ow-)o2{$)tnAAek=w|$Zrf3(OZR$gY(vUd^l0SO z*f5)%#WF<0E8cON1#h3KV+%twSY&Bql71U<45%Jew+V7a2iMm%Z z^~yqkca7rD4he6@UsGZK;LbvlUWwmn(_jbi&u;%L3hyS1WCii~PsO5mh4>YeBDYI7 zNDoQxm3}5~lE0xWD}SYqsPEJK+T+>e9AlH{k$<^yu&x)`;vJ8vEUv4 zFIm59{V4FL9k;Ku|2249s1SO0I2nGEBRQ?e?NK3mEc$r#Pon=G3&ma;`+j^Z{;@<@#-6^U#K0aeZJ?_ zz1iM#eSyBu_1{0B4patq4BR#FM|E1ivi?J81m@`rFHW3)M9;(%})UcW2@PEPdl);!-@k_U z0bv{N@rO9~3A{cheoy!;-uv+SG(9cs!LzrL`s%-vRcQECVF|C(!{cwN)}jQOqp zGrqIA&td!`zJCYb=YZNnz{et8?z>63W>TVa&^ z6F=9)*k5qm^5>Faj5#XaCX8a<57O8w%fB4Jm=X3o@oNIh-kl=b@mzO&5a<2@pLgSx z!^_6FpNdoDtL(n`u5z3VaGd@g{eUon=WhedP26V(p7CY8zXk7~<@W{L;)-w^?(skI z`Z8$!6Gm^E#4b1hlKya25sru};FVjXlbov>HqNaV= zX6euAU(vs#-xOafJ}Lfz_-65~;@ib{iSNyZvaxI`=U-jr&+EjUrtsGXPQ1XK_To;T zrk_FN@D1@b8}9T@@jcmK7WE2tr`7*^^}DNo4~%_#^;@f7U;XIn)2n~9`p(rSS07ou zfA#LwGphru_Db)C|90W67hZeeHP3$Q*{?tQwP*j=vtN1kOV57s*)M$Oy6;^5om1bw zjl+sb{lEX$5~|sjaNzLVQZ~EzVZq$9*i!Z#IoxU_T3t)aS7*;1INYM0CqAM9nI}&c zu1e%`En%r8%oa93g;~rl&(vC^+R83pU2D;5Ay>%NT4FVO>S-|)7G`E!!P)Hc^2~V} znw>e{DbBX&?EX8mEu(-BvnNiqq}_Kug+Ld#Y2{8QbL{9-W|+(*v-nt;c`8VPI9F&1 zyAPjUdJ1kGf1p%tiIrA(_Aq;1%bA^R4@hKBWm_NH-I9t&p6VjL*}0Q*EoJU-t|fLZ z?Y-hK1}DxP&bD^%#-Zt@M7A}|J`FD|WzV~#F{3UVYJbbN`q=qCHu7V;4`+dmb0@Mb z&+fy^IFe=OJnU1GeQGWzmY0^662Ml=m_6AN_8x8ti)?TX-xG_iH2aiZJn>;mILStQ zSQf5YS~_)NsYNPFOKk|2vZnw~VP>h;lB?OdY)k4q0hrX;-G^IhVWy=OWb?uY%kw*s@v*>lU; zRsd+Lwe0HR{=?^`Q=6AMTE5eTJ8P|Ab#c$(#eMF?pFXO{rU z0_MMfqn8rv1xR>a5P}6DdA21?K82u|^Fz2Q;7=va?LXYI3p3fdmI?OrL9mx+vdbTe zM)Ak7grG1pGs7?t!Z~u{d`PRb9;+nsU~dO7hAXvJqMx&{NqFuslp>>IeFJ)4XiTG; z9pp0~#NFKc%spRCK>J*Fw(Pjze1KcJ&69=U^9>Saur`4Y0C3rBmLV)BhHI^%YENXm z)*AjzL&4Z5F?<9h5}eL#Pj-Q+1fX{7xpNDJ1*o9IFnrLuut>v%ghRmaD0CMbd<>JI zPCNOq^M)|f^3GOHpX(`Pv*YJ*&#_B~WqaKFwUoll+URU+ndy}2J%^u`vT`=@v{aPi zOEXNPcUo6CL!W?Ta#XV7MNvx z4HsF=KrXg%Q5XlBCe}~2JWwXt?0jK?J(N*#a_t-k#>I3?*nhYuI}U@+W>4LNxam46 zT1qFrZN;f|-^C^3^Eq$1(54nNf2}pO_B4+DHo2CWgY#UF*GN1K{n5j)INyrQ9^RdR zP0fxk^_=e`A;|5fOU~|1?7rme%q3^nuHAUQ=Uy>eZH-no4p_Tr?SY%Ct+C2Eupi^G zbCC2Gni~k()9S+%=QzlWjf*bkPryN&aY1M7T!5tYK=NFG=BwvDur-YPIp_a&EWL0! z2K(RPVuo*~$Hoi8iQEQ$$t|_#xc~(`T3N%&7JM74ipw#%KQ#TLqL7q9{XU1-62J7pJG3(LA( zy;re{8XNr&w6DG_AXwWspM3D$7%z!`gnh-&cV?k%q^U9P$?o~iobUYRcYZTx&Yn5H znR)I3@jZy+wOZ~JoZfJ*m0h2#*onDAUEGAXt@V6Z6*||4nft?Q#&iJ&0G_~Lc+Imh z7XdaVE>1UxhQtvV7DwQcI0Bc$>$HaFK_ejU?V!={IuB$*FMwR!74ba9y(peVFNtT- z%i>w|it_A)_Nwv}@Rg^)Rplu#CeE{fgg638aRkQ25ty)g#Xyr*FQJsxOXxMLm(Vq< zm(Y~eOK95aC6u;$3C&>RvpWTtv7uZC2VQqD0?f+jqUEB5X1Uh_#hG(ZoO#R90dW>c z+_oci(S~e_B?pz_4F^Sk6FF^8U0a3NBDQ*5;oD`#MVQ!V&FV2l z-6^|n@LXRR8?sCe=(BOC>_AbV`owK->qf5Vb8gbH_2NW{iN)-{f(QN3^`?#9BExFm z_}(yMCwsi7hww%^8u(`GUSQL^vvrU0&N{xyafSb443v2Kl$)%27K-RxG`ILV?9tzF zD~|)LwzQeYK@&AU=os4v59(5<#EZb_^Lp{o6N^oo0i~Dyi=Yr8CLMj(!)E! z{10>Va~wBgcrS(Mduw28#iv=Qk#C5RuSwoKh?^!9hnXTK%|6NeIw8hz3)eB;=3$Z> znFV@V@@Z1`kh^cCMv&l}1QMswtOqRr^AY+~`oJkT7tqtkn>Pisi!(?Q);^0~Nn|yl zfwXom+}tobl& z6xvyq{I`}?d7WjQC~b9EOlx^hvPOz;mIynWw4=fQS0aTuUdxx8Ur=hha}JaDB9@U} zA?7nu4U#<<_)QSAU4kJ-@A{m9N^q|-QLU<=F38HW1?l3P=Q#2gNuRap9mCZS)lcQ9 zyyn5GSy{03QC@LsLofS&t^{#+Yt(D&*tP_f%ieWCy4}~TFy9bbE$~fIV|_fJr);kG zE#li8Blud0IY+InKr_{>47i?c7Tf9y%@8+Bsp<-x+9}<7H1FaJ{!1E~AvHV3Y%O!u z?DbhD(t{PG!7Vl=bjy{aUnQ+jm8s_K<9;5y5AxvsDE_aHG0N_7^8|0?Y8V-Ji1mv@ z=CFB^`Q45(j_nvV^=VdCK1oYai}v-lRcYe-XyFYWj(n_A)yf&4p+2|aE#6@wv~Mx| z0J}^#J**e-)9T?o`=}@VtXe-vU5aD%Vant(?ZYT@a9_crgdS6RdvEe)<`46Q`I31- zzcOE&pH0rJdO><(|D=bV@mS_p^PBnI{9>4i-790Y+zJM4J>pd{+vq-Tzjwf_nRQy) z@65Mm!#h|ypH4M2u%4UtP0gp={(NhGAIa~d#ohk&v~NSrtu4hvlgT;1q}QLmn(zZ7 qslY(0IG&n-Fr1l9P0gf(1Cy!Xz--DTh*)t=jSXG0xko?nzP|yh7T3N2 literal 0 HcmV?d00001 diff --git a/src/consts.rs b/src/consts.rs index 5386d66..548ca3d 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,9 +1,11 @@ - pub static LINE_WIDTH: f32 = 0.02; // Global scale -pub static MIN_LOCAL_SCALE: f32 = 0.1; // Global scale +// pub static MIN_LOCAL_SCALE: f32 = 0.1; // Global scale +pub static TEXT_OFFSET: f32 = 0.; // Local scale +pub static TEXT_X_PADDING: f32 = 0.0; // Local scale +pub static OPERATOR_X_PADDING: f32 = 0.1; // Local scale pub static FRACTION_SCALE: f32 = 0.8; // Local scale pub static FRACTION_PADDING: f32 = 0.2; // global scale -pub static SUPERSCRIPT_SCALE: f32 = 0.6; // local scale +pub static SUPERSCRIPT_SCALE: f32 = 0.8; // local scale pub static SUPERSCRIPT_Y_OFFSET: f32 = 0.4; // local scale \ No newline at end of file diff --git a/src/element/element_parser.rs b/src/element/element_parser.rs index 9a627b9..1c5dc2d 100644 --- a/src/element/element_parser.rs +++ b/src/element/element_parser.rs @@ -39,36 +39,37 @@ impl KElement { } => { if !super_script.is_empty() && !sub_script.is_empty() { root.push(KElement::SuperSub { - inner: Rc::new(KElement::Text(text.clone())), + inner: Rc::new(Self::parse_text(text)), upper: Some(Rc::new(Self::parse_object(super_script)?)), lower: Some(Rc::new(Self::parse_object(sub_script)?)), }); } else if !super_script.is_empty() { root.push(KElement::SuperSub { - inner: Rc::new(KElement::Text(text.clone())), + inner: Rc::new(Self::parse_text(text)), upper: Some(Rc::new(Self::parse_object(super_script)?)), lower: None }); } else if !sub_script.is_empty() { root.push(KElement::SuperSub { - inner: Rc::new(KElement::Text(text.clone())), + inner: Rc::new(Self::parse_text(text)), upper: None, lower: Some(Rc::new(Self::parse_object(super_script)?)) }); } else { - root.push(KElement::Text(text.clone())); + root.push(Self::parse_text(text)); } }, text_parser::ParsedObject::Operator { text } => { - root.push(KElement::Text(text.clone())); + root.push(KElement::from_symbol(text)?); }, text_parser::ParsedObject::Parenthesis { - inner, - parenthesis_type, - super_script, - sub_script + // inner, + // parenthesis_type, + // super_script, + // sub_script + .. } => { }, } @@ -77,5 +78,15 @@ impl KElement { return Ok(KElement::LinearGroup(root)); } + + pub fn parse_text(str: &str) -> KElement { + if let Ok(num) = str.parse::() { + KElement::Decimal(num) + } else if let Ok(num) = str.parse::() { + KElement::Integer(num) + } else { + KElement::Text(str.to_string()) + } + } } \ No newline at end of file diff --git a/src/element/functions.rs b/src/element/functions.rs index e7f5dbe..2ec5884 100644 --- a/src/element/functions.rs +++ b/src/element/functions.rs @@ -10,11 +10,11 @@ fn assert_args(n: usize, start: usize, end: usize, err: &str) -> Result<(), Stri } } -fn derive_symbol(symbol_str: &str, args: &Vec>) -> Result { - assert_args(args.len(), 0, 0, "Symbol cannot take in any args!")?; +// fn derive_symbol(symbol_str: &str, args: &Vec>) -> Result { +// assert_args(args.len(), 0, 0, "Symbol cannot take in any args!")?; - Ok(KElement::Text(symbol_str.to_string())) -} +// Ok(KElement::Text(symbol_str.to_string())) +// } impl KElement { pub fn from_function(name: &str, args: &Vec>) -> Result { @@ -27,7 +27,7 @@ impl KElement { lower: Rc::new(Self::parse_object(&args[1])?) }) } - "pm" => derive_symbol("±", args), + "pm" => Ok(KElement::PlusMinus), _ => Err(format!("Invalid function: \\{}", name)) } } diff --git a/src/element/mod.rs b/src/element/mod.rs index bddfab1..8b93d74 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -4,6 +4,7 @@ mod rasterizer; mod text_parser; mod element_parser; mod functions; +mod symbols; pub enum KElement { LinearGroup(Vec), @@ -19,4 +20,9 @@ pub enum KElement { upper: Option>, lower: Option> }, + + Plus, + Minus, + Equals, + PlusMinus, } \ No newline at end of file diff --git a/src/element/rasterizer.rs b/src/element/rasterizer.rs index 649b936..d27cd07 100644 --- a/src/element/rasterizer.rs +++ b/src/element/rasterizer.rs @@ -44,13 +44,13 @@ impl KElement { bitmap } KElement::Integer(i) => { - render_text_block(&mut globals.layout, &i.to_string(), current_scale) + Self::render_text_block(&mut globals.layout, &i.to_string(), 0, current_scale, TEXT_X_PADDING) }, KElement::Decimal(i) => { - render_text_block(&mut globals.layout, &i.to_string(), current_scale) + Self::render_text_block(&mut globals.layout, &i.to_string(), 0, current_scale, TEXT_X_PADDING) }, KElement::Text(str) => { - render_text_block(&mut globals.layout, &str, current_scale) + Self::render_text_block(&mut globals.layout, &str, 1, current_scale, TEXT_X_PADDING) }, KElement::Fraction{upper,lower} => { let padding = (FRACTION_PADDING * current_scale) as usize; @@ -110,21 +110,38 @@ impl KElement { unreachable!() } } + _ => self.rasterize_symbol(globals, current_scale), } } pub fn get_bounds(&self, globals: &mut RusTeX, current_scale: f32) -> (usize, usize, usize) { match self { KElement::LinearGroup(elems) => { - bounds_of_linear_group(elems, globals, current_scale) + let (mut totalx, mut mintop, mut maxbottom): (usize, usize, usize) = (0,0,0); + for elem in elems { + let (width, height, centery) = elem.get_bounds(globals, current_scale); + totalx += width; + let top = centery; + let bottom = height - centery; + + + mintop = mintop.max(top); + maxbottom = maxbottom.max(bottom); + } + + ( + totalx, + maxbottom + mintop, + mintop + ) } KElement::Integer(i) => { - measure_text_bounds(&mut globals.layout, &i.to_string(), current_scale) + Self::measure_text_bounds(&mut globals.layout, &i.to_string(), 0, current_scale, TEXT_X_PADDING, TEXT_OFFSET) }, KElement::Decimal(i) => { - measure_text_bounds(&mut globals.layout, &i.to_string(), current_scale) + Self::measure_text_bounds(&mut globals.layout, &i.to_string(), 0, current_scale, TEXT_X_PADDING, TEXT_OFFSET) }, KElement::Text(str) => { - measure_text_bounds(&mut globals.layout, &str, current_scale) + Self::measure_text_bounds(&mut globals.layout, &str, 1, current_scale, TEXT_X_PADDING, TEXT_OFFSET) }, KElement::Fraction{upper,lower} => { let (ax,ay, _) = upper.get_bounds(globals, current_scale * FRACTION_SCALE); @@ -153,85 +170,59 @@ impl KElement { } } + _ => Self::get_symbol_bounds(&self, globals, current_scale), } } } -fn bounds_of_linear_group(elems: &Vec, globals: &mut RusTeX, current_scale: f32) -> (usize, usize, usize) { - // let common_centery = elems[0].get_bounds(globals, current_scale); +impl KElement { + pub fn measure_text_bounds(layout: &mut Layout, text: &str, font_index: usize, scale:f32, x_padding: f32, center_offset:f32) -> (usize, usize, usize) { + layout.clear(); + layout.append(&FONTS, &TextStyle::new(text, scale, font_index)); - let (mut totalx, mut mintop, mut maxbottom): (usize, usize, usize) = (0,0,0); - for elem in elems { - let (width, height, centery) = elem.get_bounds(globals, current_scale); - totalx += width; - let top = centery; - let bottom = height - centery; + let (mut width, mut height): (usize, usize) = (0,0); + for glyph in layout.glyphs() { + width = width.max(glyph.x as usize + glyph.width); + height = height.max(glyph.y as usize + glyph.height); + } + ( + width + 2*(scale*x_padding) as usize, + height, + height/2 + ((center_offset)*scale) as usize + ) - mintop = mintop.max(top); - maxbottom = maxbottom.max(bottom); } - ( - totalx, - maxbottom + mintop, - mintop - ) + pub fn render_text_block(layout: &mut Layout, text: &str, font_index: usize, scale:f32, x_padding: f32) -> Bitmap { + layout.clear(); + + layout.append(&FONTS, &TextStyle::new(text, scale, font_index)); + + let (mut width, mut height): (usize, usize) = (0,0); + for glyph in layout.glyphs() { + width = width.max(glyph.x as usize + glyph.width); + height = height.max(glyph.y as usize + glyph.height); + } + + let mut new_bitmap = Bitmap::new(width + 2*(scale*x_padding) as usize, height); + + for glyph in layout.glyphs() { + + let font = &FONTS[glyph.font_index]; + let (_, char_bitmap) = font.rasterize_config(glyph.key); + + new_bitmap.overlay( + &Bitmap::from_data( + char_bitmap, + glyph.width, + glyph.height + ), + glyph.x as usize + (scale*x_padding) as usize, + glyph.y as usize); + } + + new_bitmap + } } - -// impl KSymbol { -// // pub fn get_max_bounds(&self) -> (f32, f32) { -// // match self { -// // KSymbol::Text { x, y, ..} => (*x,*y), -// // _ => (0.,0.) -// // } -// // } -// pub fn rasterize(&self, globals.layout: &mut Layout, bitmap: &mut Bitmap) { -// match self { -// KSymbol::Text { data, x, y, scale } => { -// let new_bitmap = render_text_block(globals.layout, data, *scale); -// bitmap.overlay(&new_bitmap, *x as usize, *y as usize); -// }, -// KSymbol::None => {}, -// } -// } -// } - -fn measure_text_bounds(layout: &mut Layout, text: &str, scale:f32) -> (usize, usize, usize) { - layout.clear(); - layout.append(&FONTS, &TextStyle::new(text, scale, 0)); - - let (mut width, mut height): (usize, usize) = (0,0); - for glyph in layout.glyphs() { - width = width.max(glyph.x as usize + glyph.width); - height = height.max(glyph.y as usize + glyph.height); - } - - (width, height, height/2) - -} - -fn render_text_block(layout: &mut Layout, text: &str, scale:f32) -> Bitmap { - layout.clear(); - - layout.append(&FONTS, &TextStyle::new(text, scale, 0)); - - let (mut width, mut height): (usize, usize) = (0,0); - for glyph in layout.glyphs() { - width = width.max(glyph.x as usize + glyph.width); - height = height.max(glyph.y as usize + glyph.height); - } - - let mut new_bitmap = Bitmap::new(width, height); - - for glyph in layout.glyphs() { - - let font = &FONTS[glyph.font_index]; - let (_, char_bitmap) = font.rasterize_config(glyph.key); - - new_bitmap.overlay(&Bitmap::from_data(char_bitmap, glyph.width, glyph.height), glyph.x as usize, glyph.y as usize); - } - - new_bitmap -} \ No newline at end of file diff --git a/src/element/symbols.rs b/src/element/symbols.rs new file mode 100644 index 0000000..2773b8f --- /dev/null +++ b/src/element/symbols.rs @@ -0,0 +1,46 @@ +use crate::{bitmap::Bitmap, consts::OPERATOR_X_PADDING, element::KElement, RusTeX}; + +pub const PLUS: &'static str = "+"; +pub const MINUS: &'static str = "-"; +pub const EQUALS: &'static str = "="; +pub const PLUS_MINUS: &'static str = "±"; + +pub const PLUS_YOFFSET: f32 = 0.15; +pub const MINUS_YOFFSET: f32 = 0.15; +pub const EQUALS_YOFFSET: f32 = 0.27; +pub const PLUSMMINUS_YOFFSET: f32 = 0.15; + +impl KElement { + pub fn from_symbol(symbol: &str) -> Result { + match symbol { + "+" => Ok(KElement::Plus), + "-" => Ok(KElement::Minus), + "=" => Ok(KElement::Equals), + "±" => Ok(KElement::PlusMinus), + _ => unreachable!("Unimplemented symbol: {}", symbol), + // _ => Ok(KElement::Text(symbol.to_string())) + + // MINUS => KElement::Minus + } + } + + pub fn rasterize_symbol(&self, globals: &mut RusTeX, current_scale: f32) -> Bitmap { + match self { + KElement::Plus => Self::render_text_block(&mut globals.layout, PLUS, 0, current_scale, OPERATOR_X_PADDING), + KElement::Minus => Self::render_text_block(&mut globals.layout, MINUS, 0, current_scale, OPERATOR_X_PADDING), + KElement::Equals => Self::render_text_block(&mut globals.layout, EQUALS, 0, current_scale, OPERATOR_X_PADDING), + KElement::PlusMinus => Self::render_text_block(&mut globals.layout, PLUS_MINUS, 0, current_scale, OPERATOR_X_PADDING), + _ => unreachable!() + } + } + + pub fn get_symbol_bounds(&self, globals: &mut RusTeX, current_scale: f32) -> (usize, usize, usize) { + match self { + KElement::Plus => Self::measure_text_bounds(&mut globals.layout, PLUS, 0, current_scale, OPERATOR_X_PADDING, PLUS_YOFFSET), + KElement::Minus => Self::measure_text_bounds(&mut globals.layout, MINUS, 0, current_scale, OPERATOR_X_PADDING, MINUS_YOFFSET), + KElement::Equals => Self::measure_text_bounds(&mut globals.layout, EQUALS, 0, current_scale, OPERATOR_X_PADDING, EQUALS_YOFFSET), + KElement::PlusMinus => Self::measure_text_bounds(&mut globals.layout, PLUS_MINUS, 0, current_scale, OPERATOR_X_PADDING, PLUSMMINUS_YOFFSET), + _ => unreachable!() + } + } +} \ No newline at end of file diff --git a/src/element/text_parser.rs b/src/element/text_parser.rs index 60fc99c..a231bba 100644 --- a/src/element/text_parser.rs +++ b/src/element/text_parser.rs @@ -1,3 +1,162 @@ +// use regex::{Captures, Regex}; +// use lazy_static::lazy_static; +// use std::collections::VecDeque; + +// lazy_static! { +// static ref FUNCTION_REGEX: Regex = Regex::new(r"\\([a-zA-Z]+)(\s*(?:\{[^}]*\}|[^_^{}\s\\])*)\s*(?:_(\{[^}]*\}|[^_^{}\s\\]))?\s*(?:\^(\{[^}]*\}|[^_^{}\s\\]))?").unwrap(); +// static ref TEXT_REGEX: Regex = Regex::new(r"^\{\}<>()\[\]]+").unwrap(); +// } + +// #[derive(Debug, Clone, PartialEq)] +// pub enum PerenthesisType { +// Round, // () +// Square, // [] +// // Curly, // {} +// // Angle, // <> +// } + +// #[derive(Debug, Clone, PartialEq)] +// pub enum ParsedObject { +// Func { +// name: String, +// content: Vec>, +// super_script: Vec, +// sub_script: Vec, +// }, +// Var { +// text: String, +// super_script: Vec, +// sub_script: Vec, +// }, +// Operator { +// text: String, +// }, +// Parenthesis { +// inner: Vec, +// parenthesis_type: PerenthesisType, +// super_script: Vec, +// sub_script: Vec, +// }, +// } + +// pub fn parse(input: &str) -> Result, String> { +// if input.is_empty() { +// return Ok(Vec::new()) +// } + +// let mut i = 0; + +// let mut objects = Vec::new(); + +// for i in 0..input.len() { +// // let mut functions = FUNCTION_REGEX.is_match_at(); +// // FUNCTION_REGEX.(&mut functions, input); +// // for i in 0..functions.len() { +// // println!("{:?}", functions.get(i)); +// // } + +// if TEXT_REGEX.is_match_at(input, i) { +// println!("TEXT!") +// } else if FUNCTION_REGEX.is_match_at(input, i) { +// println!("FUNCTION!") +// } else { +// break; +// } +// } + + + +// // loop { + +// // } + +// // for captures in FUNCTION_REGEX.captures_iter(input) { +// // objects.push(parse_function(&captures)?); +// // } + +// // println!("{:?}", objects); + +// // let capture_locations = FUNCTION_REGEX.captures_read(locs, haystack); + +// // loop { + +// // } + + +// Ok(objects) +// } + +// fn parse_function(captures: &Captures) -> Result<(usize, ParsedObject), String> { +// let name = captures.get(1).unwrap().as_str(); +// let args_str = captures.get(2).map_or("", |m| m.as_str()); +// let sub = captures.get(3).map(|m| m.as_str()); +// let super_script = captures.get(4).map(|m| m.as_str()); + +// let len = captures.iter().map(|c| c.iter().len()).sum::(); + +// // Parse arguments +// let mut args = Vec::new(); +// let args_clean = args_str.trim(); +// if !args_clean.is_empty() { +// let mut i = 0; +// let chars: Vec = args_clean.chars().collect(); +// while i < chars.len() { +// if chars[i] == '{' { +// let mut brace_content = String::new(); +// i += 1; +// while i < chars.len() && chars[i] != '}' { +// brace_content.push(chars[i]); +// i += 1; +// } +// args.push(brace_content); +// i += 1; +// } else if chars[i] != ' ' { +// args.push(chars[i].to_string()); +// i += 1; +// } else { +// i += 1; +// } +// } +// } + +// // Clean up sub/super (remove braces if present) +// let sub_clean = sub.map(|s| if s.starts_with('{') && s.ends_with('}') { +// &s[1..s.len()-1] +// } else { s }); +// let super_clean = super_script.map(|s| if s.starts_with('{') && s.ends_with('}') { +// &s[1..s.len()-1] +// } else { s }); + +// Ok( +// ( +// len, +// ParsedObject::Func { +// name: name.to_string(), +// content: parse_string_arr(&args)?, +// super_script: if let Some(super_clean) = super_clean { +// parse(super_clean)? +// } else { +// Vec::new() +// }, +// sub_script: if let Some(sub_clean) = sub_clean { +// parse(sub_clean)? +// } else { +// Vec::new() +// }, +// } +// )) +// } + +// fn parse_string_arr(args: &Vec) -> Result>, String> { +// let mut result_vec = Vec::new(); +// for arg in args { +// result_vec.push(parse(arg)?); +// } +// Ok(result_vec) +// } + + + use regex::Regex; use lazy_static::lazy_static; use std::collections::VecDeque; diff --git a/src/fonts.rs b/src/fonts.rs index 4c2a1ae..cfb41ac 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -2,9 +2,11 @@ use fontdue::Font; use lazy_static::lazy_static; pub static KaTeX_Main_Regular: &'static [u8] = include_bytes!("../fonts/KaTeX_Main-Regular.ttf"); +pub static KaTeX_Main_Italic: &'static [u8] = include_bytes!("../fonts/KaTeX_Main-Italic.ttf"); lazy_static! { pub static ref FONTS: Vec = vec![ Font::from_bytes(KaTeX_Main_Regular, fontdue::FontSettings::default()).unwrap(), + Font::from_bytes(KaTeX_Main_Italic, fontdue::FontSettings::default()).unwrap(), ]; } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 435bc03..50d618f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ mod element; mod bitmap; mod consts; +use std::time::Instant; + use fontdue::{layout::{CoordinateSystem, Layout, LayoutSettings}}; use crate::{bitmap::Bitmap, element::{KElement}}; @@ -16,15 +18,30 @@ fn main() -> Result<(), std::fmt::Error> { fn parse_test() -> Result<(), std::fmt::Error> { let tex_input = &std::env::args().nth(1).unwrap(); + let mut start = Instant::now(); + match KElement::parse(tex_input) { Ok(result) => { + println!("Parse time: {:?}", start.elapsed()); + + start = Instant::now(); let mut rustex = RusTeX::new(TeXSettings { scale: 100. }); - rustex.rasterize(result).print(); + + let bitmap = rustex.rasterize(result); + + println!("Raster time: {:?}", start.elapsed()); + start = Instant::now(); + + bitmap.print(); + + println!("Display time: {:?}", start.elapsed()); } Err(e) => println!("Error: {}", e), } + Ok(()) + }