From 07d046c1e8109770b3d7eb6cc5e3468ae1fa61e4 Mon Sep 17 00:00:00 2001 From: Sexy Dev Date: Wed, 4 Mar 2026 22:48:41 -0600 Subject: [PATCH] Initial commit --- .gitignore | 19 + README.md | 20 + build.zig | 73 + build.zig.zon | 22 + src/assets/MinecraftStandard.otf | Bin 0 -> 159732 bytes src/assets/background.jpg | Bin 0 -> 1143516 bytes src/c_imports.zig | 8 + src/commit.txt | 2 + src/config.zig | 138 + src/game_updater.zig | 516 ++ src/launcher.zig | 307 ++ src/main.zig | 206 + src/renderer.zig | 206 + src/safe_fs.zig | 148 + src/ui.zig | 1022 ++++ src/updater.zig | 287 ++ vendor/clay/clay.h | 4454 +++++++++++++++++ vendor/clay/clay_impl.c | 2 + vendor/stb/stb_image.h | 7988 ++++++++++++++++++++++++++++++ vendor/stb/stb_image_impl.c | 2 + 20 files changed, 15420 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/assets/MinecraftStandard.otf create mode 100644 src/assets/background.jpg create mode 100644 src/c_imports.zig create mode 100644 src/commit.txt create mode 100644 src/config.zig create mode 100644 src/game_updater.zig create mode 100644 src/launcher.zig create mode 100644 src/main.zig create mode 100644 src/renderer.zig create mode 100644 src/safe_fs.zig create mode 100644 src/ui.zig create mode 100644 src/updater.zig create mode 100644 vendor/clay/clay.h create mode 100644 vendor/clay/clay_impl.c create mode 100644 vendor/stb/stb_image.h create mode 100644 vendor/stb/stb_image_impl.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3c3dd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Zig build artifacts +zig-out/ +.zig-cache/ + +# OS files +.DS_Store +Thumbs.db +Desktop.ini + +# Editor / IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Temporary files +/tmp/ +*.tmp diff --git a/README.md b/README.md new file mode 100644 index 0000000..cdc15ac --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Flint + +A "cross-platform" desktop launcher for Minecraft Legacy Console Edition. + +## Quickstart + +To build and run the launcher, simply use: + +```sh +zig build run +``` + +## Planned Features + +- Windows runtime +- Full cross-platform support (Windows, macOS (Maybe)) +- Robust self-update +- Modding support when available +- Save data handling (GameHDD is backed up but other client settings are not) +- Directory junction support on Windows diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..4edf970 --- /dev/null +++ b/build.zig @@ -0,0 +1,73 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // --- Write commit.txt from git rev-parse HEAD (fallback to "dev") --- + const git_commit_step = b.addSystemCommand(&.{ "sh", "-c", "git rev-parse HEAD 2>/dev/null || echo dev" }); + const git_output = git_commit_step.captureStdOut(); + const write_commit = b.addUpdateSourceFiles(); + write_commit.addCopyFileToSource(git_output, "src/commit.txt"); + write_commit.step.dependOn(&git_commit_step.step); + + // --- SDL3 dependency --- + const sdl3_dep = b.dependency("SDL", .{ + .target = target, + .optimize = optimize, + }); + + // --- SDL3_ttf dependency --- + const sdl3_ttf_dep = b.dependency("SDL_ttf", .{ + .target = target, + .optimize = optimize, + }); + + // --- Main executable --- + const mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }); + + // Add C include paths to the module + mod.addIncludePath(b.path("vendor/clay")); + mod.addIncludePath(b.path("vendor/stb")); + + // Clay: compile as C source + mod.addCSourceFile(.{ + .file = b.path("vendor/clay/clay_impl.c"), + .flags = &.{"-std=c99"}, + }); + + // stb_image: compile as C source + mod.addCSourceFile(.{ + .file = b.path("vendor/stb/stb_image_impl.c"), + .flags = &.{"-std=c99"}, + }); + + // Link SDL3 and SDL3_ttf + mod.linkLibrary(sdl3_dep.artifact("SDL3")); + mod.linkLibrary(sdl3_ttf_dep.artifact("SDL3_ttf")); + + const exe = b.addExecutable(.{ + .name = "flint", + .root_module = mod, + }); + + // Make sure commit.txt is written before compilation + exe.step.dependOn(&write_commit.step); + + b.installArtifact(exe); + + // --- Run step --- + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the launcher"); + run_step.dependOn(&run_cmd.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..505a023 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,22 @@ +.{ + .name = .Flint, + .version = "1.0.0", + .fingerprint = 0x8b7111ec7a270fae, + .minimum_zig_version = "0.15.2", + .dependencies = .{ + .SDL = .{ + .url = "git+https://github.com/castholm/SDL?ref=v0.3.3+3.2.28#2cde21ea89c7b67d6e16f7f1e2faaa6f44618ecb", + .hash = "sdl-0.3.3+3.2.28-7uIn9MiRfwFhqxnwBi_vCQlKr82YpwkQBVwdz2uy46S1", + }, + .SDL_ttf = .{ + .url = "git+https://github.com/allyourcodebase/SDL_ttf#main", + .hash = "SDL_ttf-3.2.2-PNW5xNIRAADcUkA75pWizqojQE4hM75QjOequyw4aET6", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "vendor", + }, +} diff --git a/src/assets/MinecraftStandard.otf b/src/assets/MinecraftStandard.otf new file mode 100644 index 0000000000000000000000000000000000000000..8fa0b3e158d4bc0d81434c5c55d79839582b49af GIT binary patch literal 159732 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 OKf6uzYxTSH{r?ANLcux! literal 0 HcmV?d00001 diff --git a/src/assets/background.jpg b/src/assets/background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aed4404761370c21c6a9a6c931e7240d1ff4ddac GIT binary patch literal 1143516 zcmeFaXHe5!95omOK@>zlnp7#GbU~U_8%0DEkzPc)5JC|`qy(k+qJV&aN>_@s1PBo6 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
    L`WJG!jkjavzpRzb>pVsO*ChLoI%Q>?fd44*D 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 z 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%0A=^9K;N50vIu``hm($!HuB@s znvA`=E%C(?6YO?fvr_u2@J^<+-U2%TvPlOK*i{eDZ3=r6BL=QzI_>U~u>T~vsOHj2 zRJLdKz;l@Y9+a$|1Ptk)G@W)Li8i;w-at-?CksgBh6gBy?F{b#!LL3LFzAMI_fB2zS%uyHq-lU6WCoKc z)FE-Xk=G3*;6arX{=G;CrRyG!*vlpsPE!qOqI+6H`#I55(?AUS`y57&1?BqF2 z2Z=yt63beoH)2g|1br)YcZ7s#VvVnU547I5$+|l(+c~A=;3(VLj8eF4t+kf@@MFmP z>nWX^Od1JtKL)rgj?DckJf~YjkZ7QFQp~cS>}}J=i2luLMKj1v&Q&2xs2?9Lv4~F# z{jW7yqURk#258UeAhP)wnygVvbuMzmt)&>e!(4Hc{cR@N5E^<}XHa%4<6A~ClC{7` z|LuO7wKP~$@txpg#!vPGV;PZM77~&KC&lK{UX$1xV7hKO#_|UT6fiP-B|MjL;aKEC zH_3=?@PCfUz`<>B=$#!>p))KL4hdkvPjoojZeRcKt6> zTB^EsI$@ zLV?r2R#on_Q-yM)gRaA=pS+x?`8E*>qt$-Dd&R$lx#YS3N~K_0Ti+DAbMN2z**|6% zW2XFmFtksG3z45~erDs&4hvN>^K3{Y#g3v&(PtdGn-mrds<3uQGK(4(j=&D?=?tyV zL1iW_q%bTL@t=dTOchsYEeQ!V`+{mkBby=#5w2o1gOe+G`@upjC6qznQ)z0Ie+yg8 zF}CTx=g|C0J z^_6d+eR^g#-BA?z)O}iqTcP~OUYnC@FNJ38i{E#G=vc8tMM&7Gr(v{j}Tp?mpBF5Z6qf9J<54D6)Ms4TH`(5osjZN=>elWybJ2Kx>3e$}j}EZH^O zA9@vBGMBij2^uas-SD*Q!E?(QL*&ZGfhug9Wd^ z3f=eM6CZID9dHZwKWF7BjU(4_45eK~mwA49EnF&@XAia(dG2}@HTgy5^*bddL+Ze18!XJKVzlXiRHkfQT#1=jX-YcCjP zyu1}YWY$s0vs{xev!y)$bj~W%W%mJx?z!7eoPy3rX`3n$&Z!xzYFe*{U(jNAjrVCH zS%(*g{!|~7$;Biv`a7ic_Iuidb!FG4#tUQpCy4?vHVIdx*hN^HL!@XokisPPEIQ~8 zSlX=fFZ0rmKlp~lO_GvhcvdgtmvXKh$yz1j<#U(9=^#l2gi?(xl#XoI<&@3q0}B=T zE#KjZbgX;b&&gZn`A1pO=%J+1w<~89kNZj#h&CWJ+#6sKQ<@TS#JNTD(5^Jim%}yv z1O8e{5}Cwzu5ueSC?pf>J0`{=ql##GaH0nt)U**@Q%4bgE!?T&k=?du-b@i1dP{j` zU!6rt94eG1@Zp!(C|4U)-NK)&O^R1$l+u_w=%7w34xw!5XCKA29Jj`t6`%4hB+@G8 zxHLN4^eDLqWbkLOxR&oe!mhdcv>h{ngkrS1=yn#WL#PV=N&l#mE*k~Tv7z6T%G`9`Xi+N?8T z6kK6p@c%?!!qDsY{SFINaD@MymnQg$p=vKi=;)@rJcO|Ih~kue0-f3pLry74jj(QW z-~{66An8264(At4kJsfdYzR4#xV9?gXOQ9L{gWmi@tTm-mXTKEp3<^OVhicf7Piq3 z^qtWmYSKaXd1w~8lr_hI1;%fEVEOFL1@??XB&mhB0p0*BoN03^^bhbL!NuKZhlP*! zwD9UsSc_H(abIVpbbchBI6a&Ry|B{+ksi2u@cBgH>mvErXTH3E2lMMH^!D%N3Ef=1 zP6z29lrTR0tT8R!{qGd@L6-h)IL@vD%2 zQM+Uc%K#lzqLNX%5YF_}`|mbpmlVoZtuye?Is7u?F|bG7&Ke@>&_QWf)x#<5NXEB$ z;n&>5u88D&$fqTzzg63GE}vw5Q2#6^t@v|V@y`qvzM}|Ev$Ghippq>L?USE&3kLE3 zq`lB5_|3z5*{!jFFU=TcuE-NKJ!khK#{5V(y!{>>WU5P6qJw7tv`^0*CU+(;2rCWZ zVV@krlidfyTji_^Q|`EJV((e zUxkk-R_P?0;#xW=IT?Y46?n{`@P95NBSv!lNdG-G_UI`0srqVmFA`4{lC-Z7UOgD) z)H|<*-N;T~je)+}O1cHl7*=)u^7Tbc5Km%IvOML~zGc=Xw(ZBV&&6>_KC&0LI)^1^ z6F+w2C<;*UtSZP8-k#6s65{Eg%fg4htJQZIKQ~oc`wkmRKMhN9NU%j?P$Qdo`H{w^ zI*M*8W0esjUtk50ycQI$z=9lF7;t}QwTtZQVXT=LVvo#NeoK9L%&c=Xt25U6I=Y3e z@U5#GY?xgv;2r!u)#8wGlS$G+Ms?vnv@6E%i*&atCpCY;8@Q$YL(jak_&zIcAe!lN z>UBr&k*$K0bWloV&tW9vAK6&$o$+0r7fs6HP(lbrJ7KEO?VqDK?OV4@ra=c*7_v_v zIG46TSndD0liV5ap=f9D3EN~5{d1~f;b~M1Bf!_h$l{@q#gjXc2`}tUO1%Puld>xv zof?Mw#8Ak_387urq@Q&aPitq*Sods%HgGz;|*>up>vw2@% zTF-*PE_?P01T`Vo)>G%MGnXa3d{`9V(NDPzB{M|~f=y@|X3ZMb>SLC<&Spk1y` zgO3jDIfKpa*?t$Ba@Kdjgdg)S?tELl+h3{Fb?R7*)FZwL=in;q&naj2ZysbHCVNR% zlcX?V1F?m0QdWB6XqV6s`cak|v99IG@FtkIkFh8JURDF+zT4<_`pv?SM`AJfa%xxd!C1xCQ%((K0k8i=9$SNXq8Ke|5M9N*pClXVy!T5e1N0ZcT z?2_N!Hkdj{zVG69_*N6K#sB=;f7PVCgEl&d##Kn9FITzM(A;5o3MUm@aziy0YI zR79<^q^f_{{Z6|a!MM#h>7e47HpVV;Nq!$K+_&5i{H=?L=W1+?HNWiX$5E6ra5eho z{1Ry243HlI`4NyG0r?S-9|8FhkRSh}Dgp8%AU^`~BOpHl^5cKxBtU)yp|Ch-G=tqEl z1n5VAegx=8fPVbHL_Z#wG&qs>8MO8G4~CURa_Vx0_kEo9R;k#HIx?GFr`6dlB=S_G zkKsuDtE?w`iblR%y+^mQ7Nq4_XSENhR`>O<$$FN1ru2!V86-U#yjXy{aUA)?@F_O# zc`>J@6Gzyc?+tMFjpm09A1ksh6eO3ek+!wCeNLH#x@p&&C>g+I;yF>DU0st5PMmxY z5G^9)8fF$TJ*W}}A6G|{#Xhe+!_B2u7*t{n{Pz8g0*FDp3XEZ9jfoW?oFpx{5Ej{%MnlwBJvCqAa#mqlrGw$^r5$sl2%v%-N{r+Z$z0-tq!@4O+)4^PEEnQvy_&Z)6OCPH0 zU_ho|O~T%F0hR29!#kwz)7g(|Y$W%fCl(q{sF+B&9I0|>dT?wxS-qQusdOwt#79FH zqcZ^hXhbTMh8MDpyY%g;Y!bI0cZW^l@@KwwD+CY%LN*LzF1v?2Pj zR!fS~Nv=~@K+j{`{|xagTzp-%9*lWdQdK6{HI={II>`O{6q%bi+eV7}>%nJeEjNvo zdmwPs8nRv>Ry*d$iCK{@g+_ZYAA6l5u9f=yZh9xw{i4Ki@nZpb&yRF7Kz{INI%uS= zkO8q*!O3z>CN35QYuiEUoLq&yUcq9Gu|chk5BP0whZo;$iOo(kepGdxClJkH6XHI< zy}i(!!}s3jmfg)5qho1W0nE!WP=sJ}ziFLaS~P{U$l z6YU4BpQ20hHc%P)@{jMZ4T>qL!?qAoVWC=sg!JG+xnJ%-z)QP=q^dFnMAJkNf8+Ph zum#ONIU&g)PTr1B*I!@NFwP1cMY zZ!6fEI?2J=^<%=(M0yV-pqvY@Zcbx!kbE_q)F2_8A1`T7x_bt{B|{lM|K zKKa~61Fo-nwO-=9Y)=XO@1$g4!wPDGI_bMiHlwsrk{wu);n}dG9{A-7nPnqAvO>9) zRuG$8!^~GB3w?5XY;RsQR>FEqIwvka`hw+_zK|uVhXSP?`6|KsN4(M*-j3N>rILN% z2W-xC(7+YY@^5^cjtHtX24kl!Xkn)0pEUPdZRUI$EB6F%kolPhyLpd@>U0qAuiygJ ziJw1$Y~*COWH-geN3ZS2UAlYK-od_Hakc|0`lfaE#aqKq_sowSQ&O$+L~~p-f!;@q zRmuh#ZpopxWw##H8(Q@uGne=4t;W^*Z+r{xo_YH2Cuaxz!7f*3`jgD5s}7ndX*C6j zyD#{b5{|kSh__t5Dsq`uJZ}kfMwNCBY()$IO$Sl_1TfA^P`}TGH~VIxWxXjCG=1Y5 zu1?KLLi;<+m=Zxua#oAshdurj)KYQ~H_)NV4u^A1d>=Y(dj%t*Xeg1o_*Us;DboA} zc$QNDvGDHlv5c%vC(e3Vr;fi{8DOt+$?npk^=IKNBgb>v(;a8bz~}Cc;s?Y!tFL79 ziKn(7eJC(*$dWC54v$b`D-`}c451l*ZgxaMi+wmYf=98fvljOKHN$DjO+Jn8G1o2B zAE!jy%)aA%$$RshVUpLkHDas3XE-DaJ#4Z9ilSaCqPN zfteK#-&}+7YVYuiMt~>hv2*;e!lU?agDcyoM2Ud<}yg7@t`EnAp ziJk|CB-6s`&8W-_PyTWSth;W}6(oJ-Qw1J-8OVdHO} z%*TJ^&_Qf_0?VmRQ?w-H3ZGBAVSAaO>dp2cPDAM245oX9NGBRTw1T_=g*9w@^s=)(>sj z-4@)Z5h?>2ETByOzooA#>T{zIO>t|ud7E!9#9U8|NIW_xu`4+9`t=dRZvv|#Ab<2u zt~YvbyOd#5FE6ASSKYUsC~XeKncj@{AYO2IRV9jp_;E2$rWSf}$jz^~rcFzy9LLFb zS~~JIhEfiyKE`<3j#`e2rs+u}3w(QPqTL*I+V5w|GLq|vp+rTS*g?j7Z<_E_V7ZTp zjhwQ!@cnVZT!_r%`5aPzsArCQ63vEN6850CPax?m=coUNy*K}d>i^@vM~Z}mY}qQL zED<5Q79m+$>|3%W#@NSX8D-zMkiA04mTd-O$-Yy_GGk2kEMplpXc*Ude{Q$ycD>)% zPuGv%>vlW;fZOq!*Lj`Cd7S&>@mx8hq0+mbdsOw#b^jRh$qPx+G#dSbX%;d~ut3G* zH|LX=jpDtN$_K1EZpxfU8aN*+HoOFRd#+8F6A}WSQ>$_LX+Q+hVt$*~k#)%GmXV{( zq@kJ(b*wckhg{-H-lW)DTaU;p+v95(8+qz15Gq=MrJo;4R`~GW-@GZ7>laVbjfk?k z7cRYa#YS49?0q2RP_7Q47k-v2`$qLl_h0e-%ooO*Mv0yGJ{Y0J@{>;}$J+KN4&Pq*aP6mH zWTwbOl^36H<9mm(E1uSj8q(LxCih|QRwR_|c$R~2L1nxCrV?YhWZIrr>n?AycDbK( z&D8FuwZwQ9GYklL#h#@%C^*!Z@J7}Krr{o@5Y>#2A!@vzdQz65(@V2mu?OWC<(k*A z&sMO@I}=Gvj|a)vC{sIxsf2u2ba!E|Aq*lM?9M=|s=aP%vyEy34pKF90+rSjN$L+) z3{;p;h?J7DMWu~sJxv*$u|qeXmDdywIaNo;|CBaW#fe)b9@(wLUljM}mYkKbZj677 z|LNM_daCo$>`W$HCSrqVX?(AP6#Swio7nlMG&D>@>zPuWdhAx?#M)9_tH;VQmC6JD z?K0*kRAsV?k z_)z=I{nIOo5ZX8(w1+bS6i=W!^~e&aFtPH?>@HLtn_cr17FSOk-O%(nyYBB^EMFT- z(T;+%0BPGc%i!y)L6@tyhdKy^E}xsScP64yQB27U!hXw4?asvUix%*c5t8-AXKsr) z>~M9KGn#BuF@+Z9FZ(=7e*MkObK7lm{8BK9dV;$*b`wtOtrGA5bYFYXJvdeC)?=Dw ztPnRk4Du{D4-{kp_cs|~=;F2-?>dE)v zXH37G(QLPPl%O{bT|8%yHcR&JY7xxQT_%B zYoF&#LLY470Z2ioJ*62~Z9N}w8{yAfi+ZJ0kn6a>-~7v?`kFAe zWU=(xd%lu|TWyP8)EnQ3DzK8a&ui6|7Bd!8nnfR-x6T{BdtTqzVC>KkzIpeWe*>*_ zlHteqr%yY=EWZP4DRZ+3T4-**mNa99n6;Q`dy!a~1d|-tdQthw<89a*VqEpo?XM!? z)RK8|$t!b`70(;te#Y+4v@=e~_Hm!=Q_=E${#@g+w3cEogb#Lj1zUMYSp~YP)>eT1 zvi^2L?%HPV-jk$DW{2VF&&4C*7Pzq6bp=k?d%E?A3}MunMH;y74o7;pn*fz6Uh*N6 zv(~6q+dff8s7Kn7-N5a`30%FfBD726F@v~NWQ)iP^VWzzcromlv2L7!*7$D`HTWF7`@BeruX$l(mtJjFwR7$=Y!$S~(_w)GL`BNk;ZJSg#sE`->NY;}^I+hP@K~em(G`2i{PEaj%lmLAsLvg89ysE6fA-YdyB zf+w%fd_LjXlf6LudTdqwoLr6|QP2T3CQLC%Z5;_;ty(9 zQkC7n?_+2ON7s-38p%NgPaa)-FHdN67zae-uI#Jj7(gsd`Y1!iMkk-f&#w=YW|tZ7 zg$2YQ+-&?c17%|iB)D9m>0)PTBozJ_u$nn5FhIRH#xU(QAt&zjvgT@ky`nR(T*JUq5b_Iv+gaJn|!f$pq!n~KxPR~&J_O?Ga6 zD~m3@6d84=)Xhjs_#~XWBi<`Jr033)uj;Hfb<3YG-y-^G419XH*u)w9%*w2Cn{Z>; z)VEu92;z=)iuogs+ zAv%zsY;>=ZCcgY5?Y*c0b*+bVz+3K8D#Y_ItV{5mwRHznGB>_NG^4%;_KMFnT8EDD zC6iJyy_nyyGy?D`V!f3Oc-P!tHkPGx?s1Y?oquKecb&h&U2(m_D8tPblh|*)sWB-0 z^lI^m6FgayH*F#_uV6}W&c1bc^gRCrdCm~(%tKCM+Y|u0W8O4CtVRzt9p5TkE$Ae<;{?- zPNi!RD(cgVt)-;rCaDi&lX~Z9#NyoxXa^Ibws1V*BME<<6pkUnMpjZ5m{GaNKbRN9 z+0AR$9ZXkrF3i-;$lkqpjju}kPT8g*!)>PXRnJZ<1hKEnx16LBsFU&YD-;uoI8etn zUBZV`>o_W0J|MhI8*$r0X)bN-%A)je(wGlfMrgo-!8R7OV^pJCya(6=(!$JVG%VfIF5O!ppeSa^O`yeL%P&F7W9D4@iaO9Q;b zSh;ZB=yCZ>XC>n#^0Suq#M%%t{2<=519c+>4P9SB z>@&Fmv{-O<)yl1}KfV7NbTT_>QM9I^wS8)1OR(L!MpuNIBR)wJxTWf&aS_GbX11AmHodh*gw{mwlK8)p`rMs+asiR<Q#f9S{*Yi@=A$Q_#^!qM7M5M!o7-hSzb46?bV`PxhUZT?V8fpwbG5-B`U>X)T6!tMqlabPTv%D79e(mig z(M6^;77>>+8>W4gee=ykwk`7ZR}brCnyZmyPFmIqnrIE;n=zb50pKl|JQ z@e{Ywc2l_qJruWqNAse#9%b7*@pRvR6}{KY+dfNE|NVDXOmCUq;$LX&q~@Qj`s|qj z7xrOuk#SZ#J8`vNKUfWy?i6zih<|>Whqcz)RLK(sLiTAbj$5(=K zKz(&oUDvp$TdtjNFZG`Bbg1^{(zvNT?*!1dTK^ZH0!!vC~ zsKDSMM-vay9#g>7lXzLHt$)p=wlOxOPv3{@e!qT`(P&3Tlde?Ess{-RgkR6`E}k@D zIGy&*!1WufhGcd+T4tyE0mCdmFi`!{_5#%p$aL+iHvN-_aL-kkZ0VYh)5mS(!qUl7 zc_Y)9pd4pWJjsTekkI56yOVwP$$u7^B*>yuu(nnF>y_1FkY?8;IdD)KkMD$jV!xBX zuD8;m;%VN0M|<{}gYD%iXywyCL|9)_T!1DL=I{!d^fAWJ9}3JHl^JByd~@4vg!P?% z>D4kv%#B?Nj@q&b27 zZr5BjenI_r2y^-G$Gsnr@a0rgmY=3fsPN(IoZE}#J~4B7^m&X{cwT=Pv0$uD@#2#b zJYxv_TqA|s!T<$@i`bIprN5+$zXiq}DkM4w0aYEL*XRBipn(2_z>l0?NNPi5)m$JV zxqQ$PI6-W6m|i+?$&>ZnLA${IJ*V&3u-AI2#%C#y1kK4o>(BAI12Jjc94ReDt1B zbbF6_QWg(dNt1*4Wd!o~uqq~?HDCp$_zk@ym~`@7U;;da$-|9uA3tj@=7wA31MUfZ z+1T7#n$i812}#~3ZzkJ4l8SWfI64XTQYAg)iTcKPy|F>JX~EuToOBe2k#D_*y~M26 zoI11LMZBFCNW1H|;yc>zZln@D4JS2^&k+t>reCA1EYM zMphCxD)p*wK6=RlEqXZd$?T2x7j4Pud0B=Z4yW%bDY=Q9w&2X;qz*Vkfy~tDV@L+s z#qSy6s1%J%xzN)JBfCKHXl1y(Y+Mgd1$`G;aJxqCs@#?Wg?%IKhO?jpuoAURBbVow zcZmW)6y_`{Vd=nH7YgJ{%a@@V%&oP*8b|(0z)?yeTGT|_x|V3O^HQ6Kqo^_1=}(5C zd5N;cDybD!0kS`L=Ug!PFz8Wjrf-q(86V{j?r!s3Hg=58jHxk@2r6(wuM zHQ_ezGXI@?W}}@9WFlT$OlU zpS2P8=N1(^$@QqO5fO(II$*VMWWD^kV)8UoSXS?$=v?K6)y&wj%J3$wm)8B-0e#;5 z8sc=h(_~t?46B}jnH#&d`nQJQt=n&rV+quUK*Uuw&`d+XW)3fPul}2)NoU{r3uPs6 z{-fqUGmSo@`Bs*~mo09q*4%Cx>9fClb)dWNs;HkYF#7RyL`TBGLJ<(pA@BZE+tcnx zu%>WD%PGUE5iK@TEl71o;$NSFJa}J#U69(&uxz)xqQSq{Sh+Vq^-4-AA$R<7yOzxHx+Gw#Mr)Ml$TFwUMj}UBYkhsHE@jhq^ zkl~kW8Y-qilXJMtjv<=y&{b5YoYp#HVobOb3|fI*aH(=iUe`y^J|I<1~)MF z$jjoc?ZQSz*g2ZS8iZizq1=L%?4wMd?a4@@?z9d|#`TcmQ_se5YF`~|#On|I!_{1` z0Vm1czaGMsrB`VO4wVakrC&Jpc|YTFh*9eGg;sP6CSAYrsY_{SsO}3<)C+~_1Bp;g z9=~aklUrveTIbH~dmKYlyn%8afmxtMq27RZo$!3;ei4*xpfEt;8d=%Ex9ZoXY#vC( z!g^*EyZwggul>K48Nm`!FzN#z_g1}5QolUEPElLtcH5knA7l8S74>O%E>}^~;gT>+ zTINfVDQfcbD>8a<_I}IdR&;f*~L%W~bJe1^?a-pZI&t z-mn$3`v9lcbUEV=Tad=^dy;Wu*Y-%rNLa;YTr-+H>Ws}!yf(fW)`p3+3XGL3!pUQj zzZ`gQo9d?a1LK3UdT+KeBip&${}$npwJqNnO%Z~Q6oC=Bh5Fq9g4((-p?ZUKST~Y^ z@x#@jTmL<97qTpZ&Sh>1V84q|4w#x%#d%=d{s8I}kc>{nmHywGB!BY83F_20MR(2` ze3}~awlTv*4Gp5IRF*zk-R6{+so9Ks#Buhi*z4Exjh{-63KxSG`bYtU1D9pDU6t0q zP6GHAn-(t|-!F^(gx)u&)p{^};kVF%`EFa1912jT2c<>@vshpYzJxNydmw7JaP z>K!Ynik^p6bB!-hTr91$uBQ21Lt(cTCl4gCo&pU>M;6{K%GQ&ck|AzI| zabio~@&WSs!QoLBdUYZ>kRY-Xw@>N0HNx|C=|97kAzPQWe`o4f{Ed$&oYh*iv_d+~ zbTH#*?qh9lUfsDl8z9>wQdS@ww@hcuCeDfdV_-dTnAJPkX+kEOe44&+wT>P&_Q~am zM4PawFz3b&Pv}~8=%pNf1-4Mh7=sw3LoEt8_Ct|_6uBm881)CPwJ*+&fTqZyHgP$a zES~O#*h$)X3d_D(=GzNH{R!+9}Dy3qej$4-5z1DPW|-N)Z%LM>Vl4 z|DKM3^|o?4;#9`*-_AdVXiH-WniA1mdqK_?&gjr5SMM0~@{9{Sm^5wS*+y*F;lHC% zRIHxzljKuMIp69PJQyph!fGjwlb*(c*M%4vJR7tKy9m1Uxyt=Jn}Sa%FmgKfk_(H_ z2ElH6xd|=+%UZ#^;mB6GHfasJ3NT(@)ZV||D>V2zQM+yEkvA{kPOvQA9*mm;bTido3sfbxu@K9_>n-I;e)h)i<-EAEB{qyAy{b7dE zDke{VsMj_n%plYZfXWfcfT3BZbRY!3fI|=L4g1^WFd|tG&fTV;+Cxb9aBkH?@k<{+ z5nFC|19t@Q>|fX5mx>`i|6X@XGC-8)f_K^tOFiRyYTq!h}gB1%)o7@LqiufEcgnvDP zTgu+ENNsPXf3$qTSvqeY&20P>6HlG}yJ@Ag`lzCUjaBk}?(}H(CY#R(KX} z@fH1hmH8Ld1X6T<5FUN|jp;p%iK*P6ytBn%B5@C|gb7^98VUDXta}@p)bzE#UGq84 zx?~+oNI_iKkEYJ{|HcVva=s^Hy@y^pyc3Q&`39dWB%Nby7%H3pY2Glb%6SL2fl8Zb z-A*{jb3}(T1LXl2_`!`bf8e#q5rxNjp}p*G&3I(~46p&J%ot@Khgk0-tJM1JIFraW zQWV`m()H^wweKriU#44$RGZXA6hR!BIqNtM!BO;M%8nr)d;IN+Zz5OecrAVvCf%WK z4T(eLx1$|S>m1N3h4h)Pb9Jb(sBvN%<;et`PSHzGjiM)wTy&e4e8|$MJXh@IgN~(6 zTifdJ8$c}cJ{x&+;?PlPWd6GCg zLM_Wq?t7r)rl^7pwsSWv%IBo%RR2h$+`XrYhM(!^qt`Nv$mvOx1kt`-9u*1KrHw0;may|hp$#o;y~H5iG3!z2ltvaCA0@!*r~`U9#YzO>GUn! zD2NzLzde59i!(9rnXFmrOh9vop!|8@DDnT#6L|8+Tbd;*|LAYsxD!7D9wy!X+@J12 zz!9tlimb%seze~asb$CA6Ji^6hJ2K$bF(g+BNo9cny^EHjaeua| z{PLfmD56kAWH&;Qxz1iddbh&D7TT-c*kR^U1s6(L)G7c1QW7hwZTnWXW75_I4*$G&nSOb zb28~!qD4)oBm3w6bK&0JGY`(8B-JF3(nhh9RG>Uic4K0@t(9Wp2E2X9B?J*RCU6)x z-%VF1-gsF{mY5CoImefVF0(MQ=(NQ1QMCa;mN_GGHM@pZuqsDnUWKF90a8;M4hY*@ z3}Smq6`tSpT`b8&0gt*o7eW$jS_DT5t zSscarCQ)De{2KhuPkiU~mTt~5VwP2(X+V9fgh>CAAaZE&f=zhq9Akap6JjgY=0#YU zNY+s459^==YxSw`yztOR|fG-P%Mt{uU89*7aMMbj-bZy0iV<23KJmKRKRU$dOs%-snEGuE2VVWMNMj*U`f_|D~J3)5WB@7LeQ@bZ}L-7k)g5aZDJ zx?}a^DRq>j?y-2F20N_VRY2t09LXlQ5u^x;#CUJXL#@TzCk5^BiyeFxE#J}bwCF6< z0X=X-qyUfpa)@_}R9(#d@t<|u+4jz(rd=Ki(~N5#M|Bm~^HaQcc~M%V%c#N2nnqR? z=Xe+ulJ*TctmVe!c#3S8{o==)-zFX;|56GzW>&4)pmUMIB&65dEw^WTWNsn z2dlJpG+IY_yf?3mO1>&*_IOj7;=kVDyW#l+NMK%a@4ZLqamj}I+e8;C zD@%mnZV%Ad(nehQr^PIEr=^)Jb^fI`_nV; z-)js{+<&DfY7(e*cXUe#IKWzKHf_T3C%0Y{Gt;I3NGb%%aQQ zhi`R`^ZvsgPlqsQMv-!VLGCmB5VBnt2uZ7yt8dK^4ukTuI=N~zC8Rit&GP-;eSW|# z5p9@y`WbE5yI3a?bRIm%71(s%mv>MDq16HR$?Hc#Ut7X@@n2OK2(<$`1HTr{#he}!ZB zWOB;eYXbVC8LoV>Ogpa|$a#FoZx$8+&AdoB%(4=VY+^20Rj9JCuzYzv;blsS%EaIt z)_LI~H}NvB2V3Hqlw?FVBoKnArJmYPFJD_N({c_BwDa(IIpn3*T$Gk3?!nwNYY$iY zINTr3mEh8|D+YO;Sm7^jb7sw4J>v-T=`I)6_b7cRxql0uf*HXr>Ut~zzoRk=){Db9 zqzS2b)yO(p#A@3$M6xzVwudRZ%jRX)t6BgPZ_5Jnh{jlZ zq~oqH;vD{Zz?PKnF+_%JxzKM_kBdclB?U8EU7@raXV$7?lS~71x$5JWL`Jdm((^p| zhTjVdK0ef|Ib{_5jE|4GW30?4A%fI7WP3L45*vptXM}|K@~JzX5e5mO&JR&%@6-8x zJ(w648LzvTlKf^(XDOxslJ@iDq0m|3uQNBonS6J$cddTJ#Tv8Tl&*~}zw|caL5-Wk zy^H7Xakrnnk)1N40a1dU{URr&v)BJ)&zB}AQGpJiUh0(=?MybgBjDyaU^HBxrDi64*wc1 zt9Jv+w$b-uOxx}8tXy`}M=rL8w`!}j^}Yjiu8RlV=+BnUtqvqMuH7ag)e~bkK=LI{ z3;T_{^UD6s-J|$dp`(@AThgZEFK4M2yi}ySD1S9Gto^oXW_ybD^ldM(_G(kz%bk_t zr++LawL^GjmLlns*GSX4o^j8F&P8&VYBGccv!aj;WPINP82NWdISq<2td_e z2Z^k|YII@6+Sr)RSen;Zj#N4?6-Lk8g(+n44;R-|e+XN^?i}_9U$hs`?VoIKOiNfs zx~(R5k#kxdcI~S%_{Gg#@&Vfn!f)4hn3zS9-^BWo6Zg@NzyC&uV;MztZ(VnY9O2s- zoK9O{QD%5nvZF)%iCUC@bQgvt<{Zo=o@Q_#&*9>BK_HmBbrrMt;5 zNM-=_e zhgGtscMBu$g_&x87&vqA+A#aW*g_xniDXF9>s)CypPDEiNp0c9_l?O2ds5_M?t&Uy zg(ztSCfgAHCMT6iA3HmaP(3C|A#rVoE04=?8IMk%Vx^M~iFFd*{RTOkbw^1F-|JH^ z=~PRRjU3$^wC=vHGUY4NX`AN~dH79uO1d!Joera4xI8{UH2`squ8CSJP*mW#*a5Lt!*ww||U!1Y`|+))bp=``>)rzGEJi zsSp)i@Zn8wA;V+Gi{}{HF2??$FNB!xS9#^HwN43chm<#2YmqC-MPz8{3mg!$B~&41 z?d8>-d(z>+kwWsi;)}=+$h4Y>;mjz~dWBjhDf8aL$e7<8<$a5ebCS9m)>+}e(x|b? zU)`3asOC|=mVN;?9S|CJxTTEDu9zYn=3snv1Z0=_R#fS33EMdbX%-usP>*NwO;2}TK4r{$$JRz# z=bGZ3q($Pjw6@-_Biy`s&O1Fvr+}HS0(!@gzPeGrn6Fs<6n~Doy6%s^$R&!hL$1U8 zpK@hr;zuu`S+7A#6h*V2(SM`QGWcl|^=h^vv(q&jy{JQTGsOV zEU{5+>ql-v%Dg|#b7rNwZo8r|{~!<7Xn!NH2>2(bB`Wq1%1uWt_s{~?DpOt<$HO=b zDyO+XO>(?8P;w6Srf+nZG2^*xkFu=1Lw#qs^1Ii(Q?XZ{plKBkN<)6MTy#=3^_#3Zb)? zGUb>rm_HEi#7P^O($Tbiol#ZlG2KT>Je)YU`keKWG3yJ@_LgT)MX!4AlYHL}JX?I` z6w8w1I&Y=>m_hOC^@3b8m5=AcuSrbl%|B%B;C|Kv;~9~|n@)AvvfpLY?jPmRd0Q&K z{PYgZz1y)D7kK(qenV(KeT$Drd-A(6ni16o=Wk+axT<0jNgqWnXWm^ zDEuV7>Pk&GymI#us;16>Su?QE@}UqqTKTpEj3a5>t0>frN0z@YjRWPFy(N~62!R7} zKnBUZLsBg;zu619^EV!L&U)zlmnP#6j&bwPORhevzINwnM&!v8?%G1Cw^ubd)g&x` z{jDU^A6{ICaU}us=}V5XUSj2e(hY?TYj_580@h;*f8e(pklmM)`+{&K-REQ1C+F|8 z1WOYyc3{zYT=} z%_k{H64X2Q^0s_R!E6^4>QUUN`dRX%RA+F5lJ&^G0I}x)~c0)gT*K4?lf$a2Z)!vpgNH^ zGnj0gabz2LUGe5w{VjD^l4MVMf;`{mbgPgXu?XhO65}H&sOtxPU+rD)o*1)QmeEnD$Dz=DXk~ zGd+ACBIlj8#bEyO8zAsBZOMMt^U812bzLGmA|2!I-cL)8x*8C(s!6s;@I z?w3OA-pzdj*X=a~HR%3o0ag7O!Xzo7gD-CxlC1>IlJ{RQ1$(ESD7U(o#p-CxlC1>Ii|egNSI z5PksR2M~S$;Rg_Y0O1D^egNSI5PksJUy%I;*lVS^_eRo}SzDk$4WF>R!`ewY8fg@1AqL(6cvLYh40GEBg6B1UV+q%%%k4^`U$ zeK717KqT)eF_+2qEE`Cawp^?Ikp#~#i>YVs?eAE+_>Erc7;+}P6S+(Ii2Mn}c#k3C zF5guY=OlHt8pl30F6L*%nw-ePs!Es#EQSpd@mY7oSCi zoxf2PTS1bHwnM_e6V4u{&*y__{y+l?x25hkjx|{nkEo<=#LW%V2S+VB&R}yBeotB{cjGON9)9m1{3nxvU%!@;% z+EI^Qs?kZemFjt?nXou}Vw)oLldr90m}56B_P;X^HGhyT6I*MuV>^|a`c+Agbzk@K z#PvF!@}lZ{m|)FwxjH{mTYA$DJoszhGq34Ct-^)G9@hO7ko#WI#Bh{xR4LLtLaxLG zjT~i?^lCQLfiZepb(&6SIPNlujL5)UA-=Pk&9GaDb2smlW47k5pdytNe~y(e+03e^ zx3I%_9(GlwFY>+(U-8u!j9eaVvLLJPYrB=pv1|Yn>;n^yBqdey>J9{f`QHgV(54aD zjc>?uLXA@&+G`WC==xh1gVVK`DnOf75Z^b(*&|NI@OSEp(KE6ayI0Vj@->{MdFSav ztg!ngBoBYRl%}O1HqmB%gZFbMjZCd%tG9&q858Hn+{qfhON6!^(!(W<+xH&m;sVw6 zqOYc#nti(S27Ar0O=L0FOlX%j;!a<<-J1qJd(sr$oqtR}32j zZ0+*0m}I-J(quNrqRD+sk{7Cnt5dEySe5b>Yg~=@O6RlVrq2y-?3hbyF2-siPR%AJ z=`BOe+@y?eFZJ)p|4ciZFUccxHs~=!TQ5(G7>u$#6Z4 zNw|SMg>JZZE^A|L3}NR|ePw4E`l1%e5uiQx8|R{%HeR+8F-(AYy~lQ_1lg1NG4ok^ zW?Zk7dNIK>v1MwHnIYcD?UDds>#H}4Qcn_&AtY2Ydq*zYc&&fAl1s)$j^xD~4=yJA zI>M?|TMTue=Y;%e)5Lri7t|&F+|UbzX!oOZiqiTc=qx;E$0#XsSNHx8+7LnG z$$py?zg^0eyBcG6``mhG`7n{EbMHfkN>H0ob`a6&WjZUZ>7#ZTHF_xSz?uKkj_E?}q>h6-IbE`}*L(GH zE+VzOVyQLm+zbxOuGC&p#(CqSnVBW+haJJtI?3njk210T!n*Y*A2BTEm|1K@!O~fA z4rgm%cRM}m_A9K-LE4%r*%u2IHM+*o3bMiZH2Q7GlZZ~MV!R_bv*Ob3X7A-$?#i*i+E}K+ zE`A5Mcc(MB&^Pr`lrYPKWOX_-zvn+(rMwH8(PO`-K1TV3W!hhr{FutbX06?otS)`a zTjHJLE%BsoU8hfL4@Eurzu#U$Btd%mW_w;9(CQ_TXU; z9`@j24=Q_5*@Ma+RQ8~<2bDc&_ zriD$?_AZ3XN+iv2`otSsM9pOgqf==w!L8z!>bV^td`^cVg1;AzAt~xdBbJhJc~ghl z)JwZ6LwlKFlT&2Z9^B|sKx?g=>w(RS;bX`}*mPV8MtnU&zlz_rTzy+Wkb1d3^7laV z6SMS(W0IYdvR2lT&5cdc*GyGbGmJj@eb7kk+@Mv8z5Zi><1x+buRM13Dxz>5wUc6y z;l8^?9VK}*-pZdwZ?LTh7{lFo@NxM(Dy{#zCgavQE58A1@G<1TaJ2P6srt}1-x|M~ zsP!HzW-a;K;8LBx|24Hi$<5TZ)B-e9uFKtbd&#EaP-U6-{1d4^S@v}$F7D8i(PSh! zB8wVDW~oD{J7nrg?e9~d=#1sStf6Db<1F~b0cs1%vj3#@&kf{~&@rSXbzuKK)c?UY zInNg1kA!*AV-I^Z598T~S{gFc{G9yae=V9EST6YTGM(n-dgM20N&&l zxEd;dfD4G54J5W^=xAE7&DP*nea5J4<1rb-tkSf-H3nZ`Tq*dGs93 z^=0jpBy;;sPaa3<;%t>*fz_XR2WMuGQ))Ix*J{hv*?;k-m`nyuCe%y~A;>mDTSwso zYBqT*0WGkky~DKW*3FjLfC`q=huSDFk=j>rnAEG*%&&$;uWCOp%pV_56?vK}db0Rd zFUOZ;zwn8v)yyAD6w8fd@d5w1+gqI4n~X|5;*y=)gUJ^CuM;s9-J}^Nnc%asRJMe_ z9>Kz@ou=#NIK)8F35G_h5E8Twg3VnleNOArZO~f`gzs+;x+k!f1d;gDksD6 z?D~Uk`a_YlbABNd3yM1BF0kqDQRuNvdDTNA>4yPrR zBc5C-ZYG}=sZSMQ;hq@xO79gd~u?)Q5!!thf$cByIeevUelOB%galXeI&Hc~|1EHx9XY-=ws+3uxV z|KGW>GA#>ngG2g=V@PDhWmTE2rxOliHwQfA1}9BoU~ZL6XN*~t9F3fFuNn&9P=*ZW zGuC-_Jq?C8igu1{`0Gd_tJKDpEmbH$^Ho!}Tl`frlyb76Cp8Bb4!3*JZ6NiZ%lc8d zZ(=QT40*3keR{K!s$bU1{bCaR%AWO?d~|wDAbT&=dbZ!7hS9&!-N9M1Lp1Z-vg%^m z8JzxLF}+WrA1#$_;Izu^qw=G?qYuEo`xqEPv^JP_Odg=Bso|i@h&;z#9zP0gy4Dpuw|$IEME9V8y@XGL-PTsVS`gYNgWm-_ePsmx`5kp zyX?=^n}XsL7}58}OQ3COSG^{nRc(HGWP^Ht;E1Buwv1@;+}!m==q_yr4jFXhaV=Hl zNNmc@dCr=o+3h@KRcF6%!^UrWg1R9eWSf+_a!Vy@_%$_RM|Y2UG5zSu(4WfvlI!8I z$B;&}>DCA8xM_LUhSF=@yY6v(;byU7qAVBb8~sG7chG`lmh@xD1E{z`=U=+TAAbmS z3marv>%jD0;Fq$k9!Xz0AV_5`1|CC1P%ug*I!`>LU7bxbyALXm@3!=SE5y1cs-F$$ zsrn-)qGKeY(zMPX&XnIXn@V_5GrtyIzb(we%nQUS8F3zif+{C00*w1AQZK$GE#$R zU3=nolqZiT{%5~5AWsifI$H~jZAeMMh?~zSHW{Z|liCsJNm%$^=^=F$B;1PW5^RO z>SJ}vskxU|pD|jhkJWuA=DEv@=KmM*nHwSw}A}wBp{TOn|B7s6ba11Gw zPc51cr1gCHcMH2i_UEZo8~A4*ewlU}*gf1zA0nw8Ly~a{N0Ydb)V4XnD9&MfRKg>) zXCY%-rCH|^1Jl#mSDDELpOXuIrqc7AK(QH|!{Yd5&6`?Xa*{O>Fppx@`9A(OC(|W| z`dpr5U4)?wm(SF!#p`fGnr>wKqhpA^`hn~*Wadx%)bvq8XTrRo>>vSAY!#T`I2hP` z-DH4re|aR89J@uLJ9>xBs&1+GUT2-?A3KK7%pF7K#Um3QVt&YFcn`_U=y}Qgn8)Et zJ5HpA3}>X8J4K&oVAB68Tii~xtTMXDzZtb7OA#2=wb?MJJxFH({xhPKC~tnWYF1F& zb&n+{-?^MA?2Qy)RsMZlE>w=m*w$ZwK1gL22UKpURQClr;%vsrSIi#^=Lz>nye8=C zl#TY;RZAXoUH87_TEzr}$Q#sHjTjBy*&1ks46SZwIfryX0{7kvMC-4-XB3kZvROej z&`6ko!Xa{hcPdc!2Fw~}ZP6fA~k+KpF zkKBycM5Yca*nau?y2^(u&L=^Na%RsseFN9>W69;p7%T_fi(i?=9b}L`b>k`0aOjKz zKke(>Pf&=2910{ z_y~BCi5x?8Y64xTV!9vl)i=v0lz$=XIK@5u&%S^BZAM7zQkvbFsE%Hm&0L0K zNMd=NgIpk-=tohFoy>FiXDd#*)+`-R;RDMDg2+B-7ImGt(*JWi zp)Dey?-CeleU^TURD*FJ-hguV0c-N_%c=vOckA6Yzv+)5{o$#A=>3ttzso67v7kcu zZSWm5407vnEr#88KM{3bM z7y4(gZ?{9a6Rvf+wSR^8%hWTCsm7S@#-8+}F#l3rGL-O=RF~q69>y)Y?QTI22M?au zxgEVzMs4y2-&CK|KZcBV9YfwGQ2&}_@3$O7s2sV4bRkPf=ut(tqz%#9i403$nX#Nc z%Ilp*wmMCzj3^9S&AN?T3uqo`DB1U#KSas?U+leSS5sTu^&Lb(q$ns&K)R?@=}2z^ zB7&&&-dl*&5Gg^a(u;s}6%dgoEdfGNdPj8e??R&6}|(W$<>C*3v5E(D3YENT10H`3vDM zWYeI;{islFE~gDcYzZys@f@uMp2KZfbHd4Ah~nBoNOJos3J~T2#Dle`K|0IYb=jgt zlhC;`jXlpP9|a9fX3=Z!i_2~mm&psN_o!7yrgzDrdhbtFi(@yOQf(~fJT3;+>O3`{ zQZV-xigrp{cuW^HA1Igk-ToECr*Q8Taw3_0|1acF`6)^k>A25-6f<-nx8T8Q>o{A- z4b#sckjI+p)c@($IKOy$pz{OhXKKioNa6k}> zxV87W#O~m|vq|=H)EUjopXjzEFs33;bg6^ih1@6i)~8LAF5^|4J4f3|`u#mWmXlo; zKk4To%;y_j17#DOfF=3Yj{>ejQ2{IBe<=S#)->+{+IMSkKRPiO{$BlBgj-nJcoM`%Xn+v;hi zyGZ-W3LdrKnoWeso!$d(!t1!!`u{#K;!Erec;QZLXw(}1v~`Hv^MIF?Aa5x9XWi*| zDSuh9DAjh&FLB08g2&5Rd&SyuklfMwt%M}I+kmj>ug7gb337--6xk)DtOgx6#D#;? zf4<7SSBl(n0NyX1zmQPjkq}!X@Xn1IN2n9pQRHbymTF?P6iIIIU`Nc0Ji6-DzF4;{&3^9ZKJOBX<%k`RPOCH{TB z*`ds_E!myNZ-Zoh%ICf11?qku89&Yd>VBEttniq0=q+Q;;H$WXVK z^EZn8T(Io@NNVmCJLfcqAS#7b7<58gLG(*0_ommUycO}&mZ6C(XtY`1*#IoO8zN~U- zZm|pw2(mN&gqV;lzrU~XocHAnRREiHdFHKR%GNQJ>qtkep6BUHVxi>H#^Pzo>WaUQ zP91DUa73wMXHE*kaaR(7{MS#aM0PaqXPp9~knpwyNeFO0>b%>EnczuQuYcTP`J+J7 zgGe+EeQh}}93iad&8{>Rb9-0dlL-zVZ=QbQ!UTyhC;6+VD2VE_A|WbGwji5W|Hl@0=tys&<;;W#D!m{TY>)K4;G zZ)Bfqtgo8T)Uz$ic1c$3-F{UKu(8|QY>l7h!m%xtPFUyx>X@U2ghNWHJf6X|yD;D} z(ckI+<11~bh_?gpnroT*n`2uX(z7Pslw_KHn!9#<$SR9;CCJ+D&Jr(54IgB|bL$3d z#r<|X!Z?rGMH4n%eX40Q2`zQpac^E8y~XV(FIaFyx~)nM**}y=K$Isy`w_GsLHiN3A3^&O zv>!qH5wssc`w_GsLHiN3A3^&Ov>!qH5wssc`w_GsLHiN3A3^&Oq#r^05u_hM`Vpib zLHZG-A3^#Nq#r^05u_hM`VpibLHZG-A3^#Nq#r^05u_jg-=ZJMm!YQQ&|iNcq~Cr3 zfFCg6dG*zyVQ^VrN(EU{zlOO>u@c|$20Jc==M$V$0>Y~2d?A>UqqxCNxr<07^W@iI zT8mp)0ZCnf)Ros#G^J>x=g>JOPSo<7n`bk!x@?*1MQuC(>|{XQ%LRK%i?*JHVn)y9 zbfnwN8bUAMA6p#c>#Dw$&CZ|NapnoN_vJ;D6n!E0uMrqo_hYjS8eZ(l_%m<} z*E(lxHBd8>Ce!5E=n}1Ftl*FmWj^^+OhtHS1PlTqRAvCA$r`25Dqr_^WDZpRX z87rVdV@UeL2W{U(3+5NS;svfaJ}4N4}5)Ek~q0mSNB2BN2K#o*C{hNK|TUx$H>y0MwU z(bkomRk}3(F@Z*Qs|lBTp6a|cThvBgHn`CDv6e0N)h*Q8V_!8Lp9J|0g+3*JK_$xl z%UaD-Vvb4EkLjA7M)be9QJwpi^A|#Yz_}LV<*4+Pn*M!IyK!sHi*+**6TOi64*9Ax z^yzwRpFOj@pHL_`j`%$Py!opz_UcG7F}W7!o)%ghla|af9izH2XbB+po)^jH zM%3c(Paf`{Ticc$pZ&3whR96_Jbc^JSDa#pdeiwa+_yAgg*tWgs8_Rozpa$2{A0fl z%m-%AQt-00<)Mas-163%dFDDt6J5#9DqV-RMqD@#Z+5Gn_R-6Cp3V8F40q3uIz9m# z2$PUlAj>@ExJ zbIrbdNum3NbAt!s)4rGM(SER73S`L33(5LbkIW`Zn}g>cXhpdau3E=d@y^4%nW?5y z3*8yTme!rpW`tAD&5L)L*swPSla8w1N4r^!nT+wKsS6}?etB)6+#E^k{XJz3%}k*y zP|?PBl=0Ss%suU2?rC5yCZo*#Xac_wBrL(vH@A!HhP+qI$XD{inW2|6U)4aJ0vF?n^7r6r};YC zX7&b4gpy8Z`N#XBRRLUgccgC>1SJvlV_sToMN4d5x0Mj7coU5s4%el2{tKb?>_Q%_ zy+eMN`3t1DM9N2G3a@(P1XTEaft6w!I<&NEtR(SnYrbmw#fr5N)B<(|QZJM*^!yP% z%5gK%ev;Cq_4Wz+R#Dm+#RU6ag%S0Yw>Q6Y$L0u5)%bA+HotM6xbAC1t0o~r zXeRD{I$p)(FGPjoeQ)|Ybari_JK?wzqgeMm!EOV)zCV>pXFNp2zBF@0n2E@C$Mux- z8Nna|;ZJEOm34ob+3ulQ6o9|BSR_-LAqfNF3ZZh#$+6<%_NdHJoyX}zbB^d%23c)^ zh;t28T#qR;lw8@Al8`-jxJna$%gb1s=*I`g2cNJ1WuUwqcbU$V)%4a7T4KJ>Ssc#e z!gTA(V+sno;h#C;T?Ut}C-L>=`qsvZ@zsih!LLP`mvdr0rZhC)hA#`e66M2p!F3p1TNsNVZVy&d`0`3wO_NC_pF^fkYkd$zMoes4Dr? z3IP1AeRFzBlqVLpdacl=xwpX{x;=Gl36jY3H-*{H_;c&KAq)gr}>?0dj<*n3-Rpr@jER) z%^)JTCzGIs_T=${klhA-S2P^=|8;hL9sY>xUsl9h8{0$uK5WVPynTGo1y8e5y0;~8 zJLaL}@pEVAv$9iOXVetMk1TC~7K%dA%Ro}H8tA@ow(+Srns_`>=~zG;sytuC#3vZH zl;x=-oOZ^Vcm5Td0{_|lJjNd;a$~k#6B@O*Dx&!WFH|-?;Y?wib7W8$@x91JV+c9X z8WU@Xf4$Q1jZ!RIDs9h#;(35eduK?mf<2CHRGOmGy+C}j+RLsfla@KnR_AM23mikiIF$uaQ79wlNTBc>4%L?J*KuFJlqW6=R*AR3lNj|o#i4CyKmQO047K|pBEenfYUlOfyXod%wJhju!J|or7cD7$h zC(yw`%~(Q5p%20dJGqXnJ|S%aNv^F8;CcD^YD(tjPQl@$R9^Fw=qv}}ShyuFO1+`j z6|1G+h{zT|U09`n>+dsXMSEW(lZDIP=CIU1sn>N%(c|xxaJg*g{q_v5Q9uFOt!7Lk zEFRk?=w{I#^LzdlcEUtI(NJgd7YSZKD-(6SwYC6zJ-etqQ~RaO6G=%O{>ZZ*6*^Kb zN~#F8d%19^sBY#ho}@nUZ}r~Qo#LOu7ome2i<>eW^94M8r=Jefg%|sF^IoWHQWHEY zVcZ?gGk;`QfjzoRLXvD0;|uY&+tomr(52YVi*%8gmAeu8(NY=HHp`k-H#9OU=hd+j z4K-OXWL)&vN~iVA*LjFZyii)e-;%bpZ2FKl;m*sfAzI-Zyr?=mCW={P2X>$P z>Jz8ij5nS@2R^KZC#|sgjQFJl{kRuIy?p&;K!sH=SJel0|NgwJ7hmaU%Vo$M>k5Mm zGd+km3>^3O!%Bi3&kcPtt4KS>MEY2r<+z@9JA7Jf?&BHX-n=X3gqxZXg0)mib`-Z{R;HYSsc5nt-QaAQZ;6RQIzpI&;s>kyM zsvh$MvS!HMbCJJrKPkq2CG-Kk>65hOh2@0~QRT~qLGL0vnY+IY)QJws`LgTJ_pwLD zMKMFvG^L)c#{*(%IzSP6Ykc}~((=y{!ooVOR#Zql!rL}jJ5(y6ScKIJnstkTLPYMj zA)~pw91YZuc>>c>7jZ_bkFh}a^L4#t#k$K(XHs7%jHp3YkV~@PkQO_QmdS}TudDy5~kmOb-| zn=<%U#4+h%u9I4kdxWyoGzCr`!G|MP!V#(a$|OgL+5Nwe`#=T7YFv5APx!ODHP_Hk zF3fzl;7~c|CckASCfd#E9DnA`^H1^zC~ok+$P}TV+U*E+nf_de1EPlebHFJAG(huk{kQ9bNKo!RRg=`FJ{UZy3B&)e;o~MfRH|d{d8!r|O;hvGP zFC7}4v10?P;aR$HXDSBWFG{XwpD`!?GToQ{S`k;y8~akD-1~`+z*#t3XOdrDM6br9 z&#H{l`jrmr^7sI?!T0x9TUf&Ftj(+U7H^N31@=e{L!M$?<9`d|*uKt>4b_FB$WweH}=AwaeBq+QPFzP-T6R$D`-Rl!TA@G;IC-NvGvrWxgv|@ zkcG(T{v#*2`^t5;DzU03+1~b;lmVL^wIPAWH$I#>!=5`WZ5x|&9aDyLH_2=+hO^qh zGY=&!1cX;D2YpFc;mtf}UOSAVSCi3G8vFK-L2-k6jryLitMV6^^VpcKt0fTbQb-yy z&cA-0awUOr#1}%%0klM*$^p&u2YLFZ33z0h*JKj!1(&u?uIic~c}r&Embs>gOGZ$4 zc48{i&NZOeiz@)oB#2z_;`=Ex}p<%G}F}m{N z-r%zMOAD6jrBnDlRQK{6!?17XG4GESUI=V{6M@uElOsP`^Pdu{tb3T1$k zz`V_Uf==&(W1EXRup192y;n5KHez=L8iO(>bsZVrIUk%E7&s5Dm^m9B_>V9IQm-l& zLS#x?Kw%BEteEe=FDtd^Rbu%QjIk2X(r4}zn@TbjINPj=xgp_umu5wt|7!*8UHi-T zQBF_H>9p=KTl#)f;NfrV;q=Q}y>^GGfC9M0SbMVG?)CggWi9C{_`RO;%+@e6`Z&qA z6Lq`5jWT*d$+pa9=8K;Qi?BeO_Y$Wm1yz4>f}PAD`c`;e&4&Ev@BV)byXakYNIJ97 z`hJy(HQ2#Yvll|eWxl|A+iWxkSBH^j>tP73f^oTD>J}M=e#nTH%Xrc0Wme%~6-Bjo zMwc3rA~l)R5cQ9`?<_o6tNaW3E)xT&`wx^^6Ja-wUJ!^J?q0tF^pu8s6obF6I2r3o zSQ^=XQ&V`<;}g-hapL$E8?^W+;>kCsMsy?kQ@o+p#KMiY65T!lz7>Oh^mEb1hrU$y zd~dbYE?pX0XnF?-CYkvMxO?>t-}oDw4_eSoFUELn`4%j*7RGGvuiT_PhvRS_hzQrG zKYi3?Vqwr>Y1Qq&vsdx`bE@Q7*E9*5w4kKzuCDN6yClC9i2UM_L4)n_;RMjR^lcRA ziiXj1!i$+qY#BrDeQiKq{Mr1Z;eAEc)q>=*P2#Q+ zi{}M{U}xoe11TM(NIX-^M<=Hwo%1x0{i1lTIfWR8%?!zgASV>skNG}sKAT@it3ARd==t61y`wv|R<)GFPNBONr@`#%8-DM+T}eDd9kW+x-moV`{0kwrz*$wX?X$M7 zM>zVb?@pwH@D0u8&^jYWeo02DSSsy%ww1M z_{^%<1M7X8cm&6}X4NCoBn;WVT#IrHWj-;G7Uuid=fG$55ts3>pMtALVkK{bXaDP~ zy^CE2Oh3(R(SksI>MVAS-m8*Xm=q}*<_otV;n6O?hgku|RJ9d|eNs|Y#??KY zzt%d$^76tl3t_H}8287OUDr%(1}FBI^Nbm6tAMX|+?xrzE?f$aa-}-^GKF6$HT*uX z;kaBEILCk1FE5;;2MCKoKmLV`wiN=cB2~y^u_gmYhqV>+EDfv;^OB1&)iDF)8X3jl6 z$CFlj6u)c|ReprD94aRMtrX`Gxo&Y#J_XTQ9O1s%UFBFFW@V$cCRS! zCggyBIhfdEU$;We26`L%nN9Vlc77!~D0^cS;g~r6Y!ij?yJ%53-PgVgIwHoG$`xOo zG15FIVs?gpG4juF^>bA~zcs8hHj%%P2S}lB$PX`-{GWsdRQg#ODI8$E)jhz1_%<-do z+5N3Kvxz1nQv`7w?EWB8$mixd+od+g zyifQ9*Br=F{V3~}DN4d>@LXKKcy6N(^Jn#1cYZc{U;MxuArZuggglo@`aY%k7=<)IPa_0x#zES zKRh%#dsa%W%B`JI+5r9tHC`zipt~d1vMah{SFdZ@ht6C(tT&yIAJF&`*fZ<<<~vg- z^6@@%X8P02>D$(dEyD5=0{5S@uO^&vD&WW5zRh!!jX!S{!XO8FzM$s|dcL6N3wpkw z=L>qipyvyEzM$s|dcL6N3wpkw=L>qipyvyEzM$s|dcL6N3wpkw=L>SaAmSaAmSaAmS`T@t#`v_hj53l&>=vf@F{P3>dcY2aZ99&>lQy(v_XP$0xPm zUF+`MJ#@IYbm;YEg79U8O0J{KSg6a6kenp8>mc)Gz*)1Ifzf6et;Y&RAE~J0wsJ~| zS*fICq5cEpT6H#N=%2ZUdBK6lVaKChes5vRs(j~pNY{3+vn|gP6EQ9IkLS^S@*`3S zd9LirZj>eap%oUgrRS<6SNI>K^$5Rg>$827jIZV8%Bhv7R%o?U?@+r=?MS?ZH#KDpxy?WD{z=0aLKxn-}cBuoeYXEOElw^tNv zWUldF`iW=0h1^e7|2UB4vxr-?9xS!KCG)LAL$LlbPMTx;BvXSgp~Wlh1w6*nb}u#r z7`R<<2T8sN0}>HdLW#e3Yl4a%p-4ixYp3xW2So41QVYGqkgOiLm0iviC0EK<|6S#l zn9gzcJD9CI*Rl}YsXhUxg?@B$a32o$$gf3R6NtT0&dl1BG`ZL8X1W!Yp5K&%EeSAw zTSi;ltWB?oJu*QuX zUcG7?-M&QI82Sj`jdPH?(i2vMDk1)D;tlb4C%-rBJn|1rh&C+i9 zoHkU66{Y(haG-Pue5fHl?I+e%m)L3LQjZKfZDb~7*h_8cgOVm&U!@$U{kRiuLa$u6 zP30+xNzQ6?T<^&9$=OBzdj#vB+Hj@z&SsmJK5s0lU#8uVF*n{(BnAI$^8Zg}kKWnghf3sJ z3Ce+~u&<;|88h8IpcJNIj)y)gt@aEvSoK^MCIt&mcqLx3h`_l|$dqN;rHL@n1_mW` z1ej}F8cE81sln1++TC7@wFE{`cBrn0<}9!QaYPUN;%ZOLF77l32ZxT#4=1en^msns z4~;ny8NNp!JWTE)2_{(R%!ah%#ijZhn_5UZg>9X{%t`cdm}wdgh?jaIa*Zfq_&Z{C zf3znGS4dooAEq#Vp3C$R?|z-Q$)B{k`cEUiih2p=gpm{!&9sDiKz*ePE-Q zEH`|xgNBmCM#TrMj&sq87|UQXQ}Q1lAmCF--gv{{V2SvS+xQ}_Sce`?T$x96pmrbT zIk~d;+uHc!eI3eG+|o%cWz_e+@R;>5evadMn^|sl^@?2jn*_i zuw4`W=uwEZ(b-Wd6awNynQbn+QiDK;i4PvPM%qxW8Hs9-J&EOs*bZ)a?HCWo%I*-x z9tAG)tN?F>PoC57Hw;7Ps0jM%K&G;T?2YNHEZN>QQV!!D*ighRTJnMX-d*1IQy z+`m?UVb!XqqqZhS)LBPx{V_3V+9&&x+EnGYtb38LP`FsW|A%Q)n)8`o4ZXg?>ImlN z<0SWM@6jx90mCzS_g2V3KtsWia?3yI-lqj}({0_$i4Snw1+Xllc;V|+x?H#BDI zkI+O*eNWpXos-cz%T6kw`^6e_@_I^{gsijl#y8=bYVy40ho7p515vD~>@xLgMEgvk zQ%$@sNjwWk#v)i*=4m6~mzqgzmWC)O zT+p7dEb%Ez_!Tf&TQ0>t3lxTYl-wGueLC6$n{BHuUq(B`lx$nvw?Rh4uf z1oO+5LW7!|A8mE;%emQ52gIOO^9vC#XV>|w?{0B^ctT0p9boW?q~CYio4$J7PM98= zUq>K+jVNP6+Cw%_3SZIt+^J_Tg(kz(>3qCNcjp%@Zh7NYIe~ZLfmA|4J;nG@+mtBB zqZiX1KB=X8j?7()PU2;~#Hq&SB!h_|?^*7?IGn^w<|l!TYEB{xI!mhbLDTmqUGhU@ zmKBYO#+vK;Z?-7Rq=}=S*eeEhRBV=#b1wipN$bB4;GbIq5I~GDY(5k0dG676UH8p5 zXZ^-q{BG6SL{-MsttDP2EDb(~o*1Uj%XrsDR!?2Av!VJ(ONA3T%BA`jLX9pwsja%) z^XG7;7S1Hk#ekZhu7klo^GIU1RMwYMEc z^n}i%72&c{iM{Mq)ZIkxy*im2GP`o5%iEc^-MO8Cm8fqS4mriXp)Jn*iulgSIBEiR{=HFTVT#lTrHrbY5UKA6 zOP-h_81%G0C$LoDLV)7ir{2rJ#AsS~;&9b!g^JZ(7N#}dWH$@y8bGM^@%agIwtPlH}5s5hdV@69C zM!t_#)92};a{Eq%ma4CA<|Is1N4MyB*bL}~^!szE2~!o!5-DY}t$PROZXeh`kROJ( z|4ZbAnyX#2U78oyYWyl{+SPyMYDF2G>$LUvTywy9k+qcopQXBToqF47zZ2h$!Jhsb zLP3GRl=1U1oyo^5r9jx6c<@gkxg%)NhQt~tqX?@-wAs$IAyr-Qe*%gN;r$_wVJiD0 zQaw)<4FA2xihUu<*VD_}ir#L*Q;%6$ZD$W9I^l3e)N6waN(OR;bo>3DH);40&od(9 zx>2om;+dlu)$-O&boOX+5I+ z+ikYQtn5`QtA)^_(M9_@B<`eLxF6bP@)si0GW>x8P0VLC{|nJhBK8`&{e_Iv$)d;f z`Jzr2b4;+%r7HuM(g{=n)M*V<;?W`3LPX*IP<%To{lVnw^9QB=V)SzwKez3nDzgo9 z88L&g9UWFx4-Zck)Ju*$$(JxO-&Am?CB#^Z&OVad3=vVy)tkG1d}xU}&2r6Js|p#$ z^~_fS`wNZEDR3=V1+wyfU#V}4w(LOO|#*`?tF>cn&zitqcQ8@c~{6;P8L;m0Ew9 zWL6J-Kx<3Wb2ncw!Sr%yN?R#6&SH5>LC)|t#tF$M?A0Og-9xVroX<=}~8$&ak7=RO`~ z^F=(#5L;X`O!0UCe9~W8 zjG)T5{F9`z&gQ+dEIUE-R_EpWgQWrmQDcZVNzIJqNt8Cwx z_6KEDzLZ6_3k}~F9<*!hkkFG?ymUV{8(;ByS%g@UL`xc~Px#5Mvz1E`NMhAI`M&41 z)Ard)bl@C*Ze~P|)1{G{bq3inH2G$_WpUfE)~vfYG;0tj)KZF%&mFBtkhW!EBxKea zLKHo)xQ@L39CqTgYWijAF>l zf-=qr7Zszzz;owYiPtYqCz z;zMSJ$G4t8Uv7S11`JCNTj?i+EFOEVdmkvZ|8c!G-)7t9hvWQVd6eAu;=D>PraxL9 z9a`kQE6OYfB`^Rlgb;g=p#@|!#MYA~vFwZIl6>DpaZTJZtd@0hh2&{vt#f^IUCt|c zRU7(REAAu4ibs+K#=ln>-QxM)L+U}>)qSqA;G4V$f?oq56<32@XXvJ!;oRa+dxxXs zS7ZQpqvPRU$OF}5slSl9-@lz92xN?ZE7OWvgY9JjBahmWIXLlf+IMsMUSh!0l$X%6 z#9=Cus$fcNkvNCIoBSp0^a&p~gX%^S&?69m8wL7xXjVdlw{31r;@kSykC6_?C#SjS z&8f7|MZx{VBT}#YDEsF%)&DH=F7Mv2oCj5Zl4430bXKjbk*;%{^z(CfvG&q8_N5m> zq{(WM*-V@Byxx((;@*Qf5Wx+Cps>oNiwJ%Ts|h3y$3KoD3?hWkIZvk zT|LYN2HWei)hE{;Be6EFsXl%PVzO#zG;pVY5l*TFQ9#gs>Ol4l)gsp4+Vzx05yz!o z^cSKlfnC%ViDNwsbGLLyM?SitVc5q$dF|e`SsVKvVy|I-42>dV4HO@xox79&wNcK8 zwz?*&p5!v^Yr-wYPfO$5q_cQ{pvqdRKGN9XeoTT9v#`9L7x}jr9cR~D;MZWe8}r^c zqIH37X48Qjj3W?r_^*3~8fO>k;0t>6g&W)zmdnwL@s@)w6v_7iR#C!l=G|vLbsB~Jvp$88~f*tc|k&MTe)Tw6<5fZ&DyY|_zy`8LgR&Ffi z%T96hCXX>me}_((deMnhF6iPEuRNEv>k9ZWk0ct>4>XGapZ-8 zUpRhkmN!JK|FDPiYk~N)FXjvQCGt&-BBhGnFB?VGxbMTZQJGWid&$RzF6d}_pb85E zKfYZN3|xy`Q1duHw4bB=oKMd85L=+6fmX^m!sY;3qtfrll1j7{C+QCX_5)01Y-9J+ zY&)K8o3V;o6h`JOxJ*KDpnnSbr=Wid`lq0O3i_v@e+v4ipnnSbr=Wid`lpA=pnnSb zr=Wid`lq0O3i_v@e+v4ipnnSbryzg&7t-{D668-o{uJa-LH-ovPeJ|^QmWOSkLDwC)VMC{$r?2C(Q(G8~?YkM|}`u}``>6oZQDSxolLxpeLXX{V;& zp3Ankba5XclnK3-^RwMcTSSfu8ul$q1gYu1Zd$#VTe z*|Y131b4WFh=of&*A_0fZyUutOslh^W&m8t2K$|hxF;2@RP>TwVHqm_HR zGl7ZqpaoC%#W)TU0`ao~L8Gtz@_w=PD*Tb_Y^V1Zwe9@FQtk&Y7wjo5+RTaSP*=|B zNVl0)_q)|mYo^cFRV{q}+iGeDgne!;mO5>VFstvIXvcpkmf1|3CMx8MK=IlIo%H(m zmel-|N_O6%rM=w0Sf&7&83FwJ*QfQC#VOtI7;KwNO<#>K!WOTN%GAjR<^=UoDe6Yg zX%(L-UM=tm=T^H0Rec^N`14t_CnMI@)@;toqRwix+I_7gZcY;>m(4Zup|19A)CzY0 zWFVZ!Nw{EOx}!NWc^&D!nbJ+nZ+AX$s>RH&?i>)0ndT5d2lgZQT!QQlHjtQdgf<@g zg^tG33hB#>ImeE2ZV%07t}wCfIGlw!NW6V8N4K$Fb&p{6Hfn0hr@wx3IWC>lNSSGt#A-#EX}=x3aQxgT@9o4&KlYJmMC$Vb36+4lmjR-> z0;_MD(-2OCSYx*0I(xa75^{9*5nL^nfSHi=bJHgR=*5G93Gyir zE$H1+Xi*QAHtyKBh{}7nlC&Fzo|7PvutDRoHAuWYo#7A~LUPhH#RP3DmnLwa&SrOm%xC zEOBqQ)0)i20m3}GWFF64|K>N1fGUd!4(`R zR-0Q;#&bnx=ezjMnJ{YD+e+x&RM1FW#ox^Q()W3kjib^X><+OhIbcYlQ24fh0xJI4_#rZkB`u1J4p2^O36Amc10_`e$FwI zaN`l0QsKBf;#(UqGPHM={0;hEhX|8%O{E{-7$F?O4#b^DluB@kjf1WVY>;o`uogl0 za?R5+=9t-~;Mg5wYtbrx2HqSQ`l}ZA1iEk%Pt2$&zI>ijzSC=VgcP|yb!pRqk=KOL z&9|e??y=Af|06=+t3kU}JJ*DZ`CiM``o=U0H^hny%$43bQaf<5BFy_=rztTu^ zb}6p@OlFiz-tMh5Lyo(aPQZV@*9RKP@}W^}xyk8C$t+8+bN-GS+8p{y69$ zB(Fcez-{q*R=~UN_wHj0l}#;4J!4`+nTgWTYoaUWg9E%i!r7WTDT*;(pIk-^^E{uG z^yCt4d3lkFBa0itD5Z8G@A)*HgyR^6QS}7B!lDqxz3G1@nZNZw%RT#_N#@xftOPF4 z{?8
    =xs!N`9mnSV;O(c|Qy$R9BaM~GF6zCz!aI&=H{^Cyu;uT6)T?maLnMkwBM z{^Tnm)kC#`4FPg_>QG?fD|I@O0QE26+mlT%Ug(BGeLgfRe|&KNz)~v4y)+@*u|5{s zbx-Zq1HBD4ieD7#6GyDeHt6#AHC!dXe3ha@#=0bG-px#V zd0Tdf*@Pne=~KCj4{D}VxCbxGMBXur)O`|Z!TG8BH8afsmx@w;iR6p|8~Y07hNXO# z@#KpK_h`$6G7WD;@y6-PUJgp9eA>fy8`tAE3^$RzZKn0LV_dR6+SvKla8t&m@Q0Ti z&&&7t8Y)8|oAvxfm2)wcFQ$SG>*N!~w!60-R)%97Meec_890X-X{H^DPKaY)xC8iQZd64PK3QKedFpvna}&vtUg`akpuMc06tl~ABT2IT~V;Y8-2NzgwmDu);TXA<;#4_2D9#Q&KDeQv1yLcje#lb|d3P1AJFLtOnb zd9LY~4?2qu$b=_o@I0}f8iSoakkHas7P4^zs8^fW{dBouiYm^Rg5AE)`!EmZDvO6LcM$Vi{Xw zJE9byN)_fbDqAj7)m4NR;!UtlNSA{^#HD$$kX`Y>zYs~H)yjZ%BQ62uml{rQeVx={ zl2fmWO*IQGU~NoV6CB4bODwY&8I6?`zq@ZxckW5J9VaJ!=R`$7atxts*q$MimxO{qV8SRZQJ+xB`zrfhowu_V&lN4U4u;!#>7#0pJ5W&mg7O!Xzo7gDXcsI28~bBItU%k&}qFT^Xs z;96lR*8}G|f|ydT8_U9Ku?DQJ++Z!SsxP-UyW!o~$&-D4!wG1mn5A)D0k>jk>qvwr z-Tra<;H}*_sEm{KkDq)yl)jRmj&-Fe#(=_!l6~$I@{hu!%gDcw0qieBh9G_8v7P$O zD%V>7`ta@>FZ5=!+xm#d>VoxZiQ5cjWw`W&lZK_{#pJM?okZL1pOxBZ!~#5H39quk z5mG1~=$|bckj@Q9v~@V<0gV=w3#_Avv*p`>VhhKk}$$V@Q-+wXFs8|d_{zJU?%Szi-x;-p z(gK`ewSsL{I?x-G&(bqQ0>7`QiUv_9`g>NNLAoG4`(b{k<;2B0U`Aa_x)wpt?MC8y9^LkGKBijZ zZJJMySyrg8W;snJGjWs~Xp&U%+(4jLU*Av&AEtv_bQn&(# zGSd!me2REebV?ZGCRSvAO`WgThiTw>N7ojoaQGMNSF&m@ocY0?uYa8%&i{^ZP8}X- z_+*-JcdG_+zmgU?g_i|Qv8!ot>bCLMZ*b)IKe3v_mnrNxW^KD z2!0mblg~?EYwcfuuhL-`EyyjC$nSz{Q)jGeD`rsWg~s&164QBYwz1wxw;u;_sdf}S zt<|KxTTxe0JYa6>(R=IC1^&FXUbY^JjW=SSj-0DKWK(U_;l%=v5V5po-fgd>T4U1OVnd?wc|Rrywi0-r;7?rB{o-Kk$N z!*0K9LF8J9a(CM@NuSDM$JZK!6_yUrxVay)pGLW}m1?Vw-cOilFC^89eV5}GzW6Fa zjf?x_mM!C7$mq_?zmVrZcgRU)wbtH7WP|9npAT}@gN5{U>5O9q^0RZdlVPuzEVa%r zd|r#oZ;~G!wBe{cI$X|EbtaEAPg=tBHxM@0CU$P4`w54xdg-p_w-4cd(=Zm1u zNQq!sD;FcSd`2iW*U#gb<_pcP2=7a)t9hwCk~-?taVpp6-lpM5CQFo8#HnK{OVqwo z-Lg;?4Yy<0GxnvVOklJ&jIFG1zc8!);fSr1s#0d$+)BA*(1XBrN4K=V+0(NLwxT5` zON<=(R9=2AL6^nE6!IfPXGv7wta;J}RnX70Lvcj|A!K4g;ZQ^y&76XR&quiD2>*slhI+rh1cgt8!1!k#>94^qA-WFc?S&Y#gL+8U83ut zM>$@c{3X+dt-+FZ{-!we*QxA{ch>eU*%xTtN;J=ArQLQ)9%A64z0RI=Vbv#XcC$I1 zW=_Z72w~&&sLAL`)=WsY#wsEzDa=b^lJ-E|@J3YA5gF8K_=|N2oYI;!9 zgPI=H^q_kKx;LPE1G+b$djq;Rpid9_^q@}<`t+br5Bl`r7g_L&Ecith{2~i}kp;iV zg6s{*-hk{4$lieL4anYrlqE=6f|MmlS%Q=$NLhkBJ;>98JUz(MgFHQ$oC1?mU~&pf zPJziOFgXQgWWkIqn2`lDvS3CQ%*cXm2w)om*oFYMA%JZN|Et;%E^6!2F+YDxy>^s?)F(!LtETaYu<9@#P+;i^tdw;q= z-h0mc0i5Ua%=>viujh4Muh;u2@whU`kYlB}D}O&$402fa{^+}KUO^fEf1f70;U+9-AUSP+HXUL6~b0O29h+hu9JQ@I?57wv$QN{6)-)# zBm@8IM?w4b@J*JS)U08oA$E3%GvG;%-=KD-)8AFPET|K4SB{ILS zYA&ap!`>e#p${wyrlqj;pVhc|TydOt{BiXJ^eHTY;9xT2meNnr(!zpQVR>$Q@bS+$ zvKAPHy+ygWeSFXH(BK5LUF1(;&2kt;YZgyCIN-5!tt5;*D_r#T9;DLEB$sDiv`LMF zVyTLYYClESIqsk|se##)#MwC3`j9PC%d?DIg7Biyyb#E$dtVkWbh8zQ^qr&mcmg^H zrF@n>DunNkqSre2Pe426f37GBN|GUjpzBXhKu`CyYeO+wi>t$%lso;$WUba!Sd0JG zUJ%S^Wh-pZq&<&or8-AyOKHJ>-ZIU3*PKk&8H>S5ti>S8g(`6bhk&J^`m zL)Cr#ei{m5X~cXNnP1gtBrQ`uEwISf1(a+!(ftI}_$#uymMj=8*rDZ~(XwaSL>3tQ zKz?pll}?Hu%#$SW;Fp=n*XvbW!+h5#MJrN^DYWe;pbkq`feh#uFZuOM*M`hhud+@g z(lY9dBz=y;wYuSpSJ=37UbsJqoNavaMMoXSzUWTUEKI{p=XvhQ2ml{8>u_I1|6+*p z@hiKfz9B-pdUc(%1h6UpdsQ1#N0L=M@b7{rplg>bPe2kDj&X`!*k<{&zj^hHI$NTb zm8&4G5;Kv_qYd(PX$$WLJpu%XMrXjqZsxR2qi7nSxLsch2vH^}!<6rP@DZ(6QkBGJ zt)c4)NKg>`Khs=bn2m=%$9c*)!hhewpOX@L_gyOD;*TeE=S{(EI0^CV(QCG@w z-s3$Dyc!gS(kLSCYpoV94960uhti;zb{Zj){nrn^oXv|akc~e7^(8!rS6i;9Z!cTm z_L|NK=+;3I@Xs$8QQo=zojNj4zjX(Wvn_`LD|jkpu}I?ADf-xOC*hV<7YF;BBwCyX z`w2+cHjzxfKsq3$5Yth z)Yb*TXwD%QMB)Rae-UGAm37Ao1Cx2(^URdOFDZpTQt5e4A=peV%wqW!Y|t35oD@A6 z#IHniv6sKq-D<_PA(tn`2xcnJ6*xU_`!dRurVHNo-~@DE`%vKoH20@%dgeH>BXLns zVE_*+u@6gh8whKbvFs<`SshL##cdPmjz7$1)wDD``^7rhH+BM|SvUbLO1@3Jhni5z zcs3|MXB?n3v53W%wVz6j7|KYsagVvkz;yqcLP;CJuKJM>|5o&_0$E_x$Z^x8?l7GN z_%ed51aCo%W>$EsjNgiT&_d4i?DcoRuKaslUKEb0ILuF$6HqF%F3O!iWc#=iM*D`ns=LR^XqmzHb%a^t95FLF!5K2vD#}iI8O% z*uxCs=Pn#s4ho)A0eQgNa!x>{u_vIbg2%tB)OLZN`$$srE;D@}1xvP%v%$=whd1%E z!wrqKWbI^NmEpr*VY!fO6dIRnb{LWu@Nj3X^U&MfNIgE-4w<_0;pow6la7(}jyG01 z7!-5f2Nze^5Q|7aKK$d6*)edFiJyQBYs0)K*Ni?EXm3?as{e%7b4vP!oXHE0^H}lvL4u9I-({j*b(b4Z>wK|Z8VGgF;%|k|2hiDrZP4(dt_+ldp9EPr7eTRTQE2w zqr$MVVQkujNPla;0$S2uxN-t27Cg#00c|~B z@b;#3FY52IWUNBa<1#JXwQgGT>5?nQ1p)4TFDu zJ;kd?60@QGZ}Q-z^mp+iodScH$LXrXT9oh5CYZYy*pq)Rs~))TeswwirU!!aqEb&l zy%Xt6%$G>p_g-nzjhO91KEasFurr1vC&;r(rV8-}mpvQ4Zgfhkt>;WCXsfOY1aGr; zqJOJhj{ySY>NN1bO}>Ve^^&DVt>N9>STK56VRIhx_dOQlK8OtYZ^=|TwkdecTE3|x zGxlT2As@7+R&`~UeRIKo2733})jOd2BAC84-mlY-b*7tQyPCSwk0V3Mj7VVoQ)2x) zPvj7G*=KJXd^B+Q(AnqsgF0f1_l2VN!u=D_c;^Y|eIn(rW%fbK35ddxTSOPJ0tX*g zc1b%D9NbBe^tCy=nd7{k892s$T4Pvc$bQ~u_-bhLaAWB~z~T`?p(v*dEo0k990n6VSSGk>^34&Rk{-ii8f!?eZF~0qSs5 z*_wEC0#aYu3r)lgc&xNJe3QJYR zZ+7WaynffAjOe~MStUEW=927SJNJ|&xJKXCdQ#2WM)+9D+n=4B~nHjYgfVw)Q?_b+>}3WR#o!f z=m@N-I{|$Qh9AT%ijK+rF?O?8xQAr78w?CakT!O{6+Pbj>}rv*7;g|cL9%@|EOLc#+&m!YwI|F)R5ellyXtbdkh7>>knFe;5I^lLC**Cfw`A|!PibqO zC?YFw*TZaj>#%EJq6%yuL2(N$twx3q@MEFO-!2L66vH>30Ow2p1QaGY6zT|sRGu3) zi?~U|AShGLoK>VM1+q&2-j<9vWq8>egRk8baL-zk*;a9X-{P<~H-4nH8*a-pI<(}# zCA|W3)8@FzzN_fED?E8YcB|cem50Y6AfhMxr{GWcMGSnA0Y2)D2lg-~<)_Cmxhyc@ zuOLA$X3n9#juD6GK|=uA-XFvGmtj2p}b}OF<9xBYW7Frn6$))X6C78 z=GkodOF|ZHhKszm&Y!(3(+L4J2368qO7v)J(FUnseC0^?c6md{H9Flb6mP$;Tpvhv z_~5SgdiM$FGMt>-V9TpreUN-~YZaEYt;ASWy*ZPJEb2Rid(6!cTSjsyI`~@Nhe4K8 z=X0Lin6ZKX(9k~8^J864fBv~vCgZbhbfsGp+BPIG__5hn*qD6Ty9mi z9|NF}_^}9140JtcztfBw6H2^UXO6c0nS1LgiDVY`!4@YOA!+EtqcQpB`nK3ti`gJo z9rX&yZ-P5%RyP+}2#`$xB(SI)vu_GMC5HDaraA8JlCb}zVX9;Ue&RdWXp`>td+xkE@ikoQQ2{OhQy*UxR(yis_k=>4cK}b&Ub*3j$3#4t71$e zqH6_X@}mzjok7OhWE9{viyM4163GEbF5XH$k70R#Zb)Ohn_a!65mxWS5+QY3A$Hj?rj3q zM`lh7#b^&5hT=uH52b72xG?mgodD}WviGWy#~)tkHB#-t>_HMR-qRa6dD0YMSKTjU8PH#nkzDA3Ayroq&WOGx4P;$zN~o zSMz&UXzvIJQbZcw{_bynXq|pp}3870}JURt0*7 z526ST7ByhyLu_dLd>8?fk$24-Xu9-4xOQKa|+*i^*AL^La^{lpGDzYEu zV4c`$di%(jC)6PDH6!`!%wg0W%j_RSi5v8P1v_Q&2d^A|XQ@nL77Lp$DNr_W&Mj}q z51t^ozHXe6W^PmT=W&xO$<}xwu>K?O@Z21HTFdeHYF&jk`%m6?mQ&$ViM7*%Fp{I_ z_Hk6dmSf&pXbU8H|0rz+v(+*mTFG+u$N&)_UiUm6m3qa2`T3B<6$2p0cYHim+&op{ zbct#Y$JdnLsLAQ|%!w7U-DZkpe@OhzZBBzN#v9#|(j7YkDYkttlTeji#5pGU7v~iy zY>9t?zTBHfS*zsi8r}>SgI_qzbt4bz{@M&tb|$R*w>ZwKD2G^Ee0Yi{%2(2EL4GnC2Ti38Kll!}W9} zrWCaO%p^K*nv??E7f-^92O41eCm>IlDvs~LCYHnykSM8+WZLQ)iOY5T*Q&}KcPi1Y zCmF2(m3%n|uo>y4z&>S#(~_pm_RGXcz)Ju%|T$&_JceC{idEiug~V ztQ?6dHx~tmn0!SyV~~vzgm4!Tiax_C-fkdILjkS-%wO`T(htQPb(%T01npH0gHKyF z)nVLlq$){k<*ZOb%bjg45`JJfRT=2H5h$O92HO8nO0ePBZ2%x!Z z*CKYK?acAC_ffm3PM>jk*a>AsvoK9$Nx{%kAT??`{)Fe7p6@)wga^H_Wqe&MVe#t8~$RF z+DW6EdVc4Bh4_{z*TssR{oZ*HE?UaQ;J!YRZ8`BCSrzMkgAF#O4KJOziGfV{f>0*=d$Q%Ug6@dR+56J>NSbi3Zj z9SOyX5J|8M>}A3S`|pEau)T}w1bZ_la6rk1ir+``y$)!KoyOe_vFmRh$RECRbv>;- z>3vE^UG~7%0TA-%NdrDinkqmS?&{!IelTV}R^gn>7^aDU=bX>04f(V1GnjymZjZ>+$9`Ca0jQXk*DA@EJKoV9|X_fvYDLewWd@O>fSy{b4QHd zZRhQ4bwWs4D9PwX1k;GozA)g~(6?y=>%L~$#Pnw{?7QT-SGEOP( zoeU>CsK@3J95<@~pPyS{fH#?il%BN~_Qh5c)iT4CUOO-}qvGAR6$Q4KH(|v;-FV07 zm81Uv6@RWcR}`JHuxR-}IPOMt`T(Qk6=6i}V>X%@c-!o*;L)K+-T^bJ{nd9S#xf-e@2Xn=`ROiL{(0VeyWvG4 z&SwU`T9v09qF(#io#wrqa{a}W%-ok#{@vM2v@gfjwJ#{;2oeNc5o2OxlT^%b)Oz*u zVO9_fTJ5am zR9kJtsoOoQxdz`Z8!oEXva_8^ex){~16qPFC{MuG@Fpi9jHUKby4~mMQj=%wiP9+&yhwAN0>P{SKSFT%3X4cp}eXdnpj79HQmtR#PyeEw)4)Pz> zNZlotCa0+J35h=#y7&T?ysJremYdl<0o?;^0n0IEMZciW?^FvQ!Tcy(r}#h_-@2%6 z8Y;@e<&0?B)w7Rs`e?2QzehXq`KyK}%T0)#iXhUJl!M6=wd;dN-d zI>Bs)fFt#wn{(ZQdQom%&lH{_h`|YDOY;x5b{jY1=R6*1?D8+Cv#>QqGdYdlYk6#K zeUrI93zT%epTary_5`%ne;Y7GdI2U=Ys+jeEyW4NUsh|c@Fq~N*KebrG`P-=^ew+g zH-Ng*oi}LJa6kQ=BmTGLuHyIdm@?tF(Yj?mkMzY(L%G`%0qw*c(STW z2-NBSd~X@e`Qov?b=403`jAynm%<>(ch)`jk0h2`#`RUFOZwoPXv`0x8O7u{y2hXt zQ}#5CHEzE1d4>ls%YOnMak4Zb1F!;DP(oFV6D0<)wf)VpJbSi5P%*N zym*;$Cs7k>P=5h$6a6{YyQA;rz(%LW#3;iBo7MgB?Bf+^UOhm_2=B>-fu zylMp5j~?l{E@ZvIU}82r+#)MDz-&BXYzDgnJg>*FB|3;LhV!<5rWaq`?O&9Qw&AQ= zIELOu)J>!;JskO?sfAm6HRxnmRo-t2M(*bM*#yY^t*XGyno?d`T$LvZT2f%ExKu6w8Y`p$yOr%%5Ka@;|n0Yj>mjRb|(5kOdd z=q2mj&!xpS-5Q*KLr`{Nw~aX3WhN6W#ZEWfLS2#bzr(PkD*C+~@(C0DInw2kHPh|8 z9Jc;n)PzJEy7&UJmj!OH<>uOt_N+@ zn1*Ai!*pwa6sM$Ei_Zd|B@JC~VccV-e&n?m+0|>R-+ny%Ytq5&u20sV0oV0vjjcfT z7Mi?aT5h;p`%SCiOl&PmmAmVFSS5tt^FpW8Ft{W&N+tDGySG*OQ@cpIol^$%pd^Ly zg!;%65TjRn-rh>Z3FwE?n-h>!32pjV_?3fKL=vy3_wPVMjlnMUknc+_W(IP$rcM(& zY7e{oB6`-2oIlP6;~z#mns8}AHXy&on%o|nzw%M8(=X7!ygz_>F3N1*pUz3-qn^%% z3j_0wp8&w5gu9QuTUS3J+R(IzMmD}05q1=rw@sfPalE&5mGKOg*R?O=g%R`d!ww4@ z<2GBn&SzUY?`342J98ru**68+vo}oZ3oXl$2$f(>y-1e0WPy+M~=P zsaGgPKjHDfCJ7O^@n7m#1IKO)Om}ZEJ9Poih$` z)vPmC(1%OKvZ24YxFqPGWiStj6cTU=H3^>{P!5HUsbLO9zN|mT%_o=ZS77yh_WcY4 zu65rqm|Uq`$mF3hSc%c)aQA<4_w3`MSkO(>PM&rBjs)ohL_$Nkv}Z9hj_#B}gVh<5 zAVF4Th-{7sfriz{%DQLd)cDLq;+SSD(7B>56BCy?r*0$^KreBaXz0Vl+dC>h-Qo^p zm;JzLVR{Sf_Dzho<{wU2;p=YeKu<-|w(=7_quoFAdSB|Zcm3#Ib-qoE~$$#<8igM_#X7^V#8TwV=-r% zO8Z9h(<_N;UG%iYqv1kcH?(K9`oW(KNqLg+JmxXy-aX|_;`WoS&`Dg*%(pJN075|U zFN3J7ZXw{?<=4^WoV-suoz0f7sJvj)Q9b=Kw=l5$A8-%!(mZM4;XqTaedwyg*OIlX z-@%ps6>H>P@WpV|=Q?)&4X;Sb%~!J#=fBYOM$qh|qRLYK&Z(T0<^j&= zbBCP7xfW8)Uw0k@E17Amj5*&aE67H!NX@7Z+w7`jF*MSh?sRmLs7CUOdqBf+yDWA_ z^mIV>3z{y##|i#&0vc|~0|GxP;fFGf#?EHB>)U~9Y#ez#o)*bLMA+Qfdxj z7cC;o4&e5q6%|S@4dcJVOiYllAGKCyUf8RIPoA0Thb~sBD-T7*{8e@C7Gz%q?TIdi zkh+{|mna#4*|3+x(r9w)cl;AgpILP%D#j?oLT&U8QX0ziz2}_1l-Y$c_4lVtZ=I2{ zI>n5C{dchHr8a=y>X#ZCDHD`FvNq|dzVa||8+yW8ptD7_SQH@{7NRjgco#Gv^V4kt zyu8a#sw|a5G>!-IHhljATU76r5fBe#<8Cj>2>n)5^NQC?nMQ~|9l(aHz%Hdl1K@B9 z2u`V~Qk@L-{Ng!TJ$$AmcWa7)m95jazrG-~mqsROtB;8<`Bm`H+24}9KdTl!KU#BG z++t;mw3z#nU~qQp`WwDAHTs0t#V>$YOu9|yfZy!K%k+C0y}{C;Z%wt13oXvsUkP#U znV^NbVXh5Jgq&UfnV4SLtOk9KZ#Qc^MY);%34QqzQm`R8Rer5^yR_CLlo66GSRTl? zu{}EQ$r3QhK)R=ga4M`KHn)c8u1Dl~Wp&%!zHX#|eJbu0zw-DimQ(Uly6{ajD23*m z7}Foo3MT;G@2njidchBvo&JIMpq1bF7%d@mbJVP@Ccl}9f?vY?Z`GNLDNLN>+<~U& z&3CgO6V*;Y+&_bIRmOiz1X{~TZ%J>8jEyMn$6UF0-Ok>wOn$B%D*V2A?&SxAk_V=z zPb;WYdSF-;jiC<_qZQJD23s=dZRxGYbq1C_$h4LHI?FNDKHb)!t{MOQA8hS#^IeX# zcfM&;*X`BOlB#lI_g?ZW$DMM?6-8aYE_9V!G!R_ltB?UCZDRO>R5&h;PAwK0}Zlk5FLF6Z|~{p%{E=azH{0y;y$*jbNMS%%&NQhKjRs0DVISc-fq_i8b;O+6_nt*A|DR3&KvsfN|q#b@!#%W zJ(K$W0@nH~18DOfV3)p+AM<{zh%&eI6Fm-2ft0_gsY_FBK~Kj5B)Vr@z<`}yd>?wOTM z<>j&|JIC17i7o#8O zI>jt*tXQY5@-{LRZ7nmk>FLJ25E9O44$uSOl2=m>BGuFDi@smr8VnL}=96xV9vUgz z{HZ0Q%Z!r|q1>NJ4@%?xTl}`XF6%X-F=idNVBPvs#O3U;*kg9FUH+NqXc~i7zBM6` zA7&@(DQ0iG81U4WKp|TSNkGYcXZ@QNpw2ihlccMfp?7{TE+f8(IOqztC#L35tL&Xw`cC zFd+b;8NpjJ@OpSn%kXh#@?ha{Q99AnwrQRR%L7I(&D814cM&S58B%k!vz8YayqI&u(enLafwQx?ne!%kn z_PHS@26mx4Fi}v3#M`Zbdj{w3oLy4`(Z*9bUn=KI<$S4}FO~D9a=ui~m&*B4IbSO0 zOXYm2oG+F0rEyhQaN8L=S$^$shlsB^QCgWRGlwX=S$W3QgyymoiA1AOV#;O zb-q-cFIDGD)%j9&zEqtrRp(39`BHVhRGlwX=S$W3Qgyymo$vp1I^Wiou}{CWe^$>= zZ!in~y%{z6_o|C22DNt=Ym64j&}9qP8OkSGG#*-2N&w<23n zZ|%e4q>Hi2sFbgVe%w|@seQmS->e=*3^Tlq%lB_F7GBpf!e|8(bR!E4D=jtbh2ph- z1>tKpiAVLr8K_`vJreWp`M01|abzxYODOv-lzQm&ye8HU<@1N9>yBTxi+f2`$Ou~c z2XDNfVFNm*W8UQR^x!i`YgF{$0HRuB<&*tQPG$Mpt@sBV=gqIZe7V^4xfB>E8NSp@ z48j81eIS3ai3;q<<1FO*WJ(xbd^i4p+^ssy^KIonvyVaB!aKh+?^pg! zcvCd5ziej@cb{u##?9TCbyB>tt2iI3&@EnGC>Fm;XTc`PIs3=NVe%-eXR5=JM6&!m zbLmPwJ!0&$*F&jRF)K07&0U_?8#S+mbNE%*UQ5TC#KK+c5WoQ5*YE+d5?T&InZROt zY=~{}LqFp4j99U<*zqFqP4F5FZu1l;rgwHy-C;J7!NQv5EQGhoL zLOiMxM*xne$vx$KI{fT2d-r2bbu9nQf)kK|+$>&CDu!!6+|$+*`TF4%U6UT3ae=#2 zRxLa`u$_9`2ogb=HCBI^a^^9fLKETWru=b2SBQ~;8-hV#La>~kH{8uwHnyg`Wlf6ac>;p;%`wIYXKc|hGE zS-jmS9~JO?foji?LR{P*j_v0vk!V`Kf^`+HWUH*+-&mf?*k3AsYK>`=64`Y|B_ds2 z7GWb_L_YRKn#yTdnonrgp%dp|S|&h}iFD}Tyn8y}tzW_-`}c=JoO2lAtcMqF(ogTh z%h2`PoD`bH@konv83AjONU=JmhfbxUR63&g6Gn+wCi@pB;CK+aIjZw$udr( zi;#TuwxafI@*_*H*L1pr%i-)hQ}NFR?|4qNJBZfHx{7JJG@Z}qh%oUc(_{`*F0E{F zAAT+7b3@_(HFdS6JfB5&XDj`aod;*xqDu_3g*hsLGXHnW{>J))%1Sm?>HOT8e7yT| z4eLeMC71W?XmwzM;pElU$gjkuuBIql=;5jSW!LBeA4W*!Kbbt#9~js!sUv}UHK!COOKIjRuG^G7zniwe7Dn7y`vHbs2$|glEpXjf1e{`?2LGpikW)&LxWQ zbvj<#pGC>KlsiA?+8YtQHov&qwOF(`QBBGuJl3%IslQI~!zM1eE#W|t z93szqcI$5YN{5q^jO7(F1tK(h1ejya+P81yfhnWp+tNKBG3@dpQIZ{|mF28766Urgp&~!qv6mJdc-m^ZZ@5Y6wz-<=7drZwjm%)5f1yqia9x*~CC@03^2H(riA zZ$p8<&%FVsus{H7ubo8vpY7`4f`UjgTaYG;-CYbCOc$^o&qyqKh^?d!kpkoZSGt8G(la!+Jfyc_A+}3MCGsKn% z5p`sB#LhTmxp;gU7r@@nsJK^qI{9&uZEc4e`8=ak^0RB{k z&B>is4B65Ln9r<~7>?hZ#G-tHw!GbW6BO)NVmGxH+@6|x%|l2MGg41Rwfcb>#GbHR zz0Se4S&d^7ugWo|pNLMw0v>+bf!PyZa{T^;5uYlx;V!OS@ykFp$&0T;(aTE z@WJIvj!~Ee#)hzm1k9|XM`XE1Vr^KWd*<#OtOb%6h=R-n!NP6U25@NbEEfN-A>*I` zHtaARUW?%)E~mvCB0y-sA)m)BiK-6CD~ww<|D5OCVeU#SoO9570+AT=c59L?$91f( z+FIrMkI3Z5^72^k-zkZCbB#mi+phgXb6|Y0w%_uh7UZaYPX(6ic&w1iXa9=^d8w)Uf^L=Nj@-XcTgHr;wg4y4o(%d7XTOT>G&LC9 zM!X7fiwMh0>AC$NJwC&>~SU*3Le4@#x41EdA-~X zdvhc;c$Yb35E!*A9%rLJ6N(|oD)cloqRIMsE$zTubL3&TWeOI^N%4YZnbJZCH)Lul zvHXOD{E-t-AvYNEDNT=&j8Uwp!?s<0FD$Xlw80|oRqn`ipmAWc^%IMQgD>Zl_MXbQ z|I?1Q0T>qDHXBy!)hNl5&Kmi;(XL?o5FKH(Gj${kW%&|@BpVSf)W1n4L8>?R9Mlt4 z2KTm*V6x1xY`@)M7BU`X8}hG7yZPsz*us8b*IE_TYmtNTOE@GiV*3FAO#y`ZJZ8U{ zw{4!LVdQsKY)`rI@?UV=l-{4LhU~e1FZLlD@o`pXXGyJ}6Rf5yx+UBKl3$+Y%{u6B zY2--&SSPK;ZCjxqFlPD@uq)&>&FY~@%-f^4LJ^xG=nu}Z&{^dz;>g1wywDPGB7m#^ zPyYGzPExhUY&K^LdtCetBksz}WZ|&;+KBqjaetp&=b#?SZHt-L24BkR+h~Q|h}sMX zUR_OY1&Y5Gs#s^!Yf=Q|?deU=wH717UJSZf`Vse;LZ=@lDA;fR>jrkTFr`mFfEta+ zC^A0UmDi&yyJp`FhlD|8a-Nk;Su&hW`)=a>9a2lQJ{u#yTXUCTo*x*{F1)iu2?nCJ z4m8pKxRHHxRi;`x7vt|^H*+EBB-y;-8B}8hJS$=pPN&+P2dHhE{vj z(T7P*GQ8H-htP;^tj_jP9rV?b1u_L9GfHn(!7$T#F4eIHWZ85e;swUVrIP2dw?FNT z2Y`ZXAJG<)iW!6`JOzP|i}fbqBhJm$1=5N3^t^fyNKXMm@*f@0JfS#Hj(?Dh&R6ND~!`30*^| zeMjyJCZE0#mI!^vSiRfTdPL-8#Gpm zq~WhT<$YUP4hxA{=YUSq{O=3+*G3-*03+wupNn_BeA;r^Ab-Nus9~G1UAZz=nR;ns zL6~ipfsn~e3O5pFz2l&4cvHQ-zUtt%79V_=U;6|^kIXx&sbuf^yFZP7P>KzEH%O#( z?j4d) zMNjmBdxDX*fg$ezQ@NOhbp1wdbH$-ba@E@^!e*6mjpBo+Ea2jMlb@|$8GJR6o>^32 zm~cIN>&6Wq@w2v^d7PAfPcRT=b@l|5LGlWIj6W_z!rxu$#z073U>s5%s;m&-%~MI= zNfO+tRl1_ItwLtsOuOzW=nCva9eP+~adnR%5Kd;!qTp8!9gM(0>|ezySce%?_p@pE zuM`wu3})208oL^mSg6LWxFdWp${+kG%yiQ~?BhT$dSCEOstX z{iCnXBA25xqbF&wa=Bc~?hYs0ihm+zmB=jH?zj^rVx!$eRbNP>hD@(t?Jz;_k z#Lzqyip^mbdRUf{(T#+pZ)cObZ*WpXi9)Yfs<((v3DS5EEUI767#kV^oQGcE=AV3V z!;x#Yv*4+(&B1+n#dWDC4OyF!e^e>6Q(O;vn_%%+(L+{SH&&4^l~ZS#BC~pqBo?YJ zt!KuKRYjropE~pzg!Vq;*O8>logvXGW!QK3&t2bhy01D2#rzB8gjwqdI4dsOF)Rg z57OR0$vSPpcn309jFLK}2G-&@-2&HkBm504%!BrZI)`iR4k>i`s+s(IkJWoZH7~y_ zYbp4+4oyDf;&PnXmurVYSrH$M&uJK|l2V(0;${7;{-Iyq>LOf5{W#(b~Q z@ZOJ1{u%6Q*X|ZJ9t$l2z$t)HHT6Q~;};;HIgh`_oy!)NiDAlJUZ4IB&fWKMt&d)l zX4^PhU7ecu?CIMQ|BvQOEF2R`CIng0eF2kqz;^UIDnK&;pgyKbA`p-Fa;MjH@g3o} zFWe+)&fmMP0`(FP-^vkRcNBC{<<&gKR;i-)}PaDtrCf$U_3* zp)zwQ^;(Z7ct-s6_W4Q7!i59B6OhI;AU{W79tb*UFyZ}$f3kb07)&xz=_hjyuWjNm z_v_wm9m>W*y64rpf`{m@{vX>6V|i0J8VDVKrYS?z7Fqm7)>`HE*;-T{WB8~a{dsR8 zS54YgSPUX3|25ePG4nDV0B^@~2e(l**qT4^#P5Dt}7lPpSMVl|QBOr&Ruw%AZpCQ!0N-)t^46 z>QAZqQ>y-ysz0UbPpSG-s{WL!Kc(tVsrpl@{*QAZqQ>y;- z|5AUdsi1q~2CgTtLE62JtnhYpYrvsPMq@fizQZZc>+R8Zv1z%YbYFZ#?=MaZ?VSwf zJF`O*8cH0bR6n_d`74Aosb~xR2Sd56E-nMm%T^XMnE_~Em?`tz)R7nxzt=ZLIR@-W z-5YA?n<0v3ZM!y+nJUpSg&$w_6fu~&30+`l6^i>qUj(u`s17LDz)TD7L{y+1^nv+? z#UyZ<2Nw7Wd^K#|MOoXkI~}^RG)N4%90V%}&9tA4<;*D7e~wrss`K7P$=fK7^1esL zyUSjQ?5K2QY0}x^uW8Lv)AFm>NWTP`34I-Tw5<-$uAC+w<)DHL1r%2K)->s^4~56? zY`5DpTJBIHzs%t_di_d&{&oB4n`GklwQveyYS;I*JdLfVTC%TA2`1+If)1+ogl4%3 zjWC-lusYiza>jyH*U3@N@T!__@-pFST5Hd@VQ$_$&)x3hGr**60b^k1Wc_Gx?6=wb z??O20>$^VvB$cWu40;dof6kSsNf;GIvR(z1s!3!&rvFZ#W%9!@`uTiiW=Bx^)T9a* zXZe)EuUVMq6XXT#;HrMW7pct>r(hBqE}wsv=iFLN{Z3_3!C@Y@$>n-jG4PQyQkDBK zb@Xw|ZKQyM#&iJ6?I<3F$Z0Ork{+)MlU_i)>Kz?o%y^>Et*)T#+RzcD{^2F>bljDP zNLsbSvWSTmVAgH-3}piRS)T+^aZhF*T^l0qL-u4{hct??@eTd%YTTfSQ3zVxv+UM! zDaV_cg^;&fX7>HscvZ}P(W6INjhRuBKS zmdEB2SDqaZgWmT)UViKz$CBf{Xm4c3pms&3FxOh+)5WN(Qq#tZ_n6zcA9q7|hLvzu z)16N2w-^ojMtKb1mnpBB>(bo587H*F)2s0tMEm)BLITpC--pqfpf#|lh^pnPj!jhN zkE`Z(pIy{eWKd?h>N=u_pG+5P-mMWR0v`UT-iJ}x^hJN=wWomL>Wo+UV`WnJzBIG3?_OfIx|Z25TlG_ zkYU_&{r-8b>v;wDeH{A@IJR@neeSi_{;c(Nc~jJrg}1(b9i}p}v?&T#MG3ntBx}Z= zL@15kjrGne2vpghxi@vH{^>PB;Z3c@>o;l{r51wBa}~GbupiiDDnx}sYsDVpzQ$Q; zbMcYojF^N#C91_gAZ|`ykiPWRFjW7e@etGFr}~9ZmB;p9y`*G&C^yl8Bo6;Bm^7-G zDg}lE`{eTP%cm4Bc7ws5rOkJ~JbiL#E*s%o5*uP$9|`VytReDLdy}2)H`&I-G24n2 zqP)0I?}S@^t8OxL zBac9GK3RLzX0N}K5~dGr@cvdSDj(8yTp~Lo9pdgkN=uHCmLFmeD4^SsSh`>7a49!y%O^5itQ!E9xuH+AkbF5Ktbhvg!#Ma z0KGcJIEkI^omZ>F5w>EFIPi@L!xwblUp9r%9joTn*xQo!Wa1>a0{gu4nYFVq^Cj#` z{C89HZaZJDt!36nPkO!V!@wCODg%YNFBgVk9Xg~4plYT1-u%*Oa6wiVTiru3NtRO6 z0{@R+9!;p$MFz@dx-_c)M*m_$44blA`Po}iTu2$yy#{+tS{;}J8XkunCJqzW zayIVU;UWxkP^|_sJ5Lclm#^AgC>JB^P=r+m;}d_XQF_8y<1&D9$$ zhZM;vA8zqH9nWy(ilsex+l0^Kf_*n9&3kQMTRP3M6pn16E8J43F*mn(Es^j#B}H|5 zXc_Ii%Eyl7=k#PsyquB@?EwXWp!LLy$LW>ZTNRqlK|!{jp09_!)mlr^(nLM!S{Cgf z%AZFD!r2mBdJjcFZxgEmM6EAxo2h4 zI12Q~iV8nJ(XG2^81j;fi>`B`!Z#rT*EMWIpLU&z#fCLPOmySoJ+BD81YzeVaQa7- z{@+ffM}#ID_)?PJZELTm3|!ZGl{_4}DEMt&I-J(;Ap6jADlXQD@wQZbY~}S28ISAS z>>u!5eZby9FP)t-ssU04U;6nAa(((oL+-@Rh=2}KI)J_L&}Md!F+8YB;7}vwWk;H2 zXE1|sg{cWC-EAkw>7w#tf6^Fj*wvxpUr^6#bYRM@sqFtSDMfld!^Ygfc?4G`{87Q% zdOvvyHY%z|I3i`ic}q7AF7Ax!&~(3Mby~b3k{nLM|3OImV~B<@#XH$ao)2WUE8Qar zq(S8`VIB1F11K*Vh0dFUR~EP(!s>#%H=&8MXC*wAOX&#;{X-dZ8*w;ih?$s~Lk{;g zCaZ4;%(T<*G@<2Yuqc<^@|lgP>4Vx9d86MTrR&;B58{i3Gs+&v#CF(%C3+!k2XTJb zIYIx?kKWn8*?X1#Ds-ageEt~%&06ja`QH%et<12FyLbLK1p2PGNJp)S4rvIq=%uN( zqz(|r{CXr+@-}Wx$1Cooz?DcAlZVt{f$f5Y>z6fC>Wj^(1jtoix&@W=^8RL>CUu`c zN&o(h8SNEGa%W}T9i!=)YcU1r!nIMkIz_)s|2|3;t?>B=g%=9fay>(MHF&`4Z-a$? zy=it~L|a>%%v+e%S&UXYuNOtn-*;EY;2tThtNj$Viat0U2;s96%^jHOXiiJmfVpiY zcH?tU_J?-0$c44tL;MNTJk{apNg(%W1qHQ%}@UvVwLwfyE}s2wiSfW5HJMj=W{ zf!5|dcZ;LyjIXUNOQ`Rv ze*yUm$X`JI0`eD-zkvJ&++V=`1>9f2{RP}#!2Jc>U%>qZ++V=`1>9c%egNPH0Db`A z2LOHm;0FMH0N@7zegNPH0Db`2Ux57u*k6GC1=wGJ{RP-xfc*v7Ux57u*k2&}21MV0 z=o=7y1EOz0^bLr<0ns-g`UXVb{;!I@T}HRYBJlmR;@4_NYEy36Tb6T`YTS(XPUo^^ zr^*d!?p#i5Ek!?sUR+E}(%k@?x=9+{T^~4*|B*&tAkHB`A8bI~-pA1<;?7u=N4?+O zaJFy`!U!;HmTdJt&u~4BNsvZgL^R#HlC`rv0kw6hy>YMscB+T51ZqwE#<=LDO;&71 zj4Zi(=c7ASgY9qw$dxQzQ?@tCeaMjc*a|fRI*<=+zbsJm9HibXOr%&k2jSqY%$>PR zll1|W$}Sl@IpTcMkNJ}Q9NcT~wCQVuuLuN?r-}Hjt*VRryCGJWqCL*i3Ch2offpe; z>;3_HjXg`kg9|w~baU)_7Zye>TTE(>GgfbPx0bs^|4oWA3b0;H3Jh}&GurkZ_;m5|KA$cATxe=#)jBHf$~*?mtlUvm!79yXYHC6L>3}!1LHyOPXPM{# zL7hg5XVhysrsg|Q?&*w}$DqZ+bsg?T#nC}4vQ^7#zrN+o<)Lxr^Q0X28Di%gG*>N% zQR^hl(HkMWFbaSK)k;b_X5`em&4n#&rKu^-T&%dSKeZgaw|Q8QOQ*bOc(!^Huq=P@ zVaS{#_->Py8^mt;I36t8jUvU+L&LWx^ogSc%~{f5#qo)Lf^`x;;rJj8Genx=NcDGt zARm|oQTsvvX!HTHwZ5S>TE{nf7fn+-t(mA*GykAA-=ju3;_?qEV|%(pjtF5Z^v0bF zvX1^drgq+5m98wSjLj7q%%b-&NB6oyiu_WqM+q`KPFANh^?x}irVSd~t` zcB;M#;V7RSgt;bmjFpT1x+Rllh^`q3y%4#n`ywA`Y;y67;K zw(`f0$D*3qjr!%b%viG2<~m;)82j`+2nH)(cB#wbHlW1ZVpyrH%GA9&+cK z2P7v^w~bUPq{$=s-FY0QV>AzpyW-S!;q^`6$0PoMSp1PPU4?A#hMrh?+pYSkBuGI; zOe0<2K<7H&cPi43zntC$J0yID{UG&+oP$JN##EJ-#dS2BCyJYL`^U%0uYRT$c5omF zVAd(p#x5v)ReDw8_EZ)(6aFjs&T$FD9IURYfpuXMx@a6xPlyHM0$b;Pw61CC4( z(7WcxV|2rLAtE*%jn8s<`uD)Nhd`g3F-y?n7%w%-NhA#c~yG!;aq+pVNIzjTqw)Qh8h z#I!COI~%YkYy2(~*tbs)7dPrSdaQ#9QrC^XnQmfQeD59lmVUd?TCAzSA$i2T{&3rO zOHH*SR;5XOb8JjNMUcVAo-!t>tHZ{o4B&C8;d?YT*o2^{voVGkVkz+n#@_P}8e zD0@KJ1Iivy_JFbnls#bM05%R_;{Y}eVB-Kb4&d1Xo;~2%1D-wL*#n+E@PQWiKnr}J z1wPOMA83IOv;d6*&^Q2%1JF1CjRVj)0D1|amjHSRpqBu8380q%XAf}p0A~+y_5f!O zaP~l`3J6sJp(-F$1%#@A1TB!D1roGCf)+^70ts56E&-@Z0O}Hex&)vu;s2?+1oH1K z%;%u%<_QFQ_#>5y;{u#X?+O~jEo;_-a>+@4 z2zB=?y|dEK-R@|zZr_;8k*ovNmtpgrT{M3iu20qj>v(n!da8aRdk&ia(=j`Dme7^3 z!Yezp1TD1;N^lqoYLze^Bs|&}O~uFV;V92OqO)q-8hw5-P7h3+gUFW8K`Ww>2~Uty zav46uGV{9La#Jf9OnK*p)b}G9sb-GRSE*^Aev>Wjz*^K8@^SA*9mo=R#&xWB^y*L2 z8A$&bR#KR=Ao@;LaJz)(x})E6&MaCwg|sUFzAhIc%SWx^Gz(^D84|GaM4R8~+fNI-+3n_1oJTU$!{k1WCf>Bct>k zOxx7D%Z&;597Ny3<|5`K16SO$K#+of=M_Lsu#TK_P+815=oatU?`ow3($9S=dgn16 z)c_Ggu#7cBp<70GmLx|To9YPaNu;X`9{mcggdb_ zdG`No#aY+7^%EjwaN~p*)(_4i{=#k!{Mb+EigA=N*DS>}8HD|qsaWv}4Hq%HqpNLT zsBPf!_EZ-+VGqDcgOSB3$?- z$E1y4mC2Vx`lAQO8D|O3Le+R-bl_k_9t@wJ5;xY(GmLtfu86BcdW`IV+51Ur^6$%P zAQkES>aqS!bq*Q`PbJlnPNlEXUBmA^d3T3$%;3QNGnB3zGpCKWarddBtrTjsJ2d0! zZAor7cb-cH?bcU=U>&A5RFUcps5(;di|W#Ey{|##{RFXbQ&?{==6}sEi|&6vU{H?3 zh=BiYnJViJSr_y_?K?Ds7(&2DwV923l4~Bh;6FqChn*^25KVs6z$WL{*_Rr#%`rXA zz3FFR0p&V)@X~8sLy9wE1heLLxCcHRI(csEcJ@&PzRMYMTYdTIIcT!`9P}Z9_}4i5 zxa}N7WXUa}e7_C@pH=mUTVt&p@$TuH^A>Ywd3|#*l;f=GsKSWlqTA@Lz}C^GvSaU+ zQ@CtVPEU)3`2cPxSmd4XiqqiA{R%$oh49fK8JU(%swAcF>fedCg3m!+Oy{7&xi-=& zvIu=2%R4gq#r<0y9mCSt8liv7$&IlfaZoz!!SWjDNRQ$i)MvCxY{#90wseb}kMlI< zGux2(mZ01om(g0%KHQNv#hspmRMrmz6HsgL;d78L{&3yHUvuSQU50qUBzV5;-oDGU zr_#OqEaE)Hg=HdzWr{*~do(ITQ@Z5gy-%jACDEI9Nml0buNnMnH9btHl}z1)!|jq6 zU(kjv_$j3Qu=xP;%-jC}n@Au&IR_ox@qo+2Y!3vEBZdwY7GJYj+s@VTy6dFkh+|E4 z>p=x7r*BaX^2IyVRou#5zP0t|pl|-L4bsL!0`u}bi$z_fPtY{}XC~O^j);h%Na(JB$r>H0Ycf;;vIcG(sAjx{& zJ8srhiqF;Z;i{$WAhDx$s)!)J*8nx^ug7eHaSG5QIME@ntOgN0#EpT_eY?iHUjo~H zMY_L8rBy+qBZ1a1(mgk7@cu3i1t-qfvR31(WeEy{hr1H4#L+cZ)Kc9Jk7L%R#GZoV zhc>It`N>nIgJ5%x@sU+4Ht}_+gF4Gy=7Zbz2iK>sNbYtzZg6l|dB5+={>l3jb`=F% zp@xmSE|JzSI{By5D52ap{x2`~Vf4IJXX({?g-c_7_8&Vwd*DC;CGTKsqMqlVmnX2_ z#pCs-diid;)|Lb-wNYES%{kH*#B++B1jBN0CvBQbICF<{P%{CFYg83D;GY{x8{3xO zeeua(?w4Zrr|YBwtLFwUQb`3?O!=#PMjhHKoaVO0F2?Ct?^>;D@m)Ep7E^&nv0q#j z2
    1ZFdpMd%H-^K@X_`kmBg^WA&}xbI=VKA-B<-Q@!Rm>Gb{vG;2?ern+WlE&)+A zZ~}9hpTo6{NubK8PCw?yi%SsgZ)rdKYi%QxRw5S+Ip7}a ztHw3;?8viUk(c;1q**yxKUkoR?X{JlXl$VuRmz}|^?9c&?}<-~%A@i@tKL&xb4 zCqzWQ<=UU@r}~NAI+|r=B9F^No>J?3CW_h!9?(_ROvsfF;+y6Kjnj^sTT42q3ZBaX4uRu-nADq zz8;9GcXK_lb{aVc@wv~%l_5ocMLezHcCA$3=iw#tH%9&*Y<+H;{$xVDYev@6O1!nX zMe3G`>Q;tfvHvHH#I7B3NQxw24WXMFT>+-k2sF=Y*s9oL+mhZ z@)$wfI2PgxI8;&px+WvkvZenZG2|R{qCbv0QLa6;DX?1DO4Q6pi&%;O*1O&i5O7Ov zNPIW7J+%-)o9lA_<59ASC|E@%pL;s>2g9+B7~cUE9)XA9BeIB5c!mb3x_zdOUAfal1DoPs*H`3 zLFDk>oH`Wvait z-I7bk@2r1kDNLhQ3Y)LV61Nv@+ad_hULn}NZeJ9q>$vU3;UHC-ts26!^&{`(@;q!- z&HC(CeWg0{PtFwMnc$g(y4hhU-dbSqEPPPSI&U+u%{}SpG;I#G+qM{3#c=6V3+^pc z|0WKZdee&T&4}<#tye__lar}JFH(gmN)`K9z9##JPtR^;POTFxc9KO01LE%Pv1;wo zDEEqrckK@)n-9EAL{|0S=4oX@=w*pa34clFa(^Civx>E6bSGF4cI713fiQgU*G_=E zEq2SR%^IyBA6Q-Q2uoDh>6u(e{nJ@u5)U`ZJ7yrgUNBlbcw@gl zxM0P8wSvcI=*xtve!kW#KeC)hY_Hd&%RqZRdQo|^_+--a;#E+|P$Tr{9OMjDT;O`P zgTYgKCy1&bXm@+YVsowkbyelo`&BLW=O7K(X{odIX^v^I)L6CK?>>R=U=~T%zru;= z*47Vs4y;Fye+-P9hwI6G8VcwaV}a*n=+pmtyiav1ly=4cJ;9uyPPk9nbPotr=$5?N zVWITXz##bl8ZtLXFBZB;d>{q_|IbEQKNXN~Eh6=ler;(*A)4M}gYEc5e(U?7wKsMdhcTzLPFQIo{O%bOyU=An2LEXc7bEk3X1boo@ zp~JxSg^e9t`rtevGcQ6-a?n=Ubl#<7y`@^Lccqj+_(p7-pmN8>vsu^EP65#o4=8gE z`5D7X`rheX$-lZ;zpM`oEa^B%0?(~x>Hb-1jd{$h?}fDR$L?Krd$B$JMao%VT>qlazQ3Vj5kj_cBqwY!=zRcNrF zxl%Iv&ZC8a50aFvj?5nfr47cmUq4R%@;K$)3og6Mv)ZcnU0i*JU*pcI{IprtTYK`4 zVgw-AvRHQ4U);v=rNwCp#koOFYts`EYNj)-fR5obi zlP~^GmNfo%leHw4GfT}FEsN^o#M{1h8~D3CQK4xrs&`5iL3(Mc;=d!a_hQ<9;`X4Y z+|Z11jJsPk?PVhdWd}p&+?)D>(kh^l0-6S|?iV4DX5p^Uod9idSdH4mhJ`AD^hVK= z?G}F%4<=AF^`_=v!XdU!J$jP=xvZ(`V`D3sb5OoI@x|?G;?oKgyVDHfjUD4p`RMeR zAm%=>)#89&9Zf)yhrP3Sr*P)?jXP^;moZO=N~wH{{K<(-gO^nAo>iXZoqZzhyU#)I zu~vF>4#|T=H8l))1DfY>2%9WkAgF)p*ODl36ROCCq#~9woyF69_$Y zuX51K>g`38T2s7|e zHTw$_?;&{c4C&{f$6!&tuD_IPQ-78kR(J5SRzc}~q+iOWb~JtSWJxmXAKOO=?oOyi zaWM}~~8-{Z{2Q~c)tEnUKM)7v3 zIcBsS>NgX3hCdSCSX8IuqBV#p=Kawo|31-d32jDF5G8ZO@#FR&&6<^PYo5t{I<$gpI6YZ@XYx zWiI{Ze5lji7PTf{?QSnT7uGu7C{v%doHFd>&4bgq2rhAWLEb#xLQmgIwEhau*$xKK^nkFRDN?ivH_cm>;LQRNug1 zHqV1ijdRfb<08@@zoJ7-arirP`Xc@QBiMp@1%z}3uf>d3@Z1_jpBf*<-xup)VU9>7 zUr+_?N5Fmr>_@_@_@o;m8C770d(~4Vi*bn%z+r!+awPKHPWl70OTzo>$My`fHlMe0>Y^CN7 z&OuK|6!ltkdC@P(n@2S~2rxHtp<8IEoNG(KJPjG{WOq>@?bap3oB^_%*Waaykx}k- z1Ubxn%fpc9+Jkuu66|>nvJc85$O&XQ&cfIf~Y1@=4oEQ`5V24OyT>`avS=OyoIebMOHvyXZp7 zf|?pQ$pXdTd}VBwY()Pf4%UuIfnfh9&N|@xkry718C_ zBctw>yBTT&xVkw*a*7cdwR5#hV=<~FX-&Xu2MY##$@C;I7 zw~`pBpqn5(gsB^Bjo~;n_k%c+v$u~n46j!5vlnGx8<@WK;J%uGV_4#J67v^9+Mpjy zly8!zzoM4G{@%pE2Gl9#S5jm$73B5Ty_@%nl~pHLJU6~fIHR%G{SB9Ev^FKpYg*+} zDiQw?DJa)DDAq?sk8|7Ob#p~J>k%uS(fMa)K=;)f>{Sv~h8b=)$ixAwU5z2Z7dJ~U zT;Rx>xosVpc>`I7aW+b8E`+dILDG(-%mhW(%m=*)Xwj`~`|B@}wysV39@I9dFaFVk z+KoCcAFFZ}S+d!gZfL~f9+AoDF)n=kn0Pgoal{Km#YqC4;pOKbwZE{lzfGhq)4VPh z_c)-Wb#hJ12zI?_HhP778o#U$cILn*G41k@vI*~+K$hdD5ABz;B2cL4Z6}AYN)|8^ zhai*I!4OC21g2C2zDrGS?x}zE-Q&R($tW|{>g6-YV|e{k^6K-kKX=p?Hs1}~Xji+I zz@v(t_STbL8K*vpP3l`F6N&dIBp*tM+QV>!k0$(eR5%9-8d^%4Bgf?;{~(>Pi@UcZ z>`gYcugy2i%iia^#Z{wquVPo9`Y!F&nwOUpf|-BGw^0z6h%@mkn*?KmC@JnYSH^`= z>pUykI9Ym~HtMzqf4IJLC=1ucNFhIE86r};An4}84)o@;Nw?K|Wt)+Ef{p&ElUla) zpY0DX3=CWXSIkm`_*w76TQo_FWF|}`w{{|o}1RvNUsyNKf(3JMUV-|C-FG_p@jXhl6*=sz?Ou1yoQJ*bc z*ra=1zEE1qI12r!C%>>g3=2Pr_v(a8=Q@#xPpDd#S0TyA!SsyQs=TJD&PW@74)itIh~K~?VD z&R4p{M!}`2;R>nmI$cdFUR#7w?qATN0wu~$#y5nWgJ@hj^A6W5&p|)rNS|!QO3BkF zf^Qzb!{IrdU4Q#(s}A?51bkn$GtiPU*SDF{PKRwjAa%E_->GSD>F}a1On8)=EQv!|6hb@Sv zcVpMB`4`R87ssrhtlpxzh~cy!cpsueclNx?$V|7x+@jlOcfaDtw6m+{_-AjPF)-3IHEBT|9H-V`YwBQ$fX;`Q@|JG&+P_v%jQB)W{R>>?1C-@9my z)<>p$9Li|Bvh-cpOV*Hx)|-XJqzcVSjHcU>r?$69ud>$kdew3!2btD-^gR|wuaL)= zUKYiG?jrZ|OdIxv@#i3X3xrJ_jheG|Bo1qB%;9;lk_sb)7w`bczk54r(UmQ^0&P=M zH&|zs;|n0ySoBmip+cQqRiE#(`!Y*@WHr*i4|ez_NM1`ymo}`ZpWw#S_phCk8Z6Qb zH?NJx$>}OjEB!!BETEV9`Aw=g{OY}$_=Lu}=kC5wmeS*B=Lxe7VmZ~lEz19;?-jrP z7fkB2elqSJsnzUFzJu={_8Gc#nSPlx#j9BA%WS16NnXrnXESm+RT%P4yh%tdi(#<3cuwL0De#*-BQ8I`KsI2VC|+t2$O~)#oOFM z-wMfNb1TwWnkVi~DiWzsKe5-~YRfv%>|#s*iqUZ__@cexCXHE);N(xf`wsn{{tMYa zu52uirur&3{Y1~->}yr!Lfx(1t)XUbZsZ<3qz)I2DNl7U|LC?Rla5T3rDnM-0tyLt z_%qD5awDp0+YfD6R8_{^J(IKEI>Z`v`GgfW--eI=>&T&HA~B1Rc)@kS#C z3B4g&0ts`Zq=-rsP)!PXLOO69ZU|l!pzzKPA?qQ{>4SUW5Wu=^5V1=+VihXN}B_#T_ORbmDy6WGsUtTVm)JMPsTH?#@_8Lg9H zq%{5rAr7Ja-gjA3%-~A7%7+X3_b-Z>T%cPD{X1O!RvmE;YFKS-B2Ezp2MBI#7~DQ@Y(@4zN$E-awWa*Z7Na4hn9o{^E;)R zbq{%>x8J5e$>{eN2YqXny-d z=Y<`}7Z!t#NGWowr5uo(juFT3=3p684UPHfv0E08UIxN3J%Ckq1HQ96LMi<|&n2tZ z?18k7EatV4P2Bp+uNYR*Yw6eTwt$k!z6sL)5va6*b)fe*PfuK6$8AZfBNMrO#YbiHI)$G72n~X z7`uHGee;R5g{4Kg%zP*0`iIu}w;#1ipXpOj$SPDhp%`!LLY~9NE5&`ab|qT&#CKoT zYZ><;($j(qgEES6tr zUX_%wz32r(79B?qb}BHyU@Vj0hiS}2(1J2rf=R0%WvNRL`ftJWOkD7l{96>M>0Q=L z_2Sl@e|J;C&gDWqB?a4Wf{>#ZGdohO=JdeKPsWx8`Mat`GB^a1IxZM;Eo)I_@G>vK zW$E&Ge~-8mwZ1f4As~e=jKBQGFs<_j76UaS$#P9DjSk@&21>6I!%XM%nclJAdiCv! z+o|w0IFR}bbym&YJ&Lc!8_%7C>I{_NK@kL<;ls8m@!W+a*OlOGDE$R&i6QPt`|AmHdGDLG!ePZT!p&+Ge{EopvY6i&L5BAi6`Y^$1s6)$dev z#r`OR)|z)4CSpd~feRgqRTt<|wAsJ8q`MiCDcO#AzWuiKJ1_FuNCG~g7UP^8R5Gx% z+37Qj3mitOB4=yzoWNJddbT_1t8#q&Z;W z>4Pvw+%?O{s_P5xZY-43NqNqU63ZKQ$+Mz~7Z)VEjI1~s0|>`e#oPP0pDX?fQ|abq4b{Hm+_RdGuE*z~Vs9Akq2c%+WUGKlf;l{XVRt2Y!!X__ zsdCV=^R~=|q`|A9A|vad4_DfCSV8Y0%W8ElKlHGqZVUfi30MQHwr%t*GikVPM;&eD zo`WxQ#qSdAtgJ?5RqPh(Xqq`1%%Q59LFHebh*$Y?Kia)5lItIj(}{?(d=M_ReZyKx ztRg=gJshG%MG8C8xOBk|*FVF4$o-=n#L7qH@~*wk^sVr2cP~LUbUb)aV zDO9O_PbK&=SN7eV%RPTZk29T&9vUWgJ@{mZ5GhE$pb~4-t2A2~) ze$Dyz6F0o9Xf&iGDrSz|KW>Vt*m7)yD1v3X|E6MN*<{*Z)#_~QGIo1han01~A-6z! zl~ND#c*oLH=@p)8O#8s-8(DM z%(TXA#v(%qsf9j!NeG_soSft28F zw+NBg3g)lfAMRXQ>h`@Ydv7`#9z~l>E$F{N+u@9z8Hwz3%rc`}sFF&+V|?;cMXx{5j^q#BB(!uSRsB_>tC{M@Xusq5;{4X4ermw$riq zOg4*DGAB}%Ub(P!t?SJ~z26?>YPw8?cVV2H*)GSY#H;Zph1ZQBH!1SNFPnU&;6zdX z$+ErxBS|-xUQh3{^0QPtY-chaoM%HEKMdS!&~ZdSF#I??Gy`)3`_c0I@E1(~iVD`! zzy|DHx~<^(>BV6eB-ujs@wT9J#50+bxAyiF^3$#tG*tePg`|#^JaJM+m8_an*7Gu1wxME>{YVCu#es*>vYbQ zoUC?z*_1}Zl5DNu5ic6@-fsMn)U|a?s>V~3!|azH%0a>_lywDX1v_xGU7j-zak9T4 zubL<8HY$M^a~uZGEo9T$QmM@-#Mei@N^^uyKJMfkXt#lj8Ocf9d+$#sNFJiav8`#= z@sGA&9zDTLlOpQZNXhE77i5E5sH=f@p#gOD@HfhZxelw`tv@|$Zwa!Cmr8BtbCs0pxZ3r*--N=6WhhQc<$ihtU42T}BK2QwZ<6-QuMg95w{~-n zo+n*5Jq=HPB^n7a$AsN&D0DXILz}*@K)S{A+Z9-0FsE9ucBIpStoj5(s$=?J>E{$B+jn>*+^o@)H zRNBKRD?=F>O@UB~FG?MW3^HoMC|3tgHTA9RrPCxspH{a$t!aU2Yyl#mv9KxCHb>CK zYsO=kHl)z2`}*a&CJiA9DTD41zJ+7G3iL5E0Ye%P`r;hS1~3-4d8y957h z#J*@mLs#>vrp)Uq=QnCTt_vEY2FlZHBwbGr8%-m5L7NR~A-m2U?PTem z8mJQ=Qt;x?S?J>kjSgc0Y2256vk^lQOEbPIV3F~e7x62kgfaeQ%zJTWoc)oz=3wXYO=L>kgfaeQ%zJTWoc)oz=3wXYO z=L>MY0Ot#Ez5wS7aJ~TN3vj*w=L>MY0Ot#Ez5wS7aJ~TN3vj*w=L>MY0Ot#Ez5wS7 zaK8VyalZTR^*@_O|B6B2%I`Iai8c*w(WX}=cTdOR6YiIa^+WR#WlL34t7-yee;h8m zAPd~VXZ4wWC4!fIRX%yRt&qkz(>UXYtJW&iEFQ5kt$QV)HgI&3o!0wd^J-=IC9Guq zy=flJInoI7VgFs#C56%E1;h?g6(#Py;kGD`1@HZ9{%wd-6nOK!dmhF2-H|1Eg#^EO zH%#jz&!lWe4vuZn#oz8U{*0~8Y<#aWD^UQ)_l}`v7iSYMzT|{K z4nEYy1ip`5RqXHaOqmW*wq`?j7rgc`JANv2drRz9W7baCA4MX1hV5B@Gc*n(aKdQr zz{vllYUUDcSXSSu@N)IFt<2bo>hKoL*H!~sf&D(*8lsfBb9i#O49niZdFex&r;5W6 z)W1eg2bpT{ST8R~K4|^;$(4g!`=Z&wR7*c!8ybAiw$DJ9T&4M*HpfZw~hK z-xT)uBaQyP6w#S*vPz0`{lFjolX33wU$P>wMa!wU*FxK@XWL-v4%olGg?W(vK-*xo zgAv&t4<)^SudzyB;GG*O-2qXNX?qyY7g*@7WewSO+0?@aq zq0!y&*5@+~s2e!B1D3Pa)GhdWfI#M>fh8pDsCS>UMpic{Q=J zGzS*1ge~(Pstvo-l1djE{Pu9RCP!G}Fc=Bnbld3hY+RN5cHGuUWOXQ_Y$cUrzp{Wa2qM`B-b)rN`}Dau$i8N9>(K)^_|W^Aiq;di?{594}WBG&tW!t4t6nd7)a@ejBM*t^eMLZUFc9=W6d2gvJ_`#LC_&bYFq`Q$=P6*Vg4C$WeNxCT%M`Yf7r_xy=nAC%x z(pXdXJjxM@HD(SO>n)0>af?Z}oCi9sO$vYjyce^1)M;qMzy4E`B-A;D2wJ zDzS<}v?0@`jp)h$yoQ^MWr795vp7Nzh#CpL==wuLMDCgNZ{1@ zSAG%HHrU;ESL7$CKWRo4A1>!AiQ8WnbeEF(nq&f>`SJ#jSX+G5#*aeOw#xU7JuJNg z&yh9DaG>UI=R-I~4hb86xK8_XtMS8iSF=EKf_PCw^{D{SajtUtJq%y9Kz!oDbB3C$ zMEK2P=VwP0xEvaJ*=AuKLzDS4ElWFkwIUl&~Z3PJpGaL&XsT zOB;xc29P{);`1-{pEZlK+jP8t?}ktRy=AA5LLNTG=(g}@++zyX7|F*OHFxiiz8?*% z+Kp>P;K!ZO*@?F%cf;C|k(NQR;w2b)Wb)S&Pj(ZX)B&|~P*&e<6dkOC&Eszg23FrT zMq>iybs+GJ%B?mY1}>@n@>{Ci!JRgYW+44B4G7fo&2vx*?`g(4X!i|hLkN*e*A~cp zAE^>JyLboViFEtJ(eubN+s?7HI$*3t{0CqYj5Dxo(hVWZ(W7Su+6uIGVT~(mcUe~Kn<%V_UU9Ewn_MNhSXgS_ z*xFEVO;~#f{`esJ3(|s5hSgnQKQFw+rTDe%)3$2|++_i`t{)10_XSm43visJowkSY zN_zAThbgYgEt0AQo}Gi9s-MW7gXaJIu?In65k9R^>3WO zUY(qtWg)hvlY^Fo4&sgpy^5n8-`3Utv&ehb_kU+Tt@;}uQM9PJW?>0)obRMtn16(} zxqb8C_F|xHuTVvyVB7|!5tAq@`j4K~^l4V#OqVepZ(KZg?PdcNe4^OpxmdfPi6HCF z0Y~U|ZRqtJZUv@L@ff`rn0-B*bnJ)1h6r*kQtrel45~lQb_qd{gYRN;kXal(tFbfW zD+GpP)65Uojor>YA{6g2RlL5wC;*4YgO3=F%aQGJU!F_}(t>3xYd79ev$}L11Uec5b5u z!sDK`x!{JuTjpA)H61FrbNa!L{xxh|gR2XE*p3)|=LB(d&*JPwx+#Dc3l#fCWMgCh zu{0&H_=V2meM?o!PjL8W?G_v*n5>z|9UZFhS} zcLeF#|4V~gG6wr1Nqa#xyJq@)miKzD1hUMbs@3&f_LHwATn@;ke?5ZRD)Jd5 z_jl7jTR5?nuh>P?8NEQp6BqyPS}JcntEysR6wl9{%U^O_t7W`uziRh^xkZIEGCTDC za_{NE1StR+0R1N>x2)=39Td}jEeA2Dx+J+{t)5rsccwh9hg@ruM)CS@a;z$HmHc~^ zx!2TqQnaU*o_+s~>^qH#soo)cv_T>xF;8xU@!ZH74fkJb_z;@Z@@=5w;VX<)*)Q}` z3iR4>G;wj@H%8zgYd#)L8s%*NQ80$$-9oN_RF08;sBA&;ihfv)^MU&g{QtCfo?T6K zU89boU_%t8iBbear3wN{RZ)sa5tR-RX;MN&gb*n~QBbOYfYhjfh;#yk5{S|}QlumV zq$?0gf`OQJ_VbK!#(Ur2&c}D0vHw8E%GztMHTSiyIcMr9ej|a7cE?8Y0@bw|5cCVh z!89OAu!cnR$NIZmn)J)~83G0BJ{(lFjBug?eV4mj1QTgcC5A~aMNtv1JGiu7K7l8* zp(*n<({iYcDfbZwkzp273^Ypa2zL2=5%rr})c#j~;x+bqpBzkmBie1BF`Y{@qRVbY zs704gSFoX8oigmcfQg3^9Q;?|qIf z%V-#Th;d!Pc|J6AHCTHrey)!i!YCnz^JrA`H@(>F{H zuac=QLEEr)a>ED^owoZW=6q~iH`u_2`(6w^GfR;>hSbe;Q5y_(Uz62PA+%CcUkB{5 z8SfsbSG#`qvc(r}p6KO_0$N%kBSE%{idra3Z|DVj+5gT84y1?C2YmcLz-RSN?~`DN zt)CH|CDGyH$#i<)C=~Oy_l0SOKO*U-P8%alRz%<3pkls z@Qzfemncx<%zyq1`B8YMEiR!s?8Ihi`sY5)9q-n_{MEdu-n6SDyURVv==-X+DU%_M zE#m6OfvqI?UnlU`kGH!P*r$fJOyYL`2)UJb^-Fh}-!yUBv8TZCyxNcEYcf^Gh!;gy znW@)K$KrgFo+F~Xp&PG4fHvMankY6O94J!bARG5*y&@?0P7OmgbAc7JM5Xyo7uXqZ zhorS@%&niC)Arzc`|nxCMYRlhYBnruilrgN6a4~MX#YbR2tQHJ7vFPRhmjitCsQlm zBx$@8u`w#P_x_<1Y)^qpL61U-6Hb|~4?m8G5zW#|ZejK$-hX3{X>~vLrTa+alP8lm zkJKpWDzH+A2xDxZ(u(Tp=*BlZ!`hbvIir>kHoZDZZ14?S^YIXkS9RcvS=F0y_pD9CRcvY( z942QvcG-l~#>&fdFNmZ1=1)K)@iPZ&L+_CB1n9$;#o7s&&;*Z+%}Hbv939XAp9n;T z*)8>wa4-UK`X8Sb5PyE+0Qa=+{tu+=CLg#Mxt4@|3dE*Bz^FQ6-S}%taD%r!A#c*@ z#sj$Qu#bDaQZcD@anar;=kcK8`xsFX+iio-(NX6Gu6*5eymOa5MA7q`r|ZI*)l>~+ z4wR*u;5)4}eVH8pM1g5Ie|3+z3u(UPqGr2Q?CR%v+q)hSfw0pu9ox zBaLt0(35^?hFkIKU{<-kRT>h~YF!qav-~pFQU5%F|5k=;<6G*TUi-bbD|?-ER?XIO zO^Xq{Qukyf-=MzM=z7ibVE2enbW-Q_+i7u07G@h{Z}w5XC~<0sIS2BM3}j}@7roeQ zYNRP0_X?bLrH0@uLfBz_#H|%$-SwoMc4p{YmPhQ*$Nwzo4?(=yd1@+a9crjQv@Qi~ zV0g@$bL85Z6ihkecu)phS7a_})RNzT$|bj7AO`ldpv*hWbl!Vbju9+3>=Sp~E`sPe ztX7zMKS`edU|>*ebAau&BmME2udfNV6|~_i3HHFI->kP7nlUA;0Te1EPtUhktP@!o zLu?N1bW@8v2ZE)CStk*J zihth7ObaeJSyK_ijyXk+e?Q29HJ?JLq?PNxdy12;c{`zs$ zV&U3nVKKQcUTA`o;hHvD7Kn9Ho7~q`dw>!neq`bTV@ao?|0Vtv^OZ!zYB27dOALab zzD60m6F4m~2fPu$(f{ZD;j}hd#X~}tVAHYKI877b(lZRnP&2)n>b8-`zBw*|o$TvY z6EBRu6je8KNxD5BxDw9a9uusRXB^)zdYSDgd*L}@s>b{%v z=?Ylc-9Kb_7cMU31`6)>6qfig#v&M|>0YhU%YWnfRo{W5@63wMUK1G{_B(rRylMwa zvp4aE?iex$F!n9nKxr1o2Ijj_9R>UNrUg~?Ks{~ZXdYN8$wG0V#w|A|G zF=8mC0mTZDLP8qoht%zHpJ^aaQ0k`G!&C5ma8)k|j8$-B6gBA5STS-!Z!3bgWXrPi z0}8R2Qj-~UL*<1u^I7)Q`I3N`88i?2pd(2%7*5U`uD&dBr;v0o0&%3CA#87614GC- zQ)ZRR)8yWUav&OSJi~yxa4!^R{+QPQJ9+3w!E%ajA5A9*sK>ELZwehH+O~CU9-hkexG8fr0c2TyV`?AZZy0Pv*Gy_dwnOwJEMupuLbS})?RUypE8`$;|&_SHR7HfqBg5# zNwd^nG-5R4rD{Zyv?nOD;pqxV1D zOlsH3>!l0Br70(tXC(Ov`^g!+v@jFNqXtgu##gmks>^n+>z+gnoYLb!c(7<@W$Cf@ zzuV)u+l9o?1%5G%IvcjL<_QeJYhgl)_ojEI?X%i{xquyi%LGxfrrz) zVI-^;LR~cupdLJJH1Q3L+F7-A4aTw6)C->CZoo`b;Ra66E>g*2VGQ0(_VfbX$pi)z zGbvewUE#%p{1oJ;AU_59DacPjehTtake`D56y&EMKLz>e00-g^@>7tXg8UTZryxHC z`6r3r+l}*$H!wIpYlCuOV;h3z1V;es)$KYKNVXc>_M2-ySQKd&^fnB zjh~dKZ7B)NGy1n{FxBR~H$w9rZb@OIwXV9s1rkU63trfYsGjOd6{vB~T0ftJj6eVx zQu`IQnUpFH1n-a9{2LEH;?#G%9B1{xJ#O|v;o19@=dRsNf3;`#BO@uDtG}-Z>dHI( z`ddchVM?sPg%W}OhJ~z5FM>MY(Q2$ZA<}sh2!0DBdJr`vt1CO_;q;lbfRAmTJO`&r z>TvPxgNnVWaTg;O6y|Jux+VSO()HKgXhr#yyZ4)Cc|Xl>&!j;mB~e=a(@SzS+IU%C zQj%dTJ?iqn)mVRYZiv>#*tH)_y$@bkji;3kUQcz?KRZYtn$dinsz@Frs-cV1@n`(o>{65#~d@;L4wO5VuEQk$vsd@!#y?N!lnemeFuB}~* z!#hH=PS~PP6{qt5{E2I8D^lzIOUtpdDQQ)wA`>TiI{Gx;bP3pjL^4I<_ub+S5K(E^h8mijfGm(|qH!-jOq>hSfBrnaq@~;WzPgkn;kS>t?7P(d*jxt!`CTQI zTQV7C|9mvq9CIm7akXvr!CYUIi@bpdtu~==w`s%yTLkZpc20$x3$P~NjKGS?cUo>k zJ*|)JBhSWvx3zC`4LJFGM$q6^$78pbu=E10?!27)q&`%$384LJm*}lePBX)DGh2l) zH{_KBKHKI7z58-gSU%b!<;;U!kSzXv0~Sv$_9ny#Y3dfSo&k!uCb4ZEbuagkccoR?P*yR504Cuu6)wN3eSuuh$3IOR7I5uMkd!(vd$!E^fBd? z!_7qtM&98WqPz$udFjMdq2DJ~TBi}TpNge-lM|G>h*;OeIbG*||L-m*mwrtjW~zO! zM)Cc!E9sjmPCJp4@X1SP{~nt60n=c&y}d;Tx}4=>_;JNj)=7r@ARhjy5@IGrJ#Gr! zYT>chB7% zy@Quxf>&~~_Gs@6!E;VRn0#8)kN?eqf-0;)#tC< zSdf}3=f{hiaz$uJ+N%2m(FVVq%TAWWTo{bb+03m8wQB; zCVmURbJVJVcM=;si3I&wl8NEal;}hGZ26ycsZ!eh7*%)sSR9d{ z)v*T}h8BmS$yNkclsh#Ld4!}GvaS@!fvC_N=DHngiLo{QiQ&ACXBf>^8C80OM4QkY zq1yNbnIXcg@~lX%#bAEk$6KbDz4yZJpFGLiGF%*x5JhS2b3U9Z$uHn67$q;axcA!Q zDDwna&s#Nz4Y-58(ntGcMye%}lb$adFC=$M8a+trdod;bb@D>wp}@^7s^gEiSgWHK zm8)V)B;Tgr#Q3^hmpFc1xcTsftmJ_!5KY*=Un>a7(Vx{h!>j#LCJh&Yg=?wK<2^?s zLrbNoSCa2Hr#iNT@yQn3+5j0{u4!mr5g zd9FGl_I8)cY+HZA7Y^h;s`+ruCQ_V$C!i;4N^*UvC`?$}67tPBt3b?gIxRt?t2cdO zF^=Miu#>lQ&px$G%4_h51$g z^UakuCO`{wxqUz86PqC-lMAnSl9nkGCXeIpOC5P7U~^;t%aCuv5#{^)O&cv6??!d4Y#`0AQE7DkNn{Y!xu2X#QC}kj(%$S~EeE%-k%WV? zCYKc5UJaaF?VU)S;}>3gum}D?`GeWyp{2!AKC7K)#Vg_-jC(cd;uAPh&UFnFv{nxZ z-`kjWiIV24KBtg1f1k_RxM?HhQwbj}&r00m<(gET@spa`*h`TQM=pALo=raH^X=Y! zVICf?M_VhWTxOURsy6!hudctex$&Xr0R5Ro)`#J_F2Ws!h{R_(%DMrV*8vJf((~`@ zlaQ{ISC+zg7-x;w${L59BTnHww8sKmTm)X29#W8!Gjcm)Sxij7y>IVPZsmwr4{7Rm z$l=Uunwq4}fLaBQDu(K-p|xJ8c17*+K$TW!wD&9KcjpSz477vG2|!8sWM&FKqh zu%d@Vpzy|Y1#-$&UC*;44FPN^6#p}U$lQ<&$I%$U^a8`DHH#E2(d!K=b{B_4-(urD zl+M0vEp_9ozp{3!@>}L*UB8m$v=i`&kQXnR>sqL+(s2qiyCKk6Om*?(k`DKU{;;@> z^%lqfw1Iy4WsyeV*?;ndU#Cub)npX0!&Tjg4vu2^Np3 zt`Lx(53omweT#GlZ3Zs#{~Na?2{o4uHli$JG9hK*@jb<-1@ciiE*_(9+!n7KZf8q8P6hpR-r)D z#7yw3HA_c@QmMm|8EU*I>~2c85|!`SaPRu|by7#O!)6C1e{1x}?*~UEt&To?+}w2k zuI$+-JCwk;J@@DDd&Kf(`^-9;Snj`kRxvNfR{P`e$aC`Jrn9$rTZHd-z(oesNH*hb z&d2l*8g&ne7{4u2U%Y#5*Y&Hh5_2M5+FKB=Phj{9hQDC=3x>a7_zQ-=VE7A$zhL+a zhQDC=3({YZ{(|%uq`x5j1?ewHe?j^S(qEANg7g=ZA3*s5lpjF(0hAv=`2mz4K=}cb zA3*s5lpjF%7j%C?_ZM`3LH8GQe?j*bbbmqj7j%C?_ZLik15@9?)Hg8o4NQFlQ{TYU zH!$@LOnn1W-~LynzU{6lb5USb>K`;H#uVpu+gf{doIi3vDr=!bxP8~sd&Tk{w=ysF zM5n8WJn1{gOe_1CA8(M4ai+C05ubYuj9SodpFeuc4Uf)j^)6@*xd1~UP_)k0!QX%V zMi%Y$PS5oQK7*~@yncqESv6%tSbdE{X4)x;w?X+YveXGf3)OO$XQ)`>-r!&Wz@;wu$@YDmfPy^XkXN}PEf3iMeWSbNQZJOh$=nI&b{H?Ed-@dqnmU(n)N1?E|uLh8{t0&7nBb>6V&Yo_ae+T3WxBs-D6v z`;#i4Eg?KVswrt-8&9m5ZTjYFQ|v1>q7-L$%O+;}dp*^EuHPErdt`n-E3sY0@G4KV z?wQFCNkoR#4A*fbo~V-iD?ho<+vzBT-xo2qe9Xlad(_eVRY?{8z{HKuJHjp8C2B*q z4m$ZgUX)XsMg=);-rjZax$7G0v={y5MMZmKjRdW%A-_TlXB13q>POsba=Q4}b{)Tl zl(n*AoVH(R4IB#BD7G8P^*W>CK&OY@-NQn&R8co;*E=G~<}P|iy3qc;5z z$RSPp#Pm!odiZ44I3u3ACB&17C-&~`FOeC!7ui{*wfuBe35pvTu*^{p{LhwXrx!W)i&Dlq{+1s zpWA50R=LJrrW?ufNVxA5;tc9TWLpHswhkr3fpinL%nt{dSPrgL{Vw&Wd{PzOcJ{gPM0!&vjeb?8>Pp<| zVVgo?axAlYrhYmh>|6^Cy1G(w1B;wOq|A_Y=R`x$N`X(()dP~n5eS{Y%?BN`90Y!$c1YgK~D*H4?`a0 z=MIf??|PdeE1`Vpo509UG|^t|lt}JbGO=DZmY0DbwU&2s_L5G&w~+@aLSK zLJ&{f6YsL!D0h^17u=s!M4QF{ox^d-J`6sfySWzmwk?@N>%=wYxCeGMxC;&)zck* z{N2x*TbEDDg@1E=tA54(WOlIkyRChF**}qPiGAJG<<_wV%N3AYB?sZ8gZ#Tw2zMi; z-{_n-Vv4xU#M~=y97>{1n-4#TmKhcFeDl60`mVAuQcBQ>`E}-drSrR=rG_F3IhlpZ z*{z5hJSmGev{H5%hemb1RWy8Ov$WW9Xfqn(4({~eP7m(%;7$+j^x#ep#`IuJ561Lh zOb^ENU`!9v8<5_B^ai9iAiV+U4an(1P7iW=kkf;l9^~}kjVyR03*N|rH?rW3EO;Xe zx;LPE1G+b$djq;RpnC&amY`(`T9%+?30jt*WeNK9pid9_^q@}<`t)Gt6qq>$W=?^b zQ()#4m^lUJ$bvbtV2&)9BMaupf;qBaH3YC40$2?JtcCzqL-=1+4Pi@Z4+qj|HOKx& z;Xr%^EG`ONC1tx@F>V|_68prO{txzUaH2Fmk`ut z0wWGLNujU@>o5zUxmwI;cz5+rt7WC9G+P5It2mIaL8zVRS*c;gKc?=Es<*Jm9Qp!+ zYG}(FU-R!%Ke<_@&%WHPf2{n_vT}p9{5>O{;Ey4<*d0|#V~k^DUALA2Jj0}`{nu=Q z`*gWU7Sfhf>k+6P>k3TCKL%yTfxHZuQ~Jxrfh_3XT2~2@ZrH2LCHNuKQ!2 zmFg*vpPM7sOnIc!;2Nx(uxZR9v9U^T#*70QY2!fNCb0inE6s>hljT|np)VK6tj1n( zZ>ppa<&wGj-$hsYO!ll+ZX$HSKW^MdS;EKh#+;)|54*YXM?OP+y&7rPRYhEaQ8bX- zHEj2g!U}9y?jk3|s?C;y1ftY6~4P{;A8+5M;?pJ`wYqmzC;9AFd$#Qzm1--w=cYWaM;N@L$( zr`x;ckB=yj;DYC^=YVzY5v~V6za%u(j9P7TbcltAWc-B9)`-M(3=dW zDfaFhNIe5dsnwR+JU!8yI=HO9cK1V&+RCM@50cTT33n{-rdZz1;(tFUVbyFrD{AlZ z$=f=O>|beArm&{QgR_;Ym0vkoj6K$(VhlgeeQm8lwC9m>H{{a5&P~1F9URCR6eFkB zUR1AQCy}YYh|FA9J6Kk+I+1|Q@1~<3PEJso2D8~$rZJ*-0rSA1O`{00}`ce5Oyp|J)g(bIH-FDcU6V6+dp4RxEHJ&Q8litV{#}SK>;5QETX; zI0g}=s(W{wh<86sZixKJ%eQj}|K|c~L5c%uN?FoX!UOM`N8#1?bSM$!T@ho=uuf-X zF3`7y)KMN3b?Ev8jZd|Ynuf^4bVHT=*Sx|`?%)+-e|z|q9ZaR*FyhhgU+-k7?7Ugo zC$#O1p&p7YyZmk5>gY~1nBb1N!vCUV>dE|hd=!=K* zD!tCPFCig1!{78M0<|1Sc?kaR9Z$P3+3y->%1T*f;vN+l)Ky^hF?dr$@Ou69kZGxP zx&j783dPYK#E$MH`Yf6}{3D7uZ-G1UC(F#@O`C~fVd43kh3C!oTll?^bC%xZEvXn* zE9#-uO-Ng(Zl8#ll24oz42ZX~e?5~fjZNW_L^#GR)C#*nPI@q9#JA=+kYqj9fP+FD zdYoy*mZWO;ZD+h38>jhn5Qi2*@KwG(bm+r=4nzVz5m(qCw-RMmamuGeZ$nI+eY*D5 zR!`#{+q7H53aw+Rj!p`V^*H5oHrl__?|lmTaOF+wDwk%g;*TBy%Ux5y(8u&D$kNs9 zR)%@{BkDSPh~ig&DR%<9%D*ILh42+2#pQ}<JItAm5j>GmqDrF=`t!yd}<)_=F1WFrUDQh9%BH03AXj)}a)CJzlu zL(!r#*{^AQ)kr`Wz8IPb95|Mlh*f&cI_zJ|q-Vzf7JPGqhIU2Fo0K0j4S%7C)Ujy06suHG7Cx*mL zg_7~<=<`0nrppsZYI(#R#XL9t_Z^;+$bn4Xq@snUfeKWZk@w$2%ES5@jO53Xpp7cd}Peb3tf>K)Dwwm7I5NK-q6HM*c%O0eYZ9<#Hb*8CEh*7|c zgN*VCdZdAM)=!G8CeL5-Hl^vE=g_T8jZyq;Ui}GG_A<$(DT;CX0amDdd9MO*^To#^ z?#iFDw8O=I|3uRdOrpkhp{#RNC3?qxi6&c*g^eX(#`};osMIa2HqX_?>@$w~NlvDlN|FT>gv?~NV=?#eb0@>+;4gseX?e~lieB?0Hf zgV2KBTI4nd;)%ROI(d7QNZaq9Ag6^rwAMZtn*;sFsEVK)r8qYZRQvL39Ao&uzxI~ z-05MVZGGqoW?UMB78=n@4Ot*9u2Rx^CK(y%C|#u<7fstq@8$(unS96W=hIZf`f@Y*`Q4MQ2gZ%H^}T(b z^m$QOrGdr*3ytmXw_~Icd$V0qQfGtQjTIKIbUKc?Abq(-uJ8aO+;7Ob1cYD%9Ekte zK^G629>3nJkO9@u#SZ~Aoy-fgtvaX@Su|7EnxKmB6s5U%`F8)E87?-omosPbL~$Um zN>A&ktlu4V8@|}%r`9`W9Rv3*J9NN`Pt*OLXUXf2l{O#e^Q~ggH%hn_~_J=Rb1uRA`aS_`aw!pL&33*87MskhtuK)xTXu7|jX0@|YU5YsNOn++FT*;eIMcK# za9Rq14u&s!bZ3epR=<(3k0y700763q`;!tg54AmnTWSMd$Bp7YXD^D&G2rCD3tk+E z7gY}vg4dm099U%=^spGZ-xiTgkJqSyNRx%N&_44Pw9rCDw)~pf%;PERRF}=WNA->w zK>1HO?`E&6hdC#vEM3xm-T#aowP~`=mPlir>HAZ*Q>Yjj3&eL=oAnRu5u1{>Rn2E6 z`j6sHM%u=nm*tb-sSlE6U&D&i_|iC#n=m=^*1z2IKmJTt&#lr_okG((fhXl$IgqwQ zpH|A859L5)YT%4AELtw2S&v^Ks|zNU>$`AMD8dQzx|?6P@Zldd8RL60#^25#R@v(# zog-OYqv>9a3>(vuj;2h_Sp>HwX+7_+=(5x*q9GREv6sSl>IwQ-`uNT3jDmjw~`g&&#+_t>G5>==Z2pNlF2m ztAzt;wLU7Aj`-rO@;k$=He=Dds11v?j@&E9ldXEbYT)=;e&OtJkJ~Q^bq~H=(IOq2 z^`PnGrQ*lYo>WCK;ExfU%rp#-2dlH5JIruG%Dyep8zyZ{ujkdv;BK-LFB^wyRjN{FlKUR|i&0GW!V28)a@7yv zfJk${+Y2!X_9%OR{hf*$)cr;)lU>Ca0~@ZmIPAYAFOm4=JH9NmI*I(R^-_o0*H`8V z6dhevQi7A$?lYk2`|yd)NbH!Z?7*S*qokA39Ee<&Z;SIYt_RCXCsxFuG=a4er3q|k zQTM3nr)e#CV#7cqma4j78rML(zecR}g?tAhL@f^Fju6{YkMY~de-?PwJ62EMp5RMi zVQCG*)u?JDdZG!6_Vf3kp`_f{EcAV<_Vi^0vOznavaP#VFf$NC8ShU;oY<&?%k^B? z`LYlFI#=oS;qqswKv6yA&TeX!*!86=9Eko-KJep5OxVfpf5(`2)AS8cB>Q3nFaj@m zt6AEqEBPO44dV6X+XaqAz2PEhALBqI?GqS0Jse2BdP>1;DA!}Jzw3leTChl&ZqI+# zVflnd50$x|)<@ChK$3_WtTEz1%C{Nu*Mj}7H3_$|kMj?HE3<7~*njA5)zgfmye~<4 zKT~)_cGvKmA0ZG=soLT2-q}evkZ`}xIW!(eeBg%-O`;by%fWL7rS zK3O?B+C9vH?3&>~X60Tb+-mrtmj0wqWzy7N?Z+&USk$sRC89q)#m*!8`2Is?UsXRh zlN~DVNt{}Hy{XC&8!~~enpe@&_<(;#R+1IXjn>Hw`=;o(;1M{JJx;ig49vmXK0^)-dUO6DjEmCSfp+=rc-}99BWB%;)b&9ApwlaCOdFr==h@Hu!uEna$2}qge zHN%+myGWi+5MYg0HuD`pd82@L?;#TZ3w@tqolLXMtK>iu5^9LZ9FGay^q+Isuz^fp z+P_zg1OA2kSXCLHCru~~OWRfsE$e*&?VQmjuB0a~MIv6U#os`s^lLbme|=UFAQTsn zpu{+^ZI!l4Z2Ga_eP$S*jqM~YO%dtoluzv>hB5*+sR4P2YR(3tgcuIwoH%Q%?D8h? zxS4V~H+gxw*+hn8tR0?!8(5uI8mO(qF!U0EQHBkaBXi(c4LDMcB|RAJe`jNZ$FL8oa_jjGfVQWXkHeKU?Vu~MEPMRO;lEHlzIat36 zSNPg;7BZ(dbCv@s5ND=yAZt%&e05FjOh+Tje>+jLcFhJ0` z!g^t*6Ho`ufLAMSo*YQ{1_!b!&}#U}aXF*6-aXy-OkKwECXD=J=Go0}OSig9RND^h ziIlxBGU6OqYV+mI;qB`?>8u1#nKGIzA*3e?jiRL`#|^fL_2KWQU7}zb9`&!ngu8$_ z`RA~zf%9&q9lFH>;E~7_An5&(HphE{wtnlm4)>ttCj29kw}?1lOml`mDLqsoQ|n5# zJK2Ft`eyGrkpfw(DubY!ZJl{u=~d%_+N)LC(_7}{p+#K``5{|WM+Xsx8&F-Hg8zL> z#C!B%ga5r`N}n?%3irrTIrWus^+B)E2E)gyCs?_mXJ&`28#6 z^)c=B9ciqW!9^xC*t8d=I@uH3Pn`FquEUtU^gAxTtan;9Yog&7|4oy!cA7X4wm?ok zcfzIfx%Y{0QPl5I=(W5yX!me*70z0`Vh=A3^*G z;ztla{tG98_z}d9Abte#BZwbC{0QPl5I=(W5wssc`w_GsLHiN3A3^&Ov>!qH5wssc z`w_GsLHiN3AOEkD3EGdK{RrBRp#2EikD&edzsY{wF|CHuw*lLF{RfcBA~SZb*y|2S zXRYA$H4L3svDNy>8WwvX!rN$|>Q&k!s*16XKqus5_$B?>U|PE~W{#Z>8lp|otu7B4U=@P0N4s%zwws>{3+y_y1^)&&I7nYN5ol2yjklKGQ?K7}R{62P{<%vMAH(H6bCgEP zeD2q!9u!D|YCMXU3y*LeGEhFTKuoz}W<$-o|41`bc9t8H%gSA?A-JMFDOmajNnIPQ z>N}uFllSO@O^~t&GqLJ*cg(vvRA8T+=@nT*sxHXz}s=zJ2Zw zq9I#oa5};11Xm~cvV(gkxOaj_5qPqLff5)ffpI4o#)Dr);72C-kqLgYg9rp9N+3}J k0UgLYL3|A2W01y!mmA<+5qR|mUcG@gng3s3409&_4`(axvH$=8 literal 0 HcmV?d00001 diff --git a/src/c_imports.zig b/src/c_imports.zig new file mode 100644 index 0000000..7935843 --- /dev/null +++ b/src/c_imports.zig @@ -0,0 +1,8 @@ +// Shared C imports — all modules must use this single @cImport +// to avoid duplicate opaque type errors across compilation units. +pub const c = @cImport({ + @cInclude("SDL3/SDL.h"); + @cInclude("SDL3_ttf/SDL_ttf.h"); + @cInclude("clay.h"); + @cInclude("stb_image.h"); +}); diff --git a/src/commit.txt b/src/commit.txt new file mode 100644 index 0000000..4a7efa5 --- /dev/null +++ b/src/commit.txt @@ -0,0 +1,2 @@ +HEAD +dev diff --git a/src/config.zig b/src/config.zig new file mode 100644 index 0000000..3c5252a --- /dev/null +++ b/src/config.zig @@ -0,0 +1,138 @@ +const std = @import("std"); + +/// Launcher configuration loaded from and saved to config.json +pub const Preset = struct { + name: []const u8 = "", + username: []const u8 = "", + ip: []const u8 = "", + port: []const u8 = "25565", +}; + +pub const Config = struct { + saves_path: []const u8 = "", + active_preset: u32 = 0, + presets: [4]Preset = .{ + .{ .name = "Default", .port = "25565" }, + .{ .name = "Preset 2", .port = "25565" }, + .{ .name = "Preset 3", .port = "25565" }, + .{ .name = "Preset 4", .port = "25565" }, + }, +}; + +const safe_fs = @import("safe_fs.zig"); + +/// Load config from ~/.lcelauncher/config.json, returns defaults if missing +pub fn loadConfig(allocator: std.mem.Allocator) !Config { + const base = try safe_fs.getBaseDir(allocator); + var path_buf: [std.fs.max_path_bytes]u8 = undefined; + const config_path = try std.fmt.bufPrint(&path_buf, "{s}config.json", .{base}); + + const file = std.fs.openFileAbsolute(config_path, .{}) catch |err| switch (err) { + error.FileNotFound => { + // Return defaults with saves path set + var cfg = Config{}; + const saves = try safe_fs.getSavesDir(allocator); + cfg.saves_path = saves; + return cfg; + }, + else => return err, + }; + defer file.close(); + + const content = try file.readToEndAlloc(allocator, 1024 * 1024); + defer allocator.free(content); + + var config = Config{}; + + const parsed = std.json.parseFromSlice(std.json.Value, allocator, content, .{}) catch { + // If JSON is invalid, return defaults + const saves = try safe_fs.getSavesDir(allocator); + config.saves_path = saves; + return config; + }; + defer parsed.deinit(); + + const root = parsed.value; + if (root == .object) { + if (root.object.get("saves_path")) |sp| { + if (sp == .string) { + config.saves_path = try allocator.dupe(u8, sp.string); + } + } + if (root.object.get("active_preset")) |ap| { + if (ap == .integer) { + const val = ap.integer; + if (val >= 0 and val < 4) { + config.active_preset = @intCast(@as(u32, @truncate(@as(u64, @bitCast(val))))); + } + } + } + if (root.object.get("presets")) |presets_val| { + if (presets_val == .array) { + for (presets_val.array.items, 0..) |item, i| { + if (i >= 4) break; + if (item == .object) { + if (item.object.get("name")) |n| { + if (n == .string) config.presets[i].name = try allocator.dupe(u8, n.string); + } + if (item.object.get("username")) |u| { + if (u == .string) config.presets[i].username = try allocator.dupe(u8, u.string); + } + if (item.object.get("ip")) |ip| { + if (ip == .string) config.presets[i].ip = try allocator.dupe(u8, ip.string); + } + if (item.object.get("port")) |p| { + if (p == .string) config.presets[i].port = try allocator.dupe(u8, p.string); + } + } + } + } + } + } + + // Default saves_path if not set + if (config.saves_path.len == 0) { + config.saves_path = try safe_fs.getSavesDir(allocator); + } + + return config; +} + +/// Atomic save: write to config.json.tmp then rename to config.json +pub fn saveConfig(allocator: std.mem.Allocator, config: *const Config) !void { + const base = try safe_fs.getBaseDir(allocator); + var path_buf: [std.fs.max_path_bytes]u8 = undefined; + const tmp_path = try std.fmt.bufPrint(&path_buf, "{s}config.json.tmp", .{base}); + var final_buf: [std.fs.max_path_bytes]u8 = undefined; + const final_path = try std.fmt.bufPrint(&final_buf, "{s}config.json", .{base}); + + // Build JSON string manually for control over format + var json_buf = std.ArrayList(u8).empty; + defer json_buf.deinit(allocator); + const writer = json_buf.writer(allocator); + + try writer.writeAll("{\n"); + try writer.print(" \"saves_path\": \"{s}\",\n", .{config.saves_path}); + try writer.print(" \"active_preset\": {d},\n", .{config.active_preset}); + try writer.writeAll(" \"presets\": [\n"); + for (config.presets, 0..) |preset, i| { + try writer.writeAll(" { "); + try writer.print("\"name\": \"{s}\", ", .{preset.name}); + try writer.print("\"username\": \"{s}\", ", .{preset.username}); + try writer.print("\"ip\": \"{s}\", ", .{preset.ip}); + try writer.print("\"port\": \"{s}\"", .{preset.port}); + try writer.writeAll(" }"); + if (i < 3) try writer.writeAll(","); + try writer.writeAll("\n"); + } + try writer.writeAll(" ]\n"); + try writer.writeAll("}\n"); + + // Write to tmp file + const tmp_file = try std.fs.createFileAbsolute(tmp_path, .{}); + defer tmp_file.close(); + try tmp_file.writeAll(json_buf.items); + + // Atomic rename + try std.fs.renameAbsolute(tmp_path, final_path); +} diff --git a/src/game_updater.zig b/src/game_updater.zig new file mode 100644 index 0000000..d76c435 --- /dev/null +++ b/src/game_updater.zig @@ -0,0 +1,516 @@ +const std = @import("std"); +const safe_fs = @import("safe_fs.zig"); + +/// Decompress gzip body if detected, otherwise return original. +/// Caller must free the returned slice if it differs from the input. +fn decompressBody(allocator: std.mem.Allocator, body: []const u8) ![]const u8 { + if (body.len >= 2 and body[0] == 0x1f and body[1] == 0x8b) { + var input_reader = std.Io.Reader.fixed(body); + var window_buf: [std.compress.flate.max_window_len]u8 = undefined; + var decomp = std.compress.flate.Decompress.init(&input_reader, .gzip, &window_buf); + return decomp.reader.allocRemaining(allocator, .unlimited) catch return error.OutOfMemory; + } + return body; +} + +/// Download progress info for UI +pub const DownloadProgress = struct { + bytes_received: u64 = 0, + total_bytes: u64 = 0, + is_downloading: bool = false, + is_extracting: bool = false, + asset_name: []const u8 = "", + done: bool = false, + err: ?[]const u8 = null, +}; + +/// Update status for UI +pub const UpdateStatus = enum { + not_checked, + checking, + up_to_date, + update_available, + downloading, + err, +}; + +pub var game_update_status: UpdateStatus = .not_checked; +pub var game_download_progress: DownloadProgress = .{}; +pub var available_game_sha: ?[]const u8 = null; + +// Cached installed version names for UI display +const MAX_VERSIONS: usize = 16; +var installed_version_bufs: [MAX_VERSIONS][64]u8 = undefined; +var installed_version_lens: [MAX_VERSIONS]usize = [_]usize{0} ** MAX_VERSIONS; +pub var installed_version_count: usize = 0; + +pub fn getInstalledVersionName(index: usize) []const u8 { + return installed_version_bufs[index][0..installed_version_lens[index]]; +} + +/// Scan the versions directory and cache installed version names +pub fn refreshInstalledVersions(allocator: std.mem.Allocator) void { + installed_version_count = 0; + const versions_dir = safe_fs.getVersionsDir(allocator) catch return; + defer allocator.free(versions_dir); + + var dir = std.fs.openDirAbsolute(versions_dir, .{ .iterate = true }) catch return; + defer dir.close(); + + var it = dir.iterate(); + while (it.next() catch null) |entry| { + if (entry.kind == .directory and installed_version_count < MAX_VERSIONS) { + const name = entry.name; + if (name.len <= 64) { + @memcpy(installed_version_bufs[installed_version_count][0..name.len], name); + installed_version_lens[installed_version_count] = name.len; + installed_version_count += 1; + } + } + } +} + +const GAME_API_URL = "https://api.github.com/repos/smartcmd/MinecraftConsoles/releases/tags/nightly"; +const GAME_FALLBACK_URL = "https://github.com/smartcmd/MinecraftConsoles/releases/download/nightly/LCEWindows64.zip"; + +/// Read the currently installed game commit SHA +pub fn readGameVersion(allocator: std.mem.Allocator) !?[]const u8 { + const base = try safe_fs.getBaseDir(allocator); + var buf: [std.fs.max_path_bytes]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "{s}game_version", .{base}); + + const file = std.fs.openFileAbsolute(path, .{}) catch |err| switch (err) { + error.FileNotFound => return null, + else => return err, + }; + defer file.close(); + + const content = try file.readToEndAlloc(allocator, 256); + return std.mem.trimRight(u8, content, "\n\r "); +} + +/// Write the game version SHA +pub fn writeGameVersion(allocator: std.mem.Allocator, sha: []const u8) !void { + const base = try safe_fs.getBaseDir(allocator); + var buf: [std.fs.max_path_bytes]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "{s}game_version", .{base}); + + const file = try std.fs.createFileAbsolute(path, .{}); + defer file.close(); + try file.writeAll(sha); +} + +/// Check for game updates via GitHub API +pub fn checkForGameUpdate(allocator: std.mem.Allocator) !void { + game_update_status = .checking; + + // Read current installed version + const current_sha = try readGameVersion(allocator); + + var bundle = std.crypto.Certificate.Bundle{}; + bundle.rescan(allocator) catch {}; + + var client = std.http.Client{ .allocator = allocator, .ca_bundle = bundle }; + defer client.deinit(); + + const uri = try std.Uri.parse(GAME_API_URL); + // Use fetch API which encapsulates open/send/wait + var req = client.request(.GET, uri, .{ + .extra_headers = &.{ + .{ .name = "User-Agent", .value = "Flint/1.0" }, + .{ .name = "Accept", .value = "application/vnd.github.v3+json" }, + .{ .name = "Accept-Encoding", .value = "identity" }, + }, + }) catch |err| { + std.debug.print("game fetch request err: {}\n", .{err}); + game_update_status = .err; + return; + }; + defer req.deinit(); + + req.sendBodiless() catch |err| { + std.debug.print("game sendBodiless err: {}\n", .{err}); + game_update_status = .err; + return; + }; + + var server_header_buffer: [8192]u8 = undefined; + var response = req.receiveHead(&server_header_buffer) catch |err| { + std.debug.print("game receiveHead err: {}\n", .{err}); + game_update_status = .err; + return; + }; + + if (response.head.status != .ok) { + std.debug.print("game status not ok: {}\n", .{response.head.status}); + game_update_status = .err; + return; + } + + var reader = response.reader(&.{}); + const body = reader.allocRemaining(allocator, .unlimited) catch |err| { + std.debug.print("game readAllAlloc err: {}\n", .{err}); + game_update_status = .err; + return; + }; + defer allocator.free(body); + + // Decompress gzip if needed (GitHub may compress despite Accept-Encoding: identity) + const json_body = decompressBody(allocator, body) catch { + game_update_status = .err; + return; + }; + defer if (json_body.ptr != body.ptr) allocator.free(json_body); + + // Parse JSON + const parsed = std.json.parseFromSlice(std.json.Value, allocator, json_body, .{}) catch |err| { + std.debug.print("game json parse err: {}\n", .{err}); + game_update_status = .err; + return; + }; + defer parsed.deinit(); + + const root = parsed.value; + if (root != .object) { + game_update_status = .err; + return; + } + + // Get target_commitish + const commitish = root.object.get("target_commitish") orelse { + game_update_status = .err; + return; + }; + if (commitish != .string) { + game_update_status = .err; + return; + } + + available_game_sha = try allocator.dupe(u8, commitish.string); + + // Compare + if (current_sha) |cs| { + if (std.mem.eql(u8, cs, commitish.string)) { + game_update_status = .up_to_date; + } else { + game_update_status = .update_available; + } + } else { + // No version installed + game_update_status = .update_available; + } +} + +/// Find the download URL for the game zip asset +pub fn findGameAssetUrl(allocator: std.mem.Allocator) ![]const u8 { + var bundle = std.crypto.Certificate.Bundle{}; + bundle.rescan(allocator) catch {}; + + var client = std.http.Client{ .allocator = allocator, .ca_bundle = bundle }; + defer client.deinit(); + + const uri = try std.Uri.parse(GAME_API_URL); + // Use fetch API which encapsulates open/send/wait + var req = client.request(.GET, uri, .{ + .extra_headers = &.{ + .{ .name = "User-Agent", .value = "Flint/1.0" }, + .{ .name = "Accept", .value = "application/vnd.github.v3+json" }, + .{ .name = "Accept-Encoding", .value = "identity" }, + }, + }) catch { + return try allocator.dupe(u8, GAME_FALLBACK_URL); + }; + defer req.deinit(); + + req.sendBodiless() catch { + return try allocator.dupe(u8, GAME_FALLBACK_URL); + }; + + var server_header_buffer: [8192]u8 = undefined; + var response = req.receiveHead(&server_header_buffer) catch { + return try allocator.dupe(u8, GAME_FALLBACK_URL); + }; + + if (response.head.status != .ok) { + return try allocator.dupe(u8, GAME_FALLBACK_URL); + } + + var reader = response.reader(&.{}); + const body = reader.allocRemaining(allocator, .unlimited) catch { + return try allocator.dupe(u8, GAME_FALLBACK_URL); + }; + defer allocator.free(body); + + // Decompress gzip if needed + const json_body = decompressBody(allocator, body) catch { + return try allocator.dupe(u8, GAME_FALLBACK_URL); + }; + defer if (json_body.ptr != body.ptr) allocator.free(json_body); + + const parsed = std.json.parseFromSlice(std.json.Value, allocator, json_body, .{}) catch { + return try allocator.dupe(u8, GAME_FALLBACK_URL); + }; + defer parsed.deinit(); + + const root = parsed.value; + if (root != .object) return try allocator.dupe(u8, GAME_FALLBACK_URL); + + const assets = root.object.get("assets") orelse + return try allocator.dupe(u8, GAME_FALLBACK_URL); + + if (assets != .array) return try allocator.dupe(u8, GAME_FALLBACK_URL); + + // Look for zip containing "MinecraftConsoles" + for (assets.array.items) |asset| { + if (asset != .object) continue; + const name = asset.object.get("name") orelse continue; + if (name != .string) continue; + + if (std.mem.indexOf(u8, name.string, "MinecraftConsoles") != null and + std.mem.endsWith(u8, name.string, ".zip")) + { + const url = asset.object.get("browser_download_url") orelse continue; + if (url == .string) { + game_download_progress.asset_name = try allocator.dupe(u8, name.string); + return try allocator.dupe(u8, url.string); + } + } + } + + // Fallback: first zip + for (assets.array.items) |asset| { + if (asset != .object) continue; + const name = asset.object.get("name") orelse continue; + if (name != .string) continue; + + if (std.mem.endsWith(u8, name.string, ".zip")) { + const url = asset.object.get("browser_download_url") orelse continue; + if (url == .string) { + game_download_progress.asset_name = try allocator.dupe(u8, name.string); + return try allocator.dupe(u8, url.string); + } + } + } + + return try allocator.dupe(u8, GAME_FALLBACK_URL); +} + +/// Download and install game update +pub fn downloadGame(allocator: std.mem.Allocator) !void { + game_update_status = .downloading; + game_download_progress = .{ .is_downloading = true }; + + const sha = available_game_sha orelse return; + const sha_short = if (sha.len >= 7) sha[0..7] else sha; + + // Paths + const base = try safe_fs.getBaseDir(allocator); + const versions = try safe_fs.getVersionsDir(allocator); + defer allocator.free(versions); + + var dl_buf: [std.fs.max_path_bytes]u8 = undefined; + const downloading_dir = try std.fmt.bufPrint(&dl_buf, "{s}downloading/", .{versions}); + try safe_fs.ensureDir(downloading_dir); + + var zip_buf: [std.fs.max_path_bytes]u8 = undefined; + const zip_path = try std.fmt.bufPrint(&zip_buf, "{s}downloading/game.zip", .{versions}); + + // Get download URL + const url = try findGameAssetUrl(allocator); + defer allocator.free(url); + + // Download the file + var dl_bundle = std.crypto.Certificate.Bundle{}; + dl_bundle.rescan(allocator) catch {}; + + var dl_client = std.http.Client{ .allocator = allocator, .ca_bundle = dl_bundle }; + defer dl_client.deinit(); + + const dl_uri = try std.Uri.parse(url); + var dl_req = dl_client.request(.GET, dl_uri, .{ + .extra_headers = &.{ + .{ .name = "User-Agent", .value = "Flint/1.0" }, + .{ .name = "Accept-Encoding", .value = "identity" }, + }, + }) catch return error.HttpRequestFailed; + defer dl_req.deinit(); + + dl_req.sendBodiless() catch return error.HttpRequestFailed; + + var dl_header_buf: [8192]u8 = undefined; + var dl_response = dl_req.receiveHead(&dl_header_buf) catch return error.HttpRequestFailed; + + // Get content length from response headers for progress tracking + game_download_progress.total_bytes = dl_response.head.content_length orelse 0; + + // Write to file + const out_file = try std.fs.createFileAbsolute(zip_path, .{}); + defer out_file.close(); + + var dl_reader = dl_response.reader(&.{}); + var bytes_written: u64 = 0; + const total = game_download_progress.total_bytes; + var read_buf: [8192]u8 = undefined; + while (total == 0 or bytes_written < total) { + // Limit read size to remaining bytes if content-length is known + const remaining = if (total > 0) @min(read_buf.len, @as(usize, @intCast(total - bytes_written))) else read_buf.len; + const n = dl_reader.readSliceShort(read_buf[0..remaining]) catch break; + if (n == 0) break; + out_file.writeAll(read_buf[0..n]) catch break; + bytes_written += n; + game_download_progress.bytes_received = bytes_written; + } + + // Extract + game_download_progress.is_extracting = true; + std.debug.print("Download complete ({} bytes), extracting...\n", .{bytes_written}); + + var target_buf: [std.fs.max_path_bytes]u8 = undefined; + const target_dir = std.fmt.bufPrint(&target_buf, "{s}nightly-{s}/", .{ versions, sha_short }) catch return error.OutOfMemory; + safe_fs.ensureDir(target_dir) catch |err| { + std.debug.print("Failed to create target dir: {}\n", .{err}); + return err; + }; + + // Use system unzip for extraction + var child = std.process.Child.init( + &.{ "unzip", "-o", zip_path, "-d", target_dir }, + allocator, + ); + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Ignore; + child.spawn() catch |err| { + std.debug.print("Failed to spawn unzip: {}\n", .{err}); + return err; + }; + _ = child.wait() catch |err| { + std.debug.print("unzip wait failed: {}\n", .{err}); + return err; + }; + std.debug.print("Extraction complete\n", .{}); + + // Check for single wrapper directory and strip it + stripWrapperDir(allocator, target_dir) catch |err| { + std.debug.print("stripWrapperDir failed (non-fatal): {}\n", .{err}); + }; + + // Write game version + writeGameVersion(allocator, sha) catch |err| { + std.debug.print("writeGameVersion failed: {}\n", .{err}); + return err; + }; + + // Clean up downloading dir + safe_fs.safeDelete(allocator, downloading_dir) catch {}; + + // Clean old versions + cleanOldVersions(allocator) catch |err| { + std.debug.print("cleanOldVersions failed (non-fatal): {}\n", .{err}); + }; + + game_download_progress.done = true; + game_download_progress.is_downloading = false; + game_download_progress.is_extracting = false; + game_update_status = .up_to_date; + std.debug.print("Game update installed successfully\n", .{}); + + // Refresh the installed versions list + refreshInstalledVersions(allocator); + + _ = base; +} + +/// If extracted dir has a single top-level folder, move its contents up +fn stripWrapperDir(allocator: std.mem.Allocator, target_dir: []const u8) !void { + var dir = try std.fs.openDirAbsolute(target_dir, .{ .iterate = true }); + defer dir.close(); + + var entries = std.ArrayList([]const u8).empty; + defer { + for (entries.items) |e| allocator.free(e); + entries.deinit(allocator); + } + + var it = dir.iterate(); + var single_dir: ?[]const u8 = null; + var count: u32 = 0; + while (try it.next()) |entry| { + count += 1; + if (count == 1 and entry.kind == .directory) { + single_dir = try allocator.dupe(u8, entry.name); + } else { + if (single_dir) |sd| { + allocator.free(sd); + single_dir = null; + } + return; // Multiple entries or first isn't a dir + } + } + + if (single_dir) |wrapper_name| { + defer allocator.free(wrapper_name); + + // Move contents from wrapper to target_dir + var wrapper_buf: [std.fs.max_path_bytes]u8 = undefined; + const wrapper_path = try std.fmt.bufPrint(&wrapper_buf, "{s}{s}/", .{ target_dir, wrapper_name }); + + // Use system mv + var child = std.process.Child.init( + &.{ "sh", "-c", try std.fmt.allocPrint(allocator, "mv {s}* {s} 2>/dev/null; mv {s}.* {s} 2>/dev/null; rmdir {s}", .{ wrapper_path, target_dir, wrapper_path, target_dir, wrapper_path }) }, + allocator, + ); + try child.spawn(); + _ = try child.wait(); + } +} + +/// Keep last 2 versions, safeDelete older ones +fn cleanOldVersions(allocator: std.mem.Allocator) !void { + const versions_dir = try safe_fs.getVersionsDir(allocator); + defer allocator.free(versions_dir); + + var dir = std.fs.openDirAbsolute(versions_dir, .{ .iterate = true }) catch return; + defer dir.close(); + + var version_names = std.ArrayList([]const u8).empty; + defer { + for (version_names.items) |n| allocator.free(n); + version_names.deinit(allocator); + } + + var it = dir.iterate(); + while (try it.next()) |entry| { + if (entry.kind == .directory and std.mem.startsWith(u8, entry.name, "nightly-")) { + try version_names.append(allocator, try allocator.dupe(u8, entry.name)); + } + } + + if (version_names.items.len <= 2) return; + + // Sort and keep last 2 + std.mem.sort([]const u8, version_names.items, {}, struct { + fn lessThan(_: void, a: []const u8, b_val: []const u8) bool { + return std.mem.lessThan(u8, a, b_val); + } + }.lessThan); + + // Delete all but last 2 + const to_delete = version_names.items[0 .. version_names.items.len - 2]; + for (to_delete) |name| { + var buf: [std.fs.max_path_bytes]u8 = undefined; + const full_path = std.fmt.bufPrint(&buf, "{s}{s}", .{ versions_dir, name }) catch continue; + safe_fs.safeDelete(allocator, full_path) catch {}; + } +} + +/// Get the short SHA for display (first 7 chars) +pub fn getGameVersionShort(allocator: std.mem.Allocator) !?[]const u8 { + const sha = try readGameVersion(allocator); + if (sha) |s| { + if (s.len >= 7) { + return try allocator.dupe(u8, s[0..7]); + } + return s; + } + return null; +} diff --git a/src/launcher.zig b/src/launcher.zig new file mode 100644 index 0000000..9612144 --- /dev/null +++ b/src/launcher.zig @@ -0,0 +1,307 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const safe_fs = @import("safe_fs.zig"); + +/// Wine status for UI display +pub const WineStatus = enum { + not_checked, + available, + not_found, + initializing_prefix, +}; + +/// Game process status +pub const GameStatus = enum { + not_running, + running, + initializing_wine, +}; + +pub var wine_version: ?[]const u8 = null; +pub var wine_status: WineStatus = .not_checked; +pub var game_status: GameStatus = .not_running; +pub var game_child: ?std.process.Child = null; + +/// Ensure the GameHDD symlink/junction points to saves_path. +/// Runs before every launch. +pub fn ensureSavesLink(allocator: std.mem.Allocator, version_dir: []const u8, saves_path: []const u8) !void { + // Ensure Windows64 directory exists at the root of the install + var win64_buf: [std.fs.max_path_bytes]u8 = undefined; + const win64_path = try std.fmt.bufPrint(&win64_buf, "{s}/Windows64", .{version_dir}); + safe_fs.ensureDir(win64_path) catch {}; + + var gamehdd_buf: [std.fs.max_path_bytes]u8 = undefined; + const gamehdd_path = try std.fmt.bufPrint(&gamehdd_buf, "{s}/Windows64/GameHDD", .{version_dir}); + + // Check what exists at GameHDD + const stat = std.fs.cwd().statFile(gamehdd_path) catch |err| switch (err) { + error.FileNotFound => { + // Does not exist — create symlink/junction + try createLink(gamehdd_path, saves_path); + return; + }, + else => return err, + }; + _ = stat; + + // Check if it's a symlink + const link_target = std.fs.readLinkAbsolute(gamehdd_path, &gamehdd_buf) catch |err| switch (err) { + error.NotLink => { + // It's a real directory — move contents to saves_path, then replace with link + try moveContentsToSaves(allocator, gamehdd_path, saves_path); + try std.fs.deleteTreeAbsolute(gamehdd_path); + try createLink(gamehdd_path, saves_path); + return; + }, + else => return err, + }; + + // It is a symlink — check if correct target + if (std.mem.eql(u8, link_target, saves_path)) { + // Correct target, do nothing + return; + } + + // Wrong target — delete and re-create + try std.fs.deleteFileAbsolute(gamehdd_path); + try createLink(gamehdd_path, saves_path); +} + +fn createLink(link_path: []const u8, target: []const u8) !void { + if (comptime builtin.os.tag == .windows) { + // Windows: use directory junction via NTFS reparse point + // For now, use symlink as placeholder — full junction impl requires Win32 API + try std.fs.symLinkAbsolute(target, link_path, .{ .is_directory = true }); + } else { + // Linux: standard symlink + try std.fs.symLinkAbsolute(target, link_path, .{}); + } +} + +fn moveContentsToSaves(allocator: std.mem.Allocator, src_dir: []const u8, saves_path: []const u8) !void { + var dir = try std.fs.openDirAbsolute(src_dir, .{ .iterate = true }); + defer dir.close(); + + var it = dir.iterate(); + while (try it.next()) |entry| { + var src_buf: [std.fs.max_path_bytes]u8 = undefined; + const src_path = try std.fmt.bufPrint(&src_buf, "{s}/{s}", .{ src_dir, entry.name }); + var dst_buf: [std.fs.max_path_bytes]u8 = undefined; + const dst_path = try std.fmt.bufPrint(&dst_buf, "{s}/{s}", .{ saves_path, entry.name }); + + // Check if dest already exists + std.fs.accessAbsolute(dst_path, .{}) catch { + // Doesn't exist — rename/move + std.fs.renameAbsolute(src_path, dst_path) catch { + // Cross-device move — would need copy, skip for now + std.log.warn("Could not move {s} to saves (cross-device?)", .{entry.name}); + }; + continue; + }; + // Already exists in saves, skip (don't overwrite user saves) + _ = allocator; + } +} + +/// Spawn the game process with preset connection info +/// Exe args: Minecraft.Client.exe -name -ip -port +pub fn spawnGame(allocator: std.mem.Allocator, version_dir: []const u8, saves_path: []const u8, preset: anytype) !void { + // Ensure saves link + try ensureSavesLink(allocator, version_dir, saves_path); + + // Build argv dynamically based on preset fields + var argv = std.ArrayList([]const u8).empty; + defer argv.deinit(allocator); + + try argv.append(allocator, "Minecraft.Client.exe"); + + // Default username if empty + if (preset.username.len > 0) { + try argv.append(allocator, "-name"); + try argv.append(allocator, preset.username); + } else { + try argv.append(allocator, "-name"); + try argv.append(allocator, "Player"); + } + + if (preset.ip.len > 0) { + try argv.append(allocator, "-ip"); + try argv.append(allocator, preset.ip); + } + if (preset.port.len > 0) { + try argv.append(allocator, "-port"); + try argv.append(allocator, preset.port); + } + + if (comptime builtin.os.tag == .linux) { + // Native Linux: Use Wine + const wineprefix = try safe_fs.getWinePrefixDir(allocator); + defer allocator.free(wineprefix); + + var env_map = try std.process.getEnvMap(allocator); + defer env_map.deinit(); + try env_map.put("WINEPREFIX", wineprefix); + try env_map.put("WINEDEBUG", "-all"); // Suppress wine spam + + // Prepend wine command + try argv.insert(allocator, 0, "wine"); + + std.debug.print("Spawning game with args: ", .{}); + for (argv.items) |arg| { + std.debug.print("'{s}' ", .{arg}); + } + std.debug.print("\n", .{}); + + var child = std.process.Child.init(argv.items, allocator); + child.cwd = version_dir; + child.env_map = &env_map; + try child.spawn(); + game_child = child; + } else { + // Windows: Native execution + var child = std.process.Child.init(argv.items, allocator); + child.cwd = version_dir; + try child.spawn(); + game_child = child; + } + + game_status = .running; +} + +/// Convenience wrapper for the UI +pub fn launch(allocator: std.mem.Allocator, config: @import("config.zig").Config) !void { + if (game_status == .running) return; + + if (try findLatestVersion(allocator)) |version_dir| { + defer allocator.free(version_dir); + try spawnGame(allocator, version_dir, config.saves_path, config.presets[config.active_preset]); + } else { + std.debug.print("No game version found to launch.\n", .{}); + } +} + +/// Check if game process is still running (called each frame) +pub fn pollGameProcess() void { + if (game_child) |*child| { + const result = child.wait() catch { + game_status = .not_running; + game_child = null; + return; + }; + _ = result; + game_status = .not_running; + game_child = null; + } +} + +/// Check if Wine is installed (Linux only) +pub fn checkWine(allocator: std.mem.Allocator) !void { + if (comptime builtin.os.tag != .linux) return; + + var child = std.process.Child.init( + &.{ "wine", "--version" }, + allocator, + ); + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; + try child.spawn(); + + const stdout = child.stdout.?.readToEndAlloc(allocator, 1024) catch ""; + const result = child.wait() catch { + wine_status = .not_found; + return; + }; + + if (result.Exited == 0 and stdout.len > 0) { + // Trim trailing newline + wine_version = std.mem.trimRight(u8, stdout, "\n\r "); + wine_status = .available; + } else { + wine_status = .not_found; + } +} + +/// Initialize Wine prefix (Linux only, first run) +pub fn initWinePrefix(allocator: std.mem.Allocator) !void { + if (comptime builtin.os.tag != .linux) return; + + game_status = .initializing_wine; + wine_status = .initializing_prefix; + + const wineprefix = try safe_fs.getWinePrefixDir(allocator); + + var env_map = std.process.EnvMap.init(allocator); + defer env_map.deinit(); + try env_map.put("WINEPREFIX", wineprefix); + + var child = std.process.Child.init( + &.{ "wineboot", "--init" }, + allocator, + ); + child.env_map = &env_map; + try child.spawn(); + + _ = child.wait() catch {}; + + wine_status = .available; + game_status = .not_running; +} + +/// Reset Wine prefix: delete and re-init +pub fn resetWinePrefix(allocator: std.mem.Allocator) !void { + if (comptime builtin.os.tag != .linux) return; + + const wineprefix = try safe_fs.getWinePrefixDir(allocator); + + // Delete wineprefix directory directly (NOT through safeDelete — this is the only + // code path allowed to delete wineprefix) + std.fs.deleteTreeAbsolute(wineprefix) catch {}; + + // Re-init + try initWinePrefix(allocator); +} + +/// Open a folder in the system file manager +pub fn openFolder(allocator: std.mem.Allocator, path: []const u8) !void { + if (comptime builtin.os.tag == .windows) { + var child = std.process.Child.init( + &.{ "explorer.exe", path }, + allocator, + ); + try child.spawn(); + } else { + var child = std.process.Child.init( + &.{ "xdg-open", path }, + allocator, + ); + try child.spawn(); + } +} + +/// Find the latest installed version directory +pub fn findLatestVersion(allocator: std.mem.Allocator) !?[]const u8 { + const versions_dir = try safe_fs.getVersionsDir(allocator); + defer allocator.free(versions_dir); + + var dir = std.fs.openDirAbsolute(versions_dir, .{ .iterate = true }) catch return null; + defer dir.close(); + + var latest: ?[]const u8 = null; + var it = dir.iterate(); + while (try it.next()) |entry| { + if (entry.kind == .directory and std.mem.startsWith(u8, entry.name, "nightly-")) { + if (latest) |l| allocator.free(l); + latest = try allocator.dupe(u8, entry.name); + } + } + + if (latest) |name| { + var buf: [std.fs.max_path_bytes]u8 = undefined; + const full = try std.fmt.bufPrint(&buf, "{s}{s}", .{ versions_dir, name }); + allocator.free(name); + return try allocator.dupe(u8, full); + } + + return null; +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..5dac6f4 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,206 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const c = @import("c_imports.zig").c; + +const renderer_mod = @import("renderer.zig"); +const ui = @import("ui.zig"); +const config_mod = @import("config.zig"); +const safe_fs = @import("safe_fs.zig"); +const launcher_mod = @import("launcher.zig"); +const game_updater = @import("game_updater.zig"); +const updater_mod = @import("updater.zig"); + +// ── Embedded assets ─────────────────────────────────────────────────── +const bg_data = @embedFile("assets/background.jpg"); +const font_data = @embedFile("assets/MinecraftStandard.otf"); + +fn clayErrorHandler(err: c.Clay_ErrorData) callconv(.c) void { + const msg = err.errorText; + if (msg.chars != null and msg.length > 0) { + std.log.err("Clay: {s}", .{msg.chars[0..@intCast(msg.length)]}); + } +} + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + // ── Startup cleanup ─────────────────────────────────────────────── + updater_mod.cleanupOldLauncher(); + try safe_fs.ensureBaseDirs(allocator); + + // Delete abandoned downloading/ dir + const versions_dir = try safe_fs.getVersionsDir(allocator); + defer allocator.free(versions_dir); + { + var dl_buf: [std.fs.max_path_bytes]u8 = undefined; + const dl_path = try std.fmt.bufPrint(&dl_buf, "{s}downloading/", .{versions_dir}); + safe_fs.safeDelete(allocator, dl_path) catch {}; + } + + // ── Load config ─────────────────────────────────────────────────── + ui.ui_state.config = try config_mod.loadConfig(allocator); + try safe_fs.assertSavesNotInVersions(allocator, ui.ui_state.config.saves_path); + + // ── Game version display ────────────────────────────────────────── + if (game_updater.getGameVersionShort(allocator) catch null) |gv| { + const written = std.fmt.bufPrint(&ui.ui_state.game_version_display, "nightly-{s}", .{gv}) catch ""; + ui.ui_state.game_version_len = written.len; + } + + // Scan installed versions + game_updater.refreshInstalledVersions(allocator); + + // Auto-check for game update in background + const update_thread = std.Thread.spawn(.{}, struct { + fn run() void { + game_updater.checkForGameUpdate(std.heap.page_allocator) catch {}; + } + }.run, .{}) catch null; + if (update_thread) |t| t.detach(); + + // ── SDL3 init ───────────────────────────────────────────────────── + if (!c.SDL_Init(c.SDL_INIT_VIDEO)) { + std.log.err("SDL_Init: {s}", .{c.SDL_GetError()}); + return; + } + defer c.SDL_Quit(); + + var window: ?*c.SDL_Window = null; + var sdl_renderer: ?*c.SDL_Renderer = null; + window = c.SDL_CreateWindow("Flint", 800, 600, c.SDL_WINDOW_RESIZABLE | c.SDL_WINDOW_HIGH_PIXEL_DENSITY); + if (window == null) { + std.log.err("Window: {s}", .{c.SDL_GetError()}); + return; + } + sdl_renderer = c.SDL_CreateRenderer(window.?, null); + if (sdl_renderer == null) { + std.log.err("Renderer: {s}", .{c.SDL_GetError()}); + return; + } + defer { + if (sdl_renderer) |r| c.SDL_DestroyRenderer(r); + if (window) |w| c.SDL_DestroyWindow(w); + } + _ = c.SDL_SetWindowMinimumSize(window.?, 854, 480); + _ = c.SDL_StartTextInput(window.?); + + // ── Renderer init (asset loading) ───────────────────────────────── + var render_state = renderer_mod.init(sdl_renderer.?, bg_data, font_data); + defer renderer_mod.deinit(&render_state); + + // ── Clay init ───────────────────────────────────────────────────── + const clay_size = c.Clay_MinMemorySize(); + const clay_mem = try allocator.alloc(u8, clay_size); + defer allocator.free(clay_mem); + + var w: c_int = 0; + var h: c_int = 0; + _ = c.SDL_GetWindowSizeInPixels(window.?, &w, &h); + render_state.window_width = @floatFromInt(w); + render_state.window_height = @floatFromInt(h); + + _ = c.Clay_Initialize( + .{ .memory = clay_mem.ptr, .capacity = clay_size }, + .{ .width = render_state.window_width, .height = render_state.window_height }, + .{ .errorHandlerFunction = clayErrorHandler, .userData = null }, + ); + // Pass font pointer through user_data so renderer.measureText can use it + c.Clay_SetMeasureTextFunction(renderer_mod.measureText, render_state.font); + + // ── Wine check (Linux) ──────────────────────────────────────────── + if (comptime builtin.os.tag == .linux) { + launcher_mod.checkWine(allocator) catch {}; + } + + // ── Main loop ───────────────────────────────────────────────────── + var running = true; + while (running) { + var event: c.SDL_Event = undefined; + while (c.SDL_PollEvent(&event)) { + switch (event.type) { + c.SDL_EVENT_QUIT => running = false, + c.SDL_EVENT_WINDOW_RESIZED => { + var new_w: c_int = 0; + var new_h: c_int = 0; + _ = c.SDL_GetWindowSizeInPixels(window.?, &new_w, &new_h); + render_state.window_width = @floatFromInt(new_w); + render_state.window_height = @floatFromInt(new_h); + c.Clay_SetLayoutDimensions(.{ + .width = render_state.window_width, + .height = render_state.window_height, + }); + }, + c.SDL_EVENT_MOUSE_MOTION => { + var log_w: c_int = 0; + var pix_w: c_int = 0; + _ = c.SDL_GetWindowSize(window.?, &log_w, null); + _ = c.SDL_GetWindowSizeInPixels(window.?, &pix_w, null); + const scale = if (log_w > 0) @as(f32, @floatFromInt(pix_w)) / @as(f32, @floatFromInt(log_w)) else 1.0; + + c.Clay_SetPointerState( + .{ .x = event.motion.x * scale, .y = event.motion.y * scale }, + event.motion.state & c.SDL_BUTTON_LMASK != 0, + ); + }, + c.SDL_EVENT_MOUSE_BUTTON_DOWN => { + var log_w: c_int = 0; + var pix_w: c_int = 0; + _ = c.SDL_GetWindowSize(window.?, &log_w, null); + _ = c.SDL_GetWindowSizeInPixels(window.?, &pix_w, null); + const scale = if (log_w > 0) @as(f32, @floatFromInt(pix_w)) / @as(f32, @floatFromInt(log_w)) else 1.0; + + if (event.button.button == c.SDL_BUTTON_LEFT) { + c.Clay_SetPointerState(.{ .x = event.button.x * scale, .y = event.button.y * scale }, true); + ui.handleClick(); + } + }, + c.SDL_EVENT_MOUSE_BUTTON_UP => { + var log_w: c_int = 0; + var pix_w: c_int = 0; + _ = c.SDL_GetWindowSize(window.?, &log_w, null); + _ = c.SDL_GetWindowSizeInPixels(window.?, &pix_w, null); + const scale = if (log_w > 0) @as(f32, @floatFromInt(pix_w)) / @as(f32, @floatFromInt(log_w)) else 1.0; + + c.Clay_SetPointerState(.{ .x = event.button.x * scale, .y = event.button.y * scale }, false); + }, + c.SDL_EVENT_MOUSE_WHEEL => { + c.Clay_UpdateScrollContainers(true, .{ .x = event.wheel.x * 10, .y = event.wheel.y * 10 }, 0.016); + }, + c.SDL_EVENT_TEXT_INPUT => { + if (event.text.text != null) { + const text_slice = std.mem.span(event.text.text); + ui.handleTextInput(text_slice); + } + }, + c.SDL_EVENT_KEY_DOWN => { + if (event.key.key == c.SDLK_BACKSPACE) { + ui.handleBackspace(); + } else if (event.key.key == c.SDLK_RETURN or event.key.key == c.SDLK_RETURN2) { + ui.handleReturn(); + } + }, + else => {}, + } + } + + // Poll game process + launcher_mod.pollGameProcess(); + + // Layout + const cmds = ui.layoutRoot(); + + // Render + _ = c.SDL_SetRenderDrawColor(sdl_renderer.?, 0, 0, 0, 255); + _ = c.SDL_RenderClear(sdl_renderer.?); + renderer_mod.renderBackground(&render_state); + renderer_mod.renderClayCommands(&render_state, cmds); + _ = c.SDL_RenderPresent(sdl_renderer.?); + + c.SDL_Delay(16); + } + + // Save config on exit + config_mod.saveConfig(allocator, &ui.ui_state.config) catch {}; +} diff --git a/src/renderer.zig b/src/renderer.zig new file mode 100644 index 0000000..f32a1f1 --- /dev/null +++ b/src/renderer.zig @@ -0,0 +1,206 @@ +const std = @import("std"); +const c = @import("c_imports.zig").c; + +/// Holds all SDL rendering resources +pub const RenderState = struct { + renderer: *c.SDL_Renderer, + font: ?*c.TTF_Font = null, + text_engine: ?*c.TTF_TextEngine = null, + bg_texture: ?*c.SDL_Texture = null, + window_width: f32 = 854, + window_height: f32 = 480, +}; + +/// Initialize renderer: decode embedded background JPG, load embedded font +pub fn init( + sdl_renderer: *c.SDL_Renderer, + bg_data: []const u8, + font_data: []const u8, +) RenderState { + var state = RenderState{ .renderer = sdl_renderer }; + + // ── Background image from embedded bytes ── + var bg_w: c_int = 0; + var bg_h: c_int = 0; + var bg_ch: c_int = 0; + const pixels = c.stbi_load_from_memory( + bg_data.ptr, + @intCast(bg_data.len), + &bg_w, + &bg_h, + &bg_ch, + 4, + ); + if (pixels != null) { + const surf = c.SDL_CreateSurfaceFrom(bg_w, bg_h, c.SDL_PIXELFORMAT_RGBA32, @ptrCast(pixels), bg_w * 4); + if (surf != null) { + state.bg_texture = c.SDL_CreateTextureFromSurface(sdl_renderer, surf); + c.SDL_DestroySurface(surf); + } else { + std.log.err("Failed to create BG surface: {s}", .{c.SDL_GetError()}); + } + c.stbi_image_free(pixels); + } else { + std.log.err("stbi_load failed: {s}", .{c.stbi_failure_reason()}); + } + + // ── SDL3_ttf ── + _ = c.TTF_Init(); + state.text_engine = c.TTF_CreateRendererTextEngine(sdl_renderer); + + // Write embedded font to tmp file (SDL3_ttf needs IOStream) + const tmp_path = "/tmp/lcelauncher_font.otf"; + if (std.fs.createFileAbsolute(tmp_path, .{})) |f| { + f.writeAll(font_data) catch {}; + f.close(); + const rw = c.SDL_IOFromFile(tmp_path, "rb"); + if (rw != null) { + state.font = c.TTF_OpenFontIO(rw, true, 16); + if (state.font == null) { + std.log.err("TTF_OpenFontIO failed: {s}", .{c.SDL_GetError()}); + } else { + _ = c.TTF_SetFontHinting(state.font, c.TTF_HINTING_NONE); + std.log.info("Loaded TTF. Family: {s}, face: {s}", .{ + c.TTF_GetFontFamilyName(state.font), + c.TTF_GetFontStyleName(state.font), + }); + } + } + } else |_| {} + + return state; +} + +pub fn deinit(state: *RenderState) void { + if (state.font) |f| c.TTF_CloseFont(f); + if (state.text_engine) |te| c.TTF_DestroyRendererTextEngine(te); + if (state.bg_texture) |t| c.SDL_DestroyTexture(t); + c.TTF_Quit(); +} + +/// Clay text-measurement callback (C calling-convention) +/// user_data is the TTF_Font pointer passed via Clay_SetMeasureTextFunction +pub fn measureText( + text_slice: c.Clay_StringSlice, + text_config: [*c]c.Clay_TextElementConfig, + user_data: ?*anyopaque, +) callconv(.c) c.Clay_Dimensions { + const font: ?*c.TTF_Font = @ptrCast(@alignCast(user_data)); + + if (font) |f| { + _ = c.TTF_SetFontSize(f, @floatFromInt(text_config.*.fontSize)); + var w: c_int = 0; + var h: c_int = 0; + _ = c.TTF_GetStringSize(f, text_slice.chars, @intCast(text_slice.length), &w, &h); + return .{ .width = @floatFromInt(w), .height = @floatFromInt(h) }; + } + + // Fallback: estimate 8px per character + return .{ + .width = @as(f32, @floatFromInt(text_slice.length)) * 8.0, + .height = 16.0, + }; +} + +/// Render the full-window scaled background + darkening overlay +pub fn renderBackground(state: *RenderState) void { + if (state.bg_texture) |tex| { + const dest = c.SDL_FRect{ .x = 0, .y = 0, .w = state.window_width, .h = state.window_height }; + _ = c.SDL_RenderTexture(state.renderer, tex, null, &dest); + } + + // Darkening overlay RGBA(0,0,0,180) + _ = c.SDL_SetRenderDrawBlendMode(state.renderer, c.SDL_BLENDMODE_BLEND); + _ = c.SDL_SetRenderDrawColor(state.renderer, 0, 0, 0, 180); + const overlay = c.SDL_FRect{ .x = 0, .y = 0, .w = state.window_width, .h = state.window_height }; + _ = c.SDL_RenderFillRect(state.renderer, &overlay); +} + +/// Process all Clay render commands and draw via SDL3 +pub fn renderClayCommands(state: *RenderState, commands: c.Clay_RenderCommandArray) void { + var i: i32 = 0; + while (i < commands.length) : (i += 1) { + const cmd = c.Clay_RenderCommandArray_Get(@constCast(&commands), i); + if (cmd == null) continue; + + const bb = cmd.*.boundingBox; + const rect = c.SDL_FRect{ .x = bb.x, .y = bb.y, .w = bb.width, .h = bb.height }; + + switch (cmd.*.commandType) { + c.CLAY_RENDER_COMMAND_TYPE_RECTANGLE => { + const cfg = &cmd.*.renderData.rectangle; + _ = c.SDL_SetRenderDrawBlendMode(state.renderer, c.SDL_BLENDMODE_BLEND); + _ = c.SDL_SetRenderDrawColor(state.renderer, @intFromFloat(cfg.backgroundColor.r), @intFromFloat(cfg.backgroundColor.g), @intFromFloat(cfg.backgroundColor.b), @intFromFloat(cfg.backgroundColor.a)); + _ = c.SDL_RenderFillRect(state.renderer, &rect); + }, + c.CLAY_RENDER_COMMAND_TYPE_TEXT => { + const cfg = &cmd.*.renderData.text; + if (state.font) |font| { + if (state.text_engine) |te| { + _ = c.TTF_SetFontSize(font, @floatFromInt(cfg.fontSize)); + + // Drop shadow (+1,+1 black) + const shadow = c.TTF_CreateText(te, font, cfg.stringContents.chars, @intCast(cfg.stringContents.length)); + if (shadow != null) { + _ = c.TTF_SetTextColor(shadow, 0, 0, 0, @intFromFloat(cfg.textColor.a * 0.5)); + _ = c.TTF_DrawRendererText(shadow, @round(rect.x) + 1.0, @round(rect.y) + 1.0); + c.TTF_DestroyText(shadow); + } + + // Main text + const text = c.TTF_CreateText(te, font, cfg.stringContents.chars, @intCast(cfg.stringContents.length)); + if (text != null) { + _ = c.TTF_SetTextColor(text, @intFromFloat(cfg.textColor.r), @intFromFloat(cfg.textColor.g), @intFromFloat(cfg.textColor.b), @intFromFloat(cfg.textColor.a)); + _ = c.TTF_DrawRendererText(text, @round(rect.x), @round(rect.y)); + c.TTF_DestroyText(text); + } + } + } + }, + c.CLAY_RENDER_COMMAND_TYPE_BORDER => { + const cfg = &cmd.*.renderData.border; + _ = c.SDL_SetRenderDrawBlendMode(state.renderer, c.SDL_BLENDMODE_BLEND); + _ = c.SDL_SetRenderDrawColor(state.renderer, @intFromFloat(cfg.color.r), @intFromFloat(cfg.color.g), @intFromFloat(cfg.color.b), @intFromFloat(cfg.color.a)); + + if (cfg.width.top > 0) { + const line = c.SDL_FRect{ .x = rect.x, .y = rect.y, .w = rect.w, .h = @floatFromInt(cfg.width.top) }; + _ = c.SDL_RenderFillRect(state.renderer, &line); + } + if (cfg.width.bottom > 0) { + const bh: f32 = @floatFromInt(cfg.width.bottom); + const line = c.SDL_FRect{ .x = rect.x, .y = rect.y + rect.h - bh, .w = rect.w, .h = bh }; + _ = c.SDL_RenderFillRect(state.renderer, &line); + } + if (cfg.width.left > 0) { + const line = c.SDL_FRect{ .x = rect.x, .y = rect.y, .w = @floatFromInt(cfg.width.left), .h = rect.h }; + _ = c.SDL_RenderFillRect(state.renderer, &line); + } + if (cfg.width.right > 0) { + const rw: f32 = @floatFromInt(cfg.width.right); + const line = c.SDL_FRect{ .x = rect.x + rect.w - rw, .y = rect.y, .w = rw, .h = rect.h }; + _ = c.SDL_RenderFillRect(state.renderer, &line); + } + }, + c.CLAY_RENDER_COMMAND_TYPE_SCISSOR_START => { + const clip = c.SDL_Rect{ + .x = @intFromFloat(bb.x), + .y = @intFromFloat(bb.y), + .w = @intFromFloat(bb.width), + .h = @intFromFloat(bb.height), + }; + _ = c.SDL_SetRenderClipRect(state.renderer, &clip); + }, + c.CLAY_RENDER_COMMAND_TYPE_SCISSOR_END => { + _ = c.SDL_SetRenderClipRect(state.renderer, null); + }, + c.CLAY_RENDER_COMMAND_TYPE_IMAGE => { + const tex: ?*c.SDL_Texture = @ptrCast(@alignCast(cmd.*.renderData.image.imageData)); + if (tex) |t| { + const dest = c.SDL_FRect{ .x = rect.x, .y = rect.y, .w = rect.w, .h = rect.h }; + _ = c.SDL_RenderTexture(state.renderer, t, null, &dest); + } + }, + else => {}, + } + } +} diff --git a/src/safe_fs.zig b/src/safe_fs.zig new file mode 100644 index 0000000..3c22993 --- /dev/null +++ b/src/safe_fs.zig @@ -0,0 +1,148 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +/// Safe filesystem operations with path validation. +/// All recursive deletes in the codebase MUST go through safeDelete. +pub const SafeFsError = error{ + UnsafePath, + SavesPathInsideVersionsDir, +}; + +var base_dir_buf: [std.fs.max_path_bytes]u8 = undefined; +var base_dir_len: usize = 0; +var base_dir_initialized: bool = false; + +/// Returns the resolved base directory ~/.lcelauncher/ +pub fn getBaseDir(allocator: std.mem.Allocator) ![]const u8 { + if (base_dir_initialized) { + return base_dir_buf[0..base_dir_len]; + } + + const home = if (comptime builtin.os.tag == .windows) + std.process.getEnvVarOwned(allocator, "USERPROFILE") catch + std.process.getEnvVarOwned(allocator, "HOMEDRIVE") catch + return error.UnsafePath + else + std.process.getEnvVarOwned(allocator, "HOME") catch return error.UnsafePath; + defer allocator.free(home); + + const path = try std.fmt.bufPrint(&base_dir_buf, "{s}/.flintlauncher/", .{home}); + base_dir_len = path.len; + base_dir_initialized = true; + return path; +} + +/// Returns the versions directory path ~/.lcelauncher/versions/ +pub fn getVersionsDir(allocator: std.mem.Allocator) ![]const u8 { + const base = try getBaseDir(allocator); + var buf: [std.fs.max_path_bytes]u8 = undefined; + const result = try std.fmt.bufPrint(&buf, "{s}versions/", .{base}); + const duped = try allocator.dupe(u8, result); + return duped; +} + +/// Returns the saves directory path ~/.lcelauncher/saves/ +pub fn getSavesDir(allocator: std.mem.Allocator) ![]const u8 { + const base = try getBaseDir(allocator); + var buf: [std.fs.max_path_bytes]u8 = undefined; + const result = try std.fmt.bufPrint(&buf, "{s}saves/", .{base}); + const duped = try allocator.dupe(u8, result); + return duped; +} + +/// Returns the wineprefix directory path ~/.lcelauncher/wineprefix/ +pub fn getWinePrefixDir(allocator: std.mem.Allocator) ![]const u8 { + const base = try getBaseDir(allocator); + var buf: [std.fs.max_path_bytes]u8 = undefined; + const result = try std.fmt.bufPrint(&buf, "{s}wineprefix/", .{base}); + const duped = try allocator.dupe(u8, result); + return duped; +} + +/// Safe delete: validates path starts with ~/.lcelauncher/versions/ before any delete. +/// Returns error.UnsafePath if validation fails. +/// MUST NEVER be called on saves/ or wineprefix/. +pub fn safeDelete(allocator: std.mem.Allocator, path: []const u8) !void { + const base = try getBaseDir(allocator); + + // Build the versions prefix + var versions_buf: [std.fs.max_path_bytes]u8 = undefined; + const versions_prefix = try std.fmt.bufPrint(&versions_buf, "{s}versions/", .{base}); + + // Must start with versions path + if (!std.mem.startsWith(u8, path, versions_prefix)) { + return SafeFsError.UnsafePath; + } + + // Must NEVER be the saves directory + var saves_buf: [std.fs.max_path_bytes]u8 = undefined; + const saves_prefix = try std.fmt.bufPrint(&saves_buf, "{s}saves", .{base}); + if (std.mem.startsWith(u8, path, saves_prefix)) { + return SafeFsError.UnsafePath; + } + + // Must NEVER be the wineprefix directory + var wine_buf: [std.fs.max_path_bytes]u8 = undefined; + const wine_prefix = try std.fmt.bufPrint(&wine_buf, "{s}wineprefix", .{base}); + if (std.mem.startsWith(u8, path, wine_prefix)) { + return SafeFsError.UnsafePath; + } + + // Perform the actual delete + var dir = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch |err| switch (err) { + error.FileNotFound => return, + else => return err, + }; + dir.close(); + + try std.fs.deleteTreeAbsolute(path); +} + +/// Assert that saves_path is NOT inside versions_path +pub fn assertSavesNotInVersions(allocator: std.mem.Allocator, saves_path: []const u8) !void { + const versions = try getVersionsDir(allocator); + defer allocator.free(versions); + + if (std.mem.startsWith(u8, saves_path, versions)) { + return SafeFsError.SavesPathInsideVersionsDir; + } +} + +/// Ensure a directory exists, creating it recursively if needed. +pub fn ensureDir(path: []const u8) !void { + std.fs.makeDirAbsolute(path) catch |err| switch (err) { + error.PathAlreadyExists => {}, + else => { + // Try creating parent directories + if (std.fs.path.dirname(path)) |parent| { + try ensureDir(parent); + std.fs.makeDirAbsolute(path) catch |err2| switch (err2) { + error.PathAlreadyExists => {}, + else => return err2, + }; + } else { + return err; + } + }, + }; +} + +/// Ensure the base launcher directories exist +pub fn ensureBaseDirs(allocator: std.mem.Allocator) !void { + const base = try getBaseDir(allocator); + try ensureDir(base); + + const saves = try getSavesDir(allocator); + defer allocator.free(saves); + try ensureDir(saves); + + const versions = try getVersionsDir(allocator); + defer allocator.free(versions); + try ensureDir(versions); + + if (comptime builtin.os.tag == .linux) { + const wine = try getWinePrefixDir(allocator); + defer allocator.free(wine); + // Don't create wineprefix eagerly — created by wineboot --init + } +} diff --git a/src/ui.zig b/src/ui.zig new file mode 100644 index 0000000..0e85aaa --- /dev/null +++ b/src/ui.zig @@ -0,0 +1,1022 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const c = @import("c_imports.zig").c; + +const config_mod = @import("config.zig"); +const game_updater = @import("game_updater.zig"); +const updater = @import("updater.zig"); +const launcher_mod = @import("launcher.zig"); + +/// Active sidebar tab +pub const Tab = enum(u8) { + play = 0, + versions = 1, + settings = 2, +}; + +/// Which input field is focused +pub const ActiveField = enum { + none, + username, + ip, + port, + saves_path, +}; + +/// UI mutable state +pub const UiState = struct { + active_tab: Tab = .play, + config: config_mod.Config = .{}, + game_version_display: [64]u8 = [_]u8{0} ** 64, + game_version_len: usize = 0, + active_field: ActiveField = .none, + + // Edit buffers for input fields (persist until Clay renders) + username_buf: [64]u8 = [_]u8{0} ** 64, + username_len: usize = 0, + ip_buf: [64]u8 = [_]u8{0} ** 64, + ip_len: usize = 0, + port_buf: [16]u8 = [_]u8{0} ** 16, + port_len: usize = 0, + saves_buf: [256]u8 = [_]u8{0} ** 256, + saves_len: usize = 0, + + /// Copy config values into edit buffers + pub fn syncFromConfig(self: *UiState) void { + const preset = self.config.presets[self.config.active_preset]; + + self.username_len = @min(preset.username.len, self.username_buf.len); + if (self.username_len > 0 and preset.username.ptr != self.username_buf[0..].ptr) { + @memcpy(self.username_buf[0..self.username_len], preset.username[0..self.username_len]); + } + + self.ip_len = @min(preset.ip.len, self.ip_buf.len); + if (self.ip_len > 0 and preset.ip.ptr != self.ip_buf[0..].ptr) { + @memcpy(self.ip_buf[0..self.ip_len], preset.ip[0..self.ip_len]); + } + + self.port_len = @min(preset.port.len, self.port_buf.len); + if (self.port_len > 0 and preset.port.ptr != self.port_buf[0..].ptr) { + @memcpy(self.port_buf[0..self.port_len], preset.port[0..self.port_len]); + } + + self.saves_len = @min(self.config.saves_path.len, self.saves_buf.len); + if (self.saves_len > 0 and self.config.saves_path.ptr != self.saves_buf[0..].ptr) { + @memcpy(self.saves_buf[0..self.saves_len], self.config.saves_path[0..self.saves_len]); + } + } +}; + +pub var ui_state = UiState{}; + +/// Handle a text input character from SDL +pub fn handleTextInput(text: []const u8) void { + switch (ui_state.active_field) { + .username => appendToBuffer(&ui_state.username_buf, &ui_state.username_len, text), + .ip => appendToBuffer(&ui_state.ip_buf, &ui_state.ip_len, text), + .port => appendToBuffer(&ui_state.port_buf, &ui_state.port_len, text), + .saves_path => appendToBuffer(&ui_state.saves_buf, &ui_state.saves_len, text), + .none => {}, + } +} + +/// Handle backspace key +pub fn handleBackspace() void { + switch (ui_state.active_field) { + .username => { + if (ui_state.username_len > 0) ui_state.username_len -= 1; + }, + .ip => { + if (ui_state.ip_len > 0) ui_state.ip_len -= 1; + }, + .port => { + if (ui_state.port_len > 0) ui_state.port_len -= 1; + }, + .saves_path => { + if (ui_state.saves_len > 0) ui_state.saves_len -= 1; + }, + .none => {}, + } +} + +/// Handle Enter/Return key — commit and unfocus +pub fn handleReturn() void { + commitActiveField(); + ui_state.active_field = .none; +} + +fn appendToBuffer(buf: anytype, len: *usize, text: []const u8) void { + for (text) |ch| { + if (len.* < buf.len) { + buf[len.*] = ch; + len.* += 1; + } + } +} + +/// Write edit buffer contents back to config +fn commitActiveField() void { + const p = ui_state.config.active_preset; + switch (ui_state.active_field) { + .username => ui_state.config.presets[p].username = ui_state.username_buf[0..ui_state.username_len], + .ip => ui_state.config.presets[p].ip = ui_state.ip_buf[0..ui_state.ip_len], + .port => ui_state.config.presets[p].port = ui_state.port_buf[0..ui_state.port_len], + .saves_path => ui_state.config.saves_path = ui_state.saves_buf[0..ui_state.saves_len], + .none => {}, + } +} + +// ── Color helpers ────────────────────────────────────────────────────── + +fn rgba(r: f32, g: f32, b: f32, a: f32) c.Clay_Color { + return .{ .r = r, .g = g, .b = b, .a = a }; +} + +// Color constants matching the Minecraft Launcher spec +const COLOR_HEADER_BG = rgba(0x11, 0x11, 0x11, 0xBB); +const COLOR_SIDEBAR_BG = rgba(0x2A, 0x2A, 0x2A, 0xAA); +const COLOR_PANEL_BG = rgba(0x1E, 0x1E, 0x1E, 0x99); +const COLOR_BOTTOM_BG = rgba(0x11, 0x11, 0x11, 0xBB); +const COLOR_WHITE = rgba(0xFF, 0xFF, 0xFF, 0xFF); +const COLOR_MUTED = rgba(0xAA, 0xAA, 0xAA, 0xFF); +const COLOR_GREEN = rgba(0x5A, 0x9E, 0x28, 0xFF); +const COLOR_TAB_ACTIVE = rgba(0x1E, 0x1E, 0x1E, 0xFF); +const COLOR_TRANSPARENT = rgba(0, 0, 0, 0); +const COLOR_BORDER = rgba(0x3A, 0x3A, 0x3A, 0xFF); +const COLOR_INPUT_BG = rgba(0x0A, 0x0A, 0x0A, 0xCC); +const COLOR_INPUT_BORDER = rgba(0x55, 0x55, 0x55, 0xFF); +const COLOR_CARD_BG = rgba(0x25, 0x25, 0x25, 0x88); +const COLOR_CARD_ACTIVE = rgba(0x35, 0x35, 0x35, 0xAA); +const COLOR_YELLOW = rgba(0xDA, 0xAA, 0x20, 0xFF); +const COLOR_BLUE = rgba(0x3A, 0x7A, 0xDA, 0xFF); +const COLOR_GRAY = rgba(0x66, 0x66, 0x66, 0xFF); +const COLOR_RED = rgba(0xDA, 0x3A, 0x3A, 0xFF); +const COLOR_PROGRESS_TRACK = rgba(0x1A, 0x1A, 0x1A, 0xFF); + +const FONT_ID: u16 = 0; + +// File-scope buffer for dynamic text that must persist until Clay renders +var dl_progress_buf: [96]u8 = undefined; +var dl_progress_len: usize = 0; + +// ── Helpers for Clay raw API ─────────────────────────────────────────── + +fn clayStr(s: []const u8) c.Clay_String { + return .{ .length = @intCast(s.len), .chars = s.ptr }; +} + +fn clayId(name: []const u8) c.Clay_ElementId { + return c.Clay__HashString(clayStr(name), 0); +} + +fn clayIdI(name: []const u8, index: u32) c.Clay_ElementId { + return c.Clay__HashStringWithOffset(clayStr(name), index, 0); +} + +fn openElement(name: []const u8) void { + c.Clay__OpenElementWithId(clayId(name)); +} + +fn openElementI(name: []const u8, index: u32) void { + c.Clay__OpenElementWithId(clayIdI(name, index)); +} + +fn closeElement() void { + c.Clay__CloseElement(); +} + +var text_id_counter: u32 = 0; + +fn textElement(s: []const u8, font_size: u16, color: c.Clay_Color) void { + text_id_counter +%= 1; + // We open a transparent block just to contain the text, with a unique ID based on pointer+counter + c.Clay__OpenElementWithId(c.Clay__HashStringWithOffset(clayStr(s), text_id_counter, 0)); + c.Clay__ConfigureOpenElement(.{ .layout = .{ .sizing = .{ .width = fixedW(0), .height = fixedH(0) } } }); + + c.Clay__OpenTextElement(clayStr(s), c.Clay__StoreTextElementConfig(.{ + .fontId = FONT_ID, + .fontSize = font_size, + .textColor = color, + })); + + c.Clay__CloseElement(); // Close textElement container +} + +fn growSize() c.Clay_Sizing { + return .{ + .width = .{ .size = .{ .minMax = .{ .min = 0, .max = 0 } }, .type = c.CLAY__SIZING_TYPE_GROW }, + .height = .{ .size = .{ .minMax = .{ .min = 0, .max = 0 } }, .type = c.CLAY__SIZING_TYPE_GROW }, + }; +} + +fn growWidth() c.Clay_SizingAxis { + return .{ .size = .{ .minMax = .{ .min = 0, .max = 0 } }, .type = c.CLAY__SIZING_TYPE_GROW }; +} + +fn growHeight() c.Clay_SizingAxis { + return .{ .size = .{ .minMax = .{ .min = 0, .max = 0 } }, .type = c.CLAY__SIZING_TYPE_GROW }; +} + +fn fixedW(w: f32) c.Clay_SizingAxis { + return .{ .size = .{ .minMax = .{ .min = w, .max = w } }, .type = c.CLAY__SIZING_TYPE_FIXED }; +} + +fn fixedH(h: f32) c.Clay_SizingAxis { + return .{ .size = .{ .minMax = .{ .min = h, .max = h } }, .type = c.CLAY__SIZING_TYPE_FIXED }; +} + +fn percentW(pct: f32) c.Clay_SizingAxis { + return .{ .size = .{ .percent = pct }, .type = c.CLAY__SIZING_TYPE_PERCENT }; +} + +fn uniformCorner(r: f32) c.Clay_CornerRadius { + return .{ .topLeft = r, .topRight = r, .bottomLeft = r, .bottomRight = r }; +} + +fn uniformBorder(width: u16) c.Clay_BorderWidth { + return .{ .left = width, .right = width, .top = width, .bottom = width, .betweenChildren = 0 }; +} + +fn pad4(l: u16, r: u16, t: u16, b: u16) c.Clay_Padding { + return .{ .left = l, .right = r, .top = t, .bottom = b }; +} + +// ── Layout Root ──────────────────────────────────────────────────────── + +pub fn layoutRoot() c.Clay_RenderCommandArray { + text_id_counter = 0; // Reset text ID counter every frame + c.Clay_BeginLayout(); + + // Root container + openElement("Root"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_TOP_TO_BOTTOM, + .sizing = growSize(), + }, + }); + + layoutHeader(); + layoutMiddle(); + layoutBottomBar(); + + closeElement(); // Root + + return c.Clay_EndLayout(); +} + +// ── Header Bar ───────────────────────────────────────────────────────── + +fn layoutHeader() void { + openElement("Header"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_LEFT_TO_RIGHT, + .sizing = .{ .width = growWidth(), .height = fixedH(36) }, + .padding = pad4(12, 12, 0, 0), + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + .childGap = 16, + }, + .backgroundColor = COLOR_HEADER_BG, + }); + + // Title + textElement("Flint", 16, COLOR_WHITE); + + // Spacer + openElement("HSp1"); + c.Clay__ConfigureOpenElement(.{ .layout = .{ .sizing = .{ .width = growWidth(), .height = fixedH(0) } } }); + closeElement(); + + // Game version + if (ui_state.game_version_len > 0) { + textElement(ui_state.game_version_display[0..ui_state.game_version_len], 10, COLOR_MUTED); + } else { + textElement("Not installed", 10, COLOR_MUTED); + } + + // Spacer + openElement("HSp2"); + c.Clay__ConfigureOpenElement(.{ .layout = .{ .sizing = .{ .width = growWidth(), .height = fixedH(0) } } }); + closeElement(); + + // Status pills + layoutPill("GPill", gameStatusText(), gameStatusColor()); + layoutPill("LPill", launcherStatusText(), launcherStatusColor()); + + closeElement(); // Header +} + +fn layoutPill(name: []const u8, text: []const u8, color: c.Clay_Color) void { + openElement(name); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .padding = pad4(8, 8, 4, 4), + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = color, + .cornerRadius = uniformCorner(4), + }); + textElement(text, 10, COLOR_WHITE); + closeElement(); +} + +fn gameStatusText() []const u8 { + return switch (game_updater.game_update_status) { + .up_to_date => "Game up to date", + .update_available => "Game update available", + .downloading => "Downloading...", + .checking => "Checking...", + .not_checked => "Not checked", + .err => "Offline", + }; +} + +fn gameStatusColor() c.Clay_Color { + return switch (game_updater.game_update_status) { + .up_to_date => COLOR_GREEN, + .update_available => COLOR_YELLOW, + .downloading => COLOR_BLUE, + .checking, .not_checked => COLOR_GRAY, + .err => COLOR_RED, + }; +} + +fn launcherStatusText() []const u8 { + return switch (updater.launcher_update_status) { + .up_to_date => "Launcher up to date", + .update_available => "Launcher update available", + .downloading => "Downloading...", + .checking => "Checking...", + .not_checked => "Not checked", + .err => "Offline", + }; +} + +fn launcherStatusColor() c.Clay_Color { + return switch (updater.launcher_update_status) { + .up_to_date => COLOR_GREEN, + .update_available => COLOR_YELLOW, + .downloading => COLOR_BLUE, + .checking, .not_checked => COLOR_GRAY, + .err => COLOR_RED, + }; +} + +// ── Middle Section ───────────────────────────────────────────────────── + +fn layoutMiddle() void { + openElement("Mid"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_LEFT_TO_RIGHT, + .sizing = growSize(), + }, + }); + + layoutSidebar(); + layoutContent(); + + closeElement(); // Mid +} + +// ── Sidebar ──────────────────────────────────────────────────────────── + +fn layoutSidebar() void { + openElement("Sidebar"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_TOP_TO_BOTTOM, + .sizing = .{ .width = fixedW(160), .height = growHeight() }, + }, + .backgroundColor = COLOR_SIDEBAR_BG, + }); + + layoutTab("PLAY", .play); + layoutTab("VERSIONS", .versions); + layoutTab("SETTINGS", .settings); + + closeElement(); // Sidebar +} + +fn layoutTab(label: []const u8, tab: Tab) void { + const is_active = ui_state.active_tab == tab; + + openElement(label); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = growWidth(), .height = fixedH(36) }, + .padding = pad4(16, 12, 0, 0), + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = if (is_active) COLOR_TAB_ACTIVE else COLOR_TRANSPARENT, + .border = if (is_active) .{ + .color = COLOR_GREEN, + .width = .{ .left = 3, .right = 0, .top = 0, .bottom = 0, .betweenChildren = 0 }, + } else std.mem.zeroes(c.Clay_BorderElementConfig), + }); + + textElement(label, 12, if (is_active) COLOR_WHITE else COLOR_MUTED); + + closeElement(); +} + +// ── Content Panel ────────────────────────────────────────────────────── + +fn layoutContent() void { + openElement("Content"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_TOP_TO_BOTTOM, + .sizing = growSize(), + .padding = pad4(16, 16, 16, 16), + .childGap = 12, + }, + .backgroundColor = COLOR_PANEL_BG, + }); + + switch (ui_state.active_tab) { + .play => layoutPlayTab(), + .versions => layoutVersionsTab(), + .settings => layoutSettingsTab(), + } + + closeElement(); // Content +} + +// ── Play Tab ─────────────────────────────────────────────────────────── + +fn layoutPlayTab() void { + textElement("Server Presets", 16, COLOR_WHITE); + + // Row 1 + openElement("PR1"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_LEFT_TO_RIGHT, + .sizing = .{ .width = growWidth(), .height = growHeight() }, + .childGap = 12, + }, + }); + layoutPresetCard(0); + layoutPresetCard(1); + closeElement(); + + // Row 2 + openElement("PR2"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_LEFT_TO_RIGHT, + .sizing = .{ .width = growWidth(), .height = growHeight() }, + .childGap = 12, + }, + }); + layoutPresetCard(2); + layoutPresetCard(3); + closeElement(); +} + +fn layoutPresetCard(index: u32) void { + const is_active = ui_state.config.active_preset == index; + const preset = ui_state.config.presets[index]; + + openElementI("Card", index); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_TOP_TO_BOTTOM, + .sizing = .{ .width = growWidth(), .height = growHeight() }, + .padding = pad4(12, 12, 12, 12), + .childGap = 6, + }, + .backgroundColor = if (is_active) COLOR_CARD_ACTIVE else COLOR_CARD_BG, + .cornerRadius = uniformCorner(4), + .border = .{ + .color = if (is_active) COLOR_GREEN else COLOR_BORDER, + .width = uniformBorder(if (is_active) 2 else 1), + }, + }); + + textElement(preset.name, 12, COLOR_WHITE); + if (preset.ip.len > 0) { + textElement(preset.ip, 10, COLOR_MUTED); + } else { + textElement("No server configured", 10, COLOR_MUTED); + } + + closeElement(); +} + +// ── Versions Tab ─────────────────────────────────────────────────────── + +fn layoutVersionsTab() void { + textElement("Installed Versions", 16, COLOR_WHITE); + + // Check for Updates button + openElement("ChkUpd"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = fixedW(180), .height = fixedH(36) }, + .childAlignment = .{ .x = c.CLAY_ALIGN_X_CENTER, .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = COLOR_GREEN, + .cornerRadius = uniformCorner(4), + }); + textElement("Check for Updates", 12, COLOR_WHITE); + closeElement(); + + // Update available banner + if (game_updater.game_update_status == .update_available) { + openElement("UpdBnr"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_LEFT_TO_RIGHT, + .sizing = .{ .width = growWidth(), .height = fixedH(44) }, + .padding = pad4(12, 12, 0, 0), + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + .childGap = 12, + }, + .backgroundColor = rgba(0x2A, 0x3A, 0x1A, 0xDD), + .cornerRadius = uniformCorner(6), + .border = .{ .color = COLOR_GREEN, .width = uniformBorder(1) }, + }); + + textElement("A new game version is available!", 12, COLOR_WHITE); + + // Spacer + openElement("UBSp"); + c.Clay__ConfigureOpenElement(.{ .layout = .{ .sizing = .{ .width = growWidth() } } }); + closeElement(); + + // Download button + openElement("DlBtn"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = fixedW(150), .height = fixedH(30) }, + .childAlignment = .{ .x = c.CLAY_ALIGN_X_CENTER, .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = COLOR_GREEN, + .cornerRadius = uniformCorner(4), + }); + textElement("Download Update", 11, COLOR_WHITE); + closeElement(); + + closeElement(); // UpdBnr + } + + // Download progress bar + if (game_updater.game_update_status == .downloading) { + const progress = game_updater.game_download_progress; + const fraction: f32 = if (progress.total_bytes > 0) + @as(f32, @floatFromInt(progress.bytes_received)) / @as(f32, @floatFromInt(progress.total_bytes)) + else blk: { + // Indeterminate: animate a subtle pulse + break :blk 0.0; + }; + + // Status text + if (progress.is_extracting) { + textElement("Extracting...", 11, COLOR_MUTED); + } else { + const mb_received = @as(f64, @floatFromInt(progress.bytes_received)) / (1024.0 * 1024.0); + if (progress.total_bytes > 0) { + const mb_total = @as(f64, @floatFromInt(progress.total_bytes)) / (1024.0 * 1024.0); + const pct = fraction * 100.0; + const result = std.fmt.bufPrint(&dl_progress_buf, "Downloading... {d:.1} / {d:.1} MB ({d:.0}%)", .{ mb_received, mb_total, pct }) catch "Downloading..."; + dl_progress_len = result.len; + } else { + const result = std.fmt.bufPrint(&dl_progress_buf, "Downloading... {d:.1} MB", .{mb_received}) catch "Downloading..."; + dl_progress_len = result.len; + } + textElement(dl_progress_buf[0..dl_progress_len], 11, COLOR_MUTED); + } + + // Track + openElement("PrgT"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ .sizing = .{ .width = growWidth(), .height = fixedH(22) } }, + .backgroundColor = COLOR_PROGRESS_TRACK, + .cornerRadius = uniformCorner(6), + }); + + // Fill + const fill_pct = if (fraction > 0.01) fraction else 0.01; + openElement("PrgF"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ .sizing = .{ .width = percentW(fill_pct), .height = growHeight() } }, + .backgroundColor = COLOR_GREEN, + .cornerRadius = uniformCorner(6), + }); + closeElement(); + + closeElement(); // PrgT + } + + // Done message + if (game_updater.game_download_progress.done) { + textElement("Update installed successfully!", 12, COLOR_GREEN); + } + + // Version list + openElement("VerL"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_TOP_TO_BOTTOM, + .sizing = growSize(), + .childGap = 4, + .padding = pad4(0, 0, 8, 8), + }, + .clip = .{ .vertical = true, .horizontal = false, .childOffset = c.Clay_GetScrollOffset() }, + }); + if (game_updater.installed_version_count == 0) { + textElement("No versions installed", 12, COLOR_MUTED); + } else { + for (0..game_updater.installed_version_count) |i| { + openElementI("Ver", @intCast(i)); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = growWidth(), .height = fixedH(28) }, + .padding = pad4(8, 8, 0, 0), + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = COLOR_CARD_BG, + .cornerRadius = uniformCorner(4), + }); + textElement(game_updater.getInstalledVersionName(i), 11, COLOR_WHITE); + closeElement(); + } + } + closeElement(); +} + +// ── Settings Tab ─────────────────────────────────────────────────────── + +fn layoutSettingsTab() void { + textElement("SAVES FOLDER", 10, COLOR_MUTED); + + // Saves path row + openElement("SvRow"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_LEFT_TO_RIGHT, + .sizing = .{ .width = growWidth() }, + .childGap = 8, + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + }, + }); + + const is_active = ui_state.active_field == .saves_path; + const border_color = if (is_active) COLOR_GREEN else COLOR_INPUT_BORDER; + + // Path display + openElement("Inp_SavesPath"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = growWidth(), .height = fixedH(32) }, + .padding = pad4(8, 8, 0, 0), + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = COLOR_INPUT_BG, + .border = .{ .color = border_color, .width = uniformBorder(1) }, + }); + + var text_to_render: []const u8 = ""; + if (is_active) { + text_to_render = ui_state.saves_buf[0..ui_state.saves_len]; + } else { + text_to_render = ui_state.config.saves_path; + } + + if (text_to_render.len > 0) { + textElement(text_to_render, 10, COLOR_WHITE); + } else if (is_active) { + textElement("_", 10, COLOR_WHITE); + } + closeElement(); + + // Change button + openElement("ChBtn"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = fixedW(80), .height = fixedH(32) }, + .childAlignment = .{ .x = c.CLAY_ALIGN_X_CENTER, .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = COLOR_GREEN, + .cornerRadius = uniformCorner(4), + }); + textElement("Change", 10, COLOR_WHITE); + closeElement(); + + closeElement(); // SvRow + + // Spacer + openElement("Sp1"); + c.Clay__ConfigureOpenElement(.{ .layout = .{ .sizing = .{ .height = fixedH(16) } } }); + closeElement(); + + // Wine section (Linux only) + if (comptime builtin.os.tag == .linux) { + textElement("WINE", 10, COLOR_MUTED); + + openElement("WineR"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_LEFT_TO_RIGHT, + .sizing = .{ .width = growWidth() }, + .childGap = 12, + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + }, + }); + + if (launcher_mod.wine_version) |wv| { + textElement(wv, 12, COLOR_WHITE); + } else { + textElement("Wine not found", 12, COLOR_RED); + } + + openElement("RstW"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = fixedW(140), .height = fixedH(32) }, + .childAlignment = .{ .x = c.CLAY_ALIGN_X_CENTER, .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = COLOR_GREEN, + .cornerRadius = uniformCorner(4), + }); + textElement("Reset Wine Prefix", 10, COLOR_WHITE); + closeElement(); + + closeElement(); // WineR + + openElement("Sp2"); + c.Clay__ConfigureOpenElement(.{ .layout = .{ .sizing = .{ .height = fixedH(16) } } }); + closeElement(); + } + + // Launcher section + textElement("LAUNCHER", 10, COLOR_MUTED); + + openElement("LaunchR"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_LEFT_TO_RIGHT, + .sizing = .{ .width = growWidth() }, + .childGap = 12, + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + }, + }); + + textElement(updater.getShortCommit(), 12, COLOR_WHITE); + + openElement("ChkLU"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = fixedW(180), .height = fixedH(32) }, + .childAlignment = .{ .x = c.CLAY_ALIGN_X_CENTER, .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = COLOR_GREEN, + .cornerRadius = uniformCorner(4), + }); + textElement("Check for Update", 10, COLOR_WHITE); + closeElement(); + + closeElement(); // LaunchR + + openElement("Sp3"); + c.Clay__ConfigureOpenElement(.{ .layout = .{ .sizing = .{ .height = fixedH(16) } } }); + closeElement(); + + textElement("GAME SOURCE", 10, COLOR_MUTED); + textElement("https://github.com/smartcmd/MinecraftConsoles/releases/tag/nightly", 10, COLOR_WHITE); +} + +// ── Bottom Bar ───────────────────────────────────────────────────────── + +fn layoutBottomBar() void { + openElement("BBar"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_LEFT_TO_RIGHT, + .sizing = .{ .width = growWidth(), .height = fixedH(80) }, + .padding = pad4(12, 12, 12, 12), + .childGap = 8, + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = COLOR_BOTTOM_BG, + .border = .{ .color = COLOR_BORDER, .width = .{ .left = 0, .right = 0, .top = 1, .bottom = 0, .betweenChildren = 0 } }, + }); + + // Folder button + openElement("FldBtn"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = fixedW(36), .height = fixedH(36) }, + .childAlignment = .{ .x = c.CLAY_ALIGN_X_CENTER, .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = COLOR_INPUT_BG, + .cornerRadius = uniformCorner(4), + .border = .{ .color = COLOR_INPUT_BORDER, .width = uniformBorder(1) }, + }); + textElement("F", 12, COLOR_WHITE); + closeElement(); + + // Input fields + layoutInputField("Username", 140, .username); + layoutInputField("IP", 140, .ip); + layoutInputField("Port", 80, .port); + + // Spacer + openElement("BSp1"); + c.Clay__ConfigureOpenElement(.{ .layout = .{ .sizing = .{ .width = growWidth() } } }); + closeElement(); + + // Preset switcher + openElement("PSw"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_LEFT_TO_RIGHT, + .childGap = 8, + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + }, + }); + textElement("<", 16, COLOR_MUTED); + textElement(ui_state.config.presets[ui_state.config.active_preset].name, 12, COLOR_WHITE); + textElement(">", 16, COLOR_MUTED); + closeElement(); + + // Spacer + openElement("BSp2"); + c.Clay__ConfigureOpenElement(.{ .layout = .{ .sizing = .{ .width = growWidth() } } }); + closeElement(); + + // LAUNCH button + const launch_bg = switch (launcher_mod.game_status) { + .not_running => COLOR_GREEN, + .running => COLOR_GRAY, + .initializing_wine => COLOR_YELLOW, + }; + const launch_text = switch (launcher_mod.game_status) { + .not_running => "LAUNCH", + .running => "RUNNING...", + .initializing_wine => "INITIALIZING WINE...", + }; + + openElement("Launch"); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = fixedW(160), .height = growHeight() }, + .childAlignment = .{ .x = c.CLAY_ALIGN_X_CENTER, .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = launch_bg, + .cornerRadius = uniformCorner(4), + }); + textElement(launch_text, 16, COLOR_WHITE); + closeElement(); + + closeElement(); // BBar +} + +fn layoutInputField(label: []const u8, width: f32, field: ActiveField) void { + openElement(label); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .layoutDirection = c.CLAY_TOP_TO_BOTTOM, + .sizing = .{ .width = fixedW(width) }, + .childGap = 2, + }, + }); + + textElement(label, 10, COLOR_MUTED); + + const is_active = ui_state.active_field == field; + const border_color = if (is_active) COLOR_GREEN else COLOR_INPUT_BORDER; + + var inp_id_buf: [64]u8 = undefined; + const inp_id = std.fmt.bufPrint(&inp_id_buf, "Inp_{s}", .{label}) catch "Inp_"; + + c.Clay__OpenElementWithId(clayId(inp_id)); + c.Clay__ConfigureOpenElement(.{ + .layout = .{ + .sizing = .{ .width = growWidth(), .height = fixedH(28) }, + .padding = pad4(6, 6, 0, 0), + .childAlignment = .{ .y = c.CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = COLOR_INPUT_BG, + .border = .{ .color = border_color, .width = uniformBorder(1) }, + }); + + // Render text + var text_to_render: []const u8 = ""; + if (is_active) { + text_to_render = switch (field) { + .username => ui_state.username_buf[0..ui_state.username_len], + .ip => ui_state.ip_buf[0..ui_state.ip_len], + .port => ui_state.port_buf[0..ui_state.port_len], + .saves_path => ui_state.saves_buf[0..ui_state.saves_len], + .none => "", + }; + } else { + const p = ui_state.config.active_preset; + text_to_render = switch (field) { + .username => ui_state.config.presets[p].username, + .ip => ui_state.config.presets[p].ip, + .port => ui_state.config.presets[p].port, + .saves_path => ui_state.config.saves_path, + .none => "", + }; + } + + // Check if it's empty, possibly display a cursor if active + if (text_to_render.len > 0) { + textElement(text_to_render, 12, COLOR_WHITE); + } else if (is_active) { + textElement("_", 12, COLOR_WHITE); + } + + closeElement(); // Inp + + closeElement(); +} + +// ── Click Handling ───────────────────────────────────────────────────── + +pub fn handleClick() void { + // Check sidebar tab clicks + if (c.Clay_PointerOver(clayId("PLAY"))) { + ui_state.active_tab = .play; + } else if (c.Clay_PointerOver(clayId("VERSIONS"))) { + ui_state.active_tab = .versions; + } else if (c.Clay_PointerOver(clayId("SETTINGS"))) { + ui_state.active_tab = .settings; + } + + // Check preset card clicks + for (0..4) |i| { + if (c.Clay_PointerOver(clayIdI("Card", @intCast(i)))) { + ui_state.config.active_preset = @intCast(i); + } + } + + if (c.Clay_PointerOver(clayId("Launch"))) { + // commit any active field before launching + commitActiveField(); + ui_state.active_field = .none; + launcher_mod.launch(std.heap.page_allocator, ui_state.config) catch |err| { + std.debug.print("Failed to launch: {}\n", .{err}); + }; + } else if (c.Clay_PointerOver(clayId("ChkUpd"))) { + game_updater.checkForGameUpdate(std.heap.page_allocator) catch |err| { + std.debug.print("Failed to check game update: {}\n", .{err}); + }; + } else if (c.Clay_PointerOver(clayId("DlBtn"))) { + // Don't start another download if already downloading + if (game_updater.game_update_status != .downloading) { + const thread = std.Thread.spawn(.{}, struct { + fn run() void { + game_updater.downloadGame(std.heap.page_allocator) catch |err| { + std.debug.print("Failed to download game: {}\n", .{err}); + game_updater.game_update_status = .err; + }; + } + }.run, .{}) catch |err| { + std.debug.print("Failed to spawn download thread: {}\n", .{err}); + return; + }; + thread.detach(); + } + } else if (c.Clay_PointerOver(clayId("ChkLU"))) { + updater.checkForLauncherUpdate(std.heap.page_allocator) catch |err| { + std.debug.print("Failed to check launcher update: {}\n", .{err}); + }; + } else if (c.Clay_PointerOver(clayId("ChBtn"))) { + std.debug.print("TODO: Implement file picker for saves path\n", .{}); + } else if (c.Clay_PointerOver(clayId("Inp_Username"))) { + commitActiveField(); + ui_state.config.active_preset = ui_state.config.active_preset; // force save logic placeholder just in case + ui_state.active_field = .username; + ui_state.syncFromConfig(); + } else if (c.Clay_PointerOver(clayId("Inp_IP"))) { + commitActiveField(); + ui_state.active_field = .ip; + ui_state.syncFromConfig(); + } else if (c.Clay_PointerOver(clayId("Inp_Port"))) { + commitActiveField(); + ui_state.active_field = .port; + ui_state.syncFromConfig(); + } else if (c.Clay_PointerOver(clayId("Inp_SavesPath"))) { + commitActiveField(); + ui_state.active_field = .saves_path; + ui_state.syncFromConfig(); + } else { + // Clicked somewhere else, unfocus + commitActiveField(); + ui_state.active_field = .none; + } + + if (comptime builtin.os.tag == .linux) { + if (c.Clay_PointerOver(clayId("RstW"))) { + launcher_mod.resetWinePrefix(std.heap.page_allocator) catch |err| { + std.debug.print("Failed to reset wine prefix: {}\n", .{err}); + }; + } + } +} diff --git a/src/updater.zig b/src/updater.zig new file mode 100644 index 0000000..55475f2 --- /dev/null +++ b/src/updater.zig @@ -0,0 +1,287 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const safe_fs = @import("safe_fs.zig"); + +/// Launcher self-update status +pub const LauncherUpdateStatus = enum { + not_checked, + checking, + up_to_date, + update_available, + downloading, + err, +}; + +pub var launcher_update_status: LauncherUpdateStatus = .not_checked; + +// Embedded at compile time from commit.txt written by build.zig +pub const current_commit = std.mem.trimRight(u8, @embedFile("commit.txt"), "\n\r "); + +const LAUNCHER_API_URL = "https://api.github.com/repos/YOUR_REPO/releases/tags/nightly"; + +/// Get the short commit SHA for display +pub fn getShortCommit() []const u8 { + if (current_commit.len >= 7) return current_commit[0..7]; + return current_commit; +} + +/// Check for launcher updates via GitHub nightly release +pub fn checkForLauncherUpdate(allocator: std.mem.Allocator) !void { + launcher_update_status = .checking; + + var bundle = std.crypto.Certificate.Bundle{}; + bundle.rescan(allocator) catch {}; + + var client = std.http.Client{ .allocator = allocator, .ca_bundle = bundle }; + defer client.deinit(); + + const uri = try std.Uri.parse(LAUNCHER_API_URL); + var req = client.request(.GET, uri, .{ + .extra_headers = &.{ + .{ .name = "User-Agent", .value = "Flint/1.0" }, + .{ .name = "Accept", .value = "application/vnd.github.v3+json" }, + .{ .name = "Accept-Encoding", .value = "identity" }, + }, + }) catch |err| { + std.debug.print("updater request err: {}\n", .{err}); + launcher_update_status = .err; + return; + }; + defer req.deinit(); + + req.sendBodiless() catch |err| { + std.debug.print("updater sendBodiless err: {}\n", .{err}); + launcher_update_status = .err; + return; + }; + + var server_header_buffer: [8192]u8 = undefined; + var response = req.receiveHead(&server_header_buffer) catch |err| { + std.debug.print("updater receiveHead err: {}\n", .{err}); + launcher_update_status = .err; + return; + }; + + if (response.head.status != .ok) { + std.debug.print("updater status not ok: {}\n", .{response.head.status}); + launcher_update_status = .err; + return; + } + + var reader = response.reader(&.{}); + const body = reader.allocRemaining(allocator, .unlimited) catch { + launcher_update_status = .err; + return; + }; + defer allocator.free(body); + + const parsed = std.json.parseFromSlice(std.json.Value, allocator, body, .{}) catch { + launcher_update_status = .err; + return; + }; + defer parsed.deinit(); + + const root = parsed.value; + if (root != .object) { + launcher_update_status = .err; + return; + } + + const commitish = root.object.get("target_commitish") orelse { + launcher_update_status = .err; + return; + }; + if (commitish != .string) { + launcher_update_status = .err; + return; + } + + if (std.mem.eql(u8, current_commit, commitish.string)) { + launcher_update_status = .up_to_date; + } else { + launcher_update_status = .update_available; + } +} + +/// Delete leftover launcher.old on startup +pub fn cleanupOldLauncher() void { + const self_path = std.fs.selfExePathAlloc(std.heap.page_allocator) catch return; + defer std.heap.page_allocator.free(self_path); + + const dir = std.fs.path.dirname(self_path) orelse return; + var buf: [std.fs.max_path_bytes]u8 = undefined; + const old_path = std.fmt.bufPrint(&buf, "{s}/launcher.old", .{dir}) catch return; + + // Retry delete up to 3 times with 100ms sleep + for (0..3) |_| { + std.fs.deleteFileAbsolute(old_path) catch |err| switch (err) { + error.FileNotFound => return, + else => { + std.Thread.sleep(100 * std.time.ns_per_ms); + continue; + }, + }; + return; + } +} + +/// Download and apply launcher update +pub fn downloadAndApplyUpdate(allocator: std.mem.Allocator) !void { + launcher_update_status = .downloading; + + // Find the right asset for current platform + var bundle = std.crypto.Certificate.Bundle{}; + bundle.rescan(allocator) catch {}; + + var client = std.http.Client{ .allocator = allocator, .ca_bundle = bundle }; + defer client.deinit(); + + const uri = try std.Uri.parse(LAUNCHER_API_URL); + var req = client.request(.GET, uri, .{ + .extra_headers = &.{ + .{ .name = "User-Agent", .value = "Flint/1.0" }, + .{ .name = "Accept", .value = "application/vnd.github.v3+json" }, + .{ .name = "Accept-Encoding", .value = "identity" }, + }, + }) catch { + launcher_update_status = .err; + return; + }; + defer req.deinit(); + + req.sendBodiless() catch { + launcher_update_status = .err; + return; + }; + + var server_header_buffer: [8192]u8 = undefined; + var response = req.receiveHead(&server_header_buffer) catch { + launcher_update_status = .err; + return; + }; + + if (response.head.status != .ok) { + launcher_update_status = .err; + return; + } + + var reader = response.reader(&.{}); + const body = reader.allocRemaining(allocator, .unlimited) catch { + launcher_update_status = .err; + return; + }; + defer allocator.free(body); + + const parsed = std.json.parseFromSlice(std.json.Value, allocator, body, .{}) catch { + launcher_update_status = .err; + return; + }; + defer parsed.deinit(); + + const root = parsed.value; + if (root != .object) { + launcher_update_status = .err; + return; + } + + const assets = root.object.get("assets") orelse { + launcher_update_status = .err; + return; + }; + if (assets != .array) { + launcher_update_status = .err; + return; + } + + // Find platform binary + const platform_str = if (comptime builtin.os.tag == .windows) "windows" else "linux"; + var download_url: ?[]const u8 = null; + + for (assets.array.items) |asset| { + if (asset != .object) continue; + const name = asset.object.get("name") orelse continue; + if (name != .string) continue; + + if (std.mem.indexOf(u8, name.string, platform_str) != null) { + const url = asset.object.get("browser_download_url") orelse continue; + if (url == .string) { + download_url = try allocator.dupe(u8, url.string); + break; + } + } + } + + const url = download_url orelse { + launcher_update_status = .err; + return; + }; + defer allocator.free(url); + + // Download to launcher.new + const self_path = try std.fs.selfExePathAlloc(allocator); + defer allocator.free(self_path); + const self_dir = std.fs.path.dirname(self_path) orelse return; + + var new_buf: [std.fs.max_path_bytes]u8 = undefined; + const new_path = try std.fmt.bufPrint(&new_buf, "{s}/launcher.new", .{self_dir}); + var old_buf: [std.fs.max_path_bytes]u8 = undefined; + const old_path = try std.fmt.bufPrint(&old_buf, "{s}/launcher.old", .{self_dir}); + + // Download + const dl_uri = try std.Uri.parse(url); + var dl_req = client.request(.GET, dl_uri, .{ + .extra_headers = &.{ + .{ .name = "User-Agent", .value = "Flint/1.0" }, + .{ .name = "Accept-Encoding", .value = "identity" }, + }, + }) catch { + launcher_update_status = .err; + return; + }; + defer dl_req.deinit(); + + dl_req.sendBodiless() catch { + launcher_update_status = .err; + return; + }; + + var dl_header_buf: [8192]u8 = undefined; + _ = dl_req.receiveHead(&dl_header_buf) catch { + launcher_update_status = .err; + return; + }; + + const out_file = try std.fs.createFileAbsolute(new_path, .{}); + defer out_file.close(); + + var dl_reader = dl_req.reader(&.{}); + var buf2: [8192]u8 = undefined; + while (true) { + const n = dl_reader.read(&buf2) catch break; + if (n == 0) break; + try out_file.writeAll(buf2[0..n]); + } + + // Make executable (Linux) + if (comptime builtin.os.tag == .linux) { + const new_file = try std.fs.openFileAbsolute(new_path, .{}); + defer new_file.close(); + try new_file.chmod(0o755); + } + + // Rename current -> old + std.fs.renameAbsolute(self_path, old_path) catch {}; + + // Rename new -> current + try std.fs.renameAbsolute(new_path, self_path); + + // Spawn new launcher + var child = std.process.Child.init( + &.{self_path}, + allocator, + ); + try child.spawn(); + + // Exit current process + std.process.exit(0); +} diff --git a/vendor/clay/clay.h b/vendor/clay/clay.h new file mode 100644 index 0000000..80d8393 --- /dev/null +++ b/vendor/clay/clay.h @@ -0,0 +1,4454 @@ +// VERSION: 0.14 + +/* + NOTE: In order to use this library you must define + the following macro in exactly one file, _before_ including clay.h: + + #define CLAY_IMPLEMENTATION + #include "clay.h" + + See the examples folder for details. +*/ + +#include +#include +#include + +// SIMD includes on supported platforms +#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) +#include +#elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__) +#include +#endif + +// ----------------------------------------- +// HEADER DECLARATIONS --------------------- +// ----------------------------------------- + +#ifndef CLAY_HEADER +#define CLAY_HEADER + +#if !( \ + (defined(__cplusplus) && __cplusplus >= 202002L) || \ + (defined(__STDC__) && __STDC__ == 1 && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ + defined(_MSC_VER) || \ + defined(__OBJC__) \ +) +#error "Clay requires C99, C++20, or MSVC" +#endif + +#ifdef CLAY_WASM +#define CLAY_WASM_EXPORT(name) __attribute__((export_name(name))) +#else +#define CLAY_WASM_EXPORT(null) +#endif + +#ifdef CLAY_DLL +#define CLAY_DLL_EXPORT __declspec(dllexport) __stdcall +#else +#define CLAY_DLL_EXPORT +#endif + +// Public Macro API ------------------------ + +#define CLAY__MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define CLAY__MIN(x, y) (((x) < (y)) ? (x) : (y)) + +#define CLAY_TEXT_CONFIG(...) Clay__StoreTextElementConfig(CLAY__CONFIG_WRAPPER(Clay_TextElementConfig, __VA_ARGS__)) + +#define CLAY_BORDER_OUTSIDE(widthValue) {widthValue, widthValue, widthValue, widthValue, 0} + +#define CLAY_BORDER_ALL(widthValue) {widthValue, widthValue, widthValue, widthValue, widthValue} + +#define CLAY_CORNER_RADIUS(radius) (CLAY__INIT(Clay_CornerRadius) { radius, radius, radius, radius }) + +#define CLAY_PADDING_ALL(padding) CLAY__CONFIG_WRAPPER(Clay_Padding, { padding, padding, padding, padding }) + +#define CLAY_SIZING_FIT(...) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = { __VA_ARGS__ } }, .type = CLAY__SIZING_TYPE_FIT }) + +#define CLAY_SIZING_GROW(...) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = { __VA_ARGS__ } }, .type = CLAY__SIZING_TYPE_GROW }) + +#define CLAY_SIZING_FIXED(fixedSize) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = { fixedSize, fixedSize } }, .type = CLAY__SIZING_TYPE_FIXED }) + +#define CLAY_SIZING_PERCENT(percentOfParent) (CLAY__INIT(Clay_SizingAxis) { .size = { .percent = (percentOfParent) }, .type = CLAY__SIZING_TYPE_PERCENT }) + +// Note: If a compile error led you here, you might be trying to use CLAY_ID with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID instead. +#define CLAY_ID(label) CLAY_SID(CLAY_STRING(label)) + +#define CLAY_SID(label) Clay__HashString(label, 0) + +// Note: If a compile error led you here, you might be trying to use CLAY_IDI with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI instead. +#define CLAY_IDI(label, index) CLAY_SIDI(CLAY_STRING(label), index) + +#define CLAY_SIDI(label, index) Clay__HashStringWithOffset(label, index, 0) + +// Note: If a compile error led you here, you might be trying to use CLAY_ID_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID_LOCAL instead. +#define CLAY_ID_LOCAL(label) CLAY_SID_LOCAL(CLAY_STRING(label)) + +#define CLAY_SID_LOCAL(label) Clay__HashString(label, Clay__GetParentElementId()) + +// Note: If a compile error led you here, you might be trying to use CLAY_IDI_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI_LOCAL instead. +#define CLAY_IDI_LOCAL(label, index) CLAY_SIDI_LOCAL(CLAY_STRING(label), index) + +#define CLAY_SIDI_LOCAL(label, index) Clay__HashStringWithOffset(label, index, Clay__GetParentElementId()) + +#define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof((s)[0])) - sizeof((s)[0])) + +#define CLAY__ENSURE_STRING_LITERAL(x) ("" x "") + +// Note: If an error led you here, it's because CLAY_STRING can only be used with string literals, i.e. CLAY_STRING("SomeString") and not CLAY_STRING(yourString) +#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }) + +#define CLAY_STRING_CONST(string) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) } + +static uint8_t CLAY__ELEMENT_DEFINITION_LATCH; + +// GCC marks the above CLAY__ELEMENT_DEFINITION_LATCH as an unused variable for files that include clay.h but don't declare any layout +// This is to suppress that warning +static inline void Clay__SuppressUnusedLatchDefinitionVariableWarning(void) { (void) CLAY__ELEMENT_DEFINITION_LATCH; } + +// Publicly visible layout element macros ----------------------------------------------------- + +/* This macro looks scary on the surface, but is actually quite simple. + It turns a macro call like this: + + CLAY({ + .id = CLAY_ID("Container"), + .backgroundColor = { 255, 200, 200, 255 } + }) { + ...children declared here + } + + Into calls like this: + + Clay__OpenElement(); + Clay__ConfigureOpenElement((Clay_ElementDeclaration) { + .id = CLAY_ID("Container"), + .backgroundColor = { 255, 200, 200, 255 } + }); + ...children declared here + Clay__CloseElement(); + + The for loop will only ever run a single iteration, putting Clay__CloseElement() in the increment of the loop + means that it will run after the body - where the children are declared. It just exists to make sure you don't forget + to call Clay_CloseElement(). +*/ +#define CLAY_AUTO_ID(...) \ + for ( \ + CLAY__ELEMENT_DEFINITION_LATCH = (Clay__OpenElement(), Clay__ConfigureOpenElement(CLAY__CONFIG_WRAPPER(Clay_ElementDeclaration, __VA_ARGS__)), 0); \ + CLAY__ELEMENT_DEFINITION_LATCH < 1; \ + CLAY__ELEMENT_DEFINITION_LATCH=1, Clay__CloseElement() \ + ) + +#define CLAY(id, ...) \ + for ( \ + CLAY__ELEMENT_DEFINITION_LATCH = (Clay__OpenElementWithId(id), Clay__ConfigureOpenElement(CLAY__CONFIG_WRAPPER(Clay_ElementDeclaration, __VA_ARGS__)), 0); \ + CLAY__ELEMENT_DEFINITION_LATCH < 1; \ + CLAY__ELEMENT_DEFINITION_LATCH=1, Clay__CloseElement() \ + ) + +// These macros exist to allow the CLAY() macro to be called both with an inline struct definition, such as +// CLAY({ .id = something... }); +// As well as by passing a predefined declaration struct +// Clay_ElementDeclaration declarationStruct = ... +// CLAY(declarationStruct); +#define CLAY__WRAPPER_TYPE(type) Clay__##type##Wrapper +#define CLAY__WRAPPER_STRUCT(type) typedef struct { type wrapped; } CLAY__WRAPPER_TYPE(type) +#define CLAY__CONFIG_WRAPPER(type, ...) (CLAY__INIT(CLAY__WRAPPER_TYPE(type)) { __VA_ARGS__ }).wrapped + +#define CLAY_TEXT(text, textConfig) Clay__OpenTextElement(text, textConfig) + +#ifdef __cplusplus + +#define CLAY__INIT(type) type + +#define CLAY_PACKED_ENUM enum : uint8_t + +#define CLAY__DEFAULT_STRUCT {} + +#else + +#define CLAY__INIT(type) (type) + +#if defined(_MSC_VER) && !defined(__clang__) +#define CLAY_PACKED_ENUM __pragma(pack(push, 1)) enum __pragma(pack(pop)) +#else +#define CLAY_PACKED_ENUM enum __attribute__((__packed__)) +#endif + +#if __STDC_VERSION__ >= 202311L +#define CLAY__DEFAULT_STRUCT {} +#else +#define CLAY__DEFAULT_STRUCT {0} +#endif + +#endif // __cplusplus + +#ifdef __cplusplus +extern "C" { +#endif + +// Utility Structs ------------------------- + +// Note: Clay_String is not guaranteed to be null terminated. It may be if created from a literal C string, +// but it is also used to represent slices. +typedef struct Clay_String { + // Set this boolean to true if the char* data underlying this string will live for the entire lifetime of the program. + // This will automatically be set for strings created with CLAY_STRING, as the macro requires a string literal. + bool isStaticallyAllocated; + int32_t length; + // The underlying character memory. Note: this will not be copied and will not extend the lifetime of the underlying memory. + const char *chars; +} Clay_String; + +// Clay_StringSlice is used to represent non owning string slices, and includes +// a baseChars field which points to the string this slice is derived from. +typedef struct Clay_StringSlice { + int32_t length; + const char *chars; + const char *baseChars; // The source string / char* that this slice was derived from +} Clay_StringSlice; + +typedef struct Clay_Context Clay_Context; + +// Clay_Arena is a memory arena structure that is used by clay to manage its internal allocations. +// Rather than creating it by hand, it's easier to use Clay_CreateArenaWithCapacityAndMemory() +typedef struct Clay_Arena { + uintptr_t nextAllocation; + size_t capacity; + char *memory; +} Clay_Arena; + +typedef struct Clay_Dimensions { + float width, height; +} Clay_Dimensions; + +typedef struct Clay_Vector2 { + float x, y; +} Clay_Vector2; + +// Internally clay conventionally represents colors as 0-255, but interpretation is up to the renderer. +typedef struct Clay_Color { + float r, g, b, a; +} Clay_Color; + +typedef struct Clay_BoundingBox { + float x, y, width, height; +} Clay_BoundingBox; + +// Primarily created via the CLAY_ID(), CLAY_IDI(), CLAY_ID_LOCAL() and CLAY_IDI_LOCAL() macros. +// Represents a hashed string ID used for identifying and finding specific clay UI elements, required +// by functions such as Clay_PointerOver() and Clay_GetElementData(). +typedef struct Clay_ElementId { + uint32_t id; // The resulting hash generated from the other fields. + uint32_t offset; // A numerical offset applied after computing the hash from stringId. + uint32_t baseId; // A base hash value to start from, for example the parent element ID is used when calculating CLAY_ID_LOCAL(). + Clay_String stringId; // The string id to hash. +} Clay_ElementId; + +// A sized array of Clay_ElementId. +typedef struct +{ + int32_t capacity; + int32_t length; + Clay_ElementId *internalArray; +} Clay_ElementIdArray; + +// Controls the "radius", or corner rounding of elements, including rectangles, borders and images. +// The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. +typedef struct Clay_CornerRadius { + float topLeft; + float topRight; + float bottomLeft; + float bottomRight; +} Clay_CornerRadius; + +// Element Configs --------------------------- + +// Controls the direction in which child elements will be automatically laid out. +typedef CLAY_PACKED_ENUM { + // (Default) Lays out child elements from left to right with increasing x. + CLAY_LEFT_TO_RIGHT, + // Lays out child elements from top to bottom with increasing y. + CLAY_TOP_TO_BOTTOM, +} Clay_LayoutDirection; + +// Controls the alignment along the x axis (horizontal) of child elements. +typedef CLAY_PACKED_ENUM { + // (Default) Aligns child elements to the left hand side of this element, offset by padding.width.left + CLAY_ALIGN_X_LEFT, + // Aligns child elements to the right hand side of this element, offset by padding.width.right + CLAY_ALIGN_X_RIGHT, + // Aligns child elements horizontally to the center of this element + CLAY_ALIGN_X_CENTER, +} Clay_LayoutAlignmentX; + +// Controls the alignment along the y axis (vertical) of child elements. +typedef CLAY_PACKED_ENUM { + // (Default) Aligns child elements to the top of this element, offset by padding.width.top + CLAY_ALIGN_Y_TOP, + // Aligns child elements to the bottom of this element, offset by padding.width.bottom + CLAY_ALIGN_Y_BOTTOM, + // Aligns child elements vertically to the center of this element + CLAY_ALIGN_Y_CENTER, +} Clay_LayoutAlignmentY; + +// Controls how the element takes up space inside its parent container. +typedef CLAY_PACKED_ENUM { + // (default) Wraps tightly to the size of the element's contents. + CLAY__SIZING_TYPE_FIT, + // Expands along this axis to fill available space in the parent element, sharing it with other GROW elements. + CLAY__SIZING_TYPE_GROW, + // Expects 0-1 range. Clamps the axis size to a percent of the parent container's axis size minus padding and child gaps. + CLAY__SIZING_TYPE_PERCENT, + // Clamps the axis size to an exact size in pixels. + CLAY__SIZING_TYPE_FIXED, +} Clay__SizingType; + +// Controls how child elements are aligned on each axis. +typedef struct Clay_ChildAlignment { + Clay_LayoutAlignmentX x; // Controls alignment of children along the x axis. + Clay_LayoutAlignmentY y; // Controls alignment of children along the y axis. +} Clay_ChildAlignment; + +// Controls the minimum and maximum size in pixels that this element is allowed to grow or shrink to, +// overriding sizing types such as FIT or GROW. +typedef struct Clay_SizingMinMax { + float min; // The smallest final size of the element on this axis will be this value in pixels. + float max; // The largest final size of the element on this axis will be this value in pixels. +} Clay_SizingMinMax; + +// Controls the sizing of this element along one axis inside its parent container. +typedef struct Clay_SizingAxis { + union { + Clay_SizingMinMax minMax; // Controls the minimum and maximum size in pixels that this element is allowed to grow or shrink to, overriding sizing types such as FIT or GROW. + float percent; // Expects 0-1 range. Clamps the axis size to a percent of the parent container's axis size minus padding and child gaps. + } size; + Clay__SizingType type; // Controls how the element takes up space inside its parent container. +} Clay_SizingAxis; + +// Controls the sizing of this element along one axis inside its parent container. +typedef struct Clay_Sizing { + Clay_SizingAxis width; // Controls the width sizing of the element, along the x axis. + Clay_SizingAxis height; // Controls the height sizing of the element, along the y axis. +} Clay_Sizing; + +// Controls "padding" in pixels, which is a gap between the bounding box of this element and where its children +// will be placed. +typedef struct Clay_Padding { + uint16_t left; + uint16_t right; + uint16_t top; + uint16_t bottom; +} Clay_Padding; + +CLAY__WRAPPER_STRUCT(Clay_Padding); + +// Controls various settings that affect the size and position of an element, as well as the sizes and positions +// of any child elements. +typedef struct Clay_LayoutConfig { + Clay_Sizing sizing; // Controls the sizing of this element inside it's parent container, including FIT, GROW, PERCENT and FIXED sizing. + Clay_Padding padding; // Controls "padding" in pixels, which is a gap between the bounding box of this element and where its children will be placed. + uint16_t childGap; // Controls the gap in pixels between child elements along the layout axis (horizontal gap for LEFT_TO_RIGHT, vertical gap for TOP_TO_BOTTOM). + Clay_ChildAlignment childAlignment; // Controls how child elements are aligned on each axis. + Clay_LayoutDirection layoutDirection; // Controls the direction in which child elements will be automatically laid out. +} Clay_LayoutConfig; + +CLAY__WRAPPER_STRUCT(Clay_LayoutConfig); + +extern Clay_LayoutConfig CLAY_LAYOUT_DEFAULT; + +// Controls how text "wraps", that is how it is broken into multiple lines when there is insufficient horizontal space. +typedef CLAY_PACKED_ENUM { + // (default) breaks on whitespace characters. + CLAY_TEXT_WRAP_WORDS, + // Don't break on space characters, only on newlines. + CLAY_TEXT_WRAP_NEWLINES, + // Disable text wrapping entirely. + CLAY_TEXT_WRAP_NONE, +} Clay_TextElementConfigWrapMode; + +// Controls how wrapped lines of text are horizontally aligned within the outer text bounding box. +typedef CLAY_PACKED_ENUM { + // (default) Horizontally aligns wrapped lines of text to the left hand side of their bounding box. + CLAY_TEXT_ALIGN_LEFT, + // Horizontally aligns wrapped lines of text to the center of their bounding box. + CLAY_TEXT_ALIGN_CENTER, + // Horizontally aligns wrapped lines of text to the right hand side of their bounding box. + CLAY_TEXT_ALIGN_RIGHT, +} Clay_TextAlignment; + +// Controls various functionality related to text elements. +typedef struct Clay_TextElementConfig { + // A pointer that will be transparently passed through to the resulting render command. + void *userData; + // The RGBA color of the font to render, conventionally specified as 0-255. + Clay_Color textColor; + // An integer transparently passed to Clay_MeasureText to identify the font to use. + // The debug view will pass fontId = 0 for its internal text. + uint16_t fontId; + // Controls the size of the font. Handled by the function provided to Clay_MeasureText. + uint16_t fontSize; + // Controls extra horizontal spacing between characters. Handled by the function provided to Clay_MeasureText. + uint16_t letterSpacing; + // Controls additional vertical space between wrapped lines of text. + uint16_t lineHeight; + // Controls how text "wraps", that is how it is broken into multiple lines when there is insufficient horizontal space. + // CLAY_TEXT_WRAP_WORDS (default) breaks on whitespace characters. + // CLAY_TEXT_WRAP_NEWLINES doesn't break on space characters, only on newlines. + // CLAY_TEXT_WRAP_NONE disables wrapping entirely. + Clay_TextElementConfigWrapMode wrapMode; + // Controls how wrapped lines of text are horizontally aligned within the outer text bounding box. + // CLAY_TEXT_ALIGN_LEFT (default) - Horizontally aligns wrapped lines of text to the left hand side of their bounding box. + // CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box. + // CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box. + Clay_TextAlignment textAlignment; +} Clay_TextElementConfig; + +CLAY__WRAPPER_STRUCT(Clay_TextElementConfig); + +// Aspect Ratio -------------------------------- + +// Controls various settings related to aspect ratio scaling element. +typedef struct Clay_AspectRatioElementConfig { + float aspectRatio; // A float representing the target "Aspect ratio" for an element, which is its final width divided by its final height. +} Clay_AspectRatioElementConfig; + +CLAY__WRAPPER_STRUCT(Clay_AspectRatioElementConfig); + +// Image -------------------------------- + +// Controls various settings related to image elements. +typedef struct Clay_ImageElementConfig { + void* imageData; // A transparent pointer used to pass image data through to the renderer. +} Clay_ImageElementConfig; + +CLAY__WRAPPER_STRUCT(Clay_ImageElementConfig); + +// Floating ----------------------------- + +// Controls where a floating element is offset relative to its parent element. +// Note: see https://github.com/user-attachments/assets/b8c6dfaa-c1b1-41a4-be55-013473e4a6ce for a visual explanation. +typedef CLAY_PACKED_ENUM { + CLAY_ATTACH_POINT_LEFT_TOP, + CLAY_ATTACH_POINT_LEFT_CENTER, + CLAY_ATTACH_POINT_LEFT_BOTTOM, + CLAY_ATTACH_POINT_CENTER_TOP, + CLAY_ATTACH_POINT_CENTER_CENTER, + CLAY_ATTACH_POINT_CENTER_BOTTOM, + CLAY_ATTACH_POINT_RIGHT_TOP, + CLAY_ATTACH_POINT_RIGHT_CENTER, + CLAY_ATTACH_POINT_RIGHT_BOTTOM, +} Clay_FloatingAttachPointType; + +// Controls where a floating element is offset relative to its parent element. +typedef struct Clay_FloatingAttachPoints { + Clay_FloatingAttachPointType element; // Controls the origin point on a floating element that attaches to its parent. + Clay_FloatingAttachPointType parent; // Controls the origin point on the parent element that the floating element attaches to. +} Clay_FloatingAttachPoints; + +// Controls how mouse pointer events like hover and click are captured or passed through to elements underneath a floating element. +typedef CLAY_PACKED_ENUM { + // (default) "Capture" the pointer event and don't allow events like hover and click to pass through to elements underneath. + CLAY_POINTER_CAPTURE_MODE_CAPTURE, + // CLAY_POINTER_CAPTURE_MODE_PARENT, TODO pass pointer through to attached parent + + // Transparently pass through pointer events like hover and click to elements underneath the floating element. + CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, +} Clay_PointerCaptureMode; + +// Controls which element a floating element is "attached" to (i.e. relative offset from). +typedef CLAY_PACKED_ENUM { + // (default) Disables floating for this element. + CLAY_ATTACH_TO_NONE, + // Attaches this floating element to its parent, positioned based on the .attachPoints and .offset fields. + CLAY_ATTACH_TO_PARENT, + // Attaches this floating element to an element with a specific ID, specified with the .parentId field. positioned based on the .attachPoints and .offset fields. + CLAY_ATTACH_TO_ELEMENT_WITH_ID, + // Attaches this floating element to the root of the layout, which combined with the .offset field provides functionality similar to "absolute positioning". + CLAY_ATTACH_TO_ROOT, +} Clay_FloatingAttachToElement; + +// Controls whether or not a floating element is clipped to the same clipping rectangle as the element it's attached to. +typedef CLAY_PACKED_ENUM { + // (default) - The floating element does not inherit clipping. + CLAY_CLIP_TO_NONE, + // The floating element is clipped to the same clipping rectangle as the element it's attached to. + CLAY_CLIP_TO_ATTACHED_PARENT +} Clay_FloatingClipToElement; + +// Controls various settings related to "floating" elements, which are elements that "float" above other elements, potentially overlapping their boundaries, +// and not affecting the layout of sibling or parent elements. +typedef struct Clay_FloatingElementConfig { + // Offsets this floating element by the provided x,y coordinates from its attachPoints. + Clay_Vector2 offset; + // Expands the boundaries of the outer floating element without affecting its children. + Clay_Dimensions expand; + // When used in conjunction with .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, attaches this floating element to the element in the hierarchy with the provided ID. + // Hint: attach the ID to the other element with .id = CLAY_ID("yourId"), and specify the id the same way, with .parentId = CLAY_ID("yourId").id + uint32_t parentId; + // Controls the z index of this floating element and all its children. Floating elements are sorted in ascending z order before output. + // zIndex is also passed to the renderer for all elements contained within this floating element. + int16_t zIndex; + // Controls how mouse pointer events like hover and click are captured or passed through to elements underneath / behind a floating element. + // Enum is of the form CLAY_ATTACH_POINT_foo_bar. See Clay_FloatingAttachPoints for more details. + // Note: see for a visual explanation. + Clay_FloatingAttachPoints attachPoints; + // Controls how mouse pointer events like hover and click are captured or passed through to elements underneath a floating element. + // CLAY_POINTER_CAPTURE_MODE_CAPTURE (default) - "Capture" the pointer event and don't allow events like hover and click to pass through to elements underneath. + // CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH - Transparently pass through pointer events like hover and click to elements underneath the floating element. + Clay_PointerCaptureMode pointerCaptureMode; + // Controls which element a floating element is "attached" to (i.e. relative offset from). + // CLAY_ATTACH_TO_NONE (default) - Disables floating for this element. + // CLAY_ATTACH_TO_PARENT - Attaches this floating element to its parent, positioned based on the .attachPoints and .offset fields. + // CLAY_ATTACH_TO_ELEMENT_WITH_ID - Attaches this floating element to an element with a specific ID, specified with the .parentId field. positioned based on the .attachPoints and .offset fields. + // CLAY_ATTACH_TO_ROOT - Attaches this floating element to the root of the layout, which combined with the .offset field provides functionality similar to "absolute positioning". + Clay_FloatingAttachToElement attachTo; + // Controls whether or not a floating element is clipped to the same clipping rectangle as the element it's attached to. + // CLAY_CLIP_TO_NONE (default) - The floating element does not inherit clipping. + // CLAY_CLIP_TO_ATTACHED_PARENT - The floating element is clipped to the same clipping rectangle as the element it's attached to. + Clay_FloatingClipToElement clipTo; +} Clay_FloatingElementConfig; + +CLAY__WRAPPER_STRUCT(Clay_FloatingElementConfig); + +// Custom ----------------------------- + +// Controls various settings related to custom elements. +typedef struct Clay_CustomElementConfig { + // A transparent pointer through which you can pass custom data to the renderer. + // Generates CUSTOM render commands. + void* customData; +} Clay_CustomElementConfig; + +CLAY__WRAPPER_STRUCT(Clay_CustomElementConfig); + +// Scroll ----------------------------- + +// Controls the axis on which an element switches to "scrolling", which clips the contents and allows scrolling in that direction. +typedef struct Clay_ClipElementConfig { + bool horizontal; // Clip overflowing elements on the X axis. + bool vertical; // Clip overflowing elements on the Y axis. + Clay_Vector2 childOffset; // Offsets the x,y positions of all child elements. Used primarily for scrolling containers. +} Clay_ClipElementConfig; + +CLAY__WRAPPER_STRUCT(Clay_ClipElementConfig); + +// Border ----------------------------- + +// Controls the widths of individual element borders. +typedef struct Clay_BorderWidth { + uint16_t left; + uint16_t right; + uint16_t top; + uint16_t bottom; + // Creates borders between each child element, depending on the .layoutDirection. + // e.g. for LEFT_TO_RIGHT, borders will be vertical lines, and for TOP_TO_BOTTOM borders will be horizontal lines. + // .betweenChildren borders will result in individual RECTANGLE render commands being generated. + uint16_t betweenChildren; +} Clay_BorderWidth; + +// Controls settings related to element borders. +typedef struct Clay_BorderElementConfig { + Clay_Color color; // Controls the color of all borders with width > 0. Conventionally represented as 0-255, but interpretation is up to the renderer. + Clay_BorderWidth width; // Controls the widths of individual borders. At least one of these should be > 0 for a BORDER render command to be generated. +} Clay_BorderElementConfig; + +CLAY__WRAPPER_STRUCT(Clay_BorderElementConfig); + +// Render Command Data ----------------------------- + +// Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_TEXT +typedef struct Clay_TextRenderData { + // A string slice containing the text to be rendered. + // Note: this is not guaranteed to be null terminated. + Clay_StringSlice stringContents; + // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. + Clay_Color textColor; + // An integer representing the font to use to render this text, transparently passed through from the text declaration. + uint16_t fontId; + uint16_t fontSize; + // Specifies the extra whitespace gap in pixels between each character. + uint16_t letterSpacing; + // The height of the bounding box for this line of text. + uint16_t lineHeight; +} Clay_TextRenderData; + +// Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE +typedef struct Clay_RectangleRenderData { + // The solid background color to fill this rectangle with. Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. + Clay_Color backgroundColor; + // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. + // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. + Clay_CornerRadius cornerRadius; +} Clay_RectangleRenderData; + +// Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE +typedef struct Clay_ImageRenderData { + // The tint color for this image. Note that the default value is 0,0,0,0 and should likely be interpreted + // as "untinted". + // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. + Clay_Color backgroundColor; + // Controls the "radius", or corner rounding of this image. + // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. + Clay_CornerRadius cornerRadius; + // A pointer transparently passed through from the original element definition, typically used to represent image data. + void* imageData; +} Clay_ImageRenderData; + +// Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM +typedef struct Clay_CustomRenderData { + // Passed through from .backgroundColor in the original element declaration. + // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. + Clay_Color backgroundColor; + // Controls the "radius", or corner rounding of this custom element. + // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. + Clay_CornerRadius cornerRadius; + // A pointer transparently passed through from the original element definition. + void* customData; +} Clay_CustomRenderData; + +// Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START || commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_END +typedef struct Clay_ScrollRenderData { + bool horizontal; + bool vertical; +} Clay_ClipRenderData; + +// Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_BORDER +typedef struct Clay_BorderRenderData { + // Controls a shared color for all this element's borders. + // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. + Clay_Color color; + // Specifies the "radius", or corner rounding of this border element. + // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. + Clay_CornerRadius cornerRadius; + // Controls individual border side widths. + Clay_BorderWidth width; +} Clay_BorderRenderData; + +// A struct union containing data specific to this command's .commandType +typedef union Clay_RenderData { + // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE + Clay_RectangleRenderData rectangle; + // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_TEXT + Clay_TextRenderData text; + // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE + Clay_ImageRenderData image; + // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM + Clay_CustomRenderData custom; + // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_BORDER + Clay_BorderRenderData border; + // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START|END + Clay_ClipRenderData clip; +} Clay_RenderData; + +// Miscellaneous Structs & Enums --------------------------------- + +// Data representing the current internal state of a scrolling element. +typedef struct Clay_ScrollContainerData { + // Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout. + // Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling. + Clay_Vector2 *scrollPosition; + // The bounding box of the scroll element. + Clay_Dimensions scrollContainerDimensions; + // The outer dimensions of the inner scroll container content, including the padding of the parent scroll container. + Clay_Dimensions contentDimensions; + // The config that was originally passed to the clip element. + Clay_ClipElementConfig config; + // Indicates whether an actual scroll container matched the provided ID or if the default struct was returned. + bool found; +} Clay_ScrollContainerData; + +// Bounding box and other data for a specific UI element. +typedef struct Clay_ElementData { + // The rectangle that encloses this UI element, with the position relative to the root of the layout. + Clay_BoundingBox boundingBox; + // Indicates whether an actual Element matched the provided ID or if the default struct was returned. + bool found; +} Clay_ElementData; + +// Used by renderers to determine specific handling for each render command. +typedef CLAY_PACKED_ENUM { + // This command type should be skipped. + CLAY_RENDER_COMMAND_TYPE_NONE, + // The renderer should draw a solid color rectangle. + CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + // The renderer should draw a colored border inset into the bounding box. + CLAY_RENDER_COMMAND_TYPE_BORDER, + // The renderer should draw text. + CLAY_RENDER_COMMAND_TYPE_TEXT, + // The renderer should draw an image. + CLAY_RENDER_COMMAND_TYPE_IMAGE, + // The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox. + CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, + // The renderer should finish any previously active clipping, and begin rendering elements in full again. + CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, + // The renderer should provide a custom implementation for handling this render command based on its .customData + CLAY_RENDER_COMMAND_TYPE_CUSTOM, +} Clay_RenderCommandType; + +typedef struct Clay_RenderCommand { + // A rectangular box that fully encloses this UI element, with the position relative to the root of the layout. + Clay_BoundingBox boundingBox; + // A struct union containing data specific to this command's commandType. + Clay_RenderData renderData; + // A pointer transparently passed through from the original element declaration. + void *userData; + // The id of this element, transparently passed through from the original element declaration. + uint32_t id; + // The z order required for drawing this command correctly. + // Note: the render command array is already sorted in ascending order, and will produce correct results if drawn in naive order. + // This field is intended for use in batching renderers for improved performance. + int16_t zIndex; + // Specifies how to handle rendering of this command. + // CLAY_RENDER_COMMAND_TYPE_RECTANGLE - The renderer should draw a solid color rectangle. + // CLAY_RENDER_COMMAND_TYPE_BORDER - The renderer should draw a colored border inset into the bounding box. + // CLAY_RENDER_COMMAND_TYPE_TEXT - The renderer should draw text. + // CLAY_RENDER_COMMAND_TYPE_IMAGE - The renderer should draw an image. + // CLAY_RENDER_COMMAND_TYPE_SCISSOR_START - The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox. + // CLAY_RENDER_COMMAND_TYPE_SCISSOR_END - The renderer should finish any previously active clipping, and begin rendering elements in full again. + // CLAY_RENDER_COMMAND_TYPE_CUSTOM - The renderer should provide a custom implementation for handling this render command based on its .customData + Clay_RenderCommandType commandType; +} Clay_RenderCommand; + +// A sized array of render commands. +typedef struct Clay_RenderCommandArray { + // The underlying max capacity of the array, not necessarily all initialized. + int32_t capacity; + // The number of initialized elements in this array. Used for loops and iteration. + int32_t length; + // A pointer to the first element in the internal array. + Clay_RenderCommand* internalArray; +} Clay_RenderCommandArray; + +// Represents the current state of interaction with clay this frame. +typedef CLAY_PACKED_ENUM { + // A left mouse click, or touch occurred this frame. + CLAY_POINTER_DATA_PRESSED_THIS_FRAME, + // The left mouse button click or touch happened at some point in the past, and is still currently held down this frame. + CLAY_POINTER_DATA_PRESSED, + // The left mouse button click or touch was released this frame. + CLAY_POINTER_DATA_RELEASED_THIS_FRAME, + // The left mouse button click or touch is not currently down / was released at some point in the past. + CLAY_POINTER_DATA_RELEASED, +} Clay_PointerDataInteractionState; + +// Information on the current state of pointer interactions this frame. +typedef struct Clay_PointerData { + // The position of the mouse / touch / pointer relative to the root of the layout. + Clay_Vector2 position; + // Represents the current state of interaction with clay this frame. + // CLAY_POINTER_DATA_PRESSED_THIS_FRAME - A left mouse click, or touch occurred this frame. + // CLAY_POINTER_DATA_PRESSED - The left mouse button click or touch happened at some point in the past, and is still currently held down this frame. + // CLAY_POINTER_DATA_RELEASED_THIS_FRAME - The left mouse button click or touch was released this frame. + // CLAY_POINTER_DATA_RELEASED - The left mouse button click or touch is not currently down / was released at some point in the past. + Clay_PointerDataInteractionState state; +} Clay_PointerData; + +typedef struct Clay_ElementDeclaration { + // Controls various settings that affect the size and position of an element, as well as the sizes and positions of any child elements. + Clay_LayoutConfig layout; + // Controls the background color of the resulting element. + // By convention specified as 0-255, but interpretation is up to the renderer. + // If no other config is specified, .backgroundColor will generate a RECTANGLE render command, otherwise it will be passed as a property to IMAGE or CUSTOM render commands. + Clay_Color backgroundColor; + // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. + Clay_CornerRadius cornerRadius; + // Controls settings related to aspect ratio scaling. + Clay_AspectRatioElementConfig aspectRatio; + // Controls settings related to image elements. + Clay_ImageElementConfig image; + // Controls whether and how an element "floats", which means it layers over the top of other elements in z order, and doesn't affect the position and size of siblings or parent elements. + // Note: in order to activate floating, .floating.attachTo must be set to something other than the default value. + Clay_FloatingElementConfig floating; + // Used to create CUSTOM render commands, usually to render element types not supported by Clay. + Clay_CustomElementConfig custom; + // Controls whether an element should clip its contents, as well as providing child x,y offset configuration for scrolling. + Clay_ClipElementConfig clip; + // Controls settings related to element borders, and will generate BORDER render commands. + Clay_BorderElementConfig border; + // A pointer that will be transparently passed through to resulting render commands. + void *userData; +} Clay_ElementDeclaration; + +CLAY__WRAPPER_STRUCT(Clay_ElementDeclaration); + +// Represents the type of error clay encountered while computing layout. +typedef CLAY_PACKED_ENUM { + // A text measurement function wasn't provided using Clay_SetMeasureTextFunction(), or the provided function was null. + CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED, + // Clay attempted to allocate its internal data structures but ran out of space. + // The arena passed to Clay_Initialize was created with a capacity smaller than that required by Clay_MinMemorySize(). + CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, + // Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxElementCount(). + CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, + // Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxMeasureTextCacheWordCount(). + CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED, + // Two elements were declared with exactly the same ID within one layout. + CLAY_ERROR_TYPE_DUPLICATE_ID, + // A floating element was declared using CLAY_ATTACH_TO_ELEMENT_ID and either an invalid .parentId was provided or no element with the provided .parentId was found. + CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, + // An element was declared that using CLAY_SIZING_PERCENT but the percentage value was over 1. Percentage values are expected to be in the 0-1 range. + CLAY_ERROR_TYPE_PERCENTAGE_OVER_1, + // Clay encountered an internal error. It would be wonderful if you could report this so we can fix it! + CLAY_ERROR_TYPE_INTERNAL_ERROR, + // Clay__OpenElement was called more times than Clay__CloseElement, so there were still remaining open elements when the layout ended. + CLAY_ERROR_TYPE_UNBALANCED_OPEN_CLOSE, +} Clay_ErrorType; + +// Data to identify the error that clay has encountered. +typedef struct Clay_ErrorData { + // Represents the type of error clay encountered while computing layout. + // CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED - A text measurement function wasn't provided using Clay_SetMeasureTextFunction(), or the provided function was null. + // CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED - Clay attempted to allocate its internal data structures but ran out of space. The arena passed to Clay_Initialize was created with a capacity smaller than that required by Clay_MinMemorySize(). + // CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED - Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxElementCount(). + // CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED - Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxMeasureTextCacheWordCount(). + // CLAY_ERROR_TYPE_DUPLICATE_ID - Two elements were declared with exactly the same ID within one layout. + // CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND - A floating element was declared using CLAY_ATTACH_TO_ELEMENT_ID and either an invalid .parentId was provided or no element with the provided .parentId was found. + // CLAY_ERROR_TYPE_PERCENTAGE_OVER_1 - An element was declared that using CLAY_SIZING_PERCENT but the percentage value was over 1. Percentage values are expected to be in the 0-1 range. + // CLAY_ERROR_TYPE_INTERNAL_ERROR - Clay encountered an internal error. It would be wonderful if you could report this so we can fix it! + Clay_ErrorType errorType; + // A string containing human-readable error text that explains the error in more detail. + Clay_String errorText; + // A transparent pointer passed through from when the error handler was first provided. + void *userData; +} Clay_ErrorData; + +// A wrapper struct around Clay's error handler function. +typedef struct { + // A user provided function to call when Clay encounters an error during layout. + void (*errorHandlerFunction)(Clay_ErrorData errorText); + // A pointer that will be transparently passed through to the error handler when it is called. + void *userData; +} Clay_ErrorHandler; + +// Function Forward Declarations --------------------------------- + +// Public API functions ------------------------------------------ + +// Returns the size, in bytes, of the minimum amount of memory Clay requires to operate at its current settings. +CLAY_DLL_EXPORT uint32_t Clay_MinMemorySize(void); +// Creates an arena for clay to use for its internal allocations, given a certain capacity in bytes and a pointer to an allocation of at least that size. +// Intended to be used with Clay_MinMemorySize in the following way: +// uint32_t minMemoryRequired = Clay_MinMemorySize(); +// Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(minMemoryRequired, malloc(minMemoryRequired)); +CLAY_DLL_EXPORT Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory); +// Sets the state of the "pointer" (i.e. the mouse or touch) in Clay's internal data. Used for detecting and responding to mouse events in the debug view, +// as well as for Clay_Hovered() and scroll element handling. +CLAY_DLL_EXPORT void Clay_SetPointerState(Clay_Vector2 position, bool pointerDown); +// Initialize Clay's internal arena and setup required data before layout can begin. Only needs to be called once. +// - arena can be created using Clay_CreateArenaWithCapacityAndMemory() +// - layoutDimensions are the initial bounding dimensions of the layout (i.e. the screen width and height for a full screen layout) +// - errorHandler is used by Clay to inform you if something has gone wrong in configuration or layout. +CLAY_DLL_EXPORT Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler); +// Returns the Context that clay is currently using. Used when using multiple instances of clay simultaneously. +CLAY_DLL_EXPORT Clay_Context* Clay_GetCurrentContext(void); +// Sets the context that clay will use to compute the layout. +// Used to restore a context saved from Clay_GetCurrentContext when using multiple instances of clay simultaneously. +CLAY_DLL_EXPORT void Clay_SetCurrentContext(Clay_Context* context); +// Updates the state of Clay's internal scroll data, updating scroll content positions if scrollDelta is non zero, and progressing momentum scrolling. +// - enableDragScrolling when set to true will enable mobile device like "touch drag" scroll of scroll containers, including momentum scrolling after the touch has ended. +// - scrollDelta is the amount to scroll this frame on each axis in pixels. +// - deltaTime is the time in seconds since the last "frame" (scroll update) +CLAY_DLL_EXPORT void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime); +// Returns the internally stored scroll offset for the currently open element. +// Generally intended for use with clip elements to create scrolling containers. +CLAY_DLL_EXPORT Clay_Vector2 Clay_GetScrollOffset(void); +// Updates the layout dimensions in response to the window or outer container being resized. +CLAY_DLL_EXPORT void Clay_SetLayoutDimensions(Clay_Dimensions dimensions); +// Called before starting any layout declarations. +CLAY_DLL_EXPORT void Clay_BeginLayout(void); +// Called when all layout declarations are finished. +// Computes the layout and generates and returns the array of render commands to draw. +CLAY_DLL_EXPORT Clay_RenderCommandArray Clay_EndLayout(void); +// Calculates a hash ID from the given idString. +// Generally only used for dynamic strings when CLAY_ID("stringLiteral") can't be used. +CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementId(Clay_String idString); +// Calculates a hash ID from the given idString and index. +// - index is used to avoid constructing dynamic ID strings in loops. +// Generally only used for dynamic strings when CLAY_IDI("stringLiteral", index) can't be used. +CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index); +// Returns layout data such as the final calculated bounding box for an element with a given ID. +// The returned Clay_ElementData contains a `found` bool that will be true if an element with the provided ID was found. +// This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings. +CLAY_DLL_EXPORT Clay_ElementData Clay_GetElementData(Clay_ElementId id); +// Returns true if the pointer position provided by Clay_SetPointerState is within the current element's bounding box. +// Works during element declaration, e.g. CLAY({ .backgroundColor = Clay_Hovered() ? BLUE : RED }); +CLAY_DLL_EXPORT bool Clay_Hovered(void); +// Bind a callback that will be called when the pointer position provided by Clay_SetPointerState is within the current element's bounding box. +// - onHoverFunction is a function pointer to a user defined function. +// - userData is a pointer that will be transparently passed through when the onHoverFunction is called. +CLAY_DLL_EXPORT void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData), void *userData); +// An imperative function that returns true if the pointer position provided by Clay_SetPointerState is within the element with the provided ID's bounding box. +// This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings. +CLAY_DLL_EXPORT bool Clay_PointerOver(Clay_ElementId elementId); +// Returns the array of element IDs that the pointer is currently over. +CLAY_DLL_EXPORT Clay_ElementIdArray Clay_GetPointerOverIds(void); +// Returns data representing the state of the scrolling element with the provided ID. +// The returned Clay_ScrollContainerData contains a `found` bool that will be true if a scroll element was found with the provided ID. +// An imperative function that returns true if the pointer position provided by Clay_SetPointerState is within the element with the provided ID's bounding box. +// This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings. +CLAY_DLL_EXPORT Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id); +// Binds a callback function that Clay will call to determine the dimensions of a given string slice. +// - measureTextFunction is a user provided function that adheres to the interface Clay_Dimensions (Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); +// - userData is a pointer that will be transparently passed through when the measureTextFunction is called. +CLAY_DLL_EXPORT void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData), void *userData); +// Experimental - Used in cases where Clay needs to integrate with a system that manages its own scrolling containers externally. +// Please reach out if you plan to use this function, as it may be subject to change. +CLAY_DLL_EXPORT void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, void *userData), void *userData); +// A bounds-checked "get" function for the Clay_RenderCommandArray returned from Clay_EndLayout(). +CLAY_DLL_EXPORT Clay_RenderCommand * Clay_RenderCommandArray_Get(Clay_RenderCommandArray* array, int32_t index); +// Enables and disables Clay's internal debug tools. +// This state is retained and does not need to be set each frame. +CLAY_DLL_EXPORT void Clay_SetDebugModeEnabled(bool enabled); +// Returns true if Clay's internal debug tools are currently enabled. +CLAY_DLL_EXPORT bool Clay_IsDebugModeEnabled(void); +// Enables and disables visibility culling. By default, Clay will not generate render commands for elements whose bounding box is entirely outside the screen. +CLAY_DLL_EXPORT void Clay_SetCullingEnabled(bool enabled); +// Returns the maximum number of UI elements supported by Clay's current configuration. +CLAY_DLL_EXPORT int32_t Clay_GetMaxElementCount(void); +// Modifies the maximum number of UI elements supported by Clay's current configuration. +// This may require reallocating additional memory, and re-calling Clay_Initialize(); +CLAY_DLL_EXPORT void Clay_SetMaxElementCount(int32_t maxElementCount); +// Returns the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache. +CLAY_DLL_EXPORT int32_t Clay_GetMaxMeasureTextCacheWordCount(void); +// Modifies the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache. +// This may require reallocating additional memory, and re-calling Clay_Initialize(); +CLAY_DLL_EXPORT void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount); +// Resets Clay's internal text measurement cache. Useful if font mappings have changed or fonts have been reloaded. +CLAY_DLL_EXPORT void Clay_ResetMeasureTextCache(void); + +// Internal API functions required by macros ---------------------- + +CLAY_DLL_EXPORT void Clay__OpenElement(void); +CLAY_DLL_EXPORT void Clay__OpenElementWithId(Clay_ElementId elementId); +CLAY_DLL_EXPORT void Clay__ConfigureOpenElement(const Clay_ElementDeclaration config); +CLAY_DLL_EXPORT void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *config); +CLAY_DLL_EXPORT void Clay__CloseElement(void); +CLAY_DLL_EXPORT Clay_ElementId Clay__HashString(Clay_String key, uint32_t seed); +CLAY_DLL_EXPORT Clay_ElementId Clay__HashStringWithOffset(Clay_String key, uint32_t offset, uint32_t seed); +CLAY_DLL_EXPORT void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig); +CLAY_DLL_EXPORT Clay_TextElementConfig *Clay__StoreTextElementConfig(Clay_TextElementConfig config); +CLAY_DLL_EXPORT uint32_t Clay__GetParentElementId(void); + +extern Clay_Color Clay__debugViewHighlightColor; +extern uint32_t Clay__debugViewWidth; + +#ifdef __cplusplus +} +#endif + +#endif // CLAY_HEADER + +// ----------------------------------------- +// IMPLEMENTATION -------------------------- +// ----------------------------------------- +#ifdef CLAY_IMPLEMENTATION +#undef CLAY_IMPLEMENTATION + +#ifndef CLAY__NULL +#define CLAY__NULL 0 +#endif + +#ifndef CLAY__MAXFLOAT +#define CLAY__MAXFLOAT 3.40282346638528859812e+38F +#endif + +Clay_LayoutConfig CLAY_LAYOUT_DEFAULT = CLAY__DEFAULT_STRUCT; + +Clay_Color Clay__Color_DEFAULT = CLAY__DEFAULT_STRUCT; +Clay_CornerRadius Clay__CornerRadius_DEFAULT = CLAY__DEFAULT_STRUCT; +Clay_BorderWidth Clay__BorderWidth_DEFAULT = CLAY__DEFAULT_STRUCT; + +// The below functions define array bounds checking and convenience functions for a provided type. +#define CLAY__ARRAY_DEFINE_FUNCTIONS(typeName, arrayName) \ + \ +typedef struct \ +{ \ + int32_t length; \ + typeName *internalArray; \ +} arrayName##Slice; \ + \ +typeName typeName##_DEFAULT = CLAY__DEFAULT_STRUCT; \ + \ +arrayName arrayName##_Allocate_Arena(int32_t capacity, Clay_Arena *arena) { \ + return CLAY__INIT(arrayName){.capacity = capacity, .length = 0, \ + .internalArray = (typeName *)Clay__Array_Allocate_Arena(capacity, sizeof(typeName), arena)}; \ +} \ + \ +typeName *arrayName##_Get(arrayName *array, int32_t index) { \ + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &typeName##_DEFAULT; \ +} \ + \ +typeName arrayName##_GetValue(arrayName *array, int32_t index) { \ + return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : typeName##_DEFAULT; \ +} \ + \ +typeName *arrayName##_Add(arrayName *array, typeName item) { \ + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { \ + array->internalArray[array->length++] = item; \ + return &array->internalArray[array->length - 1]; \ + } \ + return &typeName##_DEFAULT; \ +} \ + \ +typeName *arrayName##Slice_Get(arrayName##Slice *slice, int32_t index) { \ + return Clay__Array_RangeCheck(index, slice->length) ? &slice->internalArray[index] : &typeName##_DEFAULT; \ +} \ + \ +typeName arrayName##_RemoveSwapback(arrayName *array, int32_t index) { \ + if (Clay__Array_RangeCheck(index, array->length)) { \ + array->length--; \ + typeName removed = array->internalArray[index]; \ + array->internalArray[index] = array->internalArray[array->length]; \ + return removed; \ + } \ + return typeName##_DEFAULT; \ +} \ + \ +void arrayName##_Set(arrayName *array, int32_t index, typeName value) { \ + if (Clay__Array_RangeCheck(index, array->capacity)) { \ + array->internalArray[index] = value; \ + array->length = index < array->length ? array->length : index + 1; \ + } \ +} \ + +#define CLAY__ARRAY_DEFINE(typeName, arrayName) \ +typedef struct \ +{ \ + int32_t capacity; \ + int32_t length; \ + typeName *internalArray; \ +} arrayName; \ + \ +CLAY__ARRAY_DEFINE_FUNCTIONS(typeName, arrayName) \ + +Clay_Context *Clay__currentContext; +int32_t Clay__defaultMaxElementCount = 8192; +int32_t Clay__defaultMaxMeasureTextWordCacheCount = 16384; + +void Clay__ErrorHandlerFunctionDefault(Clay_ErrorData errorText) { + (void) errorText; +} + +Clay_String CLAY__SPACECHAR = { .length = 1, .chars = " " }; +Clay_String CLAY__STRING_DEFAULT = { .length = 0, .chars = NULL }; + +typedef struct { + bool maxElementsExceeded; + bool maxRenderCommandsExceeded; + bool maxTextMeasureCacheExceeded; + bool textMeasurementFunctionNotSet; +} Clay_BooleanWarnings; + +typedef struct { + Clay_String baseMessage; + Clay_String dynamicMessage; +} Clay__Warning; + +Clay__Warning CLAY__WARNING_DEFAULT = CLAY__DEFAULT_STRUCT; + +typedef struct { + int32_t capacity; + int32_t length; + Clay__Warning *internalArray; +} Clay__WarningArray; + +typedef struct { + Clay_Color backgroundColor; + Clay_CornerRadius cornerRadius; + void* userData; +} Clay_SharedElementConfig; + +CLAY__WRAPPER_STRUCT(Clay_SharedElementConfig); + +Clay__WarningArray Clay__WarningArray_Allocate_Arena(int32_t capacity, Clay_Arena *arena); +Clay__Warning *Clay__WarningArray_Add(Clay__WarningArray *array, Clay__Warning item); +void* Clay__Array_Allocate_Arena(int32_t capacity, uint32_t itemSize, Clay_Arena *arena); +bool Clay__Array_RangeCheck(int32_t index, int32_t length); +bool Clay__Array_AddCapacityCheck(int32_t length, int32_t capacity); + +CLAY__ARRAY_DEFINE(bool, Clay__boolArray) +CLAY__ARRAY_DEFINE(int32_t, Clay__int32_tArray) +CLAY__ARRAY_DEFINE(char, Clay__charArray) +CLAY__ARRAY_DEFINE_FUNCTIONS(Clay_ElementId, Clay_ElementIdArray) +CLAY__ARRAY_DEFINE(Clay_LayoutConfig, Clay__LayoutConfigArray) +CLAY__ARRAY_DEFINE(Clay_TextElementConfig, Clay__TextElementConfigArray) +CLAY__ARRAY_DEFINE(Clay_AspectRatioElementConfig, Clay__AspectRatioElementConfigArray) +CLAY__ARRAY_DEFINE(Clay_ImageElementConfig, Clay__ImageElementConfigArray) +CLAY__ARRAY_DEFINE(Clay_FloatingElementConfig, Clay__FloatingElementConfigArray) +CLAY__ARRAY_DEFINE(Clay_CustomElementConfig, Clay__CustomElementConfigArray) +CLAY__ARRAY_DEFINE(Clay_ClipElementConfig, Clay__ClipElementConfigArray) +CLAY__ARRAY_DEFINE(Clay_BorderElementConfig, Clay__BorderElementConfigArray) +CLAY__ARRAY_DEFINE(Clay_String, Clay__StringArray) +CLAY__ARRAY_DEFINE(Clay_SharedElementConfig, Clay__SharedElementConfigArray) +CLAY__ARRAY_DEFINE_FUNCTIONS(Clay_RenderCommand, Clay_RenderCommandArray) + +typedef CLAY_PACKED_ENUM { + CLAY__ELEMENT_CONFIG_TYPE_NONE, + CLAY__ELEMENT_CONFIG_TYPE_BORDER, + CLAY__ELEMENT_CONFIG_TYPE_FLOATING, + CLAY__ELEMENT_CONFIG_TYPE_CLIP, + CLAY__ELEMENT_CONFIG_TYPE_ASPECT, + CLAY__ELEMENT_CONFIG_TYPE_IMAGE, + CLAY__ELEMENT_CONFIG_TYPE_TEXT, + CLAY__ELEMENT_CONFIG_TYPE_CUSTOM, + CLAY__ELEMENT_CONFIG_TYPE_SHARED, +} Clay__ElementConfigType; + +typedef union { + Clay_TextElementConfig *textElementConfig; + Clay_AspectRatioElementConfig *aspectRatioElementConfig; + Clay_ImageElementConfig *imageElementConfig; + Clay_FloatingElementConfig *floatingElementConfig; + Clay_CustomElementConfig *customElementConfig; + Clay_ClipElementConfig *clipElementConfig; + Clay_BorderElementConfig *borderElementConfig; + Clay_SharedElementConfig *sharedElementConfig; +} Clay_ElementConfigUnion; + +typedef struct { + Clay__ElementConfigType type; + Clay_ElementConfigUnion config; +} Clay_ElementConfig; + +CLAY__ARRAY_DEFINE(Clay_ElementConfig, Clay__ElementConfigArray) + +typedef struct { + Clay_Dimensions dimensions; + Clay_String line; +} Clay__WrappedTextLine; + +CLAY__ARRAY_DEFINE(Clay__WrappedTextLine, Clay__WrappedTextLineArray) + +typedef struct { + Clay_String text; + Clay_Dimensions preferredDimensions; + int32_t elementIndex; + Clay__WrappedTextLineArraySlice wrappedLines; +} Clay__TextElementData; + +CLAY__ARRAY_DEFINE(Clay__TextElementData, Clay__TextElementDataArray) + +typedef struct { + int32_t *elements; + uint16_t length; +} Clay__LayoutElementChildren; + +typedef struct { + union { + Clay__LayoutElementChildren children; + Clay__TextElementData *textElementData; + } childrenOrTextContent; + Clay_Dimensions dimensions; + Clay_Dimensions minDimensions; + Clay_LayoutConfig *layoutConfig; + Clay__ElementConfigArraySlice elementConfigs; + uint32_t id; + uint16_t floatingChildrenCount; +} Clay_LayoutElement; + +CLAY__ARRAY_DEFINE(Clay_LayoutElement, Clay_LayoutElementArray) + +typedef struct { + Clay_LayoutElement *layoutElement; + Clay_BoundingBox boundingBox; + Clay_Dimensions contentSize; + Clay_Vector2 scrollOrigin; + Clay_Vector2 pointerOrigin; + Clay_Vector2 scrollMomentum; + Clay_Vector2 scrollPosition; + Clay_Vector2 previousDelta; + float momentumTime; + uint32_t elementId; + bool openThisFrame; + bool pointerScrollActive; +} Clay__ScrollContainerDataInternal; + +CLAY__ARRAY_DEFINE(Clay__ScrollContainerDataInternal, Clay__ScrollContainerDataInternalArray) + +typedef struct { + bool collision; + bool collapsed; +} Clay__DebugElementData; + +CLAY__ARRAY_DEFINE(Clay__DebugElementData, Clay__DebugElementDataArray) + +typedef struct { // todo get this struct into a single cache line + Clay_BoundingBox boundingBox; + Clay_ElementId elementId; + Clay_LayoutElement* layoutElement; + void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData); + void *hoverFunctionUserData; + int32_t nextIndex; + uint32_t generation; + Clay__DebugElementData *debugData; +} Clay_LayoutElementHashMapItem; + +CLAY__ARRAY_DEFINE(Clay_LayoutElementHashMapItem, Clay__LayoutElementHashMapItemArray) + +typedef struct { + int32_t startOffset; + int32_t length; + float width; + int32_t next; +} Clay__MeasuredWord; + +CLAY__ARRAY_DEFINE(Clay__MeasuredWord, Clay__MeasuredWordArray) + +typedef struct { + Clay_Dimensions unwrappedDimensions; + int32_t measuredWordsStartIndex; + float minWidth; + bool containsNewlines; + // Hash map data + uint32_t id; + int32_t nextIndex; + uint32_t generation; +} Clay__MeasureTextCacheItem; + +CLAY__ARRAY_DEFINE(Clay__MeasureTextCacheItem, Clay__MeasureTextCacheItemArray) + +typedef struct { + Clay_LayoutElement *layoutElement; + Clay_Vector2 position; + Clay_Vector2 nextChildOffset; +} Clay__LayoutElementTreeNode; + +CLAY__ARRAY_DEFINE(Clay__LayoutElementTreeNode, Clay__LayoutElementTreeNodeArray) + +typedef struct { + int32_t layoutElementIndex; + uint32_t parentId; // This can be zero in the case of the root layout tree + uint32_t clipElementId; // This can be zero if there is no clip element + int16_t zIndex; + Clay_Vector2 pointerOffset; // Only used when scroll containers are managed externally +} Clay__LayoutElementTreeRoot; + +CLAY__ARRAY_DEFINE(Clay__LayoutElementTreeRoot, Clay__LayoutElementTreeRootArray) + +struct Clay_Context { + int32_t maxElementCount; + int32_t maxMeasureTextCacheWordCount; + bool warningsEnabled; + Clay_ErrorHandler errorHandler; + Clay_BooleanWarnings booleanWarnings; + Clay__WarningArray warnings; + + Clay_PointerData pointerInfo; + Clay_Dimensions layoutDimensions; + Clay_ElementId dynamicElementIndexBaseHash; + uint32_t dynamicElementIndex; + bool debugModeEnabled; + bool disableCulling; + bool externalScrollHandlingEnabled; + uint32_t debugSelectedElementId; + uint32_t generation; + uintptr_t arenaResetOffset; + void *measureTextUserData; + void *queryScrollOffsetUserData; + Clay_Arena internalArena; + // Layout Elements / Render Commands + Clay_LayoutElementArray layoutElements; + Clay_RenderCommandArray renderCommands; + Clay__int32_tArray openLayoutElementStack; + Clay__int32_tArray layoutElementChildren; + Clay__int32_tArray layoutElementChildrenBuffer; + Clay__TextElementDataArray textElementData; + Clay__int32_tArray aspectRatioElementIndexes; + Clay__int32_tArray reusableElementIndexBuffer; + Clay__int32_tArray layoutElementClipElementIds; + // Configs + Clay__LayoutConfigArray layoutConfigs; + Clay__ElementConfigArray elementConfigs; + Clay__TextElementConfigArray textElementConfigs; + Clay__AspectRatioElementConfigArray aspectRatioElementConfigs; + Clay__ImageElementConfigArray imageElementConfigs; + Clay__FloatingElementConfigArray floatingElementConfigs; + Clay__ClipElementConfigArray clipElementConfigs; + Clay__CustomElementConfigArray customElementConfigs; + Clay__BorderElementConfigArray borderElementConfigs; + Clay__SharedElementConfigArray sharedElementConfigs; + // Misc Data Structures + Clay__StringArray layoutElementIdStrings; + Clay__WrappedTextLineArray wrappedTextLines; + Clay__LayoutElementTreeNodeArray layoutElementTreeNodeArray1; + Clay__LayoutElementTreeRootArray layoutElementTreeRoots; + Clay__LayoutElementHashMapItemArray layoutElementsHashMapInternal; + Clay__int32_tArray layoutElementsHashMap; + Clay__MeasureTextCacheItemArray measureTextHashMapInternal; + Clay__int32_tArray measureTextHashMapInternalFreeList; + Clay__int32_tArray measureTextHashMap; + Clay__MeasuredWordArray measuredWords; + Clay__int32_tArray measuredWordsFreeList; + Clay__int32_tArray openClipElementStack; + Clay_ElementIdArray pointerOverIds; + Clay__ScrollContainerDataInternalArray scrollContainerDatas; + Clay__boolArray treeNodeVisited; + Clay__charArray dynamicStringData; + Clay__DebugElementDataArray debugElementData; +}; + +Clay_Context* Clay__Context_Allocate_Arena(Clay_Arena *arena) { + size_t totalSizeBytes = sizeof(Clay_Context); + if (totalSizeBytes > arena->capacity) + { + return NULL; + } + arena->nextAllocation += totalSizeBytes; + return (Clay_Context*)(arena->memory); +} + +Clay_String Clay__WriteStringToCharBuffer(Clay__charArray *buffer, Clay_String string) { + for (int32_t i = 0; i < string.length; i++) { + buffer->internalArray[buffer->length + i] = string.chars[i]; + } + buffer->length += string.length; + return CLAY__INIT(Clay_String) { .length = string.length, .chars = (const char *)(buffer->internalArray + buffer->length - string.length) }; +} + +#ifdef CLAY_WASM + __attribute__((import_module("clay"), import_name("measureTextFunction"))) Clay_Dimensions Clay__MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); + __attribute__((import_module("clay"), import_name("queryScrollOffsetFunction"))) Clay_Vector2 Clay__QueryScrollOffset(uint32_t elementId, void *userData); +#else + Clay_Dimensions (*Clay__MeasureText)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); + Clay_Vector2 (*Clay__QueryScrollOffset)(uint32_t elementId, void *userData); +#endif + +Clay_LayoutElement* Clay__GetOpenLayoutElement(void) { + Clay_Context* context = Clay_GetCurrentContext(); + return Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1)); +} + +uint32_t Clay__GetParentElementId(void) { + Clay_Context* context = Clay_GetCurrentContext(); + return Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2))->id; +} + +Clay_LayoutConfig * Clay__StoreLayoutConfig(Clay_LayoutConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &CLAY_LAYOUT_DEFAULT : Clay__LayoutConfigArray_Add(&Clay_GetCurrentContext()->layoutConfigs, config); } +Clay_TextElementConfig * Clay__StoreTextElementConfig(Clay_TextElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_TextElementConfig_DEFAULT : Clay__TextElementConfigArray_Add(&Clay_GetCurrentContext()->textElementConfigs, config); } +Clay_AspectRatioElementConfig * Clay__StoreAspectRatioElementConfig(Clay_AspectRatioElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_AspectRatioElementConfig_DEFAULT : Clay__AspectRatioElementConfigArray_Add(&Clay_GetCurrentContext()->aspectRatioElementConfigs, config); } +Clay_ImageElementConfig * Clay__StoreImageElementConfig(Clay_ImageElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_ImageElementConfig_DEFAULT : Clay__ImageElementConfigArray_Add(&Clay_GetCurrentContext()->imageElementConfigs, config); } +Clay_FloatingElementConfig * Clay__StoreFloatingElementConfig(Clay_FloatingElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_FloatingElementConfig_DEFAULT : Clay__FloatingElementConfigArray_Add(&Clay_GetCurrentContext()->floatingElementConfigs, config); } +Clay_CustomElementConfig * Clay__StoreCustomElementConfig(Clay_CustomElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_CustomElementConfig_DEFAULT : Clay__CustomElementConfigArray_Add(&Clay_GetCurrentContext()->customElementConfigs, config); } +Clay_ClipElementConfig * Clay__StoreClipElementConfig(Clay_ClipElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_ClipElementConfig_DEFAULT : Clay__ClipElementConfigArray_Add(&Clay_GetCurrentContext()->clipElementConfigs, config); } +Clay_BorderElementConfig * Clay__StoreBorderElementConfig(Clay_BorderElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_BorderElementConfig_DEFAULT : Clay__BorderElementConfigArray_Add(&Clay_GetCurrentContext()->borderElementConfigs, config); } +Clay_SharedElementConfig * Clay__StoreSharedElementConfig(Clay_SharedElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_SharedElementConfig_DEFAULT : Clay__SharedElementConfigArray_Add(&Clay_GetCurrentContext()->sharedElementConfigs, config); } + +Clay_ElementConfig Clay__AttachElementConfig(Clay_ElementConfigUnion config, Clay__ElementConfigType type) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->booleanWarnings.maxElementsExceeded) { + return CLAY__INIT(Clay_ElementConfig) CLAY__DEFAULT_STRUCT; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + openLayoutElement->elementConfigs.length++; + return *Clay__ElementConfigArray_Add(&context->elementConfigs, CLAY__INIT(Clay_ElementConfig) { .type = type, .config = config }); +} + +Clay_ElementConfigUnion Clay__FindElementConfigWithType(Clay_LayoutElement *element, Clay__ElementConfigType type) { + for (int32_t i = 0; i < element->elementConfigs.length; i++) { + Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&element->elementConfigs, i); + if (config->type == type) { + return config->config; + } + } + return CLAY__INIT(Clay_ElementConfigUnion) { NULL }; +} + +Clay_ElementId Clay__HashNumber(const uint32_t offset, const uint32_t seed) { + uint32_t hash = seed; + hash += (offset + 48); + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = seed, .stringId = CLAY__STRING_DEFAULT }; // Reserve the hash result of zero as "null id" +} + +Clay_ElementId Clay__HashString(Clay_String key, const uint32_t seed) { + uint32_t hash = seed; + + for (int32_t i = 0; i < key.length; i++) { + hash += key.chars[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = 0, .baseId = hash + 1, .stringId = key }; // Reserve the hash result of zero as "null id" +} + +Clay_ElementId Clay__HashStringWithOffset(Clay_String key, const uint32_t offset, const uint32_t seed) { + uint32_t hash = 0; + uint32_t base = seed; + + for (int32_t i = 0; i < key.length; i++) { + base += key.chars[i]; + base += (base << 10); + base ^= (base >> 6); + } + hash = base; + hash += offset; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += (hash << 3); + base += (base << 3); + hash ^= (hash >> 11); + base ^= (base >> 11); + hash += (hash << 15); + base += (base << 15); + return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = base + 1, .stringId = key }; // Reserve the hash result of zero as "null id" +} + +#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) +static inline __m128i Clay__SIMDRotateLeft(__m128i x, int r) { + return _mm_or_si128(_mm_slli_epi64(x, r), _mm_srli_epi64(x, 64 - r)); +} + +static inline void Clay__SIMDARXMix(__m128i* a, __m128i* b) { + *a = _mm_add_epi64(*a, *b); + *b = _mm_xor_si128(Clay__SIMDRotateLeft(*b, 17), *a); +} + +uint64_t Clay__HashData(const uint8_t* data, size_t length) { + // Pinched these constants from the BLAKE implementation + __m128i v0 = _mm_set1_epi64x(0x6a09e667f3bcc908ULL); + __m128i v1 = _mm_set1_epi64x(0xbb67ae8584caa73bULL); + __m128i v2 = _mm_set1_epi64x(0x3c6ef372fe94f82bULL); + __m128i v3 = _mm_set1_epi64x(0xa54ff53a5f1d36f1ULL); + + uint8_t overflowBuffer[16] = { 0 }; // Temporary buffer for small inputs + + while (length > 0) { + __m128i msg; + if (length >= 16) { + msg = _mm_loadu_si128((const __m128i*)data); + data += 16; + length -= 16; + } + else { + for (size_t i = 0; i < length; i++) { + overflowBuffer[i] = data[i]; + } + msg = _mm_loadu_si128((const __m128i*)overflowBuffer); + length = 0; + } + + v0 = _mm_xor_si128(v0, msg); + Clay__SIMDARXMix(&v0, &v1); + Clay__SIMDARXMix(&v2, &v3); + + v0 = _mm_add_epi64(v0, v2); + v1 = _mm_add_epi64(v1, v3); + } + + Clay__SIMDARXMix(&v0, &v1); + Clay__SIMDARXMix(&v2, &v3); + v0 = _mm_add_epi64(v0, v2); + v1 = _mm_add_epi64(v1, v3); + v0 = _mm_add_epi64(v0, v1); + + uint64_t result[2]; + _mm_storeu_si128((__m128i*)result, v0); + + return result[0] ^ result[1]; +} +#elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__) +static inline void Clay__SIMDARXMix(uint64x2_t* a, uint64x2_t* b) { + *a = vaddq_u64(*a, *b); + *b = veorq_u64(vorrq_u64(vshlq_n_u64(*b, 17), vshrq_n_u64(*b, 64 - 17)), *a); +} + +uint64_t Clay__HashData(const uint8_t* data, size_t length) { + // Pinched these constants from the BLAKE implementation + uint64x2_t v0 = vdupq_n_u64(0x6a09e667f3bcc908ULL); + uint64x2_t v1 = vdupq_n_u64(0xbb67ae8584caa73bULL); + uint64x2_t v2 = vdupq_n_u64(0x3c6ef372fe94f82bULL); + uint64x2_t v3 = vdupq_n_u64(0xa54ff53a5f1d36f1ULL); + + uint8_t overflowBuffer[8] = { 0 }; + + while (length > 0) { + uint64x2_t msg; + if (length > 16) { + msg = vld1q_u64((const uint64_t*)data); + data += 16; + length -= 16; + } + else if (length > 8) { + msg = vcombine_u64(vld1_u64((const uint64_t*)data), vdup_n_u64(0)); + data += 8; + length -= 8; + } + else { + for (size_t i = 0; i < length; i++) { + overflowBuffer[i] = data[i]; + } + uint8x8_t lower = vld1_u8(overflowBuffer); + msg = vreinterpretq_u64_u8(vcombine_u8(lower, vdup_n_u8(0))); + length = 0; + } + v0 = veorq_u64(v0, msg); + Clay__SIMDARXMix(&v0, &v1); + Clay__SIMDARXMix(&v2, &v3); + + v0 = vaddq_u64(v0, v2); + v1 = vaddq_u64(v1, v3); + } + + Clay__SIMDARXMix(&v0, &v1); + Clay__SIMDARXMix(&v2, &v3); + v0 = vaddq_u64(v0, v2); + v1 = vaddq_u64(v1, v3); + v0 = vaddq_u64(v0, v1); + + uint64_t result[2]; + vst1q_u64(result, v0); + + return result[0] ^ result[1]; +} +#else +uint64_t Clay__HashData(const uint8_t* data, size_t length) { + uint64_t hash = 0; + + for (size_t i = 0; i < length; i++) { + hash += data[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + return hash; +} +#endif + +uint32_t Clay__HashStringContentsWithConfig(Clay_String *text, Clay_TextElementConfig *config) { + uint32_t hash = 0; + if (text->isStaticallyAllocated) { + hash += (uintptr_t)text->chars; + hash += (hash << 10); + hash ^= (hash >> 6); + hash += text->length; + hash += (hash << 10); + hash ^= (hash >> 6); + } else { + hash = Clay__HashData((const uint8_t *)text->chars, text->length) % UINT32_MAX; + } + + hash += config->fontId; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += config->fontSize; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += config->letterSpacing; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash + 1; // Reserve the hash result of zero as "null id" +} + +Clay__MeasuredWord *Clay__AddMeasuredWord(Clay__MeasuredWord word, Clay__MeasuredWord *previousWord) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->measuredWordsFreeList.length > 0) { + uint32_t newItemIndex = Clay__int32_tArray_GetValue(&context->measuredWordsFreeList, (int)context->measuredWordsFreeList.length - 1); + context->measuredWordsFreeList.length--; + Clay__MeasuredWordArray_Set(&context->measuredWords, (int)newItemIndex, word); + previousWord->next = (int32_t)newItemIndex; + return Clay__MeasuredWordArray_Get(&context->measuredWords, (int)newItemIndex); + } else { + previousWord->next = (int32_t)context->measuredWords.length; + return Clay__MeasuredWordArray_Add(&context->measuredWords, word); + } +} + +Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_TextElementConfig *config) { + Clay_Context* context = Clay_GetCurrentContext(); + #ifndef CLAY_WASM + if (!Clay__MeasureText) { + if (!context->booleanWarnings.textMeasurementFunctionNotSet) { + context->booleanWarnings.textMeasurementFunctionNotSet = true; + context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED, + .errorText = CLAY_STRING("Clay's internal MeasureText function is null. You may have forgotten to call Clay_SetMeasureTextFunction(), or passed a NULL function pointer by mistake."), + .userData = context->errorHandler.userData }); + } + return &Clay__MeasureTextCacheItem_DEFAULT; + } + #endif + uint32_t id = Clay__HashStringContentsWithConfig(text, config); + uint32_t hashBucket = id % (context->maxMeasureTextCacheWordCount / 32); + int32_t elementIndexPrevious = 0; + int32_t elementIndex = context->measureTextHashMap.internalArray[hashBucket]; + while (elementIndex != 0) { + Clay__MeasureTextCacheItem *hashEntry = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, elementIndex); + if (hashEntry->id == id) { + hashEntry->generation = context->generation; + return hashEntry; + } + // This element hasn't been seen in a few frames, delete the hash map item + if (context->generation - hashEntry->generation > 2) { + // Add all the measured words that were included in this measurement to the freelist + int32_t nextWordIndex = hashEntry->measuredWordsStartIndex; + while (nextWordIndex != -1) { + Clay__MeasuredWord *measuredWord = Clay__MeasuredWordArray_Get(&context->measuredWords, nextWordIndex); + Clay__int32_tArray_Add(&context->measuredWordsFreeList, nextWordIndex); + nextWordIndex = measuredWord->next; + } + + int32_t nextIndex = hashEntry->nextIndex; + Clay__MeasureTextCacheItemArray_Set(&context->measureTextHashMapInternal, elementIndex, CLAY__INIT(Clay__MeasureTextCacheItem) { .measuredWordsStartIndex = -1 }); + Clay__int32_tArray_Add(&context->measureTextHashMapInternalFreeList, elementIndex); + if (elementIndexPrevious == 0) { + context->measureTextHashMap.internalArray[hashBucket] = nextIndex; + } else { + Clay__MeasureTextCacheItem *previousHashEntry = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, elementIndexPrevious); + previousHashEntry->nextIndex = nextIndex; + } + elementIndex = nextIndex; + } else { + elementIndexPrevious = elementIndex; + elementIndex = hashEntry->nextIndex; + } + } + + int32_t newItemIndex = 0; + Clay__MeasureTextCacheItem newCacheItem = { .measuredWordsStartIndex = -1, .id = id, .generation = context->generation }; + Clay__MeasureTextCacheItem *measured = NULL; + if (context->measureTextHashMapInternalFreeList.length > 0) { + newItemIndex = Clay__int32_tArray_GetValue(&context->measureTextHashMapInternalFreeList, context->measureTextHashMapInternalFreeList.length - 1); + context->measureTextHashMapInternalFreeList.length--; + Clay__MeasureTextCacheItemArray_Set(&context->measureTextHashMapInternal, newItemIndex, newCacheItem); + measured = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, newItemIndex); + } else { + if (context->measureTextHashMapInternal.length == context->measureTextHashMapInternal.capacity - 1) { + if (!context->booleanWarnings.maxTextMeasureCacheExceeded) { + context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, + .errorText = CLAY_STRING("Clay ran out of capacity while attempting to measure text elements. Try using Clay_SetMaxElementCount() with a higher value."), + .userData = context->errorHandler.userData }); + context->booleanWarnings.maxTextMeasureCacheExceeded = true; + } + return &Clay__MeasureTextCacheItem_DEFAULT; + } + measured = Clay__MeasureTextCacheItemArray_Add(&context->measureTextHashMapInternal, newCacheItem); + newItemIndex = context->measureTextHashMapInternal.length - 1; + } + + int32_t start = 0; + int32_t end = 0; + float lineWidth = 0; + float measuredWidth = 0; + float measuredHeight = 0; + float spaceWidth = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = 1, .chars = CLAY__SPACECHAR.chars, .baseChars = CLAY__SPACECHAR.chars }, config, context->measureTextUserData).width; + Clay__MeasuredWord tempWord = { .next = -1 }; + Clay__MeasuredWord *previousWord = &tempWord; + while (end < text->length) { + if (context->measuredWords.length == context->measuredWords.capacity - 1) { + if (!context->booleanWarnings.maxTextMeasureCacheExceeded) { + context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED, + .errorText = CLAY_STRING("Clay has run out of space in it's internal text measurement cache. Try using Clay_SetMaxMeasureTextCacheWordCount() (default 16384, with 1 unit storing 1 measured word)."), + .userData = context->errorHandler.userData }); + context->booleanWarnings.maxTextMeasureCacheExceeded = true; + } + return &Clay__MeasureTextCacheItem_DEFAULT; + } + char current = text->chars[end]; + if (current == ' ' || current == '\n') { + int32_t length = end - start; + Clay_Dimensions dimensions = CLAY__DEFAULT_STRUCT; + if (length > 0) { + dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) {.length = length, .chars = &text->chars[start], .baseChars = text->chars}, config, context->measureTextUserData); + } + measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth); + measuredHeight = CLAY__MAX(measuredHeight, dimensions.height); + if (current == ' ') { + dimensions.width += spaceWidth; + previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = length + 1, .width = dimensions.width, .next = -1 }, previousWord); + lineWidth += dimensions.width; + } + if (current == '\n') { + if (length > 0) { + previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = length, .width = dimensions.width, .next = -1 }, previousWord); + } + previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = end + 1, .length = 0, .width = 0, .next = -1 }, previousWord); + lineWidth += dimensions.width; + measuredWidth = CLAY__MAX(lineWidth, measuredWidth); + measured->containsNewlines = true; + lineWidth = 0; + } + start = end + 1; + } + end++; + } + if (end - start > 0) { + Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = end - start, .chars = &text->chars[start], .baseChars = text->chars }, config, context->measureTextUserData); + Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = end - start, .width = dimensions.width, .next = -1 }, previousWord); + lineWidth += dimensions.width; + measuredHeight = CLAY__MAX(measuredHeight, dimensions.height); + measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth); + } + measuredWidth = CLAY__MAX(lineWidth, measuredWidth) - config->letterSpacing; + + measured->measuredWordsStartIndex = tempWord.next; + measured->unwrappedDimensions.width = measuredWidth; + measured->unwrappedDimensions.height = measuredHeight; + + if (elementIndexPrevious != 0) { + Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, elementIndexPrevious)->nextIndex = newItemIndex; + } else { + context->measureTextHashMap.internalArray[hashBucket] = newItemIndex; + } + return measured; +} + +bool Clay__PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) { + return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height; +} + +Clay_LayoutElementHashMapItem* Clay__AddHashMapItem(Clay_ElementId elementId, Clay_LayoutElement* layoutElement) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->layoutElementsHashMapInternal.length == context->layoutElementsHashMapInternal.capacity - 1) { + return NULL; + } + Clay_LayoutElementHashMapItem item = { .elementId = elementId, .layoutElement = layoutElement, .nextIndex = -1, .generation = context->generation + 1 }; + uint32_t hashBucket = elementId.id % context->layoutElementsHashMap.capacity; + int32_t hashItemPrevious = -1; + int32_t hashItemIndex = context->layoutElementsHashMap.internalArray[hashBucket]; + while (hashItemIndex != -1) { // Just replace collision, not a big deal - leave it up to the end user + Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Get(&context->layoutElementsHashMapInternal, hashItemIndex); + if (hashItem->elementId.id == elementId.id) { // Collision - resolve based on generation + item.nextIndex = hashItem->nextIndex; + if (hashItem->generation <= context->generation) { // First collision - assume this is the "same" element + hashItem->elementId = elementId; // Make sure to copy this across. If the stringId reference has changed, we should update the hash item to use the new one. + hashItem->generation = context->generation + 1; + hashItem->layoutElement = layoutElement; + hashItem->debugData->collision = false; + hashItem->onHoverFunction = NULL; + hashItem->hoverFunctionUserData = 0; + } else { // Multiple collisions this frame - two elements have the same ID + context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_DUPLICATE_ID, + .errorText = CLAY_STRING("An element with this ID was already previously declared during this layout."), + .userData = context->errorHandler.userData }); + if (context->debugModeEnabled) { + hashItem->debugData->collision = true; + } + } + return hashItem; + } + hashItemPrevious = hashItemIndex; + hashItemIndex = hashItem->nextIndex; + } + Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Add(&context->layoutElementsHashMapInternal, item); + hashItem->debugData = Clay__DebugElementDataArray_Add(&context->debugElementData, CLAY__INIT(Clay__DebugElementData) CLAY__DEFAULT_STRUCT); + if (hashItemPrevious != -1) { + Clay__LayoutElementHashMapItemArray_Get(&context->layoutElementsHashMapInternal, hashItemPrevious)->nextIndex = (int32_t)context->layoutElementsHashMapInternal.length - 1; + } else { + context->layoutElementsHashMap.internalArray[hashBucket] = (int32_t)context->layoutElementsHashMapInternal.length - 1; + } + return hashItem; +} + +Clay_LayoutElementHashMapItem *Clay__GetHashMapItem(uint32_t id) { + Clay_Context* context = Clay_GetCurrentContext(); + uint32_t hashBucket = id % context->layoutElementsHashMap.capacity; + int32_t elementIndex = context->layoutElementsHashMap.internalArray[hashBucket]; + while (elementIndex != -1) { + Clay_LayoutElementHashMapItem *hashEntry = Clay__LayoutElementHashMapItemArray_Get(&context->layoutElementsHashMapInternal, elementIndex); + if (hashEntry->elementId.id == id) { + return hashEntry; + } + elementIndex = hashEntry->nextIndex; + } + return &Clay_LayoutElementHashMapItem_DEFAULT; +} + +Clay_ElementId Clay__GenerateIdForAnonymousElement(Clay_LayoutElement *openLayoutElement) { + Clay_Context* context = Clay_GetCurrentContext(); + Clay_LayoutElement *parentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); + uint32_t offset = parentElement->childrenOrTextContent.children.length + parentElement->floatingChildrenCount; + Clay_ElementId elementId = Clay__HashNumber(offset, parentElement->id); + openLayoutElement->id = elementId.id; + Clay__AddHashMapItem(elementId, openLayoutElement); + Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); + return elementId; +} + +bool Clay__ElementHasConfig(Clay_LayoutElement *layoutElement, Clay__ElementConfigType type) { + for (int32_t i = 0; i < layoutElement->elementConfigs.length; i++) { + if (Clay__ElementConfigArraySlice_Get(&layoutElement->elementConfigs, i)->type == type) { + return true; + } + } + return false; +} + +void Clay__UpdateAspectRatioBox(Clay_LayoutElement *layoutElement) { + for (int32_t j = 0; j < layoutElement->elementConfigs.length; j++) { + Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&layoutElement->elementConfigs, j); + if (config->type == CLAY__ELEMENT_CONFIG_TYPE_ASPECT) { + Clay_AspectRatioElementConfig *aspectConfig = config->config.aspectRatioElementConfig; + if (aspectConfig->aspectRatio == 0) { + break; + } + if (layoutElement->dimensions.width == 0 && layoutElement->dimensions.height != 0) { + layoutElement->dimensions.width = layoutElement->dimensions.height * aspectConfig->aspectRatio; + } else if (layoutElement->dimensions.width != 0 && layoutElement->dimensions.height == 0) { + layoutElement->dimensions.height = layoutElement->dimensions.width * (1 / aspectConfig->aspectRatio); + } + break; + } + } +} + +void Clay__CloseElement(void) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->booleanWarnings.maxElementsExceeded) { + return; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + Clay_LayoutConfig *layoutConfig = openLayoutElement->layoutConfig; + if (!layoutConfig) { + openLayoutElement->layoutConfig = &Clay_LayoutConfig_DEFAULT; + layoutConfig = &Clay_LayoutConfig_DEFAULT; + } + bool elementHasClipHorizontal = false; + bool elementHasClipVertical = false; + for (int32_t i = 0; i < openLayoutElement->elementConfigs.length; i++) { + Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&openLayoutElement->elementConfigs, i); + if (config->type == CLAY__ELEMENT_CONFIG_TYPE_CLIP) { + elementHasClipHorizontal = config->config.clipElementConfig->horizontal; + elementHasClipVertical = config->config.clipElementConfig->vertical; + context->openClipElementStack.length--; + break; + } else if (config->type == CLAY__ELEMENT_CONFIG_TYPE_FLOATING) { + context->openClipElementStack.length--; + } + } + + float leftRightPadding = (float)(layoutConfig->padding.left + layoutConfig->padding.right); + float topBottomPadding = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom); + + // Attach children to the current open element + openLayoutElement->childrenOrTextContent.children.elements = &context->layoutElementChildren.internalArray[context->layoutElementChildren.length]; + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + openLayoutElement->dimensions.width = leftRightPadding; + openLayoutElement->minDimensions.width = leftRightPadding; + for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) { + int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i); + Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex); + openLayoutElement->dimensions.width += child->dimensions.width; + openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + topBottomPadding); + // Minimum size of child elements doesn't matter to clip containers as they can shrink and hide their contents + if (!elementHasClipHorizontal) { + openLayoutElement->minDimensions.width += child->minDimensions.width; + } + if (!elementHasClipVertical) { + openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + topBottomPadding); + } + Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex); + } + float childGap = (float)(CLAY__MAX(openLayoutElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + openLayoutElement->dimensions.width += childGap; + if (!elementHasClipHorizontal) { + openLayoutElement->minDimensions.width += childGap; + } + } + else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { + openLayoutElement->dimensions.height = topBottomPadding; + openLayoutElement->minDimensions.height = topBottomPadding; + for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) { + int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i); + Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex); + openLayoutElement->dimensions.height += child->dimensions.height; + openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + leftRightPadding); + // Minimum size of child elements doesn't matter to clip containers as they can shrink and hide their contents + if (!elementHasClipVertical) { + openLayoutElement->minDimensions.height += child->minDimensions.height; + } + if (!elementHasClipHorizontal) { + openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + leftRightPadding); + } + Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex); + } + float childGap = (float)(CLAY__MAX(openLayoutElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + openLayoutElement->dimensions.height += childGap; + if (!elementHasClipVertical) { + openLayoutElement->minDimensions.height += childGap; + } + } + + context->layoutElementChildrenBuffer.length -= openLayoutElement->childrenOrTextContent.children.length; + + // Clamp element min and max width to the values configured in the layout + if (layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { + if (layoutConfig->sizing.width.size.minMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier + layoutConfig->sizing.width.size.minMax.max = CLAY__MAXFLOAT; + } + openLayoutElement->dimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->dimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); + openLayoutElement->minDimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); + } else { + openLayoutElement->dimensions.width = 0; + } + + // Clamp element min and max height to the values configured in the layout + if (layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { + if (layoutConfig->sizing.height.size.minMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier + layoutConfig->sizing.height.size.minMax.max = CLAY__MAXFLOAT; + } + openLayoutElement->dimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->dimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); + openLayoutElement->minDimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); + } else { + openLayoutElement->dimensions.height = 0; + } + + Clay__UpdateAspectRatioBox(openLayoutElement); + + bool elementIsFloating = Clay__ElementHasConfig(openLayoutElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING); + + // Close the currently open element + int32_t closingElementIndex = Clay__int32_tArray_RemoveSwapback(&context->openLayoutElementStack, (int)context->openLayoutElementStack.length - 1); + + // Get the currently open parent + openLayoutElement = Clay__GetOpenLayoutElement(); + + if (context->openLayoutElementStack.length > 1) { + if(elementIsFloating) { + openLayoutElement->floatingChildrenCount++; + return; + } + openLayoutElement->childrenOrTextContent.children.length++; + Clay__int32_tArray_Add(&context->layoutElementChildrenBuffer, closingElementIndex); + } +} + +bool Clay__MemCmp(const char *s1, const char *s2, int32_t length); +#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) + bool Clay__MemCmp(const char *s1, const char *s2, int32_t length) { + while (length >= 16) { + __m128i v1 = _mm_loadu_si128((const __m128i *)s1); + __m128i v2 = _mm_loadu_si128((const __m128i *)s2); + + if (_mm_movemask_epi8(_mm_cmpeq_epi8(v1, v2)) != 0xFFFF) { // If any byte differs + return false; + } + + s1 += 16; + s2 += 16; + length -= 16; + } + + // Handle remaining bytes + while (length--) { + if (*s1 != *s2) { + return false; + } + s1++; + s2++; + } + + return true; + } +#elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__) + bool Clay__MemCmp(const char *s1, const char *s2, int32_t length) { + while (length >= 16) { + uint8x16_t v1 = vld1q_u8((const uint8_t *)s1); + uint8x16_t v2 = vld1q_u8((const uint8_t *)s2); + + // Compare vectors + if (vminvq_u32(vreinterpretq_u32_u8(vceqq_u8(v1, v2))) != 0xFFFFFFFF) { // If there's a difference + return false; + } + + s1 += 16; + s2 += 16; + length -= 16; + } + + // Handle remaining bytes + while (length--) { + if (*s1 != *s2) { + return false; + } + s1++; + s2++; + } + + return true; + } +#else + bool Clay__MemCmp(const char *s1, const char *s2, int32_t length) { + for (int32_t i = 0; i < length; i++) { + if (s1[i] != s2[i]) { + return false; + } + } + return true; + } +#endif + +void Clay__OpenElement(void) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) { + context->booleanWarnings.maxElementsExceeded = true; + return; + } + Clay_LayoutElement layoutElement = CLAY__DEFAULT_STRUCT; + Clay_LayoutElement* openLayoutElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); + Clay__int32_tArray_Add(&context->openLayoutElementStack, context->layoutElements.length - 1); + Clay__GenerateIdForAnonymousElement(openLayoutElement); + if (context->openClipElementStack.length > 0) { + Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); + } else { + Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, 0); + } +} + +void Clay__OpenElementWithId(Clay_ElementId elementId) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) { + context->booleanWarnings.maxElementsExceeded = true; + return; + } + Clay_LayoutElement layoutElement = CLAY__DEFAULT_STRUCT; + layoutElement.id = elementId.id; + Clay_LayoutElement * openLayoutElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); + Clay__int32_tArray_Add(&context->openLayoutElementStack, context->layoutElements.length - 1); + Clay__AddHashMapItem(elementId, openLayoutElement); + Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); + if (context->openClipElementStack.length > 0) { + Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); + } else { + Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, 0); + } +} + +void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) { + context->booleanWarnings.maxElementsExceeded = true; + return; + } + Clay_LayoutElement *parentElement = Clay__GetOpenLayoutElement(); + + Clay_LayoutElement layoutElement = CLAY__DEFAULT_STRUCT; + Clay_LayoutElement *textElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); + if (context->openClipElementStack.length > 0) { + Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); + } else { + Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, 0); + } + + Clay__int32_tArray_Add(&context->layoutElementChildrenBuffer, context->layoutElements.length - 1); + Clay__MeasureTextCacheItem *textMeasured = Clay__MeasureTextCached(&text, textConfig); + Clay_ElementId elementId = Clay__HashNumber(parentElement->childrenOrTextContent.children.length + parentElement->floatingChildrenCount, parentElement->id); + textElement->id = elementId.id; + Clay__AddHashMapItem(elementId, textElement); + Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); + Clay_Dimensions textDimensions = { .width = textMeasured->unwrappedDimensions.width, .height = textConfig->lineHeight > 0 ? (float)textConfig->lineHeight : textMeasured->unwrappedDimensions.height }; + textElement->dimensions = textDimensions; + textElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->minWidth, .height = textDimensions.height }; + textElement->childrenOrTextContent.textElementData = Clay__TextElementDataArray_Add(&context->textElementData, CLAY__INIT(Clay__TextElementData) { .text = text, .preferredDimensions = textMeasured->unwrappedDimensions, .elementIndex = context->layoutElements.length - 1 }); + textElement->elementConfigs = CLAY__INIT(Clay__ElementConfigArraySlice) { + .length = 1, + .internalArray = Clay__ElementConfigArray_Add(&context->elementConfigs, CLAY__INIT(Clay_ElementConfig) { .type = CLAY__ELEMENT_CONFIG_TYPE_TEXT, .config = { .textElementConfig = textConfig }}) + }; + textElement->layoutConfig = &CLAY_LAYOUT_DEFAULT; + parentElement->childrenOrTextContent.children.length++; +} + +void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) { + Clay_Context* context = Clay_GetCurrentContext(); + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + openLayoutElement->layoutConfig = Clay__StoreLayoutConfig(declaration->layout); + if ((declaration->layout.sizing.width.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.width.size.percent > 1) || (declaration->layout.sizing.height.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.height.size.percent > 1)) { + context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_PERCENTAGE_OVER_1, + .errorText = CLAY_STRING("An element was configured with CLAY_SIZING_PERCENT, but the provided percentage value was over 1.0. Clay expects a value between 0 and 1, i.e. 20% is 0.2."), + .userData = context->errorHandler.userData }); + } + + openLayoutElement->elementConfigs.internalArray = &context->elementConfigs.internalArray[context->elementConfigs.length]; + Clay_SharedElementConfig *sharedConfig = NULL; + if (declaration->backgroundColor.a > 0) { + sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .backgroundColor = declaration->backgroundColor }); + Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED); + } + if (!Clay__MemCmp((char *)(&declaration->cornerRadius), (char *)(&Clay__CornerRadius_DEFAULT), sizeof(Clay_CornerRadius))) { + if (sharedConfig) { + sharedConfig->cornerRadius = declaration->cornerRadius; + } else { + sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .cornerRadius = declaration->cornerRadius }); + Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED); + } + } + if (declaration->userData != 0) { + if (sharedConfig) { + sharedConfig->userData = declaration->userData; + } else { + sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .userData = declaration->userData }); + Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED); + } + } + if (declaration->image.imageData) { + Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .imageElementConfig = Clay__StoreImageElementConfig(declaration->image) }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE); + } + if (declaration->aspectRatio.aspectRatio > 0) { + Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .aspectRatioElementConfig = Clay__StoreAspectRatioElementConfig(declaration->aspectRatio) }, CLAY__ELEMENT_CONFIG_TYPE_ASPECT); + Clay__int32_tArray_Add(&context->aspectRatioElementIndexes, context->layoutElements.length - 1); + } + if (declaration->floating.attachTo != CLAY_ATTACH_TO_NONE) { + Clay_FloatingElementConfig floatingConfig = declaration->floating; + // This looks dodgy but because of the auto generated root element the depth of the tree will always be at least 2 here + Clay_LayoutElement *hierarchicalParent = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); + if (hierarchicalParent) { + uint32_t clipElementId = 0; + if (declaration->floating.attachTo == CLAY_ATTACH_TO_PARENT) { + // Attach to the element's direct hierarchical parent + floatingConfig.parentId = hierarchicalParent->id; + if (context->openClipElementStack.length > 0) { + clipElementId = Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1); + } + } else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) { + Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingConfig.parentId); + if (parentItem == &Clay_LayoutElementHashMapItem_DEFAULT) { + context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, + .errorText = CLAY_STRING("A floating element was declared with a parentId, but no element with that ID was found."), + .userData = context->errorHandler.userData }); + } else { + clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(parentItem->layoutElement - context->layoutElements.internalArray)); + } + } else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ROOT) { + floatingConfig.parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0).id; + } + if (declaration->floating.clipTo == CLAY_CLIP_TO_NONE) { + clipElementId = 0; + } + int32_t currentElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1); + Clay__int32_tArray_Set(&context->layoutElementClipElementIds, currentElementIndex, clipElementId); + Clay__int32_tArray_Add(&context->openClipElementStack, clipElementId); + Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { + .layoutElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1), + .parentId = floatingConfig.parentId, + .clipElementId = clipElementId, + .zIndex = floatingConfig.zIndex, + }); + Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .floatingElementConfig = Clay__StoreFloatingElementConfig(floatingConfig) }, CLAY__ELEMENT_CONFIG_TYPE_FLOATING); + } + } + if (declaration->custom.customData) { + Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .customElementConfig = Clay__StoreCustomElementConfig(declaration->custom) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM); + } + + if (declaration->clip.horizontal | declaration->clip.vertical) { + Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .clipElementConfig = Clay__StoreClipElementConfig(declaration->clip) }, CLAY__ELEMENT_CONFIG_TYPE_CLIP); + Clay__int32_tArray_Add(&context->openClipElementStack, (int)openLayoutElement->id); + // Retrieve or create cached data to track scroll position across frames + Clay__ScrollContainerDataInternal *scrollOffset = CLAY__NULL; + for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); + if (openLayoutElement->id == mapping->elementId) { + scrollOffset = mapping; + scrollOffset->layoutElement = openLayoutElement; + scrollOffset->openThisFrame = true; + } + } + if (!scrollOffset) { + scrollOffset = Clay__ScrollContainerDataInternalArray_Add(&context->scrollContainerDatas, CLAY__INIT(Clay__ScrollContainerDataInternal){.layoutElement = openLayoutElement, .scrollOrigin = {-1,-1}, .elementId = openLayoutElement->id, .openThisFrame = true}); + } + if (context->externalScrollHandlingEnabled) { + scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId, context->queryScrollOffsetUserData); + } + } + if (!Clay__MemCmp((char *)(&declaration->border.width), (char *)(&Clay__BorderWidth_DEFAULT), sizeof(Clay_BorderWidth))) { + Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .borderElementConfig = Clay__StoreBorderElementConfig(declaration->border) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER); + } +} + +void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) { + Clay__ConfigureOpenElementPtr(&declaration); +} + +void Clay__InitializeEphemeralMemory(Clay_Context* context) { + int32_t maxElementCount = context->maxElementCount; + // Ephemeral Memory - reset every frame + Clay_Arena *arena = &context->internalArena; + arena->nextAllocation = context->arenaResetOffset; + + context->layoutElementChildrenBuffer = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->layoutElements = Clay_LayoutElementArray_Allocate_Arena(maxElementCount, arena); + context->warnings = Clay__WarningArray_Allocate_Arena(100, arena); + + context->layoutConfigs = Clay__LayoutConfigArray_Allocate_Arena(maxElementCount, arena); + context->elementConfigs = Clay__ElementConfigArray_Allocate_Arena(maxElementCount, arena); + context->textElementConfigs = Clay__TextElementConfigArray_Allocate_Arena(maxElementCount, arena); + context->aspectRatioElementConfigs = Clay__AspectRatioElementConfigArray_Allocate_Arena(maxElementCount, arena); + context->imageElementConfigs = Clay__ImageElementConfigArray_Allocate_Arena(maxElementCount, arena); + context->floatingElementConfigs = Clay__FloatingElementConfigArray_Allocate_Arena(maxElementCount, arena); + context->clipElementConfigs = Clay__ClipElementConfigArray_Allocate_Arena(maxElementCount, arena); + context->customElementConfigs = Clay__CustomElementConfigArray_Allocate_Arena(maxElementCount, arena); + context->borderElementConfigs = Clay__BorderElementConfigArray_Allocate_Arena(maxElementCount, arena); + context->sharedElementConfigs = Clay__SharedElementConfigArray_Allocate_Arena(maxElementCount, arena); + + context->layoutElementIdStrings = Clay__StringArray_Allocate_Arena(maxElementCount, arena); + context->wrappedTextLines = Clay__WrappedTextLineArray_Allocate_Arena(maxElementCount, arena); + context->layoutElementTreeNodeArray1 = Clay__LayoutElementTreeNodeArray_Allocate_Arena(maxElementCount, arena); + context->layoutElementTreeRoots = Clay__LayoutElementTreeRootArray_Allocate_Arena(maxElementCount, arena); + context->layoutElementChildren = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->openLayoutElementStack = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->textElementData = Clay__TextElementDataArray_Allocate_Arena(maxElementCount, arena); + context->aspectRatioElementIndexes = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->renderCommands = Clay_RenderCommandArray_Allocate_Arena(maxElementCount, arena); + context->treeNodeVisited = Clay__boolArray_Allocate_Arena(maxElementCount, arena); + context->treeNodeVisited.length = context->treeNodeVisited.capacity; // This array is accessed directly rather than behaving as a list + context->openClipElementStack = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->reusableElementIndexBuffer = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->layoutElementClipElementIds = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->dynamicStringData = Clay__charArray_Allocate_Arena(maxElementCount, arena); +} + +void Clay__InitializePersistentMemory(Clay_Context* context) { + // Persistent memory - initialized once and not reset + int32_t maxElementCount = context->maxElementCount; + int32_t maxMeasureTextCacheWordCount = context->maxMeasureTextCacheWordCount; + Clay_Arena *arena = &context->internalArena; + + context->scrollContainerDatas = Clay__ScrollContainerDataInternalArray_Allocate_Arena(100, arena); + context->layoutElementsHashMapInternal = Clay__LayoutElementHashMapItemArray_Allocate_Arena(maxElementCount, arena); + context->layoutElementsHashMap = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->measureTextHashMapInternal = Clay__MeasureTextCacheItemArray_Allocate_Arena(maxElementCount, arena); + context->measureTextHashMapInternalFreeList = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->measuredWordsFreeList = Clay__int32_tArray_Allocate_Arena(maxMeasureTextCacheWordCount, arena); + context->measureTextHashMap = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->measuredWords = Clay__MeasuredWordArray_Allocate_Arena(maxMeasureTextCacheWordCount, arena); + context->pointerOverIds = Clay_ElementIdArray_Allocate_Arena(maxElementCount, arena); + context->debugElementData = Clay__DebugElementDataArray_Allocate_Arena(maxElementCount, arena); + context->arenaResetOffset = arena->nextAllocation; +} + +const float CLAY__EPSILON = 0.01; + +bool Clay__FloatEqual(float left, float right) { + float subtracted = left - right; + return subtracted < CLAY__EPSILON && subtracted > -CLAY__EPSILON; +} + +void Clay__SizeContainersAlongAxis(bool xAxis) { + Clay_Context* context = Clay_GetCurrentContext(); + Clay__int32_tArray bfsBuffer = context->layoutElementChildrenBuffer; + Clay__int32_tArray resizableContainerBuffer = context->openLayoutElementStack; + for (int32_t rootIndex = 0; rootIndex < context->layoutElementTreeRoots.length; ++rootIndex) { + bfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); + Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)root->layoutElementIndex); + Clay__int32_tArray_Add(&bfsBuffer, (int32_t)root->layoutElementIndex); + + // Size floating containers to their parents + if (Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING)) { + Clay_FloatingElementConfig *floatingElementConfig = Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig; + Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingElementConfig->parentId); + if (parentItem && parentItem != &Clay_LayoutElementHashMapItem_DEFAULT) { + Clay_LayoutElement *parentLayoutElement = parentItem->layoutElement; + switch (rootElement->layoutConfig->sizing.width.type) { + case CLAY__SIZING_TYPE_GROW: { + rootElement->dimensions.width = parentLayoutElement->dimensions.width; + break; + } + case CLAY__SIZING_TYPE_PERCENT: { + rootElement->dimensions.width = parentLayoutElement->dimensions.width * rootElement->layoutConfig->sizing.width.size.percent; + break; + } + default: break; + } + switch (rootElement->layoutConfig->sizing.height.type) { + case CLAY__SIZING_TYPE_GROW: { + rootElement->dimensions.height = parentLayoutElement->dimensions.height; + break; + } + case CLAY__SIZING_TYPE_PERCENT: { + rootElement->dimensions.height = parentLayoutElement->dimensions.height * rootElement->layoutConfig->sizing.height.size.percent; + break; + } + default: break; + } + } + } + + if (rootElement->layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { + rootElement->dimensions.width = CLAY__MIN(CLAY__MAX(rootElement->dimensions.width, rootElement->layoutConfig->sizing.width.size.minMax.min), rootElement->layoutConfig->sizing.width.size.minMax.max); + } + if (rootElement->layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { + rootElement->dimensions.height = CLAY__MIN(CLAY__MAX(rootElement->dimensions.height, rootElement->layoutConfig->sizing.height.size.minMax.min), rootElement->layoutConfig->sizing.height.size.minMax.max); + } + + for (int32_t i = 0; i < bfsBuffer.length; ++i) { + int32_t parentIndex = Clay__int32_tArray_GetValue(&bfsBuffer, i); + Clay_LayoutElement *parent = Clay_LayoutElementArray_Get(&context->layoutElements, parentIndex); + Clay_LayoutConfig *parentStyleConfig = parent->layoutConfig; + int32_t growContainerCount = 0; + float parentSize = xAxis ? parent->dimensions.width : parent->dimensions.height; + float parentPadding = (float)(xAxis ? (parent->layoutConfig->padding.left + parent->layoutConfig->padding.right) : (parent->layoutConfig->padding.top + parent->layoutConfig->padding.bottom)); + float innerContentSize = 0, totalPaddingAndChildGaps = parentPadding; + bool sizingAlongAxis = (xAxis && parentStyleConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentStyleConfig->layoutDirection == CLAY_TOP_TO_BOTTOM); + resizableContainerBuffer.length = 0; + float parentChildGap = parentStyleConfig->childGap; + + for (int32_t childOffset = 0; childOffset < parent->childrenOrTextContent.children.length; childOffset++) { + int32_t childElementIndex = parent->childrenOrTextContent.children.elements[childOffset]; + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childElementIndex); + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height; + + if (!Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) && childElement->childrenOrTextContent.children.length > 0) { + Clay__int32_tArray_Add(&bfsBuffer, childElementIndex); + } + + if (childSizing.type != CLAY__SIZING_TYPE_PERCENT + && childSizing.type != CLAY__SIZING_TYPE_FIXED + && (!Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || (Clay__FindElementConfigWithType(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT).textElementConfig->wrapMode == CLAY_TEXT_WRAP_WORDS)) // todo too many loops +// && (xAxis || !Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT)) + ) { + Clay__int32_tArray_Add(&resizableContainerBuffer, childElementIndex); + } + + if (sizingAlongAxis) { + innerContentSize += (childSizing.type == CLAY__SIZING_TYPE_PERCENT ? 0 : childSize); + if (childSizing.type == CLAY__SIZING_TYPE_GROW) { + growContainerCount++; + } + if (childOffset > 0) { + innerContentSize += parentChildGap; // For children after index 0, the childAxisOffset is the gap from the previous child + totalPaddingAndChildGaps += parentChildGap; + } + } else { + innerContentSize = CLAY__MAX(childSize, innerContentSize); + } + } + + // Expand percentage containers to size + for (int32_t childOffset = 0; childOffset < parent->childrenOrTextContent.children.length; childOffset++) { + int32_t childElementIndex = parent->childrenOrTextContent.children.elements[childOffset]; + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childElementIndex); + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; + if (childSizing.type == CLAY__SIZING_TYPE_PERCENT) { + *childSize = (parentSize - totalPaddingAndChildGaps) * childSizing.size.percent; + if (sizingAlongAxis) { + innerContentSize += *childSize; + } + Clay__UpdateAspectRatioBox(childElement); + } + } + + if (sizingAlongAxis) { + float sizeToDistribute = parentSize - parentPadding - innerContentSize; + // The content is too large, compress the children as much as possible + if (sizeToDistribute < 0) { + // If the parent clips content in this axis direction, don't compress children, just leave them alone + Clay_ClipElementConfig *clipElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; + if (clipElementConfig) { + if (((xAxis && clipElementConfig->horizontal) || (!xAxis && clipElementConfig->vertical))) { + continue; + } + } + // Scrolling containers preferentially compress before others + while (sizeToDistribute < -CLAY__EPSILON && resizableContainerBuffer.length > 0) { + float largest = 0; + float secondLargest = 0; + float widthToAdd = sizeToDistribute; + for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { + Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); + float childSize = xAxis ? child->dimensions.width : child->dimensions.height; + if (Clay__FloatEqual(childSize, largest)) { continue; } + if (childSize > largest) { + secondLargest = largest; + largest = childSize; + } + if (childSize < largest) { + secondLargest = CLAY__MAX(secondLargest, childSize); + widthToAdd = secondLargest - largest; + } + } + + widthToAdd = CLAY__MAX(widthToAdd, sizeToDistribute / resizableContainerBuffer.length); + + for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { + Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); + float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height; + float minSize = xAxis ? child->minDimensions.width : child->minDimensions.height; + float previousWidth = *childSize; + if (Clay__FloatEqual(*childSize, largest)) { + *childSize += widthToAdd; + if (*childSize <= minSize) { + *childSize = minSize; + Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--); + } + sizeToDistribute -= (*childSize - previousWidth); + } + } + } + // The content is too small, allow SIZING_GROW containers to expand + } else if (sizeToDistribute > 0 && growContainerCount > 0) { + for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { + Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); + Clay__SizingType childSizing = xAxis ? child->layoutConfig->sizing.width.type : child->layoutConfig->sizing.height.type; + if (childSizing != CLAY__SIZING_TYPE_GROW) { + Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--); + } + } + while (sizeToDistribute > CLAY__EPSILON && resizableContainerBuffer.length > 0) { + float smallest = CLAY__MAXFLOAT; + float secondSmallest = CLAY__MAXFLOAT; + float widthToAdd = sizeToDistribute; + for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { + Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); + float childSize = xAxis ? child->dimensions.width : child->dimensions.height; + if (Clay__FloatEqual(childSize, smallest)) { continue; } + if (childSize < smallest) { + secondSmallest = smallest; + smallest = childSize; + } + if (childSize > smallest) { + secondSmallest = CLAY__MIN(secondSmallest, childSize); + widthToAdd = secondSmallest - smallest; + } + } + + widthToAdd = CLAY__MIN(widthToAdd, sizeToDistribute / resizableContainerBuffer.length); + + for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { + Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); + float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height; + float maxSize = xAxis ? child->layoutConfig->sizing.width.size.minMax.max : child->layoutConfig->sizing.height.size.minMax.max; + float previousWidth = *childSize; + if (Clay__FloatEqual(*childSize, smallest)) { + *childSize += widthToAdd; + if (*childSize >= maxSize) { + *childSize = maxSize; + Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--); + } + sizeToDistribute -= (*childSize - previousWidth); + } + } + } + } + // Sizing along the non layout axis ("off axis") + } else { + for (int32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childOffset)); + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + float minSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height; + float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; + + float maxSize = parentSize - parentPadding; + // If we're laying out the children of a scroll panel, grow containers expand to the size of the inner content, not the outer container + if (Clay__ElementHasConfig(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP)) { + Clay_ClipElementConfig *clipElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; + if (((xAxis && clipElementConfig->horizontal) || (!xAxis && clipElementConfig->vertical))) { + maxSize = CLAY__MAX(maxSize, innerContentSize); + } + } + if (childSizing.type == CLAY__SIZING_TYPE_GROW) { + *childSize = CLAY__MIN(maxSize, childSizing.size.minMax.max); + } + *childSize = CLAY__MAX(minSize, CLAY__MIN(*childSize, maxSize)); + } + } + } + } +} + +Clay_String Clay__IntToString(int32_t integer) { + if (integer == 0) { + return CLAY__INIT(Clay_String) { .length = 1, .chars = "0" }; + } + Clay_Context* context = Clay_GetCurrentContext(); + char *chars = (char *)(context->dynamicStringData.internalArray + context->dynamicStringData.length); + int32_t length = 0; + int32_t sign = integer; + + if (integer < 0) { + integer = -integer; + } + while (integer > 0) { + chars[length++] = (char)(integer % 10 + '0'); + integer /= 10; + } + + if (sign < 0) { + chars[length++] = '-'; + } + + // Reverse the string to get the correct order + for (int32_t j = 0, k = length - 1; j < k; j++, k--) { + char temp = chars[j]; + chars[j] = chars[k]; + chars[k] = temp; + } + context->dynamicStringData.length += length; + return CLAY__INIT(Clay_String) { .length = length, .chars = chars }; +} + +void Clay__AddRenderCommand(Clay_RenderCommand renderCommand) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->renderCommands.length < context->renderCommands.capacity - 1) { + Clay_RenderCommandArray_Add(&context->renderCommands, renderCommand); + } else { + if (!context->booleanWarnings.maxRenderCommandsExceeded) { + context->booleanWarnings.maxRenderCommandsExceeded = true; + context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, + .errorText = CLAY_STRING("Clay ran out of capacity while attempting to create render commands. This is usually caused by a large amount of wrapping text elements while close to the max element capacity. Try using Clay_SetMaxElementCount() with a higher value."), + .userData = context->errorHandler.userData }); + } + } +} + +bool Clay__ElementIsOffscreen(Clay_BoundingBox *boundingBox) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->disableCulling) { + return false; + } + + return (boundingBox->x > (float)context->layoutDimensions.width) || + (boundingBox->y > (float)context->layoutDimensions.height) || + (boundingBox->x + boundingBox->width < 0) || + (boundingBox->y + boundingBox->height < 0); +} + +void Clay__CalculateFinalLayout(void) { + Clay_Context* context = Clay_GetCurrentContext(); + // Calculate sizing along the X axis + Clay__SizeContainersAlongAxis(true); + + // Wrap text + for (int32_t textElementIndex = 0; textElementIndex < context->textElementData.length; ++textElementIndex) { + Clay__TextElementData *textElementData = Clay__TextElementDataArray_Get(&context->textElementData, textElementIndex); + textElementData->wrappedLines = CLAY__INIT(Clay__WrappedTextLineArraySlice) { .length = 0, .internalArray = &context->wrappedTextLines.internalArray[context->wrappedTextLines.length] }; + Clay_LayoutElement *containerElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)textElementData->elementIndex); + Clay_TextElementConfig *textConfig = Clay__FindElementConfigWithType(containerElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT).textElementConfig; + Clay__MeasureTextCacheItem *measureTextCacheItem = Clay__MeasureTextCached(&textElementData->text, textConfig); + float lineWidth = 0; + float lineHeight = textConfig->lineHeight > 0 ? (float)textConfig->lineHeight : textElementData->preferredDimensions.height; + int32_t lineLengthChars = 0; + int32_t lineStartOffset = 0; + if (!measureTextCacheItem->containsNewlines && textElementData->preferredDimensions.width <= containerElement->dimensions.width) { + Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { containerElement->dimensions, textElementData->text }); + textElementData->wrappedLines.length++; + continue; + } + float spaceWidth = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = 1, .chars = CLAY__SPACECHAR.chars, .baseChars = CLAY__SPACECHAR.chars }, textConfig, context->measureTextUserData).width; + int32_t wordIndex = measureTextCacheItem->measuredWordsStartIndex; + while (wordIndex != -1) { + if (context->wrappedTextLines.length > context->wrappedTextLines.capacity - 1) { + break; + } + Clay__MeasuredWord *measuredWord = Clay__MeasuredWordArray_Get(&context->measuredWords, wordIndex); + // Only word on the line is too large, just render it anyway + if (lineLengthChars == 0 && lineWidth + measuredWord->width > containerElement->dimensions.width) { + Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { measuredWord->width, lineHeight }, { .length = measuredWord->length, .chars = &textElementData->text.chars[measuredWord->startOffset] } }); + textElementData->wrappedLines.length++; + wordIndex = measuredWord->next; + lineStartOffset = measuredWord->startOffset + measuredWord->length; + } + // measuredWord->length == 0 means a newline character + else if (measuredWord->length == 0 || lineWidth + measuredWord->width > containerElement->dimensions.width) { + // Wrapped text lines list has overflowed, just render out the line + bool finalCharIsSpace = textElementData->text.chars[CLAY__MAX(lineStartOffset + lineLengthChars - 1, 0)] == ' '; + Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth + (finalCharIsSpace ? -spaceWidth : 0), lineHeight }, { .length = lineLengthChars + (finalCharIsSpace ? -1 : 0), .chars = &textElementData->text.chars[lineStartOffset] } }); + textElementData->wrappedLines.length++; + if (lineLengthChars == 0 || measuredWord->length == 0) { + wordIndex = measuredWord->next; + } + lineWidth = 0; + lineLengthChars = 0; + lineStartOffset = measuredWord->startOffset; + } else { + lineWidth += measuredWord->width + textConfig->letterSpacing; + lineLengthChars += measuredWord->length; + wordIndex = measuredWord->next; + } + } + if (lineLengthChars > 0) { + Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth - textConfig->letterSpacing, lineHeight }, {.length = lineLengthChars, .chars = &textElementData->text.chars[lineStartOffset] } }); + textElementData->wrappedLines.length++; + } + containerElement->dimensions.height = lineHeight * (float)textElementData->wrappedLines.length; + } + + // Scale vertical heights according to aspect ratio + for (int32_t i = 0; i < context->aspectRatioElementIndexes.length; ++i) { + Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->aspectRatioElementIndexes, i)); + Clay_AspectRatioElementConfig *config = Clay__FindElementConfigWithType(aspectElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; + aspectElement->dimensions.height = (1 / config->aspectRatio) * aspectElement->dimensions.width; + aspectElement->layoutConfig->sizing.height.size.minMax.max = aspectElement->dimensions.height; + } + + // Propagate effect of text wrapping, aspect scaling etc. on height of parents + Clay__LayoutElementTreeNodeArray dfsBuffer = context->layoutElementTreeNodeArray1; + dfsBuffer.length = 0; + for (int32_t i = 0; i < context->layoutElementTreeRoots.length; ++i) { + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, i); + context->treeNodeVisited.internalArray[dfsBuffer.length] = false; + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)root->layoutElementIndex) }); + } + while (dfsBuffer.length > 0) { + Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); + Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement; + if (!context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + // If the element has no children or is the container for a text element, don't bother inspecting it + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || currentElement->childrenOrTextContent.children.length == 0) { + dfsBuffer.length--; + continue; + } + // Add the children to the DFS buffer (needs to be pushed in reverse so that stack traversal is in correct layout order) + for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; i++) { + context->treeNodeVisited.internalArray[dfsBuffer.length] = false; + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]) }); + } + continue; + } + dfsBuffer.length--; + + // DFS node has been visited, this is on the way back up to the root + Clay_LayoutConfig *layoutConfig = currentElement->layoutConfig; + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + // Resize any parent containers that have grown in height along their non layout axis + for (int32_t j = 0; j < currentElement->childrenOrTextContent.children.length; ++j) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[j]); + float childHeightWithPadding = CLAY__MAX(childElement->dimensions.height + layoutConfig->padding.top + layoutConfig->padding.bottom, currentElement->dimensions.height); + currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(childHeightWithPadding, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); + } + } else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { + // Resizing along the layout axis + float contentHeight = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom); + for (int32_t j = 0; j < currentElement->childrenOrTextContent.children.length; ++j) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[j]); + contentHeight += childElement->dimensions.height; + } + contentHeight += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(contentHeight, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); + } + } + + // Calculate sizing along the Y axis + Clay__SizeContainersAlongAxis(false); + + // Scale horizontal widths according to aspect ratio + for (int32_t i = 0; i < context->aspectRatioElementIndexes.length; ++i) { + Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->aspectRatioElementIndexes, i)); + Clay_AspectRatioElementConfig *config = Clay__FindElementConfigWithType(aspectElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; + aspectElement->dimensions.width = config->aspectRatio * aspectElement->dimensions.height; + } + + // Sort tree roots by z-index + int32_t sortMax = context->layoutElementTreeRoots.length - 1; + while (sortMax > 0) { // todo dumb bubble sort + for (int32_t i = 0; i < sortMax; ++i) { + Clay__LayoutElementTreeRoot current = *Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, i); + Clay__LayoutElementTreeRoot next = *Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, i + 1); + if (next.zIndex < current.zIndex) { + Clay__LayoutElementTreeRootArray_Set(&context->layoutElementTreeRoots, i, next); + Clay__LayoutElementTreeRootArray_Set(&context->layoutElementTreeRoots, i + 1, current); + } + } + sortMax--; + } + + // Calculate final positions and generate render commands + context->renderCommands.length = 0; + dfsBuffer.length = 0; + for (int32_t rootIndex = 0; rootIndex < context->layoutElementTreeRoots.length; ++rootIndex) { + dfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); + Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)root->layoutElementIndex); + Clay_Vector2 rootPosition = CLAY__DEFAULT_STRUCT; + Clay_LayoutElementHashMapItem *parentHashMapItem = Clay__GetHashMapItem(root->parentId); + // Position root floating containers + if (Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING) && parentHashMapItem) { + Clay_FloatingElementConfig *config = Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig; + Clay_Dimensions rootDimensions = rootElement->dimensions; + Clay_BoundingBox parentBoundingBox = parentHashMapItem->boundingBox; + // Set X position + Clay_Vector2 targetAttachPosition = CLAY__DEFAULT_STRUCT; + switch (config->attachPoints.parent) { + case CLAY_ATTACH_POINT_LEFT_TOP: + case CLAY_ATTACH_POINT_LEFT_CENTER: + case CLAY_ATTACH_POINT_LEFT_BOTTOM: targetAttachPosition.x = parentBoundingBox.x; break; + case CLAY_ATTACH_POINT_CENTER_TOP: + case CLAY_ATTACH_POINT_CENTER_CENTER: + case CLAY_ATTACH_POINT_CENTER_BOTTOM: targetAttachPosition.x = parentBoundingBox.x + (parentBoundingBox.width / 2); break; + case CLAY_ATTACH_POINT_RIGHT_TOP: + case CLAY_ATTACH_POINT_RIGHT_CENTER: + case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.x = parentBoundingBox.x + parentBoundingBox.width; break; + } + switch (config->attachPoints.element) { + case CLAY_ATTACH_POINT_LEFT_TOP: + case CLAY_ATTACH_POINT_LEFT_CENTER: + case CLAY_ATTACH_POINT_LEFT_BOTTOM: break; + case CLAY_ATTACH_POINT_CENTER_TOP: + case CLAY_ATTACH_POINT_CENTER_CENTER: + case CLAY_ATTACH_POINT_CENTER_BOTTOM: targetAttachPosition.x -= (rootDimensions.width / 2); break; + case CLAY_ATTACH_POINT_RIGHT_TOP: + case CLAY_ATTACH_POINT_RIGHT_CENTER: + case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.x -= rootDimensions.width; break; + } + switch (config->attachPoints.parent) { // I know I could merge the x and y switch statements, but this is easier to read + case CLAY_ATTACH_POINT_LEFT_TOP: + case CLAY_ATTACH_POINT_RIGHT_TOP: + case CLAY_ATTACH_POINT_CENTER_TOP: targetAttachPosition.y = parentBoundingBox.y; break; + case CLAY_ATTACH_POINT_LEFT_CENTER: + case CLAY_ATTACH_POINT_CENTER_CENTER: + case CLAY_ATTACH_POINT_RIGHT_CENTER: targetAttachPosition.y = parentBoundingBox.y + (parentBoundingBox.height / 2); break; + case CLAY_ATTACH_POINT_LEFT_BOTTOM: + case CLAY_ATTACH_POINT_CENTER_BOTTOM: + case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.y = parentBoundingBox.y + parentBoundingBox.height; break; + } + switch (config->attachPoints.element) { + case CLAY_ATTACH_POINT_LEFT_TOP: + case CLAY_ATTACH_POINT_RIGHT_TOP: + case CLAY_ATTACH_POINT_CENTER_TOP: break; + case CLAY_ATTACH_POINT_LEFT_CENTER: + case CLAY_ATTACH_POINT_CENTER_CENTER: + case CLAY_ATTACH_POINT_RIGHT_CENTER: targetAttachPosition.y -= (rootDimensions.height / 2); break; + case CLAY_ATTACH_POINT_LEFT_BOTTOM: + case CLAY_ATTACH_POINT_CENTER_BOTTOM: + case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.y -= rootDimensions.height; break; + } + targetAttachPosition.x += config->offset.x; + targetAttachPosition.y += config->offset.y; + rootPosition = targetAttachPosition; + } + if (root->clipElementId) { + Clay_LayoutElementHashMapItem *clipHashMapItem = Clay__GetHashMapItem(root->clipElementId); + if (clipHashMapItem) { + // Floating elements that are attached to scrolling contents won't be correctly positioned if external scroll handling is enabled, fix here + if (context->externalScrollHandlingEnabled) { + Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(clipHashMapItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; + if (clipConfig->horizontal) { + rootPosition.x += clipConfig->childOffset.x; + } + if (clipConfig->vertical) { + rootPosition.y += clipConfig->childOffset.y; + } + } + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = clipHashMapItem->boundingBox, + .userData = 0, + .id = Clay__HashNumber(rootElement->id, rootElement->childrenOrTextContent.children.length + 10).id, // TODO need a better strategy for managing derived ids + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, + }); + } + } + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = rootElement, .position = rootPosition, .nextChildOffset = { .x = (float)rootElement->layoutConfig->padding.left, .y = (float)rootElement->layoutConfig->padding.top } }); + + context->treeNodeVisited.internalArray[0] = false; + while (dfsBuffer.length > 0) { + Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); + Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement; + Clay_LayoutConfig *layoutConfig = currentElement->layoutConfig; + Clay_Vector2 scrollOffset = CLAY__DEFAULT_STRUCT; + + // This will only be run a single time for each element in downwards DFS order + if (!context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + + Clay_BoundingBox currentElementBoundingBox = { currentElementTreeNode->position.x, currentElementTreeNode->position.y, currentElement->dimensions.width, currentElement->dimensions.height }; + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING)) { + Clay_FloatingElementConfig *floatingElementConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig; + Clay_Dimensions expand = floatingElementConfig->expand; + currentElementBoundingBox.x -= expand.width; + currentElementBoundingBox.width += expand.width * 2; + currentElementBoundingBox.y -= expand.height; + currentElementBoundingBox.height += expand.height * 2; + } + + Clay__ScrollContainerDataInternal *scrollContainerData = CLAY__NULL; + // Apply scroll offsets to container + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP)) { + Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; + + // This linear scan could theoretically be slow under very strange conditions, but I can't imagine a real UI with more than a few 10's of scroll containers + for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); + if (mapping->layoutElement == currentElement) { + scrollContainerData = mapping; + mapping->boundingBox = currentElementBoundingBox; + scrollOffset = clipConfig->childOffset; + if (context->externalScrollHandlingEnabled) { + scrollOffset = CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; + } + break; + } + } + } + + Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(currentElement->id); + if (hashMapItem) { + hashMapItem->boundingBox = currentElementBoundingBox; + } + + int32_t sortedConfigIndexes[20]; + for (int32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { + sortedConfigIndexes[elementConfigIndex] = elementConfigIndex; + } + sortMax = currentElement->elementConfigs.length - 1; + while (sortMax > 0) { // todo dumb bubble sort + for (int32_t i = 0; i < sortMax; ++i) { + int32_t current = sortedConfigIndexes[i]; + int32_t next = sortedConfigIndexes[i + 1]; + Clay__ElementConfigType currentType = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, current)->type; + Clay__ElementConfigType nextType = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, next)->type; + if (nextType == CLAY__ELEMENT_CONFIG_TYPE_CLIP || currentType == CLAY__ELEMENT_CONFIG_TYPE_BORDER) { + sortedConfigIndexes[i] = next; + sortedConfigIndexes[i + 1] = current; + } + } + sortMax--; + } + + bool emitRectangle = false; + // Create the render commands for this element + Clay_SharedElementConfig *sharedConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SHARED).sharedElementConfig; + if (sharedConfig && sharedConfig->backgroundColor.a > 0) { + emitRectangle = true; + } + else if (!sharedConfig) { + emitRectangle = false; + sharedConfig = &Clay_SharedElementConfig_DEFAULT; + } + for (int32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { + Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, sortedConfigIndexes[elementConfigIndex]); + Clay_RenderCommand renderCommand = { + .boundingBox = currentElementBoundingBox, + .userData = sharedConfig->userData, + .id = currentElement->id, + }; + + bool offscreen = Clay__ElementIsOffscreen(¤tElementBoundingBox); + // Culling - Don't bother to generate render commands for rectangles entirely outside the screen - this won't stop their children from being rendered if they overflow + bool shouldRender = !offscreen; + switch (elementConfig->type) { + case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: + case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: + case CLAY__ELEMENT_CONFIG_TYPE_SHARED: + case CLAY__ELEMENT_CONFIG_TYPE_BORDER: { + shouldRender = false; + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_CLIP: { + renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START; + renderCommand.renderData = CLAY__INIT(Clay_RenderData) { + .clip = { + .horizontal = elementConfig->config.clipElementConfig->horizontal, + .vertical = elementConfig->config.clipElementConfig->vertical, + } + }; + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: { + renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE; + renderCommand.renderData = CLAY__INIT(Clay_RenderData) { + .image = { + .backgroundColor = sharedConfig->backgroundColor, + .cornerRadius = sharedConfig->cornerRadius, + .imageData = elementConfig->config.imageElementConfig->imageData, + } + }; + emitRectangle = false; + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_TEXT: { + if (!shouldRender) { + break; + } + shouldRender = false; + Clay_ElementConfigUnion configUnion = elementConfig->config; + Clay_TextElementConfig *textElementConfig = configUnion.textElementConfig; + float naturalLineHeight = currentElement->childrenOrTextContent.textElementData->preferredDimensions.height; + float finalLineHeight = textElementConfig->lineHeight > 0 ? (float)textElementConfig->lineHeight : naturalLineHeight; + float lineHeightOffset = (finalLineHeight - naturalLineHeight) / 2; + float yPosition = lineHeightOffset; + for (int32_t lineIndex = 0; lineIndex < currentElement->childrenOrTextContent.textElementData->wrappedLines.length; ++lineIndex) { + Clay__WrappedTextLine *wrappedLine = Clay__WrappedTextLineArraySlice_Get(¤tElement->childrenOrTextContent.textElementData->wrappedLines, lineIndex); + if (wrappedLine->line.length == 0) { + yPosition += finalLineHeight; + continue; + } + float offset = (currentElementBoundingBox.width - wrappedLine->dimensions.width); + if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_LEFT) { + offset = 0; + } + if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) { + offset /= 2; + } + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = { currentElementBoundingBox.x + offset, currentElementBoundingBox.y + yPosition, wrappedLine->dimensions.width, wrappedLine->dimensions.height }, + .renderData = { .text = { + .stringContents = CLAY__INIT(Clay_StringSlice) { .length = wrappedLine->line.length, .chars = wrappedLine->line.chars, .baseChars = currentElement->childrenOrTextContent.textElementData->text.chars }, + .textColor = textElementConfig->textColor, + .fontId = textElementConfig->fontId, + .fontSize = textElementConfig->fontSize, + .letterSpacing = textElementConfig->letterSpacing, + .lineHeight = textElementConfig->lineHeight, + }}, + .userData = textElementConfig->userData, + .id = Clay__HashNumber(lineIndex, currentElement->id).id, + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT, + }); + yPosition += finalLineHeight; + + if (!context->disableCulling && (currentElementBoundingBox.y + yPosition > context->layoutDimensions.height)) { + break; + } + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: { + renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_CUSTOM; + renderCommand.renderData = CLAY__INIT(Clay_RenderData) { + .custom = { + .backgroundColor = sharedConfig->backgroundColor, + .cornerRadius = sharedConfig->cornerRadius, + .customData = elementConfig->config.customElementConfig->customData, + } + }; + emitRectangle = false; + break; + } + default: break; + } + if (shouldRender) { + Clay__AddRenderCommand(renderCommand); + } + if (offscreen) { + // NOTE: You may be tempted to try an early return / continue if an element is off screen. Why bother calculating layout for its children, right? + // Unfortunately, a FLOATING_CONTAINER may be defined that attaches to a child or grandchild of this element, which is large enough to still + // be on screen, even if this element isn't. That depends on this element and it's children being laid out correctly (even if they are entirely off screen) + } + } + + if (emitRectangle) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = currentElementBoundingBox, + .renderData = { .rectangle = { + .backgroundColor = sharedConfig->backgroundColor, + .cornerRadius = sharedConfig->cornerRadius, + }}, + .userData = sharedConfig->userData, + .id = currentElement->id, + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + }); + } + + // Setup initial on-axis alignment + if (!Clay__ElementHasConfig(currentElementTreeNode->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + Clay_Dimensions contentSize = {0,0}; + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + contentSize.width += childElement->dimensions.width; + contentSize.height = CLAY__MAX(contentSize.height, childElement->dimensions.height); + } + contentSize.width += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + float extraSpace = currentElement->dimensions.width - (float)(layoutConfig->padding.left + layoutConfig->padding.right) - contentSize.width; + switch (layoutConfig->childAlignment.x) { + case CLAY_ALIGN_X_LEFT: extraSpace = 0; break; + case CLAY_ALIGN_X_CENTER: extraSpace /= 2; break; + default: break; + } + currentElementTreeNode->nextChildOffset.x += extraSpace; + extraSpace = CLAY__MAX(0, extraSpace); + } else { + for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + contentSize.width = CLAY__MAX(contentSize.width, childElement->dimensions.width); + contentSize.height += childElement->dimensions.height; + } + contentSize.height += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + float extraSpace = currentElement->dimensions.height - (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) - contentSize.height; + switch (layoutConfig->childAlignment.y) { + case CLAY_ALIGN_Y_TOP: extraSpace = 0; break; + case CLAY_ALIGN_Y_CENTER: extraSpace /= 2; break; + default: break; + } + extraSpace = CLAY__MAX(0, extraSpace); + currentElementTreeNode->nextChildOffset.y += extraSpace; + } + + if (scrollContainerData) { + scrollContainerData->contentSize = CLAY__INIT(Clay_Dimensions) { contentSize.width + (float)(layoutConfig->padding.left + layoutConfig->padding.right), contentSize.height + (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) }; + } + } + } + else { + // DFS is returning upwards backwards + bool closeClipElement = false; + Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; + if (clipConfig) { + closeClipElement = true; + for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); + if (mapping->layoutElement == currentElement) { + scrollOffset = clipConfig->childOffset; + if (context->externalScrollHandlingEnabled) { + scrollOffset = CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; + } + break; + } + } + } + + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_BORDER)) { + Clay_LayoutElementHashMapItem *currentElementData = Clay__GetHashMapItem(currentElement->id); + Clay_BoundingBox currentElementBoundingBox = currentElementData->boundingBox; + + // Culling - Don't bother to generate render commands for rectangles entirely outside the screen - this won't stop their children from being rendered if they overflow + if (!Clay__ElementIsOffscreen(¤tElementBoundingBox)) { + Clay_SharedElementConfig *sharedConfig = Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SHARED) ? Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SHARED).sharedElementConfig : &Clay_SharedElementConfig_DEFAULT; + Clay_BorderElementConfig *borderConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_BORDER).borderElementConfig; + Clay_RenderCommand renderCommand = { + .boundingBox = currentElementBoundingBox, + .renderData = { .border = { + .color = borderConfig->color, + .cornerRadius = sharedConfig->cornerRadius, + .width = borderConfig->width + }}, + .userData = sharedConfig->userData, + .id = Clay__HashNumber(currentElement->id, currentElement->childrenOrTextContent.children.length).id, + .commandType = CLAY_RENDER_COMMAND_TYPE_BORDER, + }; + Clay__AddRenderCommand(renderCommand); + if (borderConfig->width.betweenChildren > 0 && borderConfig->color.a > 0) { + float halfGap = layoutConfig->childGap / 2; + Clay_Vector2 borderOffset = { (float)layoutConfig->padding.left - halfGap, (float)layoutConfig->padding.top - halfGap }; + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + if (i > 0) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = { currentElementBoundingBox.x + borderOffset.x + scrollOffset.x, currentElementBoundingBox.y + scrollOffset.y, (float)borderConfig->width.betweenChildren, currentElement->dimensions.height }, + .renderData = { .rectangle = { + .backgroundColor = borderConfig->color, + } }, + .userData = sharedConfig->userData, + .id = Clay__HashNumber(currentElement->id, currentElement->childrenOrTextContent.children.length + 1 + i).id, + .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + }); + } + borderOffset.x += (childElement->dimensions.width + (float)layoutConfig->childGap); + } + } else { + for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + if (i > 0) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = { currentElementBoundingBox.x + scrollOffset.x, currentElementBoundingBox.y + borderOffset.y + scrollOffset.y, currentElement->dimensions.width, (float)borderConfig->width.betweenChildren }, + .renderData = { .rectangle = { + .backgroundColor = borderConfig->color, + } }, + .userData = sharedConfig->userData, + .id = Clay__HashNumber(currentElement->id, currentElement->childrenOrTextContent.children.length + 1 + i).id, + .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + }); + } + borderOffset.y += (childElement->dimensions.height + (float)layoutConfig->childGap); + } + } + } + } + } + // This exists because the scissor needs to end _after_ borders between elements + if (closeClipElement) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .id = Clay__HashNumber(currentElement->id, rootElement->childrenOrTextContent.children.length + 11).id, + .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, + }); + } + + dfsBuffer.length--; + continue; + } + + // Add children to the DFS buffer + if (!Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + dfsBuffer.length += currentElement->childrenOrTextContent.children.length; + for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + // Alignment along non layout axis + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + currentElementTreeNode->nextChildOffset.y = currentElement->layoutConfig->padding.top; + float whiteSpaceAroundChild = currentElement->dimensions.height - (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) - childElement->dimensions.height; + switch (layoutConfig->childAlignment.y) { + case CLAY_ALIGN_Y_TOP: break; + case CLAY_ALIGN_Y_CENTER: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild / 2; break; + case CLAY_ALIGN_Y_BOTTOM: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild; break; + } + } else { + currentElementTreeNode->nextChildOffset.x = currentElement->layoutConfig->padding.left; + float whiteSpaceAroundChild = currentElement->dimensions.width - (float)(layoutConfig->padding.left + layoutConfig->padding.right) - childElement->dimensions.width; + switch (layoutConfig->childAlignment.x) { + case CLAY_ALIGN_X_LEFT: break; + case CLAY_ALIGN_X_CENTER: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild / 2; break; + case CLAY_ALIGN_X_RIGHT: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild; break; + } + } + + Clay_Vector2 childPosition = { + currentElementTreeNode->position.x + currentElementTreeNode->nextChildOffset.x + scrollOffset.x, + currentElementTreeNode->position.y + currentElementTreeNode->nextChildOffset.y + scrollOffset.y, + }; + + // DFS buffer elements need to be added in reverse because stack traversal happens backwards + uint32_t newNodeIndex = dfsBuffer.length - 1 - i; + dfsBuffer.internalArray[newNodeIndex] = CLAY__INIT(Clay__LayoutElementTreeNode) { + .layoutElement = childElement, + .position = { childPosition.x, childPosition.y }, + .nextChildOffset = { .x = (float)childElement->layoutConfig->padding.left, .y = (float)childElement->layoutConfig->padding.top }, + }; + context->treeNodeVisited.internalArray[newNodeIndex] = false; + + // Update parent offsets + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + currentElementTreeNode->nextChildOffset.x += childElement->dimensions.width + (float)layoutConfig->childGap; + } else { + currentElementTreeNode->nextChildOffset.y += childElement->dimensions.height + (float)layoutConfig->childGap; + } + } + } + } + + if (root->clipElementId) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .id = Clay__HashNumber(rootElement->id, rootElement->childrenOrTextContent.children.length + 11).id, .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END }); + } + } +} + +CLAY_WASM_EXPORT("Clay_GetPointerOverIds") +CLAY_DLL_EXPORT Clay_ElementIdArray Clay_GetPointerOverIds(void) { + return Clay_GetCurrentContext()->pointerOverIds; +} + +#pragma region DebugTools +Clay_Color CLAY__DEBUGVIEW_COLOR_1 = {58, 56, 52, 255}; +Clay_Color CLAY__DEBUGVIEW_COLOR_2 = {62, 60, 58, 255}; +Clay_Color CLAY__DEBUGVIEW_COLOR_3 = {141, 133, 135, 255}; +Clay_Color CLAY__DEBUGVIEW_COLOR_4 = {238, 226, 231, 255}; +Clay_Color CLAY__DEBUGVIEW_COLOR_SELECTED_ROW = {102, 80, 78, 255}; +const int32_t CLAY__DEBUGVIEW_ROW_HEIGHT = 30; +const int32_t CLAY__DEBUGVIEW_OUTER_PADDING = 10; +const int32_t CLAY__DEBUGVIEW_INDENT_WIDTH = 16; +Clay_TextElementConfig Clay__DebugView_TextNameConfig = {.textColor = {238, 226, 231, 255}, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }; +Clay_LayoutConfig Clay__DebugView_ScrollViewItemLayoutConfig = CLAY__DEFAULT_STRUCT; + +typedef struct { + Clay_String label; + Clay_Color color; +} Clay__DebugElementConfigTypeLabelConfig; + +Clay__DebugElementConfigTypeLabelConfig Clay__DebugGetElementConfigTypeLabel(Clay__ElementConfigType type) { + switch (type) { + case CLAY__ELEMENT_CONFIG_TYPE_SHARED: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Shared"), {243,134,48,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_TEXT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Text"), {105,210,231,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Aspect"), {101,149,194,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Image"), {121,189,154,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Floating"), {250,105,0,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_CLIP: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) {CLAY_STRING("Scroll"), {242, 196, 90, 255} }; + case CLAY__ELEMENT_CONFIG_TYPE_BORDER: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) {CLAY_STRING("Border"), {108, 91, 123, 255} }; + case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Custom"), {11,72,107,255} }; + default: break; + } + return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Error"), {0,0,0,255} }; +} + +typedef struct { + int32_t rowCount; + int32_t selectedElementRowIndex; +} Clay__RenderDebugLayoutData; + +// Returns row count +Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialRootsLength, int32_t highlightedRowIndex) { + Clay_Context* context = Clay_GetCurrentContext(); + Clay__int32_tArray dfsBuffer = context->reusableElementIndexBuffer; + Clay__DebugView_ScrollViewItemLayoutConfig = CLAY__INIT(Clay_LayoutConfig) { .sizing = { .height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT) }, .childGap = 6, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }}; + Clay__RenderDebugLayoutData layoutData = CLAY__DEFAULT_STRUCT; + + uint32_t highlightedElementId = 0; + + for (int32_t rootIndex = 0; rootIndex < initialRootsLength; ++rootIndex) { + dfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); + Clay__int32_tArray_Add(&dfsBuffer, (int32_t)root->layoutElementIndex); + context->treeNodeVisited.internalArray[0] = false; + if (rootIndex > 0) { + CLAY(CLAY_IDI("Clay__DebugView_EmptyRowOuter", rootIndex), { .layout = { .sizing = {.width = CLAY_SIZING_GROW(0)}, .padding = {CLAY__DEBUGVIEW_INDENT_WIDTH / 2, 0, 0, 0} } }) { + CLAY(CLAY_IDI("Clay__DebugView_EmptyRow", rootIndex), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED((float)CLAY__DEBUGVIEW_ROW_HEIGHT) }}, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .top = 1 } } }) {} + } + layoutData.rowCount++; + } + while (dfsBuffer.length > 0) { + int32_t currentElementIndex = Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1); + Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)currentElementIndex); + if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + if (!Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) && currentElement->childrenOrTextContent.children.length > 0) { + Clay__CloseElement(); + Clay__CloseElement(); + Clay__CloseElement(); + } + dfsBuffer.length--; + continue; + } + + if (highlightedRowIndex == layoutData.rowCount) { + if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + context->debugSelectedElementId = currentElement->id; + } + highlightedElementId = currentElement->id; + } + + context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + Clay_LayoutElementHashMapItem *currentElementData = Clay__GetHashMapItem(currentElement->id); + bool offscreen = Clay__ElementIsOffscreen(¤tElementData->boundingBox); + if (context->debugSelectedElementId == currentElement->id) { + layoutData.selectedElementRowIndex = layoutData.rowCount; + } + CLAY(CLAY_IDI("Clay__DebugView_ElementOuter", currentElement->id), { .layout = Clay__DebugView_ScrollViewItemLayoutConfig }) { + // Collapse icon / button + if (!(Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || currentElement->childrenOrTextContent.children.length == 0)) { + CLAY(CLAY_IDI("Clay__DebugView_CollapseElement", currentElement->id), { + .layout = { .sizing = {CLAY_SIZING_FIXED(16), CLAY_SIZING_FIXED(16)}, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }, + .cornerRadius = CLAY_CORNER_RADIUS(4), + .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = {1, 1, 1, 1, 0} }, + }) { + CLAY_TEXT((currentElementData && currentElementData->debugData->collapsed) ? CLAY_STRING("+") : CLAY_STRING("-"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + } else { // Square dot for empty containers + CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIXED(16), CLAY_SIZING_FIXED(16)}, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER } } }) { + CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIXED(8), CLAY_SIZING_FIXED(8)} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_3, .cornerRadius = CLAY_CORNER_RADIUS(2) }) {} + } + } + // Collisions and offscreen info + if (currentElementData) { + if (currentElementData->debugData->collision) { + CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 }}, .border = { .color = {177, 147, 8, 255}, .width = {1, 1, 1, 1, 0} } }) { + CLAY_TEXT(CLAY_STRING("Duplicate ID"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 })); + } + } + if (offscreen) { + CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { 1, 1, 1, 1, 0} } }) { + CLAY_TEXT(CLAY_STRING("Offscreen"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 })); + } + } + } + Clay_String idString = context->layoutElementIdStrings.internalArray[currentElementIndex]; + if (idString.length > 0) { + CLAY_TEXT(idString, offscreen ? CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 }) : &Clay__DebugView_TextNameConfig); + } + for (int32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { + Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, elementConfigIndex); + if (elementConfig->type == CLAY__ELEMENT_CONFIG_TYPE_SHARED) { + Clay_Color labelColor = {243,134,48,90}; + labelColor.a = 90; + Clay_Color backgroundColor = elementConfig->config.sharedElementConfig->backgroundColor; + Clay_CornerRadius radius = elementConfig->config.sharedElementConfig->cornerRadius; + if (backgroundColor.a > 0) { + CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = labelColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = labelColor, .width = { 1, 1, 1, 1, 0} } }) { + CLAY_TEXT(CLAY_STRING("Color"), CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + } + if (radius.bottomLeft > 0) { + CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = labelColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = labelColor, .width = { 1, 1, 1, 1, 0 } } }) { + CLAY_TEXT(CLAY_STRING("Radius"), CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + } + continue; + } + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(elementConfig->type); + Clay_Color backgroundColor = config.color; + backgroundColor.a = 90; + CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = config.color, .width = { 1, 1, 1, 1, 0 } } }) { + CLAY_TEXT(config.label, CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + } + } + + // Render the text contents below the element as a non-interactive row + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + layoutData.rowCount++; + Clay__TextElementData *textElementData = currentElement->childrenOrTextContent.textElementData; + Clay_TextElementConfig *rawTextConfig = offscreen ? CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 }) : &Clay__DebugView_TextNameConfig; + CLAY_AUTO_ID({ .layout = { .sizing = { .height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } } }) { + CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_INDENT_WIDTH + 16) } } }) {} + CLAY_TEXT(CLAY_STRING("\""), rawTextConfig); + CLAY_TEXT(textElementData->text.length > 40 ? (CLAY__INIT(Clay_String) { .length = 40, .chars = textElementData->text.chars }) : textElementData->text, rawTextConfig); + if (textElementData->text.length > 40) { + CLAY_TEXT(CLAY_STRING("..."), rawTextConfig); + } + CLAY_TEXT(CLAY_STRING("\""), rawTextConfig); + } + } else if (currentElement->childrenOrTextContent.children.length > 0) { + Clay__OpenElement(); + Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .padding = { .left = 8 } } }); + Clay__OpenElement(); + Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .padding = { .left = CLAY__DEBUGVIEW_INDENT_WIDTH }}, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .left = 1 } }}); + Clay__OpenElement(); + Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } }); + } + + layoutData.rowCount++; + if (!(Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || (currentElementData && currentElementData->debugData->collapsed))) { + for (int32_t i = currentElement->childrenOrTextContent.children.length - 1; i >= 0; --i) { + Clay__int32_tArray_Add(&dfsBuffer, currentElement->childrenOrTextContent.children.elements[i]); + context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked + } + } + } + } + + if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + Clay_ElementId collapseButtonId = Clay__HashString(CLAY_STRING("Clay__DebugView_CollapseElement"), 0); + for (int32_t i = (int)context->pointerOverIds.length - 1; i >= 0; i--) { + Clay_ElementId *elementId = Clay_ElementIdArray_Get(&context->pointerOverIds, i); + if (elementId->baseId == collapseButtonId.baseId) { + Clay_LayoutElementHashMapItem *highlightedItem = Clay__GetHashMapItem(elementId->offset); + highlightedItem->debugData->collapsed = !highlightedItem->debugData->collapsed; + break; + } + } + } + + if (highlightedElementId) { + CLAY(CLAY_ID("Clay__DebugView_ElementHighlight"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .floating = { .parentId = highlightedElementId, .zIndex = 32767, .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID } }) { + CLAY(CLAY_ID("Clay__DebugView_ElementHighlightRectangle"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .backgroundColor = Clay__debugViewHighlightColor }) {} + } + } + return layoutData; +} + +void Clay__RenderDebugLayoutSizing(Clay_SizingAxis sizing, Clay_TextElementConfig *infoTextConfig) { + Clay_String sizingLabel = CLAY_STRING("GROW"); + if (sizing.type == CLAY__SIZING_TYPE_FIT) { + sizingLabel = CLAY_STRING("FIT"); + } else if (sizing.type == CLAY__SIZING_TYPE_PERCENT) { + sizingLabel = CLAY_STRING("PERCENT"); + } else if (sizing.type == CLAY__SIZING_TYPE_FIXED) { + sizingLabel = CLAY_STRING("FIXED"); + } + CLAY_TEXT(sizingLabel, infoTextConfig); + if (sizing.type == CLAY__SIZING_TYPE_GROW || sizing.type == CLAY__SIZING_TYPE_FIT || sizing.type == CLAY__SIZING_TYPE_FIXED) { + CLAY_TEXT(CLAY_STRING("("), infoTextConfig); + if (sizing.size.minMax.min != 0) { + CLAY_TEXT(CLAY_STRING("min: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(sizing.size.minMax.min), infoTextConfig); + if (sizing.size.minMax.max != CLAY__MAXFLOAT) { + CLAY_TEXT(CLAY_STRING(", "), infoTextConfig); + } + } + if (sizing.size.minMax.max != CLAY__MAXFLOAT) { + CLAY_TEXT(CLAY_STRING("max: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(sizing.size.minMax.max), infoTextConfig); + } + CLAY_TEXT(CLAY_STRING(")"), infoTextConfig); + } else if (sizing.type == CLAY__SIZING_TYPE_PERCENT) { + CLAY_TEXT(CLAY_STRING("("), infoTextConfig); + CLAY_TEXT(Clay__IntToString(sizing.size.percent * 100), infoTextConfig); + CLAY_TEXT(CLAY_STRING("%)"), infoTextConfig); + } +} + +void Clay__RenderDebugViewElementConfigHeader(Clay_String elementId, Clay__ElementConfigType type) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(type); + Clay_Color backgroundColor = config.color; + backgroundColor.a = 90; + CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(CLAY__DEBUGVIEW_OUTER_PADDING), .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } } }) { + CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = config.color, .width = { 1, 1, 1, 1, 0 } } }) { + CLAY_TEXT(config.label, CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} + CLAY_TEXT(elementId, CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE })); + } +} + +void Clay__RenderDebugViewColor(Clay_Color color, Clay_TextElementConfig *textConfig) { + CLAY_AUTO_ID({ .layout = { .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { + CLAY_TEXT(CLAY_STRING("{ r: "), textConfig); + CLAY_TEXT(Clay__IntToString(color.r), textConfig); + CLAY_TEXT(CLAY_STRING(", g: "), textConfig); + CLAY_TEXT(Clay__IntToString(color.g), textConfig); + CLAY_TEXT(CLAY_STRING(", b: "), textConfig); + CLAY_TEXT(Clay__IntToString(color.b), textConfig); + CLAY_TEXT(CLAY_STRING(", a: "), textConfig); + CLAY_TEXT(Clay__IntToString(color.a), textConfig); + CLAY_TEXT(CLAY_STRING(" }"), textConfig); + CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_FIXED(10) } } }) {} + CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 8), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 8)} }, .backgroundColor = color, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = CLAY__DEBUGVIEW_COLOR_4, .width = { 1, 1, 1, 1, 0 } } }) {} + } +} + +void Clay__RenderDebugViewCornerRadius(Clay_CornerRadius cornerRadius, Clay_TextElementConfig *textConfig) { + CLAY_AUTO_ID({ .layout = { .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { + CLAY_TEXT(CLAY_STRING("{ topLeft: "), textConfig); + CLAY_TEXT(Clay__IntToString(cornerRadius.topLeft), textConfig); + CLAY_TEXT(CLAY_STRING(", topRight: "), textConfig); + CLAY_TEXT(Clay__IntToString(cornerRadius.topRight), textConfig); + CLAY_TEXT(CLAY_STRING(", bottomLeft: "), textConfig); + CLAY_TEXT(Clay__IntToString(cornerRadius.bottomLeft), textConfig); + CLAY_TEXT(CLAY_STRING(", bottomRight: "), textConfig); + CLAY_TEXT(Clay__IntToString(cornerRadius.bottomRight), textConfig); + CLAY_TEXT(CLAY_STRING(" }"), textConfig); + } +} + +void HandleDebugViewCloseButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData) { + Clay_Context* context = Clay_GetCurrentContext(); + (void) elementId; (void) pointerInfo; (void) userData; + if (pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + context->debugModeEnabled = false; + } +} + +void Clay__RenderDebugView(void) { + Clay_Context* context = Clay_GetCurrentContext(); + Clay_ElementId closeButtonId = Clay__HashString(CLAY_STRING("Clay__DebugViewTopHeaderCloseButtonOuter"), 0); + if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + for (int32_t i = 0; i < context->pointerOverIds.length; ++i) { + Clay_ElementId *elementId = Clay_ElementIdArray_Get(&context->pointerOverIds, i); + if (elementId->id == closeButtonId.id) { + context->debugModeEnabled = false; + return; + } + } + } + + uint32_t initialRootsLength = context->layoutElementTreeRoots.length; + uint32_t initialElementsLength = context->layoutElements.length; + Clay_TextElementConfig *infoTextConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); + Clay_TextElementConfig *infoTitleConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); + Clay_ElementId scrollId = Clay__HashString(CLAY_STRING("Clay__DebugViewOuterScrollPane"), 0); + float scrollYOffset = 0; + bool pointerInDebugView = context->pointerInfo.position.y < context->layoutDimensions.height - 300; + for (int32_t i = 0; i < context->scrollContainerDatas.length; ++i) { + Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); + if (scrollContainerData->elementId == scrollId.id) { + if (!context->externalScrollHandlingEnabled) { + scrollYOffset = scrollContainerData->scrollPosition.y; + } else { + pointerInDebugView = context->pointerInfo.position.y + scrollContainerData->scrollPosition.y < context->layoutDimensions.height - 300; + } + break; + } + } + int32_t highlightedRow = pointerInDebugView + ? (int32_t)((context->pointerInfo.position.y - scrollYOffset) / (float)CLAY__DEBUGVIEW_ROW_HEIGHT) - 1 + : -1; + if (context->pointerInfo.position.x < context->layoutDimensions.width - (float)Clay__debugViewWidth) { + highlightedRow = -1; + } + Clay__RenderDebugLayoutData layoutData = CLAY__DEFAULT_STRUCT; + CLAY(CLAY_ID("Clay__DebugView"), { + .layout = { .sizing = { CLAY_SIZING_FIXED((float)Clay__debugViewWidth) , CLAY_SIZING_FIXED(context->layoutDimensions.height) }, .layoutDirection = CLAY_TOP_TO_BOTTOM }, + .floating = { .zIndex = 32765, .attachPoints = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_CENTER }, .attachTo = CLAY_ATTACH_TO_ROOT, .clipTo = CLAY_CLIP_TO_ATTACHED_PARENT }, + .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .bottom = 1 } } + }) { + CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_2 }) { + CLAY_TEXT(CLAY_STRING("Clay Debug Tools"), infoTextConfig); + CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} + // Close button + CLAY_AUTO_ID({ + .layout = { .sizing = {CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 10), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 10)}, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }, + .backgroundColor = {217,91,67,80}, + .cornerRadius = CLAY_CORNER_RADIUS(4), + .border = { .color = { 217,91,67,255 }, .width = { 1, 1, 1, 1, 0 } }, + }) { + Clay_OnHover(HandleDebugViewCloseButtonInteraction, 0); + CLAY_TEXT(CLAY_STRING("x"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + } + CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(1)} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_3 } ) {} + CLAY(scrollId, { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .clip = { .horizontal = true, .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { + CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = ((initialElementsLength + initialRootsLength) & 1) == 0 ? CLAY__DEBUGVIEW_COLOR_2 : CLAY__DEBUGVIEW_COLOR_1 }) { + Clay_ElementId panelContentsId = Clay__HashString(CLAY_STRING("Clay__DebugViewPaneOuter"), 0); + // Element list + CLAY(panelContentsId, { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .floating = { .zIndex = 32766, .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, .attachTo = CLAY_ATTACH_TO_PARENT, .clipTo = CLAY_CLIP_TO_ATTACHED_PARENT } }) { + CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = { CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + layoutData = Clay__RenderDebugLayoutElementsList((int32_t)initialRootsLength, highlightedRow); + } + } + float contentWidth = Clay__GetHashMapItem(panelContentsId.id)->layoutElement->dimensions.width; + CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(contentWidth) }, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) {} + for (int32_t i = 0; i < layoutData.rowCount; i++) { + Clay_Color rowColor = (i & 1) == 0 ? CLAY__DEBUGVIEW_COLOR_2 : CLAY__DEBUGVIEW_COLOR_1; + if (i == layoutData.selectedElementRowIndex) { + rowColor = CLAY__DEBUGVIEW_COLOR_SELECTED_ROW; + } + if (i == highlightedRow) { + rowColor.r *= 1.25f; + rowColor.g *= 1.25f; + rowColor.b *= 1.25f; + } + CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = rowColor } ) {} + } + } + } + CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(1)} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_3 }) {} + if (context->debugSelectedElementId != 0) { + Clay_LayoutElementHashMapItem *selectedItem = Clay__GetHashMapItem(context->debugSelectedElementId); + CLAY_AUTO_ID({ + .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(300)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }, + .backgroundColor = CLAY__DEBUGVIEW_COLOR_2 , + .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }, + .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .betweenChildren = 1 } } + }) { + CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT + 8)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { + CLAY_TEXT(CLAY_STRING("Layout Config"), infoTextConfig); + CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} + if (selectedItem->elementId.stringId.length != 0) { + CLAY_TEXT(selectedItem->elementId.stringId, infoTitleConfig); + if (selectedItem->elementId.offset != 0) { + CLAY_TEXT(CLAY_STRING(" ("), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(selectedItem->elementId.offset), infoTitleConfig); + CLAY_TEXT(CLAY_STRING(")"), infoTitleConfig); + } + } + } + Clay_Padding attributeConfigPadding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 8, 8}; + // Clay_LayoutConfig debug info + CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + // .boundingBox + CLAY_TEXT(CLAY_STRING("Bounding Box"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.x), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.y), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", width: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.width), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.height), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .layoutDirection + CLAY_TEXT(CLAY_STRING("Layout Direction"), infoTitleConfig); + Clay_LayoutConfig *layoutConfig = selectedItem->layoutElement->layoutConfig; + CLAY_TEXT(layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM ? CLAY_STRING("TOP_TO_BOTTOM") : CLAY_STRING("LEFT_TO_RIGHT"), infoTextConfig); + // .sizing + CLAY_TEXT(CLAY_STRING("Sizing"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("width: "), infoTextConfig); + Clay__RenderDebugLayoutSizing(layoutConfig->sizing.width, infoTextConfig); + } + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("height: "), infoTextConfig); + Clay__RenderDebugLayoutSizing(layoutConfig->sizing.height, infoTextConfig); + } + // .padding + CLAY_TEXT(CLAY_STRING("Padding"), infoTitleConfig); + CLAY(CLAY_ID("Clay__DebugViewElementInfoPadding"), { }) { + CLAY_TEXT(CLAY_STRING("{ left: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(layoutConfig->padding.left), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", right: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(layoutConfig->padding.right), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", top: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(layoutConfig->padding.top), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", bottom: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(layoutConfig->padding.bottom), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .childGap + CLAY_TEXT(CLAY_STRING("Child Gap"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(layoutConfig->childGap), infoTextConfig); + // .childAlignment + CLAY_TEXT(CLAY_STRING("Child Alignment"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); + Clay_String alignX = CLAY_STRING("LEFT"); + if (layoutConfig->childAlignment.x == CLAY_ALIGN_X_CENTER) { + alignX = CLAY_STRING("CENTER"); + } else if (layoutConfig->childAlignment.x == CLAY_ALIGN_X_RIGHT) { + alignX = CLAY_STRING("RIGHT"); + } + CLAY_TEXT(alignX, infoTextConfig); + CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); + Clay_String alignY = CLAY_STRING("TOP"); + if (layoutConfig->childAlignment.y == CLAY_ALIGN_Y_CENTER) { + alignY = CLAY_STRING("CENTER"); + } else if (layoutConfig->childAlignment.y == CLAY_ALIGN_Y_BOTTOM) { + alignY = CLAY_STRING("BOTTOM"); + } + CLAY_TEXT(alignY, infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + } + for (int32_t elementConfigIndex = 0; elementConfigIndex < selectedItem->layoutElement->elementConfigs.length; ++elementConfigIndex) { + Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(&selectedItem->layoutElement->elementConfigs, elementConfigIndex); + Clay__RenderDebugViewElementConfigHeader(selectedItem->elementId.stringId, elementConfig->type); + switch (elementConfig->type) { + case CLAY__ELEMENT_CONFIG_TYPE_SHARED: { + Clay_SharedElementConfig *sharedConfig = elementConfig->config.sharedElementConfig; + CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) { + // .backgroundColor + CLAY_TEXT(CLAY_STRING("Background Color"), infoTitleConfig); + Clay__RenderDebugViewColor(sharedConfig->backgroundColor, infoTextConfig); + // .cornerRadius + CLAY_TEXT(CLAY_STRING("Corner Radius"), infoTitleConfig); + Clay__RenderDebugViewCornerRadius(sharedConfig->cornerRadius, infoTextConfig); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_TEXT: { + Clay_TextElementConfig *textConfig = elementConfig->config.textElementConfig; + CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + // .fontSize + CLAY_TEXT(CLAY_STRING("Font Size"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(textConfig->fontSize), infoTextConfig); + // .fontId + CLAY_TEXT(CLAY_STRING("Font ID"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(textConfig->fontId), infoTextConfig); + // .lineHeight + CLAY_TEXT(CLAY_STRING("Line Height"), infoTitleConfig); + CLAY_TEXT(textConfig->lineHeight == 0 ? CLAY_STRING("auto") : Clay__IntToString(textConfig->lineHeight), infoTextConfig); + // .letterSpacing + CLAY_TEXT(CLAY_STRING("Letter Spacing"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(textConfig->letterSpacing), infoTextConfig); + // .wrapMode + CLAY_TEXT(CLAY_STRING("Wrap Mode"), infoTitleConfig); + Clay_String wrapMode = CLAY_STRING("WORDS"); + if (textConfig->wrapMode == CLAY_TEXT_WRAP_NONE) { + wrapMode = CLAY_STRING("NONE"); + } else if (textConfig->wrapMode == CLAY_TEXT_WRAP_NEWLINES) { + wrapMode = CLAY_STRING("NEWLINES"); + } + CLAY_TEXT(wrapMode, infoTextConfig); + // .textAlignment + CLAY_TEXT(CLAY_STRING("Text Alignment"), infoTitleConfig); + Clay_String textAlignment = CLAY_STRING("LEFT"); + if (textConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) { + textAlignment = CLAY_STRING("CENTER"); + } else if (textConfig->textAlignment == CLAY_TEXT_ALIGN_RIGHT) { + textAlignment = CLAY_STRING("RIGHT"); + } + CLAY_TEXT(textAlignment, infoTextConfig); + // .textColor + CLAY_TEXT(CLAY_STRING("Text Color"), infoTitleConfig); + Clay__RenderDebugViewColor(textConfig->textColor, infoTextConfig); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: { + Clay_AspectRatioElementConfig *aspectRatioConfig = elementConfig->config.aspectRatioElementConfig; + CLAY(CLAY_ID("Clay__DebugViewElementInfoAspectRatioBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + CLAY_TEXT(CLAY_STRING("Aspect Ratio"), infoTitleConfig); + // Aspect Ratio + CLAY(CLAY_ID("Clay__DebugViewElementInfoAspectRatio"), { }) { + CLAY_TEXT(Clay__IntToString(aspectRatioConfig->aspectRatio), infoTextConfig); + CLAY_TEXT(CLAY_STRING("."), infoTextConfig); + float frac = aspectRatioConfig->aspectRatio - (int)(aspectRatioConfig->aspectRatio); + frac *= 100; + if ((int)frac < 10) { + CLAY_TEXT(CLAY_STRING("0"), infoTextConfig); + } + CLAY_TEXT(Clay__IntToString(frac), infoTextConfig); + } + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: { + Clay_ImageElementConfig *imageConfig = elementConfig->config.imageElementConfig; + Clay_AspectRatioElementConfig aspectConfig = { 1 }; + if (Clay__ElementHasConfig(selectedItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT)) { + aspectConfig = *Clay__FindElementConfigWithType(selectedItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; + } + CLAY(CLAY_ID("Clay__DebugViewElementInfoImageBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + // Image Preview + CLAY_TEXT(CLAY_STRING("Preview"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(64, 128), .height = CLAY_SIZING_GROW(64, 128) }}, .aspectRatio = aspectConfig, .image = *imageConfig }) {} + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_CLIP: { + Clay_ClipElementConfig *clipConfig = elementConfig->config.clipElementConfig; + CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + // .vertical + CLAY_TEXT(CLAY_STRING("Vertical"), infoTitleConfig); + CLAY_TEXT(clipConfig->vertical ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); + // .horizontal + CLAY_TEXT(CLAY_STRING("Horizontal"), infoTitleConfig); + CLAY_TEXT(clipConfig->horizontal ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: { + Clay_FloatingElementConfig *floatingConfig = elementConfig->config.floatingElementConfig; + CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + // .offset + CLAY_TEXT(CLAY_STRING("Offset"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->offset.x), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->offset.y), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .expand + CLAY_TEXT(CLAY_STRING("Expand"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("{ width: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->expand.width), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->expand.height), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .zIndex + CLAY_TEXT(CLAY_STRING("z-index"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->zIndex), infoTextConfig); + // .parentId + CLAY_TEXT(CLAY_STRING("Parent"), infoTitleConfig); + Clay_LayoutElementHashMapItem *hashItem = Clay__GetHashMapItem(floatingConfig->parentId); + CLAY_TEXT(hashItem->elementId.stringId, infoTextConfig); + // .attachPoints + CLAY_TEXT(CLAY_STRING("Attach Points"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("{ element: "), infoTextConfig); + Clay_String attachPointElement = CLAY_STRING("LEFT_TOP"); + if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_LEFT_CENTER) { + attachPointElement = CLAY_STRING("LEFT_CENTER"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_LEFT_BOTTOM) { + attachPointElement = CLAY_STRING("LEFT_BOTTOM"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_TOP) { + attachPointElement = CLAY_STRING("CENTER_TOP"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_CENTER) { + attachPointElement = CLAY_STRING("CENTER_CENTER"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_BOTTOM) { + attachPointElement = CLAY_STRING("CENTER_BOTTOM"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_TOP) { + attachPointElement = CLAY_STRING("RIGHT_TOP"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_CENTER) { + attachPointElement = CLAY_STRING("RIGHT_CENTER"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_BOTTOM) { + attachPointElement = CLAY_STRING("RIGHT_BOTTOM"); + } + CLAY_TEXT(attachPointElement, infoTextConfig); + Clay_String attachPointParent = CLAY_STRING("LEFT_TOP"); + if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_LEFT_CENTER) { + attachPointParent = CLAY_STRING("LEFT_CENTER"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_LEFT_BOTTOM) { + attachPointParent = CLAY_STRING("LEFT_BOTTOM"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_TOP) { + attachPointParent = CLAY_STRING("CENTER_TOP"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_CENTER) { + attachPointParent = CLAY_STRING("CENTER_CENTER"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_BOTTOM) { + attachPointParent = CLAY_STRING("CENTER_BOTTOM"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_TOP) { + attachPointParent = CLAY_STRING("RIGHT_TOP"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_CENTER) { + attachPointParent = CLAY_STRING("RIGHT_CENTER"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_BOTTOM) { + attachPointParent = CLAY_STRING("RIGHT_BOTTOM"); + } + CLAY_TEXT(CLAY_STRING(", parent: "), infoTextConfig); + CLAY_TEXT(attachPointParent, infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .pointerCaptureMode + CLAY_TEXT(CLAY_STRING("Pointer Capture Mode"), infoTitleConfig); + Clay_String pointerCaptureMode = CLAY_STRING("NONE"); + if (floatingConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH) { + pointerCaptureMode = CLAY_STRING("PASSTHROUGH"); + } + CLAY_TEXT(pointerCaptureMode, infoTextConfig); + // .attachTo + CLAY_TEXT(CLAY_STRING("Attach To"), infoTitleConfig); + Clay_String attachTo = CLAY_STRING("NONE"); + if (floatingConfig->attachTo == CLAY_ATTACH_TO_PARENT) { + attachTo = CLAY_STRING("PARENT"); + } else if (floatingConfig->attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) { + attachTo = CLAY_STRING("ELEMENT_WITH_ID"); + } else if (floatingConfig->attachTo == CLAY_ATTACH_TO_ROOT) { + attachTo = CLAY_STRING("ROOT"); + } + CLAY_TEXT(attachTo, infoTextConfig); + // .clipTo + CLAY_TEXT(CLAY_STRING("Clip To"), infoTitleConfig); + Clay_String clipTo = CLAY_STRING("ATTACHED_PARENT"); + if (floatingConfig->clipTo == CLAY_CLIP_TO_NONE) { + clipTo = CLAY_STRING("NONE"); + } + CLAY_TEXT(clipTo, infoTextConfig); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_BORDER: { + Clay_BorderElementConfig *borderConfig = elementConfig->config.borderElementConfig; + CLAY(CLAY_ID("Clay__DebugViewElementInfoBorderBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + CLAY_TEXT(CLAY_STRING("Border Widths"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("{ left: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(borderConfig->width.left), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", right: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(borderConfig->width.right), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", top: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(borderConfig->width.top), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", bottom: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(borderConfig->width.bottom), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .textColor + CLAY_TEXT(CLAY_STRING("Border Color"), infoTitleConfig); + Clay__RenderDebugViewColor(borderConfig->color, infoTextConfig); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: + default: break; + } + } + } + } else { + CLAY(CLAY_ID("Clay__DebugViewWarningsScrollPane"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(300)}, .childGap = 6, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_2, .clip = { .horizontal = true, .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { + Clay_TextElementConfig *warningConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); + CLAY(CLAY_ID("Clay__DebugViewWarningItemHeader"), { .layout = { .sizing = {.height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childGap = 8, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { + CLAY_TEXT(CLAY_STRING("Warnings"), warningConfig); + } + CLAY(CLAY_ID("Clay__DebugViewWarningsTopBorder"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(1)} }, .backgroundColor = {200, 200, 200, 255} }) {} + int32_t previousWarningsLength = context->warnings.length; + for (int32_t i = 0; i < previousWarningsLength; i++) { + Clay__Warning warning = context->warnings.internalArray[i]; + CLAY(CLAY_IDI("Clay__DebugViewWarningItem", i), { .layout = { .sizing = {.height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childGap = 8, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { + CLAY_TEXT(warning.baseMessage, warningConfig); + if (warning.dynamicMessage.length > 0) { + CLAY_TEXT(warning.dynamicMessage, warningConfig); + } + } + } + } + } + } +} +#pragma endregion + +uint32_t Clay__debugViewWidth = 400; +Clay_Color Clay__debugViewHighlightColor = { 168, 66, 28, 100 }; + +Clay__WarningArray Clay__WarningArray_Allocate_Arena(int32_t capacity, Clay_Arena *arena) { + size_t totalSizeBytes = capacity * sizeof(Clay_String); + Clay__WarningArray array = {.capacity = capacity, .length = 0}; + uintptr_t nextAllocOffset = arena->nextAllocation + (64 - (arena->nextAllocation % 64)); + if (nextAllocOffset + totalSizeBytes <= arena->capacity) { + array.internalArray = (Clay__Warning*)((uintptr_t)arena->memory + (uintptr_t)nextAllocOffset); + arena->nextAllocation = nextAllocOffset + totalSizeBytes; + } + else { + Clay__currentContext->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, + .errorText = CLAY_STRING("Clay attempted to allocate memory in its arena, but ran out of capacity. Try increasing the capacity of the arena passed to Clay_Initialize()"), + .userData = Clay__currentContext->errorHandler.userData }); + } + return array; +} + +Clay__Warning *Clay__WarningArray_Add(Clay__WarningArray *array, Clay__Warning item) +{ + if (array->length < array->capacity) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__WARNING_DEFAULT; +} + +void* Clay__Array_Allocate_Arena(int32_t capacity, uint32_t itemSize, Clay_Arena *arena) +{ + size_t totalSizeBytes = capacity * itemSize; + uintptr_t nextAllocOffset = arena->nextAllocation + ((64 - (arena->nextAllocation % 64)) & 63); + if (nextAllocOffset + totalSizeBytes <= arena->capacity) { + arena->nextAllocation = nextAllocOffset + totalSizeBytes; + return (void*)((uintptr_t)arena->memory + (uintptr_t)nextAllocOffset); + } + else { + Clay__currentContext->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, + .errorText = CLAY_STRING("Clay attempted to allocate memory in its arena, but ran out of capacity. Try increasing the capacity of the arena passed to Clay_Initialize()"), + .userData = Clay__currentContext->errorHandler.userData }); + } + return CLAY__NULL; +} + +bool Clay__Array_RangeCheck(int32_t index, int32_t length) +{ + if (index < length && index >= 0) { + return true; + } + Clay_Context* context = Clay_GetCurrentContext(); + context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_INTERNAL_ERROR, + .errorText = CLAY_STRING("Clay attempted to make an out of bounds array access. This is an internal error and is likely a bug."), + .userData = context->errorHandler.userData }); + return false; +} + +bool Clay__Array_AddCapacityCheck(int32_t length, int32_t capacity) +{ + if (length < capacity) { + return true; + } + Clay_Context* context = Clay_GetCurrentContext(); + context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_INTERNAL_ERROR, + .errorText = CLAY_STRING("Clay attempted to make an out of bounds array access. This is an internal error and is likely a bug."), + .userData = context->errorHandler.userData }); + return false; +} + +// PUBLIC API FROM HERE --------------------------------------- + +CLAY_WASM_EXPORT("Clay_MinMemorySize") +uint32_t Clay_MinMemorySize(void) { + Clay_Context fakeContext = { + .maxElementCount = Clay__defaultMaxElementCount, + .maxMeasureTextCacheWordCount = Clay__defaultMaxMeasureTextWordCacheCount, + .internalArena = { + .capacity = SIZE_MAX, + .memory = NULL, + } + }; + Clay_Context* currentContext = Clay_GetCurrentContext(); + if (currentContext) { + fakeContext.maxElementCount = currentContext->maxElementCount; + fakeContext.maxMeasureTextCacheWordCount = currentContext->maxMeasureTextCacheWordCount; + } + // Reserve space in the arena for the context, important for calculating min memory size correctly + Clay__Context_Allocate_Arena(&fakeContext.internalArena); + Clay__InitializePersistentMemory(&fakeContext); + Clay__InitializeEphemeralMemory(&fakeContext); + return (uint32_t)fakeContext.internalArena.nextAllocation + 128; +} + +CLAY_WASM_EXPORT("Clay_CreateArenaWithCapacityAndMemory") +Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory) { + Clay_Arena arena = { + .capacity = capacity, + .memory = (char *)memory + }; + return arena; +} + +#ifndef CLAY_WASM +void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData), void *userData) { + Clay_Context* context = Clay_GetCurrentContext(); + Clay__MeasureText = measureTextFunction; + context->measureTextUserData = userData; +} +void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, void *userData), void *userData) { + Clay_Context* context = Clay_GetCurrentContext(); + Clay__QueryScrollOffset = queryScrollOffsetFunction; + context->queryScrollOffsetUserData = userData; +} +#endif + +CLAY_WASM_EXPORT("Clay_SetLayoutDimensions") +void Clay_SetLayoutDimensions(Clay_Dimensions dimensions) { + Clay_GetCurrentContext()->layoutDimensions = dimensions; +} + +CLAY_WASM_EXPORT("Clay_SetPointerState") +void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->booleanWarnings.maxElementsExceeded) { + return; + } + context->pointerInfo.position = position; + context->pointerOverIds.length = 0; + Clay__int32_tArray dfsBuffer = context->layoutElementChildrenBuffer; + for (int32_t rootIndex = context->layoutElementTreeRoots.length - 1; rootIndex >= 0; --rootIndex) { + dfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); + Clay__int32_tArray_Add(&dfsBuffer, (int32_t)root->layoutElementIndex); + context->treeNodeVisited.internalArray[0] = false; + bool found = false; + while (dfsBuffer.length > 0) { + if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + dfsBuffer.length--; + continue; + } + context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1)); + Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO think of a way around this, maybe the fact that it's essentially a binary tree limits the cost, but the worst case is not great + int32_t clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(currentElement - context->layoutElements.internalArray)); + Clay_LayoutElementHashMapItem *clipItem = Clay__GetHashMapItem(clipElementId); + if (mapItem) { + Clay_BoundingBox elementBox = mapItem->boundingBox; + elementBox.x -= root->pointerOffset.x; + elementBox.y -= root->pointerOffset.y; + if ((Clay__PointIsInsideRect(position, elementBox)) && (clipElementId == 0 || (Clay__PointIsInsideRect(position, clipItem->boundingBox)) || context->externalScrollHandlingEnabled)) { + if (mapItem->onHoverFunction) { + mapItem->onHoverFunction(mapItem->elementId, context->pointerInfo, mapItem->hoverFunctionUserData); + } + Clay_ElementIdArray_Add(&context->pointerOverIds, mapItem->elementId); + found = true; + } + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + dfsBuffer.length--; + continue; + } + for (int32_t i = currentElement->childrenOrTextContent.children.length - 1; i >= 0; --i) { + Clay__int32_tArray_Add(&dfsBuffer, currentElement->childrenOrTextContent.children.elements[i]); + context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked + } + } else { + dfsBuffer.length--; + } + } + + Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, root->layoutElementIndex); + if (found && Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING) && + Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_CAPTURE) { + break; + } + } + + if (isPointerDown) { + if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + context->pointerInfo.state = CLAY_POINTER_DATA_PRESSED; + } else if (context->pointerInfo.state != CLAY_POINTER_DATA_PRESSED) { + context->pointerInfo.state = CLAY_POINTER_DATA_PRESSED_THIS_FRAME; + } + } else { + if (context->pointerInfo.state == CLAY_POINTER_DATA_RELEASED_THIS_FRAME) { + context->pointerInfo.state = CLAY_POINTER_DATA_RELEASED; + } else if (context->pointerInfo.state != CLAY_POINTER_DATA_RELEASED) { + context->pointerInfo.state = CLAY_POINTER_DATA_RELEASED_THIS_FRAME; + } + } +} + +CLAY_WASM_EXPORT("Clay_Initialize") +Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler) { + // Cacheline align memory passed in + uintptr_t baseOffset = 64 - ((uintptr_t)arena.memory % 64); + baseOffset = baseOffset == 64 ? 0 : baseOffset; + arena.memory += baseOffset; + Clay_Context *context = Clay__Context_Allocate_Arena(&arena); + if (context == NULL) return NULL; + // DEFAULTS + Clay_Context *oldContext = Clay_GetCurrentContext(); + *context = CLAY__INIT(Clay_Context) { + .maxElementCount = oldContext ? oldContext->maxElementCount : Clay__defaultMaxElementCount, + .maxMeasureTextCacheWordCount = oldContext ? oldContext->maxMeasureTextCacheWordCount : Clay__defaultMaxMeasureTextWordCacheCount, + .errorHandler = errorHandler.errorHandlerFunction ? errorHandler : CLAY__INIT(Clay_ErrorHandler) { Clay__ErrorHandlerFunctionDefault, 0 }, + .layoutDimensions = layoutDimensions, + .internalArena = arena, + }; + Clay_SetCurrentContext(context); + Clay__InitializePersistentMemory(context); + Clay__InitializeEphemeralMemory(context); + for (int32_t i = 0; i < context->layoutElementsHashMap.capacity; ++i) { + context->layoutElementsHashMap.internalArray[i] = -1; + } + for (int32_t i = 0; i < context->measureTextHashMap.capacity; ++i) { + context->measureTextHashMap.internalArray[i] = 0; + } + context->measureTextHashMapInternal.length = 1; // Reserve the 0 value to mean "no next element" + context->layoutDimensions = layoutDimensions; + return context; +} + +CLAY_WASM_EXPORT("Clay_GetCurrentContext") +Clay_Context* Clay_GetCurrentContext(void) { + return Clay__currentContext; +} + +CLAY_WASM_EXPORT("Clay_SetCurrentContext") +void Clay_SetCurrentContext(Clay_Context* context) { + Clay__currentContext = context; +} + +CLAY_WASM_EXPORT("Clay_GetScrollOffset") +Clay_Vector2 Clay_GetScrollOffset(void) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->booleanWarnings.maxElementsExceeded) { + return CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + // If the element has no id attached at this point, we need to generate one + if (openLayoutElement->id == 0) { + Clay__GenerateIdForAnonymousElement(openLayoutElement); + } + for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); + if (mapping->layoutElement == openLayoutElement) { + return mapping->scrollPosition; + } + } + return CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; +} + +CLAY_WASM_EXPORT("Clay_UpdateScrollContainers") +void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime) { + Clay_Context* context = Clay_GetCurrentContext(); + bool isPointerActive = enableDragScrolling && (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED || context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME); + // Don't apply scroll events to ancestors of the inner element + int32_t highestPriorityElementIndex = -1; + Clay__ScrollContainerDataInternal *highestPriorityScrollData = CLAY__NULL; + for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *scrollData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); + if (!scrollData->openThisFrame) { + Clay__ScrollContainerDataInternalArray_RemoveSwapback(&context->scrollContainerDatas, i); + continue; + } + scrollData->openThisFrame = false; + Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(scrollData->elementId); + // Element isn't rendered this frame but scroll offset has been retained + if (!hashMapItem) { + Clay__ScrollContainerDataInternalArray_RemoveSwapback(&context->scrollContainerDatas, i); + continue; + } + + // Touch / click is released + if (!isPointerActive && scrollData->pointerScrollActive) { + float xDiff = scrollData->scrollPosition.x - scrollData->scrollOrigin.x; + if (xDiff < -10 || xDiff > 10) { + scrollData->scrollMomentum.x = (scrollData->scrollPosition.x - scrollData->scrollOrigin.x) / (scrollData->momentumTime * 25); + } + float yDiff = scrollData->scrollPosition.y - scrollData->scrollOrigin.y; + if (yDiff < -10 || yDiff > 10) { + scrollData->scrollMomentum.y = (scrollData->scrollPosition.y - scrollData->scrollOrigin.y) / (scrollData->momentumTime * 25); + } + scrollData->pointerScrollActive = false; + + scrollData->pointerOrigin = CLAY__INIT(Clay_Vector2){0,0}; + scrollData->scrollOrigin = CLAY__INIT(Clay_Vector2){0,0}; + scrollData->momentumTime = 0; + } + + // Apply existing momentum + scrollData->scrollPosition.x += scrollData->scrollMomentum.x; + scrollData->scrollMomentum.x *= 0.95f; + bool scrollOccurred = scrollDelta.x != 0 || scrollDelta.y != 0; + if ((scrollData->scrollMomentum.x > -0.1f && scrollData->scrollMomentum.x < 0.1f) || scrollOccurred) { + scrollData->scrollMomentum.x = 0; + } + scrollData->scrollPosition.x = CLAY__MIN(CLAY__MAX(scrollData->scrollPosition.x, -(CLAY__MAX(scrollData->contentSize.width - scrollData->layoutElement->dimensions.width, 0))), 0); + + scrollData->scrollPosition.y += scrollData->scrollMomentum.y; + scrollData->scrollMomentum.y *= 0.95f; + if ((scrollData->scrollMomentum.y > -0.1f && scrollData->scrollMomentum.y < 0.1f) || scrollOccurred) { + scrollData->scrollMomentum.y = 0; + } + scrollData->scrollPosition.y = CLAY__MIN(CLAY__MAX(scrollData->scrollPosition.y, -(CLAY__MAX(scrollData->contentSize.height - scrollData->layoutElement->dimensions.height, 0))), 0); + + for (int32_t j = 0; j < context->pointerOverIds.length; ++j) { // TODO n & m are small here but this being n*m gives me the creeps + if (scrollData->layoutElement->id == Clay_ElementIdArray_Get(&context->pointerOverIds, j)->id) { + highestPriorityElementIndex = j; + highestPriorityScrollData = scrollData; + } + } + } + + if (highestPriorityElementIndex > -1 && highestPriorityScrollData) { + Clay_LayoutElement *scrollElement = highestPriorityScrollData->layoutElement; + Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(scrollElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; + bool canScrollVertically = clipConfig->vertical && highestPriorityScrollData->contentSize.height > scrollElement->dimensions.height; + bool canScrollHorizontally = clipConfig->horizontal && highestPriorityScrollData->contentSize.width > scrollElement->dimensions.width; + // Handle wheel scroll + if (canScrollVertically) { + highestPriorityScrollData->scrollPosition.y = highestPriorityScrollData->scrollPosition.y + scrollDelta.y * 10; + } + if (canScrollHorizontally) { + highestPriorityScrollData->scrollPosition.x = highestPriorityScrollData->scrollPosition.x + scrollDelta.x * 10; + } + // Handle click / touch scroll + if (isPointerActive) { + highestPriorityScrollData->scrollMomentum = CLAY__INIT(Clay_Vector2)CLAY__DEFAULT_STRUCT; + if (!highestPriorityScrollData->pointerScrollActive) { + highestPriorityScrollData->pointerOrigin = context->pointerInfo.position; + highestPriorityScrollData->scrollOrigin = highestPriorityScrollData->scrollPosition; + highestPriorityScrollData->pointerScrollActive = true; + } else { + float scrollDeltaX = 0, scrollDeltaY = 0; + if (canScrollHorizontally) { + float oldXScrollPosition = highestPriorityScrollData->scrollPosition.x; + highestPriorityScrollData->scrollPosition.x = highestPriorityScrollData->scrollOrigin.x + (context->pointerInfo.position.x - highestPriorityScrollData->pointerOrigin.x); + highestPriorityScrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.x, 0), -(highestPriorityScrollData->contentSize.width - highestPriorityScrollData->boundingBox.width)); + scrollDeltaX = highestPriorityScrollData->scrollPosition.x - oldXScrollPosition; + } + if (canScrollVertically) { + float oldYScrollPosition = highestPriorityScrollData->scrollPosition.y; + highestPriorityScrollData->scrollPosition.y = highestPriorityScrollData->scrollOrigin.y + (context->pointerInfo.position.y - highestPriorityScrollData->pointerOrigin.y); + highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - highestPriorityScrollData->boundingBox.height)); + scrollDeltaY = highestPriorityScrollData->scrollPosition.y - oldYScrollPosition; + } + if (scrollDeltaX > -0.1f && scrollDeltaX < 0.1f && scrollDeltaY > -0.1f && scrollDeltaY < 0.1f && highestPriorityScrollData->momentumTime > 0.15f) { + highestPriorityScrollData->momentumTime = 0; + highestPriorityScrollData->pointerOrigin = context->pointerInfo.position; + highestPriorityScrollData->scrollOrigin = highestPriorityScrollData->scrollPosition; + } else { + highestPriorityScrollData->momentumTime += deltaTime; + } + } + } + // Clamp any changes to scroll position to the maximum size of the contents + if (canScrollVertically) { + highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - scrollElement->dimensions.height)); + } + if (canScrollHorizontally) { + highestPriorityScrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.x, 0), -(highestPriorityScrollData->contentSize.width - scrollElement->dimensions.width)); + } + } +} + +CLAY_WASM_EXPORT("Clay_BeginLayout") +void Clay_BeginLayout(void) { + Clay_Context* context = Clay_GetCurrentContext(); + Clay__InitializeEphemeralMemory(context); + context->generation++; + context->dynamicElementIndex = 0; + // Set up the root container that covers the entire window + Clay_Dimensions rootDimensions = {context->layoutDimensions.width, context->layoutDimensions.height}; + if (context->debugModeEnabled) { + rootDimensions.width -= (float)Clay__debugViewWidth; + } + context->booleanWarnings = CLAY__INIT(Clay_BooleanWarnings) CLAY__DEFAULT_STRUCT; + Clay__OpenElementWithId(CLAY_ID("Clay__RootContainer")); + Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { + .layout = { .sizing = {CLAY_SIZING_FIXED((rootDimensions.width)), CLAY_SIZING_FIXED(rootDimensions.height)} } + }); + Clay__int32_tArray_Add(&context->openLayoutElementStack, 0); + Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { .layoutElementIndex = 0 }); +} + +CLAY_WASM_EXPORT("Clay_EndLayout") +Clay_RenderCommandArray Clay_EndLayout(void) { + Clay_Context* context = Clay_GetCurrentContext(); + Clay__CloseElement(); + bool elementsExceededBeforeDebugView = context->booleanWarnings.maxElementsExceeded; + if (context->debugModeEnabled && !elementsExceededBeforeDebugView) { + context->warningsEnabled = false; + Clay__RenderDebugView(); + context->warningsEnabled = true; + } + if (context->booleanWarnings.maxElementsExceeded) { + Clay_String message; + if (!elementsExceededBeforeDebugView) { + message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount after adding the debug-view to the layout."); + } else { + message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount"); + } + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { + .boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 }, + .renderData = { .text = { .stringContents = CLAY__INIT(Clay_StringSlice) { .length = message.length, .chars = message.chars, .baseChars = message.chars }, .textColor = {255, 0, 0, 255}, .fontSize = 16 } }, + .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT + }); + } + if (context->openLayoutElementStack.length > 1) { + context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_UNBALANCED_OPEN_CLOSE, + .errorText = CLAY_STRING("There were still open layout elements when EndLayout was called. This results from an unequal number of calls to Clay__OpenElement and Clay__CloseElement."), + .userData = context->errorHandler.userData }); + } + Clay__CalculateFinalLayout(); + return context->renderCommands; +} + +CLAY_WASM_EXPORT("Clay_GetElementId") +Clay_ElementId Clay_GetElementId(Clay_String idString) { + return Clay__HashString(idString, 0); +} + +CLAY_WASM_EXPORT("Clay_GetElementIdWithIndex") +Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index) { + return Clay__HashStringWithOffset(idString, index, 0); +} + +bool Clay_Hovered(void) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->booleanWarnings.maxElementsExceeded) { + return false; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + // If the element has no id attached at this point, we need to generate one + if (openLayoutElement->id == 0) { + Clay__GenerateIdForAnonymousElement(openLayoutElement); + } + for (int32_t i = 0; i < context->pointerOverIds.length; ++i) { + if (Clay_ElementIdArray_Get(&context->pointerOverIds, i)->id == openLayoutElement->id) { + return true; + } + } + return false; +} + +void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData), void *userData) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->booleanWarnings.maxElementsExceeded) { + return; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + if (openLayoutElement->id == 0) { + Clay__GenerateIdForAnonymousElement(openLayoutElement); + } + Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(openLayoutElement->id); + hashMapItem->onHoverFunction = onHoverFunction; + hashMapItem->hoverFunctionUserData = userData; +} + +CLAY_WASM_EXPORT("Clay_PointerOver") +bool Clay_PointerOver(Clay_ElementId elementId) { // TODO return priority for separating multiple results + Clay_Context* context = Clay_GetCurrentContext(); + for (int32_t i = 0; i < context->pointerOverIds.length; ++i) { + if (Clay_ElementIdArray_Get(&context->pointerOverIds, i)->id == elementId.id) { + return true; + } + } + return false; +} + +CLAY_WASM_EXPORT("Clay_GetScrollContainerData") +Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id) { + Clay_Context* context = Clay_GetCurrentContext(); + for (int32_t i = 0; i < context->scrollContainerDatas.length; ++i) { + Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); + if (scrollContainerData->elementId == id.id) { + Clay_ClipElementConfig *clipElementConfig = Clay__FindElementConfigWithType(scrollContainerData->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; + if (!clipElementConfig) { // This can happen on the first frame before a scroll container is declared + return CLAY__INIT(Clay_ScrollContainerData) CLAY__DEFAULT_STRUCT; + } + return CLAY__INIT(Clay_ScrollContainerData) { + .scrollPosition = &scrollContainerData->scrollPosition, + .scrollContainerDimensions = { scrollContainerData->boundingBox.width, scrollContainerData->boundingBox.height }, + .contentDimensions = scrollContainerData->contentSize, + .config = *clipElementConfig, + .found = true + }; + } + } + return CLAY__INIT(Clay_ScrollContainerData) CLAY__DEFAULT_STRUCT; +} + +CLAY_WASM_EXPORT("Clay_GetElementData") +Clay_ElementData Clay_GetElementData(Clay_ElementId id){ + Clay_LayoutElementHashMapItem * item = Clay__GetHashMapItem(id.id); + if(item == &Clay_LayoutElementHashMapItem_DEFAULT) { + return CLAY__INIT(Clay_ElementData) CLAY__DEFAULT_STRUCT; + } + + return CLAY__INIT(Clay_ElementData){ + .boundingBox = item->boundingBox, + .found = true + }; +} + +CLAY_WASM_EXPORT("Clay_SetDebugModeEnabled") +void Clay_SetDebugModeEnabled(bool enabled) { + Clay_Context* context = Clay_GetCurrentContext(); + context->debugModeEnabled = enabled; +} + +CLAY_WASM_EXPORT("Clay_IsDebugModeEnabled") +bool Clay_IsDebugModeEnabled(void) { + Clay_Context* context = Clay_GetCurrentContext(); + return context->debugModeEnabled; +} + +CLAY_WASM_EXPORT("Clay_SetCullingEnabled") +void Clay_SetCullingEnabled(bool enabled) { + Clay_Context* context = Clay_GetCurrentContext(); + context->disableCulling = !enabled; +} + +CLAY_WASM_EXPORT("Clay_SetExternalScrollHandlingEnabled") +void Clay_SetExternalScrollHandlingEnabled(bool enabled) { + Clay_Context* context = Clay_GetCurrentContext(); + context->externalScrollHandlingEnabled = enabled; +} + +CLAY_WASM_EXPORT("Clay_GetMaxElementCount") +int32_t Clay_GetMaxElementCount(void) { + Clay_Context* context = Clay_GetCurrentContext(); + return context->maxElementCount; +} + +CLAY_WASM_EXPORT("Clay_SetMaxElementCount") +void Clay_SetMaxElementCount(int32_t maxElementCount) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context) { + context->maxElementCount = maxElementCount; + } else { + Clay__defaultMaxElementCount = maxElementCount; // TODO: Fix this + Clay__defaultMaxMeasureTextWordCacheCount = maxElementCount * 2; + } +} + +CLAY_WASM_EXPORT("Clay_GetMaxMeasureTextCacheWordCount") +int32_t Clay_GetMaxMeasureTextCacheWordCount(void) { + Clay_Context* context = Clay_GetCurrentContext(); + return context->maxMeasureTextCacheWordCount; +} + +CLAY_WASM_EXPORT("Clay_SetMaxMeasureTextCacheWordCount") +void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context) { + Clay__currentContext->maxMeasureTextCacheWordCount = maxMeasureTextCacheWordCount; + } else { + Clay__defaultMaxMeasureTextWordCacheCount = maxMeasureTextCacheWordCount; // TODO: Fix this + } +} + +CLAY_WASM_EXPORT("Clay_ResetMeasureTextCache") +void Clay_ResetMeasureTextCache(void) { + Clay_Context* context = Clay_GetCurrentContext(); + context->measureTextHashMapInternal.length = 0; + context->measureTextHashMapInternalFreeList.length = 0; + context->measureTextHashMap.length = 0; + context->measuredWords.length = 0; + context->measuredWordsFreeList.length = 0; + + for (int32_t i = 0; i < context->measureTextHashMap.capacity; ++i) { + context->measureTextHashMap.internalArray[i] = 0; + } + context->measureTextHashMapInternal.length = 1; // Reserve the 0 value to mean "no next element" +} + +#endif // CLAY_IMPLEMENTATION + +/* +LICENSE +zlib/libpng license + +Copyright (c) 2024 Nic Barker + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the +use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ diff --git a/vendor/clay/clay_impl.c b/vendor/clay/clay_impl.c new file mode 100644 index 0000000..cbc311b --- /dev/null +++ b/vendor/clay/clay_impl.c @@ -0,0 +1,2 @@ +#define CLAY_IMPLEMENTATION +#include "clay.h" diff --git a/vendor/stb/stb_image.h b/vendor/stb/stb_image.h new file mode 100644 index 0000000..9eedabe --- /dev/null +++ b/vendor/stb/stb_image.h @@ -0,0 +1,7988 @@ +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data); +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#if defined(_MSC_VER) || defined(__SYMBIAN32__) +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { + h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + j->marker = stbi__skip_jpeg_junk_at_end(j); + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); + } else { + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); + } + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + int hit_zeof_once; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); + } + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } + return 1; + } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (len > a->zout_end - zout) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + a->hit_zeof_once = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub +}; + +static int stbi__paeth(int a, int b, int c) +{ + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16 ? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + + for (j=0; j < y; ++j) { + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; + int filter = *raw++; + + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; + } + + raw += nk; + + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; + + // expand bits to bytes first + if (depth == 4) { + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; + } + } else if (depth == 2) { + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; + } + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; + } + } + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); + if (img_n == 1) { + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; + } + } else { + STBI_ASSERT(img_n == 3); + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; + } + } + } + } + } + + STBI_FREE(filter_buf); + if (!all_ok) return 0; + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + } + // even with SCAN_header, have to scan to see if we have a tRNS + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } + if (z->depth == 16) { + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } + + if (req_comp && req_comp != s->img_n) { + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/vendor/stb/stb_image_impl.c b/vendor/stb/stb_image_impl.c new file mode 100644 index 0000000..8ddfd1f --- /dev/null +++ b/vendor/stb/stb_image_impl.c @@ -0,0 +1,2 @@ +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h"