From 843de325abeaa0a95bca80376a73d95f91e56cad Mon Sep 17 00:00:00 2001 From: falkTX Date: Fri, 23 May 2014 03:56:21 +0100 Subject: [PATCH] Experiments with NanoVG --- examples/Makefile | 13 +- examples/nanovg.cpp | 244 ++ examples/nanovg_res/LICENSE.txt | 18 + examples/nanovg_res/Roboto-Bold.ttf | Bin 0 -> 135820 bytes examples/nanovg_res/Roboto-Light.ttf | Bin 0 -> 140276 bytes examples/nanovg_res/Roboto-Regular.ttf | Bin 0 -> 145348 bytes examples/nanovg_res/demo.c | 1094 ++++++ examples/nanovg_res/demo.h | 25 + examples/nanovg_res/entypo.ttf | Bin 0 -> 35392 bytes examples/nanovg_res/images.txt | 13 + examples/nanovg_res/images/image1.jpg | Bin 0 -> 25760 bytes examples/nanovg_res/images/image10.jpg | Bin 0 -> 3439 bytes examples/nanovg_res/images/image11.jpg | Bin 0 -> 3818 bytes examples/nanovg_res/images/image12.jpg | Bin 0 -> 5452 bytes examples/nanovg_res/images/image2.jpg | Bin 0 -> 24091 bytes examples/nanovg_res/images/image3.jpg | Bin 0 -> 29282 bytes examples/nanovg_res/images/image4.jpg | Bin 0 -> 23830 bytes examples/nanovg_res/images/image5.jpg | Bin 0 -> 27131 bytes examples/nanovg_res/images/image6.jpg | Bin 0 -> 25116 bytes examples/nanovg_res/images/image7.jpg | Bin 0 -> 25590 bytes examples/nanovg_res/images/image8.jpg | Bin 0 -> 24607 bytes examples/nanovg_res/images/image9.jpg | Bin 0 -> 4035 bytes examples/nanovg_res/perf.c | 164 + examples/nanovg_res/perf.h | 43 + examples/nanovg_res/stb_image_write.h | 511 +++ examples/nanovg_src/LICENSE.txt | 18 + examples/nanovg_src/README.md | 104 + examples/nanovg_src/fontstash.h | 1670 +++++++++ examples/nanovg_src/nanovg.c | 2474 +++++++++++++ examples/nanovg_src/nanovg.h | 590 +++ examples/nanovg_src/nanovg_gl.h | 1311 +++++++ examples/nanovg_src/stb_image.c | 4673 ++++++++++++++++++++++++ examples/nanovg_src/stb_truetype.h | 2065 +++++++++++ 33 files changed, 15028 insertions(+), 2 deletions(-) create mode 100644 examples/nanovg.cpp create mode 100644 examples/nanovg_res/LICENSE.txt create mode 100755 examples/nanovg_res/Roboto-Bold.ttf create mode 100755 examples/nanovg_res/Roboto-Light.ttf create mode 100755 examples/nanovg_res/Roboto-Regular.ttf create mode 100644 examples/nanovg_res/demo.c create mode 100644 examples/nanovg_res/demo.h create mode 100644 examples/nanovg_res/entypo.ttf create mode 100644 examples/nanovg_res/images.txt create mode 100644 examples/nanovg_res/images/image1.jpg create mode 100644 examples/nanovg_res/images/image10.jpg create mode 100644 examples/nanovg_res/images/image11.jpg create mode 100644 examples/nanovg_res/images/image12.jpg create mode 100644 examples/nanovg_res/images/image2.jpg create mode 100644 examples/nanovg_res/images/image3.jpg create mode 100644 examples/nanovg_res/images/image4.jpg create mode 100644 examples/nanovg_res/images/image5.jpg create mode 100644 examples/nanovg_res/images/image6.jpg create mode 100644 examples/nanovg_res/images/image7.jpg create mode 100644 examples/nanovg_res/images/image8.jpg create mode 100644 examples/nanovg_res/images/image9.jpg create mode 100644 examples/nanovg_res/perf.c create mode 100644 examples/nanovg_res/perf.h create mode 100644 examples/nanovg_res/stb_image_write.h create mode 100644 examples/nanovg_src/LICENSE.txt create mode 100644 examples/nanovg_src/README.md create mode 100644 examples/nanovg_src/fontstash.h create mode 100644 examples/nanovg_src/nanovg.c create mode 100644 examples/nanovg_src/nanovg.h create mode 100644 examples/nanovg_src/nanovg_gl.h create mode 100644 examples/nanovg_src/stb_image.c create mode 100644 examples/nanovg_src/stb_truetype.h diff --git a/examples/Makefile b/examples/Makefile index 73b50a1..21c824d 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -16,9 +16,9 @@ WINDRES ?= windres # -------------------------------------------------------------- ifeq ($(WIN32),true) -TARGETS = app.exe color.exe demo.exe images.exe rectangles.exe shapes.exe +TARGETS = app.exe color.exe demo.exe images.exe nanovg.exe rectangles.exe shapes.exe else -TARGETS = app color demo images rectangles shapes +TARGETS = app color demo images nanovg rectangles shapes endif # -------------------------------------------------------------- @@ -51,6 +51,9 @@ demo: demo.cpp widgets/* ../dpf/dgl/* images: images.cpp images_res/* ../dpf/dgl/* $(CXX) $< $(BUILD_CXX_FLAGS) $(LINK_FLAGS) -o $@ +nanovg: nanovg.cpp nanovg.a nanovg_res/* nanovg_src/* ../dpf/dgl/* + $(CXX) $< nanovg.a $(BUILD_CXX_FLAGS) -Inanovg_src $(LINK_FLAGS) -o $@ + rectangles: rectangles.cpp ../dpf/dgl/* $(CXX) $< $(BUILD_CXX_FLAGS) $(LINK_FLAGS) -o $@ @@ -73,6 +76,12 @@ text: text.cpp ../dpf/dgl/* %.exe: %.cpp win32/distrho.o $(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) -mwindows -o $@ +nanovg.a: nanovg_src/* + $(CC) nanovg_src/nanovg.c -c -o nanovg_src/nanovg.c.o + $(CC) nanovg_src/stb_image.c -c -o nanovg_src/stb_image.c.o + rm -f nanovg.a + ar crs nanovg.a nanovg_src/*.o + win32/distrho.o: win32/distrho.rc $(WINDRES) -i $< -o $@ -O coff diff --git a/examples/nanovg.cpp b/examples/nanovg.cpp new file mode 100644 index 0000000..b9ab6b1 --- /dev/null +++ b/examples/nanovg.cpp @@ -0,0 +1,244 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2014 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// ------------------------------------------------------ +// DGL Stuff + +#include "StandaloneWindow.hpp" + +// ------------------------------------------------------ +// NanoVG Stuff + +#define NANOVG_GL2_IMPLEMENTATION +#include "nanovg_src/nanovg_gl.h" +#include "nanovg_res/demo.h" +#include "nanovg_res/perf.h" + +#include +#include + +// ------------------------------------------------------ +// get time + +#ifdef DISTRHO_OS_WINDOWS +#else +struct TimePOSIX { + bool monotonic; + double resolution; + uint64_t base; + + TimePOSIX() + : monotonic(false), + resolution(1e-6), + base(0) + { +#if defined(CLOCK_MONOTONIC) + struct timespec ts; + + if (::clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + { + monotonic = true; + resolution = 1e-9; + } +#endif + + base = getRawTime(); + } + + uint64_t getRawTime() + { +#if defined(CLOCK_MONOTONIC) + if (monotonic) + { + struct timespec ts; + + ::clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t) ts.tv_sec * (uint64_t) 1000000000 + (uint64_t) ts.tv_nsec; + } + else +#endif + { + struct timeval tv; + + ::gettimeofday(&tv, NULL); + return (uint64_t) tv.tv_sec * (uint64_t) 1000000 + (uint64_t) tv.tv_usec; + } + } +}; + +static TimePOSIX gTime; + +double glfwGetTime() +{ + return (double)(gTime.getRawTime() - gTime.base) * gTime.resolution; +} +#endif + +// ------------------------------------------------------ +// use namespace + +using namespace DGL; + +// ------------------------------------------------------ +// NanoVG Example Widget + +int blowup = 0; +int screenshot = 0; +int premult = 0; + +int mx = 0; +int my = 0; + +double prevt = 0; + +class NanoVGExampleWidget : public Widget, + public IdleCallback +{ +public: + NanoVGExampleWidget(Window& parent) + : Widget(parent), + fContext(nullptr) + { + parent.addIdleCallback(this); + } + + ~NanoVGExampleWidget() override + { + if (fContext == nullptr) + return; + + freeDemoData(fContext, &fData); + nvgDeleteGL2(fContext); + } + +protected: + void idleCallback() override + { + repaint(); + } + + void onDisplay() override + { + static bool init = true; + + if (init) + { + init = false; + + initGraph(&fPerf, GRAPH_RENDER_FPS, "Frame Time"); + + fContext = nvgCreateGL2(512, 512, NVG_ANTIALIAS); + DISTRHO_SAFE_ASSERT_RETURN(fContext != nullptr,); + + loadDemoData(fContext, &fData); + + prevt = glfwGetTime(); + } + + const int winWidth = getWidth(); + const int winHeight = getHeight(); + + double t, dt; + + t = glfwGetTime(); + dt = t - prevt; + prevt = t; + updateGraph(&fPerf, dt); + + if (premult) + glClearColor(0,0,0,0); + else + glClearColor(0.3f, 0.3f, 0.32f, 1.0f); + + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); + + nvgBeginFrame(fContext, winWidth, winHeight, 1.0f, premult ? NVG_PREMULTIPLIED_ALPHA : NVG_STRAIGHT_ALPHA); + + renderDemo(fContext, mx, my, winWidth, winHeight, t, blowup, &fData); + renderGraph(fContext, 5, 5, &fPerf); + + nvgEndFrame(fContext); + + if (screenshot) + { + screenshot = 0; + saveScreenShot(winWidth, winHeight, premult, "dump.png"); + } + } + + bool onKeyboard(const KeyboardEvent& ev) override + { + if (! ev.press) + return false; + + switch (ev.key) + { + case CHAR_ESCAPE: + getParentApp().quit(); + break; + case ' ': + blowup = !blowup; + break; + case 's': + case 'S': + screenshot = 1; + break; + case 'p': + case 'P': + premult = !premult; + break; + } + + return true; + } + + bool onMotion(const MotionEvent& ev) override + { + mx = ev.pos.getX(); + my = ev.pos.getY(); + + return false; + } + +private: + NVGcontext* fContext; + DemoData fData; + PerfGraph fPerf; +}; + +// ------------------------------------------------------ +// main entry point + +int main() +{ + StandaloneWindow swin; + NanoVGExampleWidget widget(swin); + + swin.setSize(1000, 600); + swin.setTitle("NanoVG"); + swin.exec(); + + return 0; +} + +// ------------------------------------------------------ + +extern "C" { +#include "nanovg_res/demo.c" +#include "nanovg_res/perf.c" +} + +// ------------------------------------------------------ diff --git a/examples/nanovg_res/LICENSE.txt b/examples/nanovg_res/LICENSE.txt new file mode 100644 index 0000000..2a03a1a --- /dev/null +++ b/examples/nanovg_res/LICENSE.txt @@ -0,0 +1,18 @@ +Copyright (c) 2013 Mikko Mononen memon@inside.org + +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/examples/nanovg_res/Roboto-Bold.ttf b/examples/nanovg_res/Roboto-Bold.ttf new file mode 100755 index 0000000000000000000000000000000000000000..aaf374d2cc000c058f434f43c3b53bcaf315e2a0 GIT binary patch literal 135820 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 TYqd&VnqJLHIX9iq_T+y7#;O#c literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/Roboto-Light.ttf b/examples/nanovg_res/Roboto-Light.ttf new file mode 100755 index 0000000000000000000000000000000000000000..664e1b2f9dbafbf6280305123d2df7fa0c66cee9 GIT binary patch literal 140276 zcmbrn2V4}#`#(N2ySGQ@=ql0$6hTlx)TlAW78`cKE(#h%K}E$9d+)tbR8+uzoQO4Q zj2dGSL;RS;Bqk<)j4_EV#x&)Y|9j?GxHHN3`~AKChqJr0ZRUCAnWxW6C?UiR8<`k7 z_e$)0yL75b==(Pa>HAaXzWv+Zu6s9|Fu(JJC~@8U_3d&k^+h}(?k(_uXJX&zW+x}p zU_$6tyk*3w@guXQADFR|5by4U*tHuqH9N45KRZiE&@5b!Ny{2DKKfqI1BCdm#4~5c zjGUZB?1%^2yMpUZV=|_vJ#<}gmykvkgic#JHg)9a-_jfMd+XzR^RYPLu-EQK)cc{{ zVC?wp9JlmYuc7`YA%=DtnWILIdYfHFdk4{e@c5BASxOuB4A*mTf8d0X<5PEq-8K=r zD+&GImX$dfO-ju=*6;OL+}Z`t#1f7hlwlF`J{s=Y`ZrS74krk? z_{6OV-(U0mu+eiT^QZQab}6XZ6UNWthiB|k%%5Ug^xR1uY8NNoWVt4`vVot$b_?+) z!-=l?x(mT8XeE(Qm*fRB5?6?ByAC~~0hYb=2}$G4XiX9FEn!u?NZ??c4CF8GoS8WW zJLf8Mbsth#<3K`pB=Qzp>5RB5kTCj`-;4KR&kO5L=E%R3*X2x-t^0{gkROo2bO_02 zbID4!lDJE$#7|yGy3wViFY8L0(jQ5&6h-{y{bUKAIfIgpG76<5%085>C_PX{qa;%k z*-p!Gj}OkxB%9<=GD|8XJ@q}w7`ciB%D<6wx*)Pp*N&W%^HB0ppH9~6O2|1DLB5d3 zkRH1FIG;eyDUWgOFiHxUCI5>3C^Ade0e$$G442;~QMzHISbj_b^%10{e4o4`-y%Ws z6*8RuOm<7*BnEpT^&>r`Y(m)^WT@N&eaI&H=+j8-Gvxp>gzX~ju z>5@X$$Zbh~>2)#{a7=@pR!V0{ zhW-uWCdZJW9B=Yja$Gi&Ey_iVOEJc8CFugV2g`HFF5N;>mtG+y@<383M-m_AM(XLd zlX4ETemdSWnhXOzidjL`CEX^nLY_ee>m~#DCo$GHs#bD5N?By5)PXFOMv@iu2a*ri z4}f+DOS1rrjx1(t$O%HspnUUALMo47KOTGV81tJZBokdkA4o8lYzrZ@&=9z2k<)XS{^tl`GMd-f3{Ni;K z=^GNG`w;zKje2`BK}jZilyPK-&Vhs|4EJ?G=}fYedDtg`)~8f`kNO1VGtxl0i)W@( zJ>l=s*CPj&&7kGZRsYq8k(;_-f%A_*%PmM6 zLC@#OROKlN(Y-?abUn!-IgM<^zC>{#jpP$me~NdsAceZ!;1{3?-8S-;{0dnr=aNaH ztRnTKu9zR2NO$E`l-ndz=}lsFVWbIYB1L9oqW%nNuVj%}{RiMhd1SNv3t1-D0X>$G zAe}Gfau>2*_5}BD#HFNV5B@-y192FHb9z@MPtr&~;n$^vp6^EHxYfcNq; z5@kH;%0`pH>`j#3!0{OH==r1{+f3F=y8wR>IitS@el?P0prlHhNn7yM9?~%I4Dc0Y z1vx1bp>rpjls#lNcw~mYKN+uFB4g#9WR$KwIShQiDz7Db z@p$$L&QBq8q%<-S{a!1LAa8-U86`qy%d1E)v~>piV(C}Nwgl2z zK1b%GK32eWfMg1O$mK3%DdbfLUI1$lmtU-ecnX=w<>EgSE+>VI@NeCuS33hV}C_iN4}7bfuBGR;j)eEB3vK9*p}iRzCVZke7=__%O<{cR6j2K=HxXW37$-Y*jR4RnnEb#I}euxeVe>?=qZA9Fq)|JOa& zpSTXgX+(@$wH}4zL0q4*-gBLT>ku06m)BQ46ot=0KA-=s@Hx)sw^ae|FM+wr+>N+q!7H#HuaPF6S%OMevxam1uiobzj_gf2?@UHqLjn!g-HY zZ2HIfkXAT9vMwAi8h^5eq54?xD-MswuQ<;_vAc-(VBY9R)icG3xUtocIaA3ix^`8Y zbiGLuORF-9c?Er1%nyu{rbBa@eOY(rG$MEa^kmLA#4*>Qpf`!RVbPazg$`($+l3hS zNVIbb*K;}j^1Z_KBt9RZr*ZnkocTh$uUc>CZF60yrf>W{+|TEQ=o99m;8R?u<2pU( zalCI_f9E`c&vUNxSlSc1I;TmLX8fAamoPUl2M=M)_Hlik>r4C|jgDX5YjiE_9N0fn z9(2S>q&=VCT>kQY30=RMPw7&sdMgh}d%Xv2gX+F({Sx{o^u=l&NM|>U47Tr%`3+r= z>u(xQt8O!r{tT)*STFY`ieJ_(!xK21;2o~d~+=P$sE;4gfy@f5DxLnqU8GaYnH zt_Sil;5hl)9=fT_^+)KW3fCKfcY#~jHah6BTTUG@RD;5l`uTD2>7&Z4ebqX|PWeez-0k6nD}ll!j7IH&Ka*2oBeai>{W7 zeqz^*Hn8X2z;ofz!L%uLapGu&<3@)r(U6f(UFw$9yH*^-twq;E8yohGuj{qZxqVcJQdDg;cWTwYqTzY)R+Il@e`-4 zH+%>!KR)*S?;n2+fIa>fRTq?jzhE&y9yBZ%e-xC$$s#JsSN#3s8|sHg)&7a6qd`;A z6oZI?iSn}iY919}TWYvcbD9R@XQ=@>_yq=9-6^QC=H3{v9xw zLg$o%pFOqr6>V6m*rLBaK$gI@1>?5cnj5PRIJPZ#)wWGBX)J#nqfRwH?MXliQUnNr zVv^%@RHH`XB6?%F$%ocK?_mbQ{1K%-;WeW46244A>JwRs!-;RmT%wR+#F+$<*<>d< zNDh&6Piizb0By-yBONY}jbT*ws*VA3pNT1Lu=F1XTGCRZGWAC%C*{>3n zB*|WKl|rQD(n<;Tuw;}@O6R2arEAi4=`%T0j+Q&hiE@gZDNmOd%h%-3{KBP-1}%zMoj%}30;&4uQC^H%dBylqTX8o$G= zL!nijDxX*W0q;;b%He8HQ9hpEh_V*-&V+p5?R&?&r|&+zn|Sx;@i`1PraRaJLC3< z+Z}F4-H!O`vcoml;u{gi;BGMzgHz*2i>M^&RtZ z-*)W^%|i{54hF9mYl6}i$2>boS_mENa6(x2$h z^Z|WH=Fq$JPx=>qPG8VUYR3GWM^(%W<%IeHjE8tBgks9hK*z?Y!n;KQsJdtOV+U|Y$}_^a@b5Zi{7I@&_~RkZDgC+ zX10Y*X4z~yn?d%G{p>M(%1_uY>?!9b>@W75yKB{P#FOl4KVD^n^_Vv?97!KWyb zGIC7PNs6SG?7%@BBuB{!_PmSa3Xik1)JyUr*CcQ9vE(Dwk?KnIFnd2C*QNSWZ^@7R zNAf2(qyQ<9d@2Qz&&cQGrW7ps!aooqHIPE3FezMWC^aHqlCPu)sWCX)59B`iQHmr# zk)O!}L`@!&N8ol(q$cDSDO!plPo<{hSE-rQM~Wq?6i2G0c&WLRAhnhPY7#oX@E3PN|ur!^FqN5sPr7XB^=zP5si>4B{S5CNa;7}cWIC` zSo%S_4?)pHQl%;guo&q_=_hcZW;B+@(RkWi8X^sqewH3c!=&M|oop{XlpfIp+Cq9P zJ)tdWEBcD`7i~@3NPkFwN+YC^QVMNLUzHVEPutPgWF||}FVa(4mUXl}eO=0s#!C~V zOess6NITGu(y!7pX|c3~cA}l7rP4ClL3X5Fq~)@cv_e`*N7Gc#6@h1V`*FoUeA*2BbC1E6-G$f5k1ZhknNfc>9qT%IiN}7>a5=Y`mbCN(> zkd~wsd4;qlZAe@4DrrYvBkjrSqyy*hC&_=CnLZQFdyk-`U~cx8M9Bu%+q1kDa?*JFem27oS6$VFjwZr z+%XHiFb};UGkh@r>hc*#Cy-2%LPn7>;6_u(0y3VwN#~Hsu!>iaIhfbWNfupA*N{|_ zO&8H6bTN5{t{~}TIyr`!y@bpr@FLS?WCL9ZxiyZ=pp)o#;1)k4CW-mTM~RN38Ia3k z$zr;e`Lk7YD#k0DPT^xlGvU8|la8m8!Cjt$$NUPZ_8a}3{z0E{{sIX9;+%!5WI5Rm z={SMpk$l(^(;z2zkX$2)>}Is@l~hXU>f{r&YLRiFU#Yz`d{~r`Mg|6Ue6xd*j)*d{ zNF$91iZV)(ft`#}Xs13&A<2Qu1DC%sdU;@{z_BAo8|6^3$A#48$W|(>LR-Z8l|vaNxhSdb36DN+jU6x z4GIeEXguF5$#}klZ%}e_lu@baBl`N*n{_O`)khkYh$y3-F_TrYXjO zPBvxn&0M~z;F~}Pnr(X32}i^EW)9y(JKR58ooKgH&uKS;6$hJC#LWxrtl}G zm|R`>vwZWIZvtF!-*CQJ!#5xEO%>m~>Vlgy`R1H~H)O!M9ejgow#n6i3pj^2yW?sV z-+b(j3yFLa&8yL#*u2U&bFd-cFKOTeV<8RqLwo)VJkCoULA>Nv#1nCPFL@O)K*BgG zb^gZxA`z92peG0$+^O%nM;AoRaeC| z&UK~h4TEfGZAdZ9FccWB81A~+xh;2l;@-}Ej|cO}@Ob3e+%wJdu;-s%o?eT+D!l7^ z&-cFP6Xvtk=TV)wI-~2HsLSehtvkQ&#k#lZ`PUm!Z)v@!z9W1u)NfF~L;WXy{(fct z&7h|a2~Yy+2Mh_=9&j+=Y`~qs=7A#urv{n=F9tb4?~V!T8ZsC($j(5GSj!m`57hdYIL4d2s{HC);#uu)p0dl9`N${Ra1&T3p3DMvPs92>bh zvLy0OlzUW%sO+c{Q58}5qnrnjtU)uh#) zSCm&~z4D;-kk*G=KWWph&E~dB+qrGeyh>i}@~Y|8N9|(TWwa}Q&F-}kukC5?(0*(C z2d^i+e&Y3SJH&M;>FC|DPsfsu4>~pPw6IfIr*AvwbS~@sdzU6%a=Ki7BlwN9H?DSV z(RFnpdEHqj|@P~!ex-n|z0de(bb@1Oci>RZ3>t$w}w znfg8J->QGf0Pg|!1|Cdmo%C69!{n*S2a_)+KOf{WD0p!F!7B$p8q#6Ni6Qrg))|^H z^!%`T!)6ToY5?&g7IbJ zuaAE+!EQqR32_s;Pna~plxfI}%xsa_CCeo%EURr+pRBxzY~t!k^(Sqg+3l1)n7p5<~v4|{cy(oRr*~Kjuk6pZT@lQ*)znpISKV3NX?4!(vuhfxS-a-)+SzNLuN%Ft zWW8a19sC8ZU%dXohJG8iZnWE2Z)4=fwi|nGOxrkXzo}%?sZAel`exI!&C2FFntgZ96 zZrob5^~BbzTW@WBw2f?Y-_~$jt8Lx44cnHrZT_~6+jed{xb5t=ifwncJ=?Bqud_X3 zd+Y68wh!8#v3>6L_1pJsFW-J;`&TW$0F1Jtau-x?AS-Goox91k+ zp2@wQdnfm4o}A~M7o685uU%fBys>#R@>b>*41>Y1r+d+0X z?eO0bv!nfveml~4%-XSf$Icz59T#_ew&VVe=R2Kt`tNM9v(L`4J7?@%xwBxWap%RI zU+#RmQ{83Q6}&5MSC?Ibc4h3Ey=&vHqFpC;UEOtS*P}vK=vf$6*rKp!;fTUXg$oPU z78Vwk6<#j9S@=_70ZPsN@)d!Fr8_PXzFus3FJ`@Q}4rtQtyyL#`Qy~e#4 z_TJcgfA8}mry~ENh@$32?TdOA4Jt}2np8BqXme3PQBl$1qVl4%MVE`N6@6CpP0_ug zM@7FEsl`fh{o-cD9f}7Ok1d{2yuNsQabfZPVpH*z;#B}FBNOD>jt zSaPG}Udf;P$UeJ$hJAJRMeJ*~uhqWx`?~Jyvv1J8lzr*@CheQCZ{@y?`||cZ*!OH- z<$lBd;QbN%o9*wizt{eh{n`5$@87zA|Nb-kukXLV|M>x@1O5k^A83D|=YbIiCLLIK zVDo{(1BVZsKk(s!TL&H;BnRCOHaHl0Fz#U2gCh=R9Lzbm@Zj2mc?U}l8V{a0c>dt^ zgLe-;IH(>n911(s>QMVbT@MX8G~v*~Lt76WK6LTWmxmr5CWqY*H$2?>aG%3zhbJ7K zdU)mGorlX0Up;*5@S`L0kvd139O-am(2)s8mL4fQa`?!pBNvWbJM!g`dqWOiHG2J-VxY4-Zc-DBs_){q<^(>7jZCl!}bZqJD()Fb!rDsd8mVQ=xzx27u$>eW} zGj%nMFlC#Tn+i;2rYojzOwW!gN9!DIa5Uy<>!V$c4mz4~bpFw;M-Lypc=XGoPs)_C z`en_^x|9tn%P5;&wzjOWtgP&E+0C-MWlzc~kJ%locP#Q)+he_sr5wvXw)oiAWBZSt zId3b{LXjfs>8H^4U zF-<+Bkc9YHA1TBmKK6c47b)#=P!~B>*G_#=_03K71!Z61wRrFA@cHk6Ug8UDDa-O+ zXS~Z@L++toC>eJDLSg6>pB8KHI_igSNZr02jq zA=yVR@D%LF}UW5hL*R47)l%0-U1}Vg@uQO#m6-8aVVIjeam#4Rn zx3`z4qW21k4+~33i1qZ2ZPr{`w6?tB%9Vp@N6s@TYv7j|(1>|Rhni)dr3XaT+i@Ffi~VW@<0TsVYD9Bzl`Qa8pyvrq!yu2Wkz1z2th zh&F~9N~3Bo7y}HYakW*DXPKlviG{mTjv};Jg3L(Lx*N3&6<_eBWd>Rq{QK`?- z6QR@xdQ5Tk6}}@8aeU_-7r}QfmTPX7YjrI@TKT|^daGjz#b^bqgnF=+!n4^IQ>WTrGm!Yj(> z9pagH1kb8x!JiB)$(&;e65t$5FeqDI1TxM>lwUj8e4G zIU2VbNP>@#!rZ(J?f^TCxUHn#2b6m2Zr)s1PdTJMVBP6B)-HvXKw7VyLgL$QON->{TH;H%pvA3kI;GZt=6A2m-FoH%*&aN^aMdxS}lb!hyi#9nq zGaQ-VAm%6Z6()NpeuDE%N6rsJ6LeCn6zZe%&`VM{eN!z9q}}DQcCt`qMjE^g4f6UllPMat0j2K#X#9t$c}T8Y%<|bxrTnahkjO zfcg~U{-&%IJXkw0YupK-gr}#5F>oFqAH)o55WUS>r2=KRA9JmSFyd zRP(l1EF2f|;9}!9BK_yIZ)0Gr0*?9507F z`{L#w&%XHL_Z(w!@!`^%eo@pa`LBhjYegbp=DV`FqRlB^NJBQcal)SfhXMF@a=4aUFl#BZ5uc}cEp%;1N}O{YyE}=S{p4yuni6^m4&WBqf1#WEJj6WPL35OJLbrd z0ae7A&u6GhVm`y)ALJIUm&V+^JI&mm?O0&WV17z0{WMiwOS3vim6f4VTZSZjj`w|- z8+PbR02yuR2?eUDhIbcKT=>kO2835?X0pl7h4Pl|qM_26bOCm}0xi#j>w2Z$wKp2= z45jsJt6=6rui@EJyI39`L+7z`;;^C^lZ&Sy4wvXs-`c(!Jq@M7wN>D^nYWKVOsHdC%|YxI05BQih(|CqGYcPx z3u8;Vt#297GOK6dr>##-ym+>yPZ$T?!6N3G}c0XkO1@>eDYi zSMSqEs-u~ew5@sj>`~)BdHVg{BXAW`#1)7b8z)331T>BSz)^!nqY|T|?JOmn78sNC zAqfbFfaA(`51vOj<~T-ByDux>kxuKns`t(3)MYc^{bvKI9)Vejj0WzUfjb{q6w%~O zOM6~u&r54hkSM3hD0?qH-cgu?QG5B1!k4=r7ESZ+DBv|*!jb1(kq`lWg28Z|UdJyY>X<*Ma}4lQ3(xLfKxyLjiKdAYfu znPl*t@2hF1rv+*on@6LWQc~M%lipJZalI|UIKMHd>j_Ro*H#TJ| zbfPI*!SLVao8e-k$5Eyp7jE;TXuh%M8>;9zCBW{}RT$-pvJg2|W)qEi1GiD_E6O76 zTidgVpas2`4}zZYD8U&SEUogA^a^WVU#YJ?n@wkqq%&r7p2f%}S|oig9RStp5uL;9 zrN(6j8c5kt6Oytfa%N@4FHHvCS8RrIE!xUtMT1=VL6JVMJS(;5MS3mW{30DN&muFB zWl{|@qY{Ui;XT6{2aRuT^Nl?=+=7MmO8^ME!*Gv;E143<5I?$&4Ri@W4*1JLbg??z zqnEg{2ZqR@ji(9m;j^S3l}A}lMuyaXrn-2fx@Z=4_f`A=Z%<6nE`mn=5ls-IX(PpJ zq+>l_E}D(?q9MTR@uPnh;Pu2^kcEDlN&!@Go?I;jp#;}Ra~Kb>%46wt8Zk6u)Y92g zAwrn+S<%rC7R_kuYgn;@Zd@>?f7X`lxht1VQTCp_dT_}nlj4VrP&0TY1S4bASCsp@ zm58gpj)Ny}TwH>O;P}et z4xe4?`KDWZUfP1C9zG9BhE3|oq-GAi5?1eZbXSe)X7z1#h?@sOqrH>L+6J_{He6k( zj_`1&pVDcF!!CPq!q9KTfK_ocIfc+L%KDJL4egrOPHeing7T=F<=txD;DTe?~!F51VTRChV0fu)9+-V3(evX zDAoKmwew#n6-?M7P%7-KuqInvMcfMm{UL+~(Gck)bL!?Og~y2;M94kuU0 zWkc;*K92)xt1(Su-GVFzuSGSZs0xY=t=0=>5S*jXB7q3xw`P(>+Gz#c*mLrC8;ZxEMEaQC|L;kcMx;T50QNV!@tQ|?$Cn7 z^{DNbQMPsT0F{1R2Ky)wY6Qk4iESox8x^_(gL?xc*gQer&pgye_kLABqY=N}rv?v} zRpuRx1-h;;d})&(&fcZSX#(DOUy{)mN8~)z(wU|+XgZ3?LFPUXc#2HX4j6kYtB_;Q zid%CYZAF)yN4r5~v=r%A^XpPbWtz09@(z2Oy<^^!sxzjUzpn1b2!WGoJ@y}G0#s%} z{C3b??C8+40J~^AFyll!IqqR)aZKOlBVdhnJ`C1oMoiNnAQ?6dCcay&{NuFBi_@k_t*5a9bBfMr?q^APqvQ(^ zVsG?M!t50dYt8^s(Yhqw7E62Qt1DZlr;+I!D=>{94Vh@AW&`##=6DQSmgEUL#TRX+O+pr znOrxz!SKS z{r{gIimNSLu<%`Y9QR6DGvsd^)@ASlKakJ{xM@tuy|3{=)sV<11F46u64i zxd2xl$l9y!hXIqspiz;gJDMbW1LuwgL#a>g4CPj*4VR>z4Bqijq2m1;;>M>w@2md# z%7QNcg1tTJUwt2R`^(Soe?%ereg-(K&>ex#Eku)w|A<3Dd+vbB-4>x%UI>!|5mq$t zYU_-s!7I0(d3?wV`h|#=)Z#gI8h!ZF^TjikObZO15$b;^R~<{oTq-|yTwTuAn$-}xcKES@uakJM z8*)eg&hu>eyCxQ25-DyL9|)MX#gpVFpH#p8=?C>r&aKk2J33=!=%Y&WL+Pu^&{H2= zJuQitErotTZ{mZNn#pn?#$NB8tiz;!ItmZ=;jJsE6kg9smP$9^li*hxfoI z!P1ZL5Iy*LEPXptNuY5PX1l&wI&3 zw{3d?&Vivte_sJEjHoyw7gw}xqZQlqR~rLyn;z7x=hSSk7n&9vsv=}!lsHWr72a9& z4Bg}ZVp%rLU8mNk&(^5@)dB11Gu3yU)SgW+H#7UQ`R3W|4!b7$6NLVpLVxU#S^W3@ zh*mX}Y5g$~nfppC4RX;I%QEP@E6fk4nSWWqmPm~%N1GooUkL#x%u&359NurQS${UK z)YJ%Td2PbVK+Y}oHjWUJz0T5T0)%-ycZzsDlHPjpOd14t;U}r7L*z$k1JT}Y^$LBc z%*EUZCq{iV4+r5zR*o^Q%UijL0R`kDSCZ+&>(|wC+EuCdmuH&(1l|=>wLsFL?;?i7 zEdBsZ{eo8nCTPqQHe!m-_!ri9;C_O(yNR|N0GH7S1D49Qd<3%C+M^rO6bfTdJhTMb zO+EIX|0r|+?2@Kn(FJ^}>BMG_YVw^QLVvQSwXt<|oQso+HVk!@QQGBcA6n$5B z5t+FS0TKtxG=~Q?Y9c*lJLDrW8(Quv2VkL-G?XJ$N{FTJPWgpx$HQ3TD|{@_W3R|P z!3!Fbd6pj31xeS12TycI(||=q;{qm0;gMyNb6sJXAv78743#p_ImQ{jxw~LmofTt}GJyif+>qw^)FAq zGCh>pxFd587Z>$fI$ha(l}0~`ctB&WA5iA!{`Gdm`9B4&Gts~QRO5PtrH2Be8mO~QyIHvg~t`2&W~pkFWEA3KCr4x0ju3pj=Vjtbz}8#L3;3J$I7 z+PpD265+sh1e>z;P=Wnft2por*${~?SU6QgR!fj2K*ZgQ63E0S2s4AleW3nI9nZdZ z*DPNs$SP|)BKMA-nLbrcHJcN2itW1tIl#7X%C`5 ziU79{zO=;SaGb_%@a_Q);PEmfMh}CvwFoQ5G=lp(p72c;%x~`b7+Mx>_bNBgWB7@k z{Djf&Jol3jJ35Jn^sHQWoKqsF%NxuRBNUJM&f~x}fii9#{&w`}?hQd5LR{;_2B(ar zOjV_uDFwSt4)&uI{j98llu9fwIp(r20zF^CRR&cweVeM=OWzNSBG;fxk)t? z`1Pg9B?&I6KV0i+Y#-2Wb?<^*2_;$Yex0)uv=|Y-WqLuOg%(qX9C~r%lc!-psewV| zg-iP+)8MD?{+K?L{yr>6t+eP21rQ@&Vs3dL`X?|T^rwHESvFiu7)KlfL=yH80CKVh z2>yLehEOR)FwJ7IdN(i2IdD8@#f+JI!(p_}8<}zAwmFsM8@J6HXXf0LVU-Y}4RBRv1=&k_)aJK_IljH2maNw0Q}}ylR!&jW)*nT>){I ze}GuLQhUG9k}&2rCa1f>=vLd78*htRyffOUxrqoy18q9;J>u?hX-(l*+&qZWm`*j zp0*@NCC9i!IkSozG(9%|(3zZ7vsY|&r_^E5X7;=Xy~&2|W00yliVd;u71=F7jss0mI1i3p(a`9ft84TI)t>*0I3=Uf@L zbBrsUZak5*V(#o6AvtoZw?~iv{H}RCTYqZ%q6uba-XqS(c)nmA^ojdhII*K zqwUg{tYw>IBab+Q4G2to#Ec1K9*-cyz8Ah8s6jWspo;q9=9lX8oU{9j&z&zRIm@CR z^CY9kK~LcPY5L?$70`1g=p<*jJoyzmk~%`xpqx*{ ztiY!G%Q}+o);p69G|x$OZGHecQqG=&>081Y^UgnQ=UzQ9?;X2YaDo-9_ z{xdo4!Mg&MOm&2Gtr`|<9YpKC4Z?*EqQRn3_y4;N0%;K9##P&Y&_Obbn>&r2mQXZ7 z=pb3!A{%X)tmz;_2kdwr#+oGd<2uOCsRNmN@7=Jw%2>1DSMw;ka25fWDl zdu#WFlb;+s{+W(tZkfAa!Nh&ty1H4Ms7OMqzaFtS(&w5 z!L|xfL3c1B@yU}jJy^RT7Wa(^C&LEfI*}V4^ypEKc76#B-2DeMo;2t5X~_wBk{o6V!^0vG$0}6wZ7JPR%&#=TKR7){JGzg_odpk z1V^=7h%_qBm~-Gl&XQS63j=e~ONWoS|BOvGuef}0#yE++xD32Sg15elzS#PfwB~JO zp=MPI-x96yEz#>C3O@HTOHCc2j-Ewb=gg5BzqoASlN9TziS8mQw*DpU{Wc;=_?NUR z|5yJ~Y=TFu2l~Rj3EYMkq3tD&l-PS??!_rlmu9J#(w;;sF%QP5muJ!8@4UmlGKXHi z%)WYYncY2o+FW0Blwwslk;-uFsQJNc=*Om?njfcj=){;faQnfe2=@{UZ|;LJ*^}x> zjg@N&i>gCctLy0OmFkD;hpXuvb=@j9m!2{|G=D)qQe#;-^RUd@5a`NR1@5ZzMr|0L z4Z6fYf%}=LohKT$*50|CMl^*A`lwcygg@?+FokwrtVTpEp6Aut#leNozoX{wU7&1#Z~$ajVfWtEikPrfL2 zuJRCDXtg`4+C3i6 z)p#VD!489P70GAZea_xGo;z{s)QP#r>&=-mc<9i<)8?uf2@m(*nXzo_;+c03JWfb# zbal<$35B8IyT{*Ib14FSFsVi;d&R9(^8e)o1@`lefR< zvv;($9cj}0c#A%JAS0(bDGt^mA0O`OkM+We5Z-nBIXS~djT$y(PQCKHtZCD-a*wNL zA}+1DlU3Ltq;TThweL4dOn7|Y?u;d4m(94d|6u~}!x~yF|0sFj+X&5w$=)05c)V?y z8=`DZ(VVdxYRw0}Os6iA?vbp=ogS7X$Z(6LSMBcjv2c3Qx}7!j2ly~jkGInkX^pJJxavA_y;WkACxSYj$6)XeT>Bi4dcGV8C8zbJ$%161li7F;@Pltk;uWdEo(=mqk;QN zutf$Mwlzz6j9y}`61JW-+#i*CSh!>;^{d55tye`X$zd2K_m|e}aNkOoY31XXd}CA2 z*^jQhom23EB-MIXmR)fF?_D1n{#C>mG`!BOuiQS&uko@_u@)-^@t0stcenAdYwXs> z2WnSg3R3tCHwM9(7X%qS9GkK^L2H5#B(@448J#PPL1JZ=r&xi7OjjGX1@!@OaaV9G z=r0@sOcFy~Bd3wWOPA?mn@F^>uS2I!`j0DfJNBP

d7@`p2QLizj&|rCuF{#nA?9x>vkR6mGm#}d0|}og~a^> z4z%siuGQ`VFaD&yS1wau_4~_L)bAH{O-y{FV8E{47bj$W*yq5&{ja~#?e#-RNBX>< znQ^h#o`G!U=jwIZa$PPZ;;b*r(pNV?Vtbp)0FfZ`96FS7S? za+x|Hduj&`;y< z^3ZnniOO&#Q<7T^O(;j{psBl}Xlsiv@g%I1Pk4^KPzdBG-S;)LRPIScn@ zWG_;GROirn`0kw-^sIU6&lD@{)L!Z(^&(#fg>hKI%GjTvZys04v0zHXRXA}gk(P)^ zv?-8h79(B=4Wv4x;v^y~5ToT8PP3d=I!QxK-JOt^{Blsm>7Rlsa9xJgk~)wjXp^h& zz}Ei~PGQUJ9a-qizSHIxY%cGUI3#rx)9qO@bAywMn%8N8=dlrQCbA246Y9rzoRR)! zcANe~yLGl#Qs%E&q+X6mj_ln%dP9#E?eXn3aw{En_R}$$5zR_jG*We46b)C8*T>?3#fjq}F*6OL? zSdn8|*I;ZcmH@p{dyV^hNf2^r#Mb(_Hh<-Bc^2W^Aqpe>eN|Tx=^_e}R!zll{ z>D^j1Z0}|8>K)O%U)RCCyY%nM^|^FiqQuwlNW?&lG}?l9D=f7(4{+_s17*WqkYD5#HWbqPNgAZIktpC17oznD8w-&R!5? zu&jC#31Bs14+=J5WFzTPHD`jFLJKC)-IZ@kuS;!G)i>zzR9gO`4bRU2Jx^u1?1=6? zjLQc=MJ~q{=X_coeN0o_GnuVnmvonL&xg2YyLL}QFvNzSPpq8a>sZgwFLakh%ohLE z1!sYCWX=FI{FUt>M+lGEBB&HiM9h{iy{E5lkp9ZhT~^deF^>x2zxt1`->Q8QHhxPR z+ge0&H1Cz>I~2XwycO1JpvlRV8$2d=J8oHke^mP;xXo`#f)9r{r*&6PU$(h^&KSp- zyzxoPqu>rVFZsA|TBanwxSZR6aO?KwKXhLL-afGF0zqF6_->kLQ*=$6t(x!IM2W@9 zf<6LoC0v)WZmcBzN7YQFAMY?bDt+=X)jjV8(DBY9mPKEa8dzu+v40!Bfwcy&<>s=i zGRwUwY7fd+61d_EY@&Uw$=aukb{g(W^;WbC!g8stB0ObL6hp$rv<*-A-6?To!3O=H znSIClL45WdJicd}S6W8d%@Oo6kZq$$x=Wyit5sEOn??)T>P*mrkPi~iQR9AI&~uF& zhRX+s`hrIx;TfN>8d~~4dwNA^#xkp3TKB%>$i|JvBO5juGsb6($;=u%HdCIu@$~79 z*_%$D+%#_CqV(*six-at?al|k?jiW~6hF?dr_dw;$4>ZIPJ!o!Q~cjGY%BIt@uf>U zs;3{ZBfx9*Jg9{W@8ZwAFP@nSo!U-&7SDWuFD2GIBUrVV7kFkRVht;F?}_(+E}r>F znv22KV=mRLoo}r%ms(rq5+fPvR4G+{1$lD*Jlf@Eu>-|w7H$U;H^IxfT>W2{qhd{` z&1x1!(F=tRJUYP=`xakSfNsaXD#2Q_#zV%ZESWQ9Ez4r;v%O_k7tU-`AFEkd(Sou4 zCT^KjvzlemCzIobj-Z?PS}`{uHUM+M72g;vvW&Md#9p2af)X@OkVKTFZ3$5;|n z9)J%*zA8x*Vcc4K5s~O;lOb;vyyHP#?)X4>Fe!#g#5n zXYwre+iB)Px=!kh|D+6ab|`r)UB)-U9bxg9WCs{B)S)^A#B-(~8YsRc$v+~jr*tSy zQ}-p(0d!!Zx{szMs@v6Ed`ogX9o9=N6dQFzBF*5Rdj)$zUhF~S#eN5$L?|+)sw43> z*`7j0;5um&E5MQRX=T)!{F?-Gz84VOgzq9OyT;-qxF96ENY~)@BTt+~j5)j9vPAmr9pcS}1ICtK8ug_obPQ z!xi&}*abbb{MzWK+#CJ=*ep_P+qcd8rr+)P52wDW#a2ic%{2m-uKPo3Gp{}|XFdW(lx#s9tBdYFVgqjo z`-3yM8unWwJ-LI^-i?PKtXIpM{B$C&hRpc}wPPk?l$s8K(3XYgX>nS7c{8qh37Qvm z&H3oS$`dnb)~&n&$HIdfZRqsYI(6;roF3EH$*n%Tvt!NpPkRnf@2=anJ31&eplQzV zQPg+I&@cK7nS7uCG=v@7gbzUIVceg|6RX^U#OGqDmSQ6Rviw@>reE*afAY?!RGB?%N&W<; zyM1a_X~Q<>CSAF0b~#!;Ccj;D>(a`uY3CZKmAOUprIpJMIx{vg*DEt^;Ou?t3XUZ$ zUcWfiNm+g@W6Y?mKW~oqJNM|d<^vOYjvm*R2CjZ{!X z?&B}~TVVF^$RP}7?J|NN*~Vo=z1_9wDfAIO-(gAV!nv&vN2*V#50)6?V>%qA7EM-a zotdpZ`nW>A?3Db=;|hJC0*_p$eko4&2O%=ugwazkz+ug4Kz zv6q1F6jLH|*O#%z%W2eWx(sN>=vY&!QE@PM-QPaR!V07=#u6 z&Qp2Zx^+fO;?UH=sR&*tjEd?xbkykaIh|fp=)4B~qY=Zt)~= z>*Fpzt>rOoFVCGdH>r8^=;pKy%c3N4>%BODlzfF;F&*^R#;@kE}ez1yf3-+mH zO8h+sP}8b4`C=OrHvZpo0J(SUpK}21E%EJ~=KCi7b9h^S!aR52@PCSL*M$x?7IMJ@ zw%R{^5ksiqob?)nx^FaKb(kT*0GA?M5ic!7ZYN|4|~1BIX8I&bA=mMdgJEDg*-LIod3N(vzpeNl12H_H z#3Dm|JVLhk)OSnuJ-*H=FE2-m9zA}vd3P$yYdXFu!$)*D-k_V)zd-+v_yUkXhz<4E zD5O-b)+GL=Ulm4poxy$VK{mV>30^#_z|x1#CeyU?@@dkLPUAb7-Pk*w$9HBw@wUs= zZt@SnNgaIeTC^=Bs)iGr;Du(jirZ|0*rJP=lQ#KXh*4n4EeMn67`~L*6<@RTO8#Nj znt(Vz7uU9415+w5@-U`riCt@g zTLropI@V1Zv10C9c?~cbPK+Ff>>^JNz!;swTiNXAQ`@JHseF!^0o$qSXMG@SPao*a zt1Z2^S+OjTtf5~U`dT2tIe1{f%8oF3Si+|s{~v2#0w2@a{XfrLW)dWsB(hi{8;L!L zBt>f}s@7Oa?SdwDL1`w0pq5f9wxVilRqZAtlu}DeH%b((mbQvoT18*9Mdr!>d!9RY za<8TD^8542&D>cs&vKr#e$P3G9R=gG7%SO9eKEpZF-HSb3Gp*?ns#(F1#3_kg;G*O z!^m4BepFIVG@*d>R9jThivDw3-{hxRwVQ3IlYwM`yk`lR~tP&wZ(z+7fY39 z?a+dx4)bB{%lIPGH^aTQtj$NcW%J>G|H6-5;&&nI`eH32v5!=rtpO}yF-?=KGl5!! z7Dx*~Dy+}~iH}-kp?3I{6COps1XAq`K^a%YNMX>&6_QiTplCSj3QZ*0sJD3}qXxM& zYG@Qa6^ru_pv$YS^@W67aJEsSZ*o1D;1y5Re`e2=*UvU;_I&4^K%^9BLu0Wk}ek^H@nfjA?9 z>du;ZmFh+M2Rt46V*i2y4eVU59>C6b2Hs)2mj099Xh#i_-HazPUTR#oFGF z2Th8+K5L>i86LjQW*b=F9YL)6jaLI<{=!B_kqu)32I!LpK8n2wp=cxdr)-bO{@YlfE`ncls$;;Wq=22t8UVHMVU&|S<=~{*UeBq zOehECP3cuGTPcElQk?<=&X@KcvluJOYACo>{;{N1Es zmnQ9sqAII!gsue@A%O39h9R+ z;-aviH8dtY=0kclCvDg@vDnsqN<>wk5O zT5Ch#&p*SVO5gFivQ_#Lx?=#YcZgI%T@zAB*j%pOso=@q}FWNA5Zk|-73C* zV%4fi$zsmExPz?$e+;HLYA~Qq`u7<5AWS2#7aRT^EeM>#dOhDRE-|WZczC7Ob=wWd z=<{^iQ<2F*{#}C0_2}5@wIuQPGnAWBvHAo4ek|%LSfJhrkPRvIKvluggS`P6iDIL{ zq*e~(~iTtG+Ucg`&spi?qf$jmmJh>+{ovFC6g>OluL4` zdIPcG(w2B6;>$Hec~);I%o_u9B6x@I6Tv&wqlk@{DI^vzy*gjwXGTkr)8)GvvU&rt zF)LGW6?qSmWfy;&ohe=`_5~=%aAbYztK{_6A2kiL_}YD=eCzl&_3h}J<~z!FvTvU6 zYTwep&xmgg!TiE>pCgvg7`NQj?dgUubZPWV*XL_QR;pI5Qe=$_4cfPFkkYnoO1UbP z%STkM44QKLEBkn;S_GO35q#A<3ReJ1dtvoZrkaQL+b~Jl#~S1PtCjtHt?Jg^4;Y(w zL0kby@5K8PtR zJN@GtGXG_(TUk5t5Ad#c)o?k%x*U(lw}u>ie4mzU?p?X2rE_ibY^!YBZ2N60sz@2Egc3w1|`DP1q{x+$P68(@g|Mgibb;@zdQ;hEf8jq-X<#tZTk9uTit}@Lrd(_MXak0w&FkR6Dh2^BZtJC zD5@DN3KYajZ(4c1iBjkoDKtk#`6pUe(1Hv!l|?Dz*|XBf&KG~*6ff85z(@oxRT3Fv zDhzLA%DMXCQjn|8QvupE)SSYZH0qLd>Qb^k1@&b7jFXoX4M~%Zx$AXBhWQ)m>9RdT zw(7N;sD^_Lma!PmzmHf#Ko~1>3ICi%roUzteUhcrD4|n^sJum~jlb`Q9%+xxS2tZu zw4f4yG0p}4CY%HcQYJB$gkLr$?dPF#kD@?HaTm$EimprSN9l??H=~dAmejjXhU)$l z>)iuqq#wRJ1o7i|bXpVNZ*nzx)!Lx4LahxV?Zs8Pl!zkW+{4ioWb$6X$wdAPLx~b+ z7!58l3b86nbS&!c$3_vrAvzI$(Lj=u1Od`w2#JAm81;5-$@lPL$-w|e`-n9M5XhmN z{_F2k6TkiI&ofN9!;fG2l^;KNkR|+z{NBC%$D*aTKRNKP+XoNbk_Quw=3rjFm{&Qy zdW^}&2!}x&t{+l8ZZD?p1)7GI2_khplx9o#Zlh@>9E)Ujz^hvpDr{_EoJCXJ!{B6; z?pwG2=-@wG&%VYlInHdeX0s!b@AZ$RI_|aCls#+s=tf!7^d7tN)WteY%`|9CTtKj2^2 z8JpgdJ@@rEwDs-F*YL~ZPVn75)1k*8pB=qbJ3teoeqCbFCW}L?xNd&vaB}fBJ&TZN z#^B6#`3Dl#%vC}BqLS0$rdSmS%9JG}d_puik?Nkpw1}R^S0;a*RHsZ>Kwwy%3LSg< zC2bu0@!7M7#;i~Bcjv5FIc{l<>PyG2T%|NDIy1P7qV(}A)n{wG`|3g z@NmO0GFs~}fy^EVW6?&qQb2O`$pG@iGd%?p=olqQop=2_r0?uSe}9>_yRqZ$`X%$0 zygq4Dt?eV%xVF7LPd)tYhV=Jp#eXsT^PihF<*K@K>n-<~yjMrP=bBOUS?jKw7tPs0 zJg+A2FY=a3>vdO5yD1o;zMFzUkt^3%_GIHEGK>%SYUB;ct#bHi2OpKgsw0gFM&vL2 zS+-B9P;@#MAR$klr#Pq8(c`UzZuG+`s6+oIofVN8Y0TbJL|oq$ss;7KMKM4_q5%$t zG0F+b0ee)ud7+ec;0bLe$$E}xoXF6SLgWJX4H>7Pt>*iL>zbI`%Ia%6_ zO|I3d;7wJ@trK2VxKYCs3NpiU!==#${lYWDNk1S|roln(lEVcE_Hih1XH|QCR0+}H z)=7*37(^`zzzpL~M99h;%7se>#iQbejmaD^{Nmvqf2-1n9n+R9o&VPE-K$n@T)JQv zg5dnGornKq)%w3$DJHkWf>j%4SbL2d)4g}bOX)+?#=Y9#`t~}~LtFDv@(S#6G*ARo zz1Hh1)i+t-{%Ojr5}gzyriZrLRKOHSW6hq!&_e)Gf+Ii^9{?u>rLg$uBzXlNJ8?+f z+~=mwVvBm!YCOE%3mrc?oHd(uk=-p+XKOAm|8@Mh`6CAnSRVUgxxp=-diwaXu`F@} zTcZ-t5&5K`eTr%Pr}@z2Or<&Lob12Nhu)Tzj{*XdbCy>@#NvarVP9W+a7?ND;oI36 zMSp4WJlQe>{M9aSa#U$!rbai9cX9e_E>3^wn#eCL*yg|ApL9)sXqd2O1P2C(&oJWIKs2)H?l48Gk`qaN}ojUzT z(GBUXP4B+5QF_arvH2bReoSMBmuFIEhu$P#0jmi0v>OF zgQLr_wms9?if5v_wk%cMuKGV!Hfv%W8&au#qF@nO_NMW5^qrhmi-b`k0>I>5Wnvbv9 z?;2&UW)xRSFeher)*{_b=Nn!I^6=>_DNLUdn(y7k`2FX7iEk_v$8<#J z9`MtBz4`5NQ#!QaK+nA~dZc4ezH`C0vBUcU<2`Q#wTww1tA$lxP{rSRa=Os-^M9qPNGtN9-I#KYF!BPUO* zorEzZ#@DC12A;YAA$sj3`TK*THa_KG?CwX$FF97OSowDKw_aYcMpOmp*w+2M{Fm>q zOo1`@0I1?StNHHk>AxM)Xeg7X%O^xE(6go{*)I|U^-=46n!u*v+8}0a%<8Ua0-|G0 z4M{`r+agn_goDSEW?DZ;dd7JP^^@M2vd($vqa}0M;$F3y4DXTtd+|M`!x5Kt+A_YY zy6e~Az>LBD7R9_6-oI(1My0}8_{HkiSfZ{^3cQbQ&C?HAwcvSU3@)`p#~+zN%1Hrpfwx` z3}nIYyuR*Dsd3WOx(!bJ@a1_x-`9S15GH3H3w&*2@ut6d;az5tdSVQcWi*db{V|4c zaM|vn>YY~u(7)TXM_#7^z0I45PbztsK*10m$j47)!6I?kQziit1ilrSzyU%ldTZo_ zy7R59)C;F0+oeh*+pmfvv-C?Os(^ng{L=%tSvTpM`j^i#X z?SVN|MYg?=7p8qP zB|Vq$HjtqRq2mM^g+K7-1eZo^0Ot~;2X2}qCuq?s*@!=A4gvv%HXa|3sY>eV(&xKK zZ_J;(G<(UEym?ZW=chJLJ#u)8t7^6U$)A4Np@q`$HGVViV6o-kSFAEWhZANwIFAL+ z!U?;>k4~97bqcG0XA@%+#5tP|SvUncVtM5Ack&_uOpq4u>RW^(Su`7TDM6wK5~4 zg+C-)-2;D!r@pk1kGjDiwno44Vxb@-jgjIIQk=j4E8n zWfI4CtCSoXRw|}!ozyD)ii2N@?~o7@RVpkrsZ!VR_4ie_R#v{v8SG!CuUe{MSN9_K z2C0WMv}?msYTq*cgL8PdQKQ5@JO$iz70_5H{Mhgf5EnBMmhKf6uZZ$A#M+UrZInP2 zNGoxb@*@NwH-!tYqj|(clE}BJ^bZ&tK;dIku6S>GB{Pe)y0r`}=4rkWQORX^C~1-aq#bUWRZOV4h@#b~j^8*VykaE?clB!5D;3wY`Dju)=Scf64Lxz}i2 zFFt=<*NbP;+v|NV>RT*63D_^PB1aiCW_`zWPotnsUG}<|FD2YlEEu}80jUJUYrfGX z352FGkdI^vgY}3BmLyX|%aR0V6%^OtV0u~@@yZ1@;+F?Smv{yDvEaZ1e^slpZBbyD zT#1b+*s*gvYVEK5^`nn|#b%!5ZTZr4WGLrJ3sHMN=fm04J-B(mn0+XA9R#$=WNm&X z&#%rbT$TXg-$iB=b-^phwai1tp=gHZO0^*54tWHr1jr=X^(k6cNs8N>(nDw;4dh9{ z1;Y`>d{NflFlAEKy>mqX^O-C*;_`iX#3~{rkR=z;Gn(f8-l6jrDkhKIV{@{LcDh1CFjB$I z$+ZpDQd#Cc-h0QOn|B@CclY0oLW`}$k@~S*g_GC$K(=D{>I&uDjlJh-FJC2atlnBb zLdNP8W(w+=R6Tvb#>j~iDBn^Dd+VWU_^t$!8pVk*w7pqaM1+BPGzI0oRZQgR zZioD!CKZ$u1X)NcWjfV5X%oXS*a0rdSI9!ijHP-cD+q8*;gOBS1Mik9k?t~V_?7z) zF7t}=aC*FB`}Q3m*Xuv-+50ngGRnO;f7Yv8Nhf}uuTo#b`23$RKHVI2mZUWx6blJw zz*B0W7bk!Sn1sQi1>s?BP@XC=8K;X{OGt3_2S%&8nGd!N^&8OT9ea5>_U)oo0W?57rJJbgc!3UZfB>s9WKW0a4Lns2#I2xsPcgbGZ6Nou`6}vAaxf?FhT1{8AjW- zm)2%&-~mge2=1H5j^t-;VMp?~k5p-?^ntsz__-w=K&`=r;-{(GuFVfMY?Xn~o4o#5 z;eyp?p;HP{=BpADESIn|wOqe)ndM}#`*Ij`0xm{A_dob!jUD{FC>`O=_e96wl|@4_ zD)6tgn>}Gp9zS>OD}EC7+`iki|C=vA+qUiF-3RwAKb0w2S^1;an4eO2#I_pot{I>F zaOQ}0&aU!(?r=e~yA-jDNf6MO3I-(fnVLVed4;0fC(41VanVn2{c z6Np|S*orX4f$EvmWkv+c2{0Z5z<6b73#2bp!}pSZDnPGPYWiN2=gn)XgXtPe$C(44 zBvnA|263WXaJb(H_TTh3E3Ge&V(Wb(qOD4XCaiMitRBX6;c7Ip=a4q;c6kB7uD##s^icqkTIiS&vm zMqViji^wZk&d4^GQz`%?-Vv?X{;rT+9{Txq)`9t!&yM(kF<$(G^Cw=+Ea%Is75VOa z_xT5>PqS9{@3Gc}{7BLB%;)q;=EMIyd79sscWHaQldn-nVvYUC?@t`qRMJORd`;XQ z{zTxMP^tiDotikC*}h1l0q`d<8tD^_q^!BVBOW07a({=Uw!Z%XZ^-%y8ruRITY~Qm zws`76nmE5O$MucYY1PTq(SumJUmQRzrsi-#nOJ(K#m8!rwz*a zLO&H42W!ZBNo7&L{fpJ$r~h?>UqsQVZ}#oG>V8{#>FU0H-^kajr%}gu760{j6o^^# z_c?3+Ar^P7e@4}7tm?s?(8<1q-TpcBfDm{c-hPbEXfh;pq7{ry*VbHqn)-;m+N7KUft3elP-a>5@v9pXjArzj^c$HH>g=!?PyGIheR1z)NbM+1KfTd65nbo{*&7 znzIqCpX)#`{s)rO1OB0vRf-!~lJ54Mp48Eb^i?R;N)wQtA1p^tmqL@0AP%%1Y0;S& z#Fc<$l}Ru*w023Nd>gskP1XkjWLjx4q1rPo2|F?R!W7nX9-8Z3VS63CCtJt&ez3n_ zHOr8)57wND36?rc3evA`xy^=Lh;7~l#3!#(h`KyL=A#&?s|ACDAxXKG8 zy0JVVIbBR?_YCc-JxLN+@f3KKMb-gg(C{#aK|}N8KqCk$bv)tb_s@Js!GNEaOzbNu z+wzy6M%my`2Cn~T--h-3_RIg`w@yW06S62<>W+r@EI_-vLuXTDaZQFVs{K$kQqm*19RQuG^}f zA}?OMrb@IHV~^gz=6-oTz1Pqw-|pt`v0gJ4E}n+p3A6K-FXg`~4d>@|nN+dtygmz; zv48O~g9r2+`jXVYPyar|C*H**8e{xsY^OeUT@2IFH&|oDD}{2`*!_b;S}va~s)aux z_Bn}vth5#D;kEAs=h6S7&UsAs65vuW>!D!4mE#p@B;xS3Ci@ASn?i;YsT#1O$e#-l z^J=$@(m_)0DBqDqCx`g5IlgIarEYv!`taZHvYACScpui7f6U&MI-KGcdduk;Cj%W? z)FO;CjH>&Q4kXToK1x$KP_P&M74M4BXH2$}?o)FGN&-_F2o8#h{RRC3GXnv4G%O!k zPiV_dMhg-^F-?HO%8)+jv7X{h<`dw$-{v{y@lo?-c7TtZ&j!wSEMNl{^5N{DyZ{}V z%FvH0)s%dB6?T?>+<&@HtF#{sz0YsR?+8pwe{G&s3C5z&MrW4!24|70T%gCEdb08K zc=U>HEfJXiAFL$`vdeD_=s9S}#ShotgRQi2%ABQh7ryu28@V%HA4hF+eqFb_P%fW3 zDW=lY0gK*$f4Mbn+SvZX1`Hn9r{}0wdRnJ%Tq)+Z7PNa7duXKUd!g@NvH^RRa#=s=sp7qV6dGn@;QvvY}e__ z^1s`4fk2qVrlizM4h)pfIe7A7XAP;!$bn+HPV!m&V}4;6`vI+21)pTZC)Fe|4o}}q zu_O98Oy|w(*rg*TmR6TvUZemp{NNcdM__Hy-c=!YgRpe9q-p7vm2Fz&u=0$qystHD z#d>)Gws9ycS}f9so zAk!z8TFOwn-GX^Ot9-DX21D5kIu>SweAeOK?I5ecvwam9;ZF6jnX%3K*d99^3PMXI zm@@CzL~CCmr4IIFLW|}B4S7N8^J;Kc%%s;;tl!1t~La}sJLwPk{MIFa;)9?6bPcE zvl2y^f(TR#rbO^evKpnNDulj8exC@`CPkY-shLMVOzIh%72CVP?qgr=YS5>0R+S#f zyN)VZOBSYdC|5BxY2o4`mbb88hlmR8>Mxovm8DLk0Uvaly>CTM>o_J1Y5*K3QLq@0io`XF>B;Zm6}qaOB7M{A23 z=Sh-;h=zb(>1es2#fhU?uP=XoQ4tE}guF>MQ*vat>Zv;$WnT|i`(QEid2eOLnX9xSiasPXL!t&{on*6KyerqdypVWChx(TVIa_pVdi6cET7R zwZZjKn!sJUDlfM5|CbFOr4Kht8>{pHJTsq0i%|z^qjl3lVof1y27@w2CYenm|R7`q;9`Pc3%gAFP+d$$7aFwO2F>dFI2oq*oM8ZR8 zPt6NWj1IOAF*;rQLLP2XG!Vs-I(F-DNxl8fsr&a%ZM~{W!{5(Yy+xLJC3Znlx5!st zb66K2;m3Zh{u@7bVzYJHTi3U#>FUNy*ng}i?C)UAgZdSFu;3ZBf zvKG2D1s{n)!CFB~gpBCqL`s0bk&Lm*`3~~vPGj~_0e_ zPa@X7{KgKxr*|&A5783X+lY+88}WtltO=01J*PbjDN1W1$QP2uH|6DeB%RB|^zb zXp<_n=3j}1Y^(F48=;y+%W^OF*IKjf-tAzC%1OReG-xYB6=4RbrKXIN{?t0PDJL_2 z`ib}uvy8-f`d*wT4@Ss@PSZ6-wu;Rbt4)W> zy}$;So_@5enD{ne2t@mW`2I#5Qw=o}z>!)k;G4TeI zny=4COiowk4N-i=QXY!jBZm541@9}89-aK*i4&NNP^ns3D>Q^v`YbWYatRUW(8V{4f>nn};m zIhvS{J~^Z9=>*sU2`t7MeI9*_TriB*o3C~76`FSQ;8TiOgB-+p-3+-g1ir~A6Q)m( zs0gh}zY(gxexuu0VoZ~}4f5!sg!HoxIh+4Ve$CoDvbLRe zEOO+ooUnA_gk=bQ{&w<*VMB%t`{AS#^5C~sn>Vj2`K^r~eRtK1n2dS+y3S*=S;hGE zSN{KwU%hx_?a+6s)m%I5ox@qvhL3r}F)MTAERb-E!^isd?c49zVQH_s#rzd3=9m1Y z@k_-QLDxT9%Zal&R-(*SvG^$xV2%>6`a@BOI5u~i&#Nm}VUH(Pkh7u}F{;Z&IAq$Z;3$CNu;YPsKgmHtj z-zWy7>`q|+0C%V?jw@Wvi28@wVhQRdg#ZesgTWGYxR|E1bVSwQ>bI{ld$2Sr{EJ=t zuSh}e8v+zIN(YVcdk84iieaIw|DQiucO2knu2jxQ7tk;dY%JZ~fZ|K2FAkcV1{oNy zuYs7UiSqOtp&jUJpw|L3$_E!f0VMAOAy!0f70u&n2#V4E8#Ekeq$o}bU#*b+fU!Ix z9<_3#T3Ko-o*bHqtkQ#0x4!nYUw#k$E!c5hM}B{kM~a^LL%k1OzQ+$NT=Zf-zY)^1 z;5V&DbIcW91}9Jga@L|8#1c?_eNRO@Au9C^_j)WI@ymJ{h{Y(+OxlPpu*JxAHQ#0R z@zkTt4=I5z6FphQnZngyc!Zx|?(leZVG3CvHsh(gmWcw1zD# zZ7xU#UV#_|ypFhP^Aaia(T|-Mc8N7(otZCf(L%9Z^Z6N=D21NgIw)mB*Z1t1wr}p4 z+OE<3;N?~?W8WfRcX_hL1S#oTCB(XZz;)P$n38}s;}w^$pQyZ!vOl1CuxI081}FuR zy%Oz|IgFWz9Ogu948!RT!5$Idi4ceJl!Qw8!_g}*ewWTWD#Lkd+IJTnZ*5#t%3)i# zahX&(m<@DSWjSB(lh(R>e{_;hV>$0lx4VZ*%S+E#3uj>&sj-kvVs6ytwj!jfac=tF zndWBNA+JZlr>vM8s=8oqFrtl$a2P$~j1Y4($I{3Kqq$MuP^`#M^-e8>B?d>Y{QiWM ze<;7rKRo`uW5Md?rLt^o-pOM-%ieR}|K#hB*i|0RK05KqCu^tMd1ZE?)bzEVNSzt% zGsLNZaRs;vxhK^u^R;s(PLsZ0f+6cS`q3BrWpZ}vnT_I=7|5;|+DTim;MKqu;cVIL zq?aKOYBzX4QQiSCyeRQ%oY15Vb-BUYq94Df$daNxPEOH|BdtxU92)Y-{_b3}AUml( zuZmi>WyCgPGauPxWyWgS^Yle^-8~Vz?v3r1s8+7^2==-%vO7HMVoFMh#T;YxjW_Y1 z$8Nk>7k!b?b1ar*7_c3_hP6O>B+#}V9zWS3-(ihg{Wno8!3(}c_^%#d- z&||hf$U;c6e5lb$O)No8oiqf6>wa+kTpMk0S8Vy3H0js`V>JSoHAXB&O}nvan6swc zS+CGpFQKX5xX= z*rI~{wU5`9sQOv$$l3|Do77hNIFm>w)g!pS2udrlT7Ehe#uNukGb{;5E&${xev+bB zNjrCj$#W*Yv4CIrzIm@s!<*FVy=cL_rrS;&IXrFJ!Cl*(d_P<1DWvB7a>vLm)#J9$ z{OC-xCe8YXy%@c?@9=T&&U$OnpanB>mSr%-Q)x}I^u?MEx0V%adR1FfA-r_55(KBO zsc=^k;{?~R8K?$;Y(sRxRWnuy<(dh`^>hW)5Ni+iqAhEfaS64>Jzwuuz0!S}MqBFH z>qXUjc$rJyV;|i#_|f9%j>W$9MuiC3h?q?-=@0!(nR7u(=+4ervC!M$Wb&INQy(TZc zwq8}&UnAMepifwoNzh)CWnNTiY{BXQv^So#;1uc z%~GdHnf`u#)KX2m7g6V93H8E8E0?W=1xe6zlTC}D#|uc$fP_UuY^2v1v+E?AY`TS?|A;CH3c*w=NBym=m~st02>DZwF>M$}Zi;uSnPP zvV$GjfjN2CZ{En87&u{kaCY8JtTWYdnSfpid&S&}Gpl)PcMEe8k6QAC^-)T+F2$ss_0b}-)wah)JiCLW!vdf z8?BB-a2csh=^VeD+ z^8IC1>ixkw_rdpfZeGQ{r-uE}sZVDw|F}Of0#5bc?vHB~?Nnx6;5unjwC!0%k232+qWlMY;%XK`9!Jxr z;8g$H{lIE#UJx>zp12>wt#2Rxbj^tOYSdUe{GG#xR#=BmBj;Y$8!r!=Hf`9Om8S4J z4O_Rzexx#cCXasVnb(zPB=y^){N}N5`{CcW$5`OeZ?PZmFXK1gTDM~Uojdbatb2~*RtBLQ>Up@`PqNt zU`Eb9U!ZIPF1wuGlS8P&V zP~;M&n~n?oCY-6ivj}u4{`RB&SFY^ufEJ{^?5rm%^*i}b@1M0g55s?Y6W&wObf?!F~*r$AOCdk^p-0qma=}rI_I|atuesffvm=lR0>6SI8iEP zZxP0ZgnH!CH-`Tz*y8kuVGrS~o4;T*s;vr~WK~jB@slXyUTTK0IRD|pnLqQNl30~< z{Kpg)dt}-ltV%=ZsLsv(Hu8M-#-_fT*z8Sdo7iC9b5q}qq;D0ys~ji}Q2zvdqYgEU zWx3>p;;V4b&=Ebv9XyKhk!qs}Uk=}c&*Z9M#oeqS&=YG~TxIN#poqm8K1j-t$osPU5SBdm{hcUB(rzSoYcGh^9}j!$&u>I?~Ko=6x6KKimn)&d#qZUKW*&~ z46={K)0vxLG+{RP*x8L6sOP57$*xWBdmr!1)ZZtgq%TGupAwI8pKs8Zy{^_4yw2x- z8GeYpt8Rm*nA(&!L5-h&mP|`7{ySetaK4n_91AgPSZ+|Pu`ajs1reBg!Jgl{WLl)- zy+po$T=Rz;p08Qx9BX&hr)z$W;rXWvoh|J7?Mj}Z!N!rMs>hQ<9==B=#Xr9O@caMW z{lkB+s8~`)^-CMANQ3Lvt5=)-BB`U((#9y#kUEL=YIhQM^djyO_}8XecX!iA-CZSR zbXxz>D!sXG9ex?N{lslT{knCY5qD!$X$bBTl-t(SL4#7QH5xXk$$ztU7>vg?8#JuJ zeM=s@@0C2}m#iH#GCJZt4Qi_8_0K-}{UC3P&;4r1cOHz`kYe!#E|)yQm4L0PN@d*< zt%^x-R!nflCpZ!AL1|PvXeOISuzCc)pw*&ycd#5EoQMjOYDg8Fxg?dPR0*@H%!USQ za=e@p%))|gGOtm3e7}|r%4DZKCsp!8bEtI>#`z&Lucy3RV$E7q;l)MZu?JeRxEg_r zntdww3tar{;ivui7Q{#2@dwmnH4iM4GoV!?y0ESGWi7(;i{1!a*79@dlEj{C$xjzu zl=z-jtOn+%$MOb>Sl&=gJ_*l|&TUNzzE2L{3DCJWd{^ebjo{%_{s$2}WOZg!1P_Vl z+qV~O@x<>W%H>k>AY<*=8xJl^SkC$!bZ(6P-M(4UUs-Bvc7LbpWCQT`SHVzwMqT*O z-+x5@{`21_&opIyJmdb}A;o8DIA+#^#=nPOQCfqhLM<DA*wk zK%50^g|T!7^btdp1hd08qw-!^NiS?d;j3sWYkrFPvE*OBv$A!Y+3jyni7MyrKlso5 z(nbEIBK3H%|LQe274@KX;J4*X@HT`P#%ZD5_DA`#h1c!&hFrIF0{RrBBQr$F}Y6dl(x2 zG0P6nLm`G-)`U*dPpGg3^cyd65IfbqUasI$qHeCI)t0Lt_1iL*4`xa*8CB355K5<* ztFMHD0T?R%7c~PA6I2$VHTKOV{JGv4^Ru$%XAESM5KU<6woBXj1y;%39(RLTdB~lm z81p+Aa}!LUtX{X(gm)2ReuUs2Kjwmd!I{Cp@n;6-1`~iEgD;pDyeb#~{(nCQC{dm{ zfK6f!etaF9vyyLboAFw9_G=mKSTsNzf4U3Vts6JA)_Z^a!Omm+oe&=xiZ!=eJSYuQ zl+WZuHLbZG(scPqo=BLhA5spqyn|eydB{jGL3t48GtR<~k$W42L!$;xlrmkZ$8oGY z{_`vzD)s+NdS<5NUMEeL-jKGqyMUd$2S_Vb6n2Rgxh08+Pu9V@+o4?vnJUJuFSQuA ze)I3fEm9@4B#QrO$kK&%*?g4b`%pFv%I)b54k;Z5YmGWIwgXseKg$$t%wl9ZNt@`~ z6t?%$x225mSnKs^wxMDJtl6I)TdM{~)Ue>#V6y`ioCT;6T6`jX6rOon?&OZ69+X+K zB5lsNzmr-|Vz3xD_^3*GD}FleLyTKrUK5Q!Vcgoev>@jQ+eNu5G;-vsKrJRxrwPo% z*GOZ?{r6uCJ4|XNRpu{fSu9=IFN2C|X_+iyy|?q5HtK&9PIag#cnMx%0(%L?g&dId ziZ^a;2^6juU^UEMXIevJ%rnds7yvClhE>x7D?e^9G3yOlwST$!L)<#vSpOe9+X|o+ zbO6-f_l(`-6wnW#7`x!+6sfh?MNuX(cH(mwa)7~iiXf9&zQE3Xb3Ha z9c9l?GK?01ROnr`OP+C+lWUV&6JJ~CXrXpz^+IQLlsoe@bcUz|Y&KFNOcdGhC}=Gu z1rm@p2o6gL;S4~agQ%FGR>~+uT|8u8=jT>-<5&3h2ZycF(p}8|d}6;E^H-g5xqf)n z(QsNDcjsjb$4^@_C3j)@*x0CO_Bu;A##v`oO5?vAEkr8vhqcn2xt>a5h@VmbRUr8(wwfq=YBy6| z*2LswWpbA#&!$Xn`3?GNEwc8V{K@;3TSkt|dT-Xu4PD#UN>5pp(fP%+%yjE>sbwlw zh>Bt@|7`yiE3HcXPOeBj!v8K^rv0^fLl55J4-OB`{jQzAKRYmN(U-dxjGX#eN`r&o zGqHS)bvVvzpk5Ke#2-zv$a`|ZFN`@lNX-Fk%vg*Wb_SrxqKqWu4E4+`sTcyNd#1dD zcPXhG(k4CqL4}gaA&6HYN9qR7VSwHT!^HbcVP4&#Ogzk>?NPcAJo?5l0q=)MfS>Md zC;dqq8Yv-^U(8zbQ_>-RUQeNtPp79B<&`8+F^gW$B!YU#6Z;ZDW?hz;LP%o7#2i8- zQ8g*VT*MiGA_ONitTwW#ym2Gp=R|}EV}eSjl{o2_^i;;Lj%-Jd7q@35*B_`CItlPm z{<_Ns57Ek}d!#~FwddF(AW<-9SPW5`qgUgo%5X}NSWEEaJ z1GdviuqR6Vdg4$M<Fh(xdetFqxewR>MJ9jtj{l~v*A zWt%@`g^9k($^y`zH!n?$Mqg9!88oSHR*OicN1sIlpG9GBS~YU0crZ)|QUCxl1}5`bAB799Fh%4a`U;@M{XDg|gB>lx^hS z4_)>XzxHfOj|T1JPSt;e(tP%N_!IsHAM5}=7y)FZXO1R7vv-a`77>1d;{?%9c%2OZ z2|@s4j!zDo5u*c(15cx_>Dpe<1Vc$IV1hEXB#esFORgjQcIL|&&6<@j-uT%ixAO7I zQ7fNHd}dFc=ma}{T?ngrxnAg7IUR=te|pc*4OVu0I=>Q)@8Cz3^+MMeqMb)!Q|Yv0 zig1`3&4{@Mc~lS~#`POrz;B0!jc)dstiJ)|C>WYzG!2C!C-i7(IC}dgUek?3{%KZL zz7#)8lA|Q39G49SVjI{pqoh1iPcesgU?*M{ItO@)M|=z7(@&>Rk@OoA>|D>T*j`rl z!(hwW2`OfwLZm=AnH3Bi4e0t(oiBdhc_4b}3`zR&!+q2od{dqzbqoIC$W+cN_lemt zcFTHik8q4_ucr@on5CVzzGC6@^)4wuM*11KNukzd5ih9rUeHNnuyqGBw{OsGHLdh)z&cy+sjB5G|Dup#Za0J zrxHT~s|i7U9^yEjsyw7`2G=57T^yy=NGP$Vh z-|At$gnz%DpW)Nl3oL$Iue+>%^W+{+b-4dJd-``Z={&zeeDgK*9W7E)#hCxczN1=4 z9`o=i>R^B)U^aSM;1l|eN|mYa=-qvvU6P7SjKn=-osvp@N3Zf@1}0J@>r8}M#(*!D z1`nyIueyn>g`EX4z8&TKuN$n&;%A#Y7UK&YwC(&zRu;i{Gr%U|V zi#K@jS9|y^RT{l_$(o(p^Rv~KYDORTAJWBBAOGm76S!l&)>mN+A1AQp6)k9JNK#NI z?d@ez1ii*%2Zj~<$C31v+(|T&*5BXa{OE`Lre`Dvw-1$%se}5sZ~9z1df=z;zOc^T zwSI2FhC%2RSRTB9{BCiU%`p~B96Pn5g~i5}qu(L^;q^eA#|%(%oaJg&@%lS6#Ou@O zbJmnO()MjFm^kQ1Vnv#WZhc$)OHu~wnj18FzbnK8`5~JlwawLzq0zI#pWIl8xZA8T3sCZ4XD4=dbXf|FSqf}EHU{=of=?aUg^m0_J zq7J3(I%XDMv$jX>T8t%8uNy2_gP!A~lZT*{ zf9eiT4a*Gc=?aPzb?`l{;j#7x-32F7v==F6Hh~CeM;?oap=caF)Bf7rz77-+-b0hoHOyi|sy~yN`z)x;nULP;?;VRATW} z^;7I;O}$>6&}?)$C6b0fSw^1XQY#8A2c->+o)4G|#w7_+C}qeMQ;psWf+Jjf3Bt7m zgBe1r5#-%#u^Q63g|qjs-{IQ1pD$yBZn|gZzWfp!{9@iZg~dj_5>r`H?tGZH%El(n zT%ED_%~uy>eDY!L?rN2{PDyD3<1S; zd8g|w#UZW7wUV&PSCHw5ytET?LGvcViQ=u^?M)tN_i&>&V{t zl6gy?v&N~DXDd?W$`fK@nDXP=$+_xe$4Tkf5d&A z@LyPkYxif2pP6MH5Xi5yFuy9VwwylW?etZzd@E^s>e8|&8k_2cqv04j|=iV z&_ZR)%Z7E*X+cL6lt>E@KdFYOpCNW!Te>QqrF(dahWcAB&lK^&*e6ZVOBX3#x`>bx z<$0K3{jf5xI*Qp(Z_a#eF+VD?dY^oqmX_YLTPd2MEqp*?TA!imXi+vIef8oVlcOt4 zO&d@~%!kyGjKL)eZu~Fd1k0hqa18jn?ZM znHW?#Ds)8k&;tXNV;a|riF}2a)}Q+pziV@UcIZMFg;sP=Y6y_6z*UN?KmDrLVSMCiktEu1UIk^Z21#nEV-L;CdxfEXWmBiDEF|P}=CV zGELE*`FWVMc9!MVJbmfL2CU}j3~3fi?>>D#cyiP@;z#GV&Rc0k-uH5JI{s=Hy9@v6 zQFkQfc*NCXe>%(A-0k|`m?7q~ANv!B{fV$l)#hW`A5+6jaf1JY&2m);6TR~)Bou6` zu)hL4?ER2gU65O09z9N=EbE`Vsn&*5NNT|2w~>NN(SgcZHjD%h$(_oQdUxOAwN`g2 zq;dRd+Q6JWy-CDidaQ+eAgqoq5}ZA>cW+|v%3<#!^ty54cQX{{qJW2+X8lf@BRzi>~1Eo&b(!c9M?aW!EH!JzLz_ zy%!T~k>%OmSTS{Nd)aS11U5(r`%T~3j|P?OEX{Wo^x^riL!$MFy$Ls=YewXyH{i0# zP&OPjR|QeTQFsFa(VxVN)|TKA4)Q1tLcS;xOb}Ef_-p98rd6Bgb^YS{^)J@%R9J=Z ztP17O73+@>)(3P5LBjgRSLqua%fCCz|NfDGC+&Er^Y)h}FY9>bvbIm-P%GjS_&1_~ z{}CHt3bqO_FX;e=Gv%`SiAbwCP!ACZ#Rfb9<3m%dlBy6I-drHlqz*I2$$q8B`uQtI z-#h2NeWLekHDYRyZ@G5;&W_U9K@BE6)#Bx(m1|kUi`6f%hEb9IDn#)6&g%L6o;KEK z=qnL8Kjm~ySWu3xuL!>d9hpa6ng}Hr5c#fvP)fz5V=p|Jk2wcYWw%?I>@JeDLjc8t zlrJ0t8-jZoMFAQI`(l``V`=BRdGS?c8ED=-KAqLD8J4}gU^B!;gH5c zjre)XqP0HceRWU6_&!?Kd3zr=ZdK|}ztQN3Mi+oaFMvj?qZ+E%YZDH!DuFd=uZ{Xm zc$5HTgeMs5xw_?!{F*2jNV@Id;kSDye8lIDZ98uVc`oqxsZ?4-TwRa^_lSH}0+wLbPG ziUgq@@d@iot?FkrgHBNDDCXG)ipmP#1H#qO5bw8TP z+WZfK=tze$FfMY;su(DwJ;D|g$49&~ns;>YHf*PZIpq^Y3G#^{>=iy6Q^fbb0BloT ze19;yx{LKOg}qI2dy`LCETax{^0IzfU|q*FMOa`x8JiBGouk$648lO8^?`Z@O93IJ zE^nF5Ut}BTC!6izE%B4aR;SA&2hqp}6)g~BUm#VKRQVv{;J}K?r3eFxBPsBv!h@K! zApM1|>GUJ}cSuj~&@6SpfK==^;{MW4s8$nav4;QxV6iha_|u+O26>;4*Pf@>hF z_r(W4*HOmQ0o@L_yu*s*P1qG5OF4M9!?E{BJ4<4TS6SI1{5uvuM2h1%Y(g+KUSpPY zy#KEE{Z{e*MD!_+W$8m$JpXP8D=Tl}(}UsFgj zyhd}#7J~pyNS5|e9l1Sdq7-;5D(s*;77>CjbX=2F=x(>@(v&WCx>!{qZxEs?Q?g}b zgyBfYg)W~mTAIFwh++^XgvgGkRQteqoAmC+jU#vkg`J&%l=Dn$xZJ+i#~-(oX1ZTv zze#n(xtJimF8!{4h%-IQA_{t7L?$&{98u^Lt|$~Gc5S0eWFYL|{dB>jF|JHp3VsT_ z87PgR*T=Y;(B&q*K94SVghUUz;7^Qp-J(ZK5fLy%ceUCf2K5x13>a@JfmwI;?UT&1SWk$;mZ?519yO}kl%k%D|at!u?_+uR*&)?GclPOxG2jyKs z0Z2g!2*4;Z>CyxjDwt=|Lur9cojH;E6%a)4rb{L+6w@-6c?lO~1JKjnpe|@tUnl_s zd0?o_N9k`Y47tyUWhX&@O^SzwD1O8d^y&pvFbqFz9!sn(v!X7go_mg+K4Cq4(00nT zXRj=en&p@{AMiocsAX%|&4^(iH%(6uxa^3?Seuc5^xE;Q)kD2((DF3JfjMswZlNRe{iC3n8hRxVQ@=aiZ||xRk1U27X7LHr+St1lRRSQY zz;}ZRcLu~q#wWzfeNgT$-FlzrUs+x`zDWTbdKNLfPXCDT!YM}zR9DX+Wff`>;3^nP(p$~y&f1iIKa+g?gnS9 z&!}>T#o7ag+Jmot7N3FA3JPIb)M=P|1ZLmB@{DD=#n~{y`LxECM7F1HOX#{VWqW${ zJi4)q6whD-ZvrB>yy=#zX+Q~%_L5Ikdwzo99ntpuNoJ9R z@GudD2kN4N=Ss6BW!~8Al`9Je zpTfFTfX3{Jftx@Z;yl4}01!feHqd$7ctFCa5UnY34Qatft^qru3OI#ov%>!e11E{P z)ac5JWTTY`_DmVwRgnk~BB=zRWxZt4C zWy?w||I5vJFdldG?!M|HrBuzq!{*H3t)%DOLZ8`%WQe-z$A|~ifpr;c$yQ^F-O#rK z)K`l`5#cCd2SgK<3tdswDa%ywmo#~a(j5FH08vy=KNyq8)vKmL;j}=L2^TKIVA1o4 z+QEsME5JR#KM42}+@tmqdT^E{o*9plN+bmmgYlmlh2b#-oqXTAI`KX0^L;m2w{~ys zzPz-}N;X*yWqdMNCm$n~L%rPh-B;KtUXz__DaN`O{Y~;<-&0>E&sc>3*4dvoe&jC; z$&2(VXjVan0Ri?MYPf~AYM^)We1m$a50fZD8m(1|PGKa)q66dNl;9Wl_WPb6W6i%f z&Z69wl)3HuefT2JYcsc@R5AJWzA24Z>#G-7D*y6I0^gO?;OWM4^=1wDZ>&=NXGrgy zRD9RkNOf7_*_W0$X$F43f(SHOE+^zv%+P4#V5fwQGaR2epdP?`j~3sP7KzW~dp>ij zz^5c&s>KOJ)RvazAn8XK66R?jDnBo-=oLg#PwerJI!n%_8H$nW0%cHAdP3gh49to$tQP~L_OAFIE2=QH@3 zb9ea{Ea=M{%-*-N;%2e!?tfMMX}5uMFK%Cao@UQ1TZ{dag`jyGq70<-kx&&H1{Tm% zXd7Zdp~DOl3q%{OEOfX_JLF$(Tc5f2iS=Fax0>+V%5wazGE_xC*8Y}$W?%=1Vj|G@ zp!IIIYcn;2^>o0h{%r`=w5lSNLQs?0}~w8`RoWXlBD zQorKNmIJ>{vW(Ywk>I%csTKBvkyoLr28D=9uVBrgAi89&E__f5x|9>m*?zr$ zbmyJtpTARZ?B3sB?YR4V>$^LT+{@awa_0{b<$v0-%6V_{)-~JDMaFzzuxcy2dyaqY z-nh}tYM<+HjwKYY-@y5abKCypyGB+Y!FSxr&%eW-8&iEOdls$cS@G+2O^|CJ4Z!Kj>wCEE{09?hM(vrt`JrKO??1h8P_SH3TJh@GZDKU3;-J1 zNDG9K-kUux&IXPyKk=2!gq&tAo2K_{)~soEU7tERO`mnq}7s(q8HNOtU8A z>)NzeMmB8GbbKATTZ;}Xyk6~c9iAK4vVFOl^}|_eOa7yHvQBua_GEGx>(~-!Z?R>W z{H<~bc3TIFE1(Qk&mIsJfYq}HAXOYHx!o`ka1K}>uovxRu&TI>b|MlFX$%$C2#p{& z7mUbg89YOdr>4`vIQ79{(v55U+(vfH{nz3~6ECyq4Ln&=-+o%&G?X77%5T@WFqG9D z%F5M%d|HDXyi4*OWC4+Uozc|N;4Sd~*NThWFX$day@HvRr`{0}3(_O-+4=bFkK(gY zkk2Kb{qu33RWOr|xOj9BcwK-wal+3Q0Q99e`X)2BgeeG<6U5>tCW|(sC|pJr_XI=w zI*q-}Gt&4#xdQ(N{lWS7tXixUwFBMp?rPGvQdHNj?(^=SyLBV}H@7&Q#pAmk=aaO> zqIw34@6xJ%M)HZ1D5nUThs7^70$B&+ERzrgoMD-5dBd{6 z@+PZbAwxt?$Z@`s5H8lK4gB$KAYEPaJHvjTn}^HdIk>c3gi8|{oFdQed{wBE*j>~M zC?(X%NrfnF=eiXIng?#XP9c?4=?XKf&{d2w=5#J<6gpelosA2fPurbMbk;&QV+x%k z3!Os?ozE6Jo7kN%7dkTwox=*9%?q7v?9R6OTV1Jb2?)>@f336K*}l-(*6z$GbPgzV z_APXF5bqsS=p0z+>{sYa#ZX?MJ$MD=TZoJE6}$5VJoy3}z0>l7{e`F(gs_29KvB#1)ks7Bo?As*YR*clj5NL2<^f1I9Pi_EVOUUHYMVyVt6bk zPfi~^d2&X^l;(TWGiz3@lRTt1pVqc%(>86MZPG^mu=mhTy@pt&7n18$O}Tey^dYtC zXPJMGU`dD6>nu65sQ09LNr?$+Aq#c)f`Q%}!nRb&^q2gwHIhY)-LPRSzqp}j=Y|?p<7!KCo3Y~Gy{EKqkq=d^8Xv_^7EQWe6moBp z_6tWDC%(7)wC!7;0nn8y!DfmBU58m*QI$Z7QZz0WKU_j8kek_8R#6jB>$n#4|A>1J z_^gVhfBfw3d7g(9NPrNkB=k-?RRt14uOcGd0D(k80!e_#MX;bKh}gXXp(A`kfi_XJl+9o^n>ZR|p&T7%=@-dg(df8>cjLZR9>7!fqo^t-wyJ|++(d%HN3uMT$>`wMNtQWxB zNX8jk>u#=Q_Sd@)t|OkoJnP#E(Vn&HU^ItxsUX?n)ln9iM36#QZ&04}{|uQXrd$iR z`<`o^+nf#8A!qu`^@t69zvhXWYH`YGD96YF#5aLcf-N-?G{w>md!mU)gWr*Xyiq=D z5vDK73(AJ@ZpJt2O_u1$!}nG^b9c=mrR>gMIk!6}AAC@B5LX<{hn`)DIYkua=k=jy z%?U9P8&JHl05+*_D=b;p5f+a?2uE9qm1o+7g!EfHgE0AH?5I<(o%sIS6R&+&archx zcippn`&}{%(e}qh8jfAe#eeC}i_T{W?|=8*`_9ep{`iAAiy*o7-{P0Ren^lxr?Z+4pwArxDxTKCQdajmuc)j1=8)b~xq9aOXZOKxA|J>*gfKE*nRbV6K+hX8oNKh&rVUY z@nq?>-grWTM!k}HP3uc?c&$^3bn{C9Spg+w!^KVbrso^${)IB|>KHlxiHs)Qwi|;#@KY){l!zic5(b z5H~h%W?XQ36+5-7ydwz@#>FPZro^h!>c<JhFE$GwjBA|KIHj?gevmc~uf$Dt zE%pspZvja4CG_VwJwBd@yDj*0FfK7EF(r}U6H5|T;%5Jr#JyxN*OIM#oc>$_IC458 zfhksv&T#2Fc>Ij_&-nHX#lIzo-zLAG{B1Jcl4VRiOnctEv7#|EmZmVBkUFeaV(-LW zS@N(9Zd`Tc#*mygW!8*ov!@R}`|=*0du30}?Y*W)r#^wI;MDT+slfqb$DJ+Yh*{J7 z^^6-fTZoxLoCWiTc>1AyEEEJ!ldWrrr}4~FT>g@y2Uo_ekJ}Q5Bid`pU0;X4$aBdI z9uRAnRnahEy=I-6^E8i)rS1VytZV&Bpe6A{WB9v5|A@4-mMX2k}E$T zo_G<2om{thJ5dHk`HT#1x^i_T5^znMHDgBcT(uJ+bHT}_rIUkaj~RQ`(*b15MjguR zkL6FnLs&s;Wj#zzia6aDXzD>NCy&$LkXL$CTqr3-iM^2YRUO}dDz-Kj?J+a9Bz9#i z+G8&!B5tohz!GL8)hqF5|EaiIOkxhsj4O#-8Aotv?Sr~=;pr6SHq|R>f`jQxBc9YG zx+NYwp7?&^w~3Hu{N}am!Q;)}Z~kp_yhVTDV;@58I^q8vGJ3OchdQ!wLyNF*|0I__ zb8M*;^$TB`cxJD%&TV@RoY+5gS*Nxs_Lso}2ZCR}l|A)*DbMawf6zQ3CP|suMe{}- z5AFkRnpvZWH&MEM|C%SnuPB%LaJyACbl;)HG_k!inqt!hd3=p5Hj#!<=gJ?%=rgAe z4U}JXP7X+X=%L`(p>pTZ`~#~&%sux6zn=CP_%qA?SUrRM zOH}?4TK0?uf3Qahr{1$$3usNoB?t@NkefK3oLgu~k3#^8N{UK}8W6>0X9P5FL`JBc zxUgJ=SI;Z-O4T*XWe~1&| z{ut%za~q14gzj4sy1H5?rYiRF*yFM9$9@|dSX4ceRLoeYnCeqT&m_1liN2VSl#r4z zAYp96%!EJei|sn~&cOUKaL2XlcLwE**+m8OE?@kJM0{xQ!Yfx728W$LWk~d^Iv2sM z_Ce7r*c3LzU&Y|`&Oj2;TPF?cyfiT2PU6-_+(f>SXtaZw)xEFn8VI&j`?g-^OWY; ze6_y$7QE7zQvY_b>Uwb$7)o=xzEzCU+*(Pghc=sYH} zuef2|#r2)XPT#ue?;B6s>o#nhy5@`~8}ln~mq|`;SyA3nd**_|`4_;ZSXp}tdu%GP zo;Fn1?ayr6s(8I@=5x`|vRT9v{;)R#6`KIKq z&9^=J$cD{#ibcT(j)Jz|Pd8TMebQGQE!im^jbquPsqv#zq9b#bkYsX_49sJZ z5}}~x0lDS$7piGZo*X8-JALNZ>++pKv>DOz81rMnU-Sr^*h(RiaM|cgDM{oB-P{UR_af zrFdkGbdqI5dA)32^BI`;FIhiO^J2|jv)7pV%q-9sa6PSmIEw6xSU&1NvYsK5vFBXd;t+{!L@Pg>52e18FPTVpY)9zq5&@^+yqr{USbW5V=A##8RF0h zgA*?uON7O{XFJafR#e`pKK`vt-78OzhnMB_0MOyeD29a_%IDSO0 zKO3KgJ6;TQetzSm^RuY;#v3AL>63TvKJ@sVcRns&c*{90?6>}PQUsjePKy195AXls z;Nd6l*K}QP{~dJE8J;Hh9Y!U+AcJ%2vl~aJb?m|QcyH9-5dz?^-$?mf&pmtG$1T-kARr&U|RBo5pk z4{eLS;!7)B9c{HF-4A8z%hP~NE_x<0#rE^89eOPM(z4iM|tTY7)cY`a9|@ zsc^1P7u%hGJ85^$*E(|(`p~y*G_{b6JvERy26M>aIEATk(nz;Vk2t6AyUh*y#w^UChl74h$ zoF-Gyj97`MnGV~QI07M~F&%_J%kWqN*4>Wh}CCD5BU4IO&A2((d~Vtw9j0)J~)%h>cBHt6~^!zb>v9d@slcUyVH5_Z?}+y5CS5rY1gBmmbM%}qoe0{_nC|dYwbQw9-Sv>) z?VlHSuf2WOmW}J~TPOqne(b5QFI&2HO|A9JA?F*&@RPS~seb6*wW|5%6<2M)V|DPZ z4_|+5-hu9&9$fL{yKg=bTwAs6mfLRB?cM|J-U9WnXQk#{6td0ukLVIHX&f*zmWmgr~=5W=yY2{mUEGinnTG!1#k<10kBeDK~|0vVYiH zWDK7aPV+vj0qXHC-2YTj{^YqAzPoSxm*`d-mS29;+N-v0yL|a&moB_%p$xvgt?C6e z`MkC5lCLeStiESkaNg>bg^Nq(EXtp?eA%qvHG5%ULMCaf_z8W0;^j@|n1~V3>sZp} z5m2W1ZCo-1gA{6sbVJqkVz_T`Wl3LP5gUVv8z`@uzV}{5&D~Oty`<)GaV*$U6y!UI zejK3&o_<@dmT;UziiIDm=VaCkfi$U0tsM=cB+3GLS@* z*XqXl1>y&N`%q!UMh3JsZj645IFsJJ%sR&asbgMwH7c}zu)M0~gKlYmo4)(Gr(fLt z9=Lt;@*CE!K~9L)(vs_noYD?Gl{&Vs`ju^!!O((>ZdkkG#t{?>3I47|eGYOz=wzM$ z2cxE&nUHj`@3^b#1_`W-v0Xl<0d0NsTT4~g1{rB4`NEgol~1i9+SB<&#xzALr<`Hy z`#0~qXw2G&(r%x>|L{lqt3F5ailS{Tw1ox4-bq5z*&~ zf7cwJIg`5BoCJ+c$sy%dU;6x(e~mss$U? z?!2c&27Y?wkq>UY(kYShn`!clTaHazea8bEf(K8ZJXrDb-<^Imky6$|@V^TDZwgyA z#XZfCvkd*NfA@%LLjAi3`gaeS(e+5_QO8PEEqjnu#r0rg6=tA6&j@miMV?*f>&dYD za72{0{uE1b-|J)kh__v_jN|@ zCTh9kjkRvAUAc=G-9ia`gm5UO+78iOgwP=jG(zY)&|)J^GEBX&U)WtEY>Z%PQd-be z2nPM|ihI+yO+Rq_qy5#N|M+0`x}3!Ri^nglOy2&t6R`@rJQ2UZ29{<|`OWr?pz_3xfuDIylfXttJ?v(uL)0Ztff8vz$N6wxt zgWD=rZIC1T&1jyOH*~?W#l^t^<3ztEJzad_EIXsk`2&{Ua`VO& zy8?3d+=+8$&YU=5N`jan4$u5H@eHZfFJE@ug89Kv&M<7=n9;K<__`K#`I`Geo8V8zDJaH>aY~%*RoFGL%eh$>T-JW^$V;!g z>&jL4K5@~Ql7=PcO`bnpb#K&gQheOv8FRa5TwPJR{;t(;th(D-bY^rG(B-^LJhy1EUY<;usjYe%(d$uV3JtDSOMOwtRGS<;tf-;(Tm& zrmyO^pa&Tskk{&C2&;evUk}$p;T-jrNk1ajW4SkHOb_W_B zZ^+-!Cd|ev8fe0g;Oc)l(8v>`2acSw=dv~TO&yyv=Ju5p+s-W?Ke?h}%7jHNW)B-T zuW;b{JGn%)YJE(Zsvf}Lgmd$6JHLtYP=`iS=a|UIP86)oNH#GOmGl#)=q&~R_ ze0bH!&NSax<8r~3oqblA>}qeU*-xw3h_G;noG=XSJZKw!#Du=?kP{9|i5zlnn7X9h zf&o`;yleI4dmgwty)Z4I>%;-0FH9(zFzLb>V=tUIw`=!n7ME_^edVc(D;qXEzfl9x zu}{}d)0bR0Y}5>0r|W8egr4bc)%6v+b@IF@6yFr4kvv#FeaekPBcvYs5#zywqA|!+ z%%1h@_wKp&y1fe)<>eJG%)4NrXnA#I<<(!U-m+!2m{C+T z$SsGh%kN?6sg9#RV--#G=`IC-{Np)Fhc8EdP_s$S(HjaBVr%BhQNfManHG;I1FXkj z^NpP{FzO}yJ^PehYhy7sj@D~enm?u0TWs2CN{=(~Xa87xrj6E0LRfHBc)KFPK3lje zu~qfFJG}N5Xh97HLxl9Km2HLIJ~LY!yHp*WSuu6sfXR~w44gVRqkX&d^mgqtf*XTR zy*Oy{#W zX`InS(P}VOZ^hr{-tp3zHJ461XRUpW=-b43V%<~EU(Iqo4z})jh4d z(2_*C+R5h>QQL^I#y2nWCPX+V(#(k{Lbvg8{QVE7M2(Q16cX$OEa^GX@$vFV&5qu^ zuUPodC-1(yANdjI?^?TK+xF|%-qc3ksLm*<`FC1xrE0B{?>N6ab2ND4rXvsSI68R> z^K}>M(v#;9kzWmcv?9hDdCSNf`X=>VIA;56XFjy($j3h(dB*uo zw7&DA18p9^<%Yd?1&+2&o|WAC^b41cnc;l@<{QrEqPqx)lGEbsnhh&v&wu&*Pwv?J zI_f3h)wa}qXrst6$E}qsEj;Fg+XxFP5J)K%qS+fYqP+m>zPCQ>6tgtC^S_=w{kgg^ zFv9t~<}qi@vSp%7P7*;Z^I_~c(;1<@$9^3;Lu<5kla`5Y@LP0qgV{NLoiajni?2#K zUX>ED*N9%`xZQMrTuxFBhHn4rZ^L?=Zlz(0f44?;>r~oOUfsT44oNdMV)yk`1Y37U zgdb6T(Q`?5;-CoG$M$2m)YZ@`A%LY4- zWZhT%)DN|fyy`eVyfwOVSYpl<1()sZaA4!Q`|la|?n?)!&k^-sJt2~ue>%T96Kb3< zl3M4tX(dBf&RX))+n?XD^=&jI>IBc1`jvfNjotOOKU}}6w%q)t{qpbtT7*W2UE0sDG2ctwb zQR{jhzV6Al-#)VLP->4Qc?C;qx>RmndB+(oHeJ1Wi|BP*HS&2jp_HH18%5um4{JUa zwMV}C?EbO$)H=Vt#Cwa@+Iu9<6Oln{6F;ZPe#mXK1a;4B!a{P{Z>e8MjwU1ig5P|9p`N)iS_G$)bHC5`b`6EmoUo8x-sy z?YTr}(k2FOXyXeu(#R{Ey}kBV+O*lZ^6IooV5D|v0% zeeg~~gSi^Es_S^UgN@y@6nvgy+Mec1gjSYD!ncYY z=vFrJ_sqm<+I_JqU}IMf?KjjgNghm@com1k0VZDAkuVt^%m(XSF`Zx<;F!M%Jn0_H zR_j$UkznHL!UR2-0_#3;9;D#D`T^jyj#rVD!4BLX%dXcgetERfntDIc>p1@Jy?f|_PvX+Vuai&8p&UCWu$;_`# znqSuUF28Vsf&Ny*5Wh4`nW%F4RR@OnrC~N$4{LtK+jU@w7aFF(x=!;VK0v1>#OuA8 zAg{Kz_GeHto#`UC7TU3PgQg3?k1kyZp^+&eqD#Y+S?6iG>V;vjTrYcuh%ODYp?0>W zD`+c&r}iztOb-!V8m6GOkEW|$0A<15RYmF2m@ekmsSvd}wl#pRt)iLR=3YIQV+{Q? z)CVy5h-k;&pmLInRw~-1UBhI0FlFMDC?=TXx-i2$m<`siqLg4-+jZ~^^ zlT;U`r3X`By(H$7TqOs9rz)8;7`gSMdC-H*_uhu~Gd15Esd_H$8ix3;Vamj>n(v8q zVTkV9@xXp4)C{ownXWQ%uQ-S5Szm=* zy5_+yJQny6Fgcobob=b4V7lAQBVpe4V9LY=h)M=bYF(J`4NTD5FuxT+^bBY$Y)9y_ zzVl$VUXVaADZhuIK1cKft%89~i0i2#;HgU0DbYzEF|J@IkEYGmGsqeT$Sk6%Dl5LK zzsCm+>(x=bX1#`}WAyueieUTkyb6B@(igy;=j5sv>@mPii+|Mbr*OYVo;r1$c{SAV z>VW1|vhDFx!w|1DOqnRtylP(;hImCV;MEG|Roj5Z!o1RWhz}a3U;^`@eF!jB?U5o# z(^~r!Fmw+*4>%+q+^S_&v%Di=$2Jb8v=j8^LnA*_4&=PMcxd23s`txsLxxMBQ6g3ng+b_ zVk-Jgv%m*{8Lw$a8=gTht-`cxn0GvwGO-$SH^3yW;~t$i<&E{hUjYXI;3~G>5F?28SQ>vdjPA=10!H`K z;yFfm|Kqo-12 zx-wjco)I3Njl$MrWZSy*{L7`YM+5V$cTw)SgzsZTbFmKUwhNM5u)D_+X- z(kAHBdD>8CxX)-j^*ub5;tD;E{*E5fn;K80 z7zRH&@Fe^2I2HC}mOIcx@+F|l&?l!k`_*xlJABLUf3nWRdtEN^74V>KqxFL3Ys0WT zr}^-MS1z6A!sD%ve#*7{Vu<9f3**^f{ifxmUD!S;L4SWeKyo+Sz+9^3rDs^5X*?4= zJewddJE)Ew>f$-=;n{?7WR&iKuHNU|&o72Zel1%((b%7Uv zW#wY$1j^mw^j9BpT}GStZJgKfejnr`6nq~zdos>4>nzcNa7Kscr(3{7J2VY2h!#e< zWvK5h%){{9=aAFi4sp4_!{z>s@n7cj7wx%Rd`s_Zf8_ThmmWOnNkKb5jGlD;6=)vj z8Ok5dJmc~K+cvCIIs7?Nr~2?(mEv&CZnP=(^KkySdx4A2By#IgDVobl(w7>~ zCTMxqm&sv0r}13i;n{|WjZ(6-u&E4|R+3Ix3ARCQ8DB8U!?(@)33`)Y$-a$*MSgAj zIJYUbDwF(*J9L|NMrIGAhrwHUKkF?-*szShtJ}1RjROm{o_GXpdOWZfWdaYk-2!gA zi<|*+E4L}WWf}k4c?&e^a%o)Cxn zxonqp4QMQy9{6`Ecsv3g=&+^YPM+r#IKxyW^8w%T`@yxpfe$mCPJVdw-zCnIcwgXj z^~2+RL+2>LbA8EP({ygMp40Z4uP&NS;+v*(<6P!jcR!syoGkK#0;ZQ~uL9>d@OO05 zygyIBw@x^e&%cK=-Vcv@Ax?ndN6T$tc)SlWAE06F^`TUaAMZ9=kBBLx7wd=Z zmX7d&t`4?kQ~-syHM{3d3^27ZMYRa820fsF|`6MdVWN@N6&q> zBBL&kuT9C1q2-kE@c63n6kCoSUmMrOL*uK)Q;gG8czkVQEK8K7>7nsef%ma?KaGRl%lZ5s$zFnAvW-P)Uob9bWS*ef3;)!hR1qjb71G7o{oQ8(|bIWV*!5BFmFqaw_C2plQ>80Y`ww+pA4j&1% z$!f!&QOHO~FrJ>T{U`X0J@K-mxQk%KiQ0v=i=d&a4>d((Q4->0t5fK%mLq1lZT>gk zxO?AF&Vwi?C{B$080)LmRtyRlM}Ly>hZ-OK2in!18i4I5b}`-A*3)_}7aO+yGz{uy zV8|cCb@Q!XXqaz381l!k4cQ>P4x(XBc`&568WE3t>lYg4uz?|;O=FVUc(STIkG)HG zu!Dz-bi@RL{DZX%#!2?ArXN1cU*T~`u&P_wNGb#84bmjwFE3pXPgXj^D8B;W@8rI} zR9wVk7_FHfVqV>Cc(v8#l_yUchIpl6%ESzpS9M^BR|JFcwU9X#Z={mjMdKkpXqbYt z4IhBT$A{XZz|f6p+N)`*EzmUi)-7nJ!2U@(Qv~IewddAzS1qW#+L!UmY4Y(g49zRj zbo&K8`=}N)$!-CnNtabn8-yPaJm-4LE(EE5Wo5uWe{N(1!-kR%f*K zZlPgbH87C9`R%B7Y{B|{n7jv+e6QjmL-()R2KTL5Xc&yq2Bu6rplvbVnuUgeon>G) zScmnz|93F(s~VUB%=_50%Hwb3ng!V+Edp-<7PEBLR_pUnfv~dotfJgC!dSnM|2)?IPv8XD|kMG|VPzEvy8zU57d_0phKOsT89$ zo;KmR+DfOVjhrAFhUYocoM)gLiJyJA&+aiaY|?d%_iTmQYoI4OaNpN@Vry+j-SD1iUD;utBiDxAKJXr3s&rl0IsmU48|k3>I=v}J24MElSb+ISl=BMS z--fYZ7R`S|HNK^Fp<3dy>oL)7>hz`j6&i-VwZ33K40srR&+~!y>K%7J;BL1z<8vKY z$W&HMebm$SWl(!eCA-7uS*%n3e!PJ{2)QOS4A)_?O9#FsnbSIYqxGyFJ6x%wu@F2O z7$M*9`3D$=WlqCvvbJcM`<;%4AI;#|hB3G+X;+rZst)mp&UNiks#625 z)BW5oWgKINt&5s0R%G1zJb> z=F$wqeT`s()`kl1XoOB(-1_%B&6`S1^eY#=`Y zU|^$ho5V8x!yp?+1K;TPME?c6T3p|+2lTi>W3?WCw&MI@UXN=Mp2KSx_-qVJF+34$ zA^YZk8s@YIv&nj1`(qNrJ_UvecJyF2i#G65pxs&$Oys;x!@?h9%G@S8X@ATg%*8fg z{6HQB4a<6P1@jc%y};wFUmoGM*?_eL?zt_LH%HRr+*kOP#yPZ2K@;kG^-VMY<7GmG&sS+n#dfI zuRCiI^$(gcuL+Fi_g&5V@%$chK5-y89`N1F`#i?qA3lX&`F&b~#`8DEe>3p3=J)+I zyz?5vpO12n^ZNsU2R(n+^i;u*ab}QweA>RatG0ga0~8C8K-=(Ah!qQ5yV6e23?BekPeAUr~^JMfe^3v>)PU%*FAHzjhTr z_oL_kDvpP7{fF~$T0dMC>;^t#FvB>1rzl5Rkno;%Gk3l@zxEsTNw6zmv<|pabgg|; z!+5^F%}$Vw>-iKb1_*1sMlERD@dF4ub_{EGkLcE?HzWc#jh z-0CfCtT^bkZ~Dto6%4-}d};|FNf5RN@s0ho@CgRV?~CW{&IRgK{=J-^vD%>y^D`t5 ze0>NsodrI-a#m2=3<8MWn-KEk%pEJ3Hip%-6~HAz+9?EWLE6b(muDK77odT8I$ZOD zeZ{~Ax^z8>7le!Weim#HaHDzHhl6$P;N&2|JgZ@3p@!*hcXr!`brpZq{1*sA!YeS41{eDA9tosZsm%Xv5;i=BJ!eNE&qy|v$&kMG#^%CGx_ zH$1Yby!ggGXFk7c&x>!r61?knC=~F!PJjNd`V;&%Y9IGSNDpuk&+F&{`y1vfdNtah zOl;MBZ5;M%^hG~=j@J&~Vwg?V+uFD3i~IPWbQrfE(}DIgbog}|(RGT;IH}9Pb|(|Z z;ra6b^EDPu&*K7Z7eAwSyw~tri4A%hTI0u3*EROX+oc|e!8t1@i&fVjN{_+AI za!mPmP>?@C9XE-Gk-Ep1UrKM7z%j-_4(I0+nEXjAWy;cn7s|k|x4-k<_itDK)0Xr1 zuH3V0=eVmb3f}UpNcpDg*CO?myMvc){P&|>AOD5x$UarpK~Ny5Ge!ippP#3MdBW=* z;0bi#pTv||JMi7v>?i%4c~xe8rTwJQVV%qC7R;N8jQd_d()wtStP$ux%!>chSg~5p zk)1`ET8~1mPSteL_%WI3Di9s@_z@Yc#r5QIIn)ZU=oM5?#Iv4H{EG7C zvzF!;)sy%|FjxGpp5=D7g|&4>o_KE!o431#*LGIg{lyy$V*|$Ig>+%KQ}E>m$~ku%m#dOdis0mZ-cK1C$V4$!m>c4a{qLUf4A(=gH0%`#m0; zVgG^7?TBkppkbb~MID3ubNm~n?84b?5hUz4x80Vf3DO6Tlbvj`Z+<5_f)zWnSy*Y>}sBJ~kX@%)0S^h-20I zlH^6}%WRa+{USCTi@Y3tCyI6Wex~mtCl?FuWHUX(&u4(%mqKS@KGB5vyUWhd>o*Cs z=0H|9?S(-VQ8!E%Y@`_hY@&u$$E{9e1vR5}C2UcwYVE;Jbw<`Z8+w@6;K84%wzk2R zJ0qjI!^U9V)BC_I#y!oMV9#c~_XhD(REjON2T|r6=Ulbj9*H`BW!`7i-jDaI0PlpXW^Q#|7j4M1IfxkNJT9hwr1o1*={;qml!uInao(jBwC zHTqWI?P(sbzBDk4^?21P(7?6P>f%iX58q?FkX6XMFW%DAoAWR(9t+fHxh1{%NAZ@7 zhxC-jjyns zP513Bz@__kF*2Z>ssqD#*#6ge3anaP=Z3uY8e#uyd{k$`2ig#ZiyAi!p~NZ|611@m z$n|C2h_?CjJr!(=QQL5w4RHQREH>jLJ5e;|HuY>cfoZ>2ra&9xcjoa|V!W0Iip6mw z>BHahIuyR8dW=hDNRIz@A%Sxn&P9EO$J>t#&euc{L3ZT+(b2!QzYzWHRj$Wx8s}h^ zv5w3e7ZNzxj^}pMc6>2nMY!FX20dFvkKq*SKx25&Djv#_4lV@OLC*(BRyCdiYnLml z{wQyT)%d90G`>HJ2iG`BPimaoL@ynmkVN(m#VCbkTH~bn1i~4@8F$t2zJNDLH2;i* zh0SQ+$};wj;p5Ai)@BqJO*%gu_fQObVKa}zKWaWEv5YkfQ7gKT;N!#4o8REfl#bSi z^(?EC)eXKXZg+8$#(f6w-$DFEm}c0j0qAFZ^DFvlCu@yuEoW8ev^82p9 zJAuFd#^CMD?~|RQ5q8LK8?pzy^PHm*cF1lUx(x4=S%+h7!szgw_DQVbLJ#=X#94O- zmjH(JU&z{N=fk1{jBoFc#~#QB}|Qs;mf{KY2K} zhjG%LK|fBKE82a62RxkH{}Shq9?ospYt>8BX>dl=9dsV_aBiFTa|E4U-KiZp#-<(G zw-eUCutyTS=k_GtGY|B*!);6J0MtH^PVA{-NO}Tf#NH~+)8{-|w_5)Y?Bn*L4#H~_ zy4}}!v~DfrvBYbq0e+s=i}i5cVZASwQLQ|jbV>|}AvxrBqVh?Wg5r(^eF@UTd1eGo zidjSbVIOY+&f$NBGt|Jtxn)7UzrdN`;oJ((9XB}v6tnL^HRi~g5CE0Ry6oG&3QUvPr+{cQuc>yyNLR+ zwu^S!2i$(_#UQXf&;6L@_0*3mbU*g(OVfDLJv>{jDzgW(?j8-S<@271ptZG(_vf|q z$0HG*^&!#+Ob_(IQ(7N1qFBMIMt*%j_RTr0511~%UX7e8kP6=GKx1#%z8Ma@lS8Bv zG~OMs+gK;?URcsIo=&i0A#2B3ClKDC*zq^k3A}%s2Ar2^!~i7bnLe zlVuXdNwkh+ouF}U{|lUxL!=Wl&TZEHS|@lo!#Y8?3+V)nbK5f32_BtXd$c{v1nUIg z1YX_;EJ!CnHmIljVy9V7SSJ7{>4X(pCqP;VGGY(mF>vR{5XnwAEf;F4mK|SQ&8_(5 zNRVQc*BBUg4cqJv1bt88fbZ!%)&URZRlC)6jwij%c}b*lO#DdmK9yy8x$~s`DbKe^ zIZ!=*t*rr`sm`Nt{?nX!ym@~u$J;Ik?>`OETvLy`JMAB^YZA5b#n7-_7hJ5r1#foR z*XVU#-(EqDhiqiV11misW|6#k(J!4neMR`@28&#Lkj+YDpdwgWyRX9j-Y~4j`>=<1 zhxIe!V*$&1imO`t|ScJ<;h zko%DEB%>}GPqr8h&mr)1tc!>CoN7GT?w-?*b@33dHJ+V;c){@}b@8w~YCN7i{ti!y zhi5A?l$=BKbP30(=z4wY(c|q)?c(E)b2M-|I7Q>({&bqhT;lPYHs-oi7k;n%(=O+5 zMBMLg``@(=`riiFif^!OImy6}U_86P+Xq=s^wv7SuGMQZpo^V+t$EipJv}OX&zeBF#BTLA`~JV_YFMlP?pA%ExKwS zR;#*rNM~p~6!Xdcsn&Jzkp9zncG~~c^{R`9^`FM$)$4b7XzbK@wpyR-u`|i8Q?GYC zdc1h{Her7a+9j9~?4|K=dwuS6E_uoYsjorH3a?$23_7}f3 ztvqj1i)nA- zfVK!`UWq?i7anpd11-7DvEoI(O1la7(H%~u-G$p6Ya;Ys!7fFc6Z~+7?`q)D5O{w( z;QKK=c2Y5XCcp22_x`;kmEs3}pXyYqdl~+F4KH*oOMk#0V*GlZTd5vo_;kR(#qfHb zTPaWQ`+EWZ6T@Rp&*h?g&{GWf8ixN-zn4FI@V7?5>o^C3-y8woo8cb@{!R>kkH%xI z;K|Rv&WF_!af1=A%9QB+(c-7M;@x@9)i~E0Ju4tl z|3qt*u0J=LTYo;1%6)K?XtCC1gXsw z2`W0pe>xD(7s1hbRiWe6jrlMKlWoh0CLe5;(Ny3(RN3g*(trG8DfY^pdRm^fTGs58 zSIBGSy)_f;O>_6`nd>Z(n*%j{YuaFGNWeD7`4$*U60vKSiREgzJmm_y!-~t`I(3rq zG)OdvXl+!1&${?uRzVzCQ?Yw@g&6ESvO?T&jdaF}ch%K0uI4-YhT zG^GCbXL5rYv30l+9R`HsNa%>VP7o*@3RENxuXTFt`8>E();#I-Tq{?rE~j6c8&mVO zY-!I`G+!0&dK_X7r~34PD?7ipLMYiPWz6$VB%lX3?W_N2%Id)}IO-NVZyI7=8K9(c zOqI~*Rb{BIKUMtn4`d^b= z`~->2&oI66kLVr!pV8a?MKR+V=XvM(YsHM?&pG$6Lv=b_>)d}#{NmITFE|zXNcz{j1L!Ze*ybpqnxPS?v&&G19DH`Tjb^*BCe{k=FT06 z@tnqYowbw#Hyz=X~ zT`1SGT4VP!j~2KGP3B&}N0wynB}(mP?iKd*rJH-(2NQr!9AVxE@%{pHAF`6IB6A;w zH5fYeh5i=ji(XOFr=ioJb6xDd0z7Dl7$zQ7S1auPaaxQ zGOs8u&2hX|Mr|+vLL6Rw6LtaFt0cnSn^BD z%Swtz70%8tF3X>jytsHyera-fL4NX}g?Y1aF~4+AcIim(nOaa@zOY|P%F?Avd*(4z z&yv!4DMcDbS<0y4Ik{sd|OYC05#8A4Cp*y@qgRzPrvDBux4OGc|N`> z12&>9871|^f2mw%Chq!s6l6qT>k&bLpRz(=OXj+j19KkgnvWFuMB9A4FR|w0ub;}E z5l@l75O)?}v7i876W((C&0{(>H;DFfyeHa;BePLfF`n~LYBG3I%%zv&z8pA+n}fjZ zJizIv$Zxvi_qzN@2R~A|UCQxgKcoszv6k|`p1>2v)e|(7q8(Du4t_ey@MaX!^5kH{ z=ooA;%|)oW#_4a*aBHHMOHvfjl^25eKlq;=4`n3*g#^FQEFt#1L_@R0!sewt3h@{l z8bP-tAhJFYF;(OPXn`3~E38YjhIUJauc9qhR@x)87J1S;BePIfXu9qottZIU;+6r8 zn}s;`-c}z(%=LpHoN1k9osE6;1Hr^>RA?|NGX#w{4AmO}Ykw4SL5;D-BCp7Jh{^=) z@|XyAPQuAUld&E<75lZPTQjVg(2;qFa$JQNxJTjB`5b5PT#GpGO6yLI3wZ6khaF_KXcH94iSg-4>Z()!96YCjU5$Ev$R=K|s0c1%FS|3^; z!HZfCBgH-uW$hHv@cG4x`p7sCXPvY@u->-*0nPe8;)dT7@uHzqEaLY{*;B45lE^F#r16I>|fiz2Z=6pIqE zP%MHExJ;Cb#bOEe%2$Ys#4_io^(7ICZiJFJflVx!oE8TMwe1rg8N#CEX*zWh6o7iyQ-E$&2g z`Q73k~tg97{_5Yk%_XYY$lt_7P6&m zCC`wpWs*#mZDd>7PPUgFWJlRac9vaaSJ_Q=mpx=p>mHdRQ>`y#noO4&*xCFHJg9r1 z-e=29^!n$Zs*YiQz)RRq^@8;xROYMJaqDGR2=fpHbpjbQ3lNEPAr#GgsK+9!SY|=} zEP^^BRlXR?ZV6OWg|!R=%ipYvt>xBAYlU?QvJZT1JtTYK7@j_|FY=1_w`wd$o+;0g zXJdq~6_y+*2gz)iBL~Y|IRxjt50k^?2su)YlB2C(tbfZfa;zLD$IEl%1bMEUD9@9V znX>z)pA!o`9WS*QQXXCh(d^uOnlLfL+UMT0wBDp{o%M!UzE|R6POqR>V za*13jE96CTnf#kvE-#iVcnGd7JgH+#ol~O|sJZ)cQ@RR`5kbyA&G7u8jDQ{7b$)l;RYRF$UERfft`S*n-nt@^0G zs-Nnw&Qxcqv(*4KPz_SqDn|`gxoU_Ss)niIIBs&J8l^_7F>0(Dr^c&u)C6^|nyAiG zlhpZYvYMi%s%dJvnxST@3sjz(rDm%+Dqqc2^HhN*stR?HTBiP{maB`^3bj&QqApdJsms+Wb%nZ8tyWj5tJNB{R$ZgkscY4Gb)C9i z-Jot%H>sP|E$UYFcXgZEpf;*a3O-D=MQv5v)ONK)-LCFXJJl|=TivPdQg^F+)V*qt z+N-M6K2@#mQ~T8cbx_@}9#9Xeht$LB5%s8gOdV2>t0&Zx>aaSZj;g2B)9RRdMm?*Z zQ_rgx)QjpRbzHryUQw^A6Y4efx_U#MRBx)c)Z6MG>K*m2dQZKtK2RU3kJQKN6ZNV3 zOnt7tP+zLA)Ys~t>Kk=ReXG7x->ZMAAJmWPC-t-XMg3d-s(w?aRgH2~Ew)GrECwqC z=LE3GAA*x23JX{<$RSf7i!E_>yxkBBA&n6*-^5O|ezu!hwmoLzs8PWMd9zDPiW@A< zFD)#Y1Bp*0 z|4!t;i|KD@adBa4dPeU++3bR)dHQKcM%290ye0XtqGv_r&0but@5&2{=J1_eP*O6V zK{HZ^)SFXMJ}bYdWNDzhq`0K4{+vRPQwGzUo}%)KOUm<$@(c3mSXI8E1QaJsqn;%jEAe$GtIG|zp_b&$Dco9hsLP1ATX(+qr?!IRe8{NBf0&3EY; z`Z?XeryKZm1D|f-(@nYQrrdP*`yBKAU~|niS3^&Rp(n%CBg2%JVam%e0H`CzFG;4$YztmD$^r*V~lW+u-YM@bxzMdK-Mb4Zhw6UmsJCKBgXh4E{a_e;$!zh}F@8~$XQa{cf>BE)V6<4MMSFR_nOb4z^2d+#9u1p86Ob4z^2d>%m*{M-B zy99&5tfKmh7DMrt<}ZO?C4UadWO{14!Pz@As<>=14CxQ9r6mlS zW{OWs(`2Qk^@_@eGi5#XdVVV zDjEY&>}+_Pir|l-hj{-(QU2WW823ea;p_&ph!NiKRi7W}D;nVRkKQQPcy?jw?8OV_ z7UfrXA2bTTbHDO|c|)b|org|i_dpu@cu|T`t2{qAh^rgS)|VW88LTh4`Z7dchU&{O zeHpGVBlKmYzKqhB(fTq*U&iXoczroXUnc0wx%x6uU(VB)NqmXs)@9BIC+i=k@I^Nb zwHG%H-;^&c@qa|`qUM$?F4Z59Xz*RVvcd{|LmyDA;d?GQ z&8V%k)ND|Y2P-l-hcBUAQwF@MWLySwbs715iOz+Mr#o^$E?%&_oRF$LlK;BfAk!9u=-h8xPEoS5?An({); zo#BSYLVb-M?vXEtkC25I#*7Ffj=3;=A3M@VNh~P}SB1fBC5ssLCS77j`F@Hm@;wAc zX?7Ix#U6!%?IQdMj?v#2>+i?-K8Y>%Jp{+-?~64%i}Mzil$Dp3EG)#r~(zMAP_-^)+TvnA(`7zWd3(`-{T&!6~}l%lHyK#S@+A zW!`OQP%i%(%FEZ>8CzDAS5~0!OMLgb!)x7_TV9~G1oeEriy6d2gt_Oh`96M7m>lyX zGl=gS407jM{HV1D-^PU%2|w4*nVmleV`Uyc#pL=5)8zB}_}n^$HOP%9AZEDlyWu_} zhu8gXc;t6u!ZgQ>_2C%n!!foFj+n9Gj~a}P#5d80W1LTUiR*y4SP zRR1`Xdx8FnX-H4yhlYMt%5UO*DpY@~b*KK;XioEk52;a_{6kz=WAbynPhs+lhJH<{ zacK>yfA#A}^J}3glz_&(OCH1k84+tIAe_;sTQWoAC} ziAB6mf$^{LKF!518cdFqc&)+sr?{~G;^&wt;cCRetX=@8(X65}{w;bC`Cr(B6Pv3Y zC;U(^7bYwFb%vAiN_S|ELK=7V!$co&^jLDQ@cV}3p>ZkWHvule&c?JOWJ<87;6b4T zn~(=a<}F;9Cr2-qV-{n;<1*Aa1tscSICD%fDyN_@mVjNJ=1)jX{PU)20qKY z&ob|`-1o+PkY%R#S!PAWw?@MxZc}5_cq+wgN!q2km3Fy z(~3C;e~!VQV_GrC{65(HZk(Q(#_5?k*tE)EQ;)%>bp{*y1{?YYoAL*n@&}t%8f@w} z*w8cBl$UGD&o%gS4gOq%KiA;THTZK)eREBHb4_`D+{k5Fq+0Sn~%bXQFdx|R98uYvDtp0#-4 zd((}z7^Y_SG2ffHWM*GxGw3vQ^<}o;*?iZ>)T587hZZl;Vcu(T!n65Kizl94`i*+j z-3s&?IPJDf&C+W;h)>Hx5D%8t5vN1BH{&?6FB(xcgDp9D$f$EII4sMH5usyKun!;v zF5!E8IX}O&*lM8vGA_brbDWU0qO;z&gqlWQh&xkXPklXD9jcdBk$MS23)-jG3pL1U zo7FabVdjZcTyfVvbxriJ=x5V!N?(}zkJPG+Rat{mXT)TteVLY!-a2MU?AG)VX$h(0 z>fe&Sr2Y>XpQbNq&>$_L!MMyuX%*@1(<&M~8@E4xd&5f_wN78ssC}cJ3Ew6@+jM_g zLbE;1_P5y6;^S6JTJ1igW5(ARUnlj+YMb<8^5Yq++B#HnT2K1h?il^nVMfQ~j(a-A zcKS49S?8jRPkY|e`IauH(-OKb>%PC|nx1RY5_-Jb^8&iVeMS0+)PJNe>3L6D0ur?? z%RG^JBE5CSGODZo*YowBpQkiPX_nGDB{gMA%EFY(Qf^9lDCO~#*Hhk44W_mNAKIti zlvWY(Po~$?|A;F6kKqWmefmQEFKaOP;`^80+WZ@l?*3bnz65x`^!`Ig2mJ$%dVsC- zpIZ`Lb(^HNrGH-eBk&#nmpYFAp_S;Lp@y!hRat}6mr$G0Kk%j=<7y8|(dOy(QfJU} z<_Y~abxrzBS%cBWZSmEJ^dSIocDo*0rS8Ns~-h+l*BipX%TEcdCDDddKww#6P=j@EtF_0~Lec^PpXL0r^4BtE7uAzo3PGd?BhqHD(2nT<3Dvk)0C?nK{;Lfme{ zh?u!Qj+m<(FGo>w6d^a7W8%(5Je(U1H{H4lkxTa=_Gb^`da5WE3K38T5zqD@@~BYc z)8mMQI)aF(rx6SE45EQv{D1A;d7NEEnLqwI_jGqUou#uc7!Y@MFd$$65l{&ri-2Jf zL6F5^5fuf2VG|Hh7z_xwj3chYFbaH?K|x5^nkCSf1u~N9kcH0e+sRGe+v&70I^_J` zRh=dsU~p!B^ZoC8@9T4`&beo)<*BDW^_;5nq&Ywf%>{}xfEIgi(I3rZTVhVzi(#2r zZ7ah|alTBP`C?XGx4Cg^%#w>U>DGsr&EENQoK+KN`DB@O`@=WReA{Z{ytW<9XFI?g zwK&UcV2;@>W_;aYu2-DnH88(xaCTRm+x23aTUGvFnO8M9t7_9+syEJ|dY}1I+nYOO z-c)Q}rCm$Al|Ha>F4KNyEPd2$rGv~=I?Vi}Bh5{kWnR+p<|LiC*=(fGnTd3^IY$?o zZ*+;dMpu-sG`r{;Gm8f26@A;BqM@^L{@RS3QRd)`OLK4DZ060tyqlr3ZsMGq!5KGk zp3Oey*bL6FiF0cPX4V{GPEDLmbHba>p^5Wn24>I188a8ZZl+9}A@jdKH|Do*IWOi} zvtSl&Hru7kJeM_Tj?3WOmN=(ngSjjX4b5hz?rDDN2hCI6D@^i+x(TIHm{htpj4ORh zcs;BSlkyyzV6QMP-v{=E{cO()6LMZr@@23BUJ9I9nCqH_vBlQf$>?HI7z6L}X5Ae& zykZvXgm6IdV)$sWCVUJIgoD8Q0>e>obWsk+6syCrMa8>@D`B>MH^Z&={mM1&0`EBv zzw_Ut!pGoo|2^xv3!%?;QLM>^7j4-D;iRIHZ6VwWb`BG=-GzGy4-mR-LM+gKo!1O-r%>6I-Yty2$F_m(US z;myKZgtrR6EBv1D`@$ax|6ce*;Xep}B)m=dW8qJPKNbE=_zU;A9qxc%!kyr~>G`kW z9=IPKfI09GJObn~e;l5KdGHK83-e*2@_G&y!yjP@bij+ST%J}!C#)gITZi3?=fj>w zr#GTi!(PSG@T~2H#q-(3qBGlBxCh)GMwd#(($WRR^Q8;nVz{=bmcC`*^{~EJn&;32 z+ZNB~dwJ8}KCmzBXWy)%Ge1$d3|7EPu%>vvq1pYmkb=9VV6D5)clW#9{cd+(>+W~E z``zw5-<|Jv=eyncZg-vUuJhe>zPrwM*ZJ;xw>!;ur?u`h-<{^W(|mWD?@o8S(|oh! zcPM(o9z{>K0g9rh6d;4c;1akDu7E4yYPiN*0Jnezu*kUw;{{_dgE8jbPcScj9Q{(E zUn=xVB^(4t!AWM~&lcVc_rjy_81zAr^hAZ8sL&A=`k_KUROp8a{ZPry2xGiubxg+B zpH)3$va`b&?YyF$SG4nrc3#oWE81v98?9)g6>YSljaIbLiZ)u&Ml0HAMH{VXqZMtm zqK#Ix(TX-!(RM1@O>8rj{D3ef{|L;4BW=@%+Db*csAv}zZ6daT3K^}C(Fz%@kkJYm zt&q_Q8Lg1f3OTHh!wNa9kim*|10N+%AA#AH=<+>`@Rk^Opbycpba$S|{ zs$5s)x+>RID=$7F#S`2qVj@g}$uI>@fm7jhN6Q7>R;9zLbWfGuscPp{?Yyd;SGDu1 zc3#!atJ-;0+pb#qF}k=)FLzb;H*gMo5xx}nk?bnTu9EC3$*z*@D#@;r>?+BwlI$wU zu9EC3$*z*@D#@;r>?+BwS|!p@tP8{8*rHF5*r(^~3+syhY*Nveol&ggr(|7rCY%Ll z7whyQeR`2T8oZCT?xThKXy85?xGz7TSeJhUX2LPHPlC(f3b+!M!%FCMj~U@Tezyfz z2)lx#!nff&a3{>Q%H0WS#Ha6TbA3h z+?M6GEVpI3Ez50LZp(68mfN!2mgTlAw`I94%WYY1%W_+`is)T>l^MlCdVLX2)rnJe zhPhTOFD@41O`TZ=wd5vaVxoOm%DbkYY&fT8^eos?Q<*bb1Ut0EA4YDIc=BI zb~$aA({?#+m(zARZI{b-xonrqcDZYpvv#>@my33(ZUODam+E$@ zZkOtIscx6*cByWc>UOMnu61Fh(56?VqvAT+qJuW>pp83dQ7jHe3Xihy z7`^ea#R{!|1x?*SQ+MFQOKIy4+F(VvPY-y%ntj0W58}Ubitg|b&(RMTU9@=zZQkLI zFNMdQ^F;A%c&d0V%q#vaR2*N0wV;iLI`l%Hj+Bj3$7T}JD!pmkT|2a6wRKV3-cuF$J>=+!!C-xYec z4n12(exCon0vEX6g|;siUgrNR;7Yj0f8VtK7jQe=0l$Ph;coaf+ynPJ?*W(t55Xhw z7(5P7`hOlg1JBx?FI*@u&%t8&BP@XqcoCMn?@H(dvXZX`a?wBr8gghP6X)xn__KkJ zlDLn-fp8EU1;==wvfPr~4w73Zxpk6TBe`{w+d*2ZT0|>JT}@K!B(+XbJ4kAcq;`?S zI;pFZwhq!(CvA0-Rwre35>_W+brM$B4=&LUF3}Gz(GM=s4=y2fby8O+b#+o#Cv|nw zRU=(B(p4i}HPY2Vx_Dgk-!`xv>;OB#E-)Q-1NS6db<$NQU3Jn`CtYz8W zH4;=KK{XQ8L4xX}r$%x*NKTE^)JRR8q|`}D2T7@ukPZ^kksnYj(JwF2FE1e}byCtn zO6sJfPD(mRNu89`Nk*MibdZV;Qqe&o>Lj90BI+cfP9o|gqD~^}B%)3t>T12N*6V7$ zuEy(Xyr#x$YP_b#Yihiv#%pT4rp7zec!wJA(Em3Sx2f6N!c6+)n4(urwyDWBHQ1~6 z+SFc8>J=YWi*3amwbmP+RQi*Qk~)d7F0$7}_IT)kZD2dt0d|62U^?su zu0zJU$XFK{>mp-aWUPxEb&;Dca??d-y2wWt+2|r0UF4#RTy&9(F0#-?7P`nn7g^{c z3teQPi!5}Jg)XwtMHafqLKj)+A`4ymqc$U^OO2jJ8a<6v4x2_zGi}F_(uf|S4MdEJLdtI0}sI?@EEX)sqamrzh`Y50p`nHcO`VH zvr*xAwRb{dBP+t{Vr}ULm<=}>uTO_l;9R&Cdf-ovJ(awSATJ}x%O)${OxpvNyl(Qc zhMWvo?xMABfT64-BLfz@C!(g%rsmP}gBH69MK?Jau+&8>-RmuMXON@M!I^LtoUKk< zShL<}Y3n8f-DIGf40Mx$Zgu}Zvz(pc+*9H7qFcSMQSaUAyjz_|D_6HVU!%U)sOJ&t zd4zf%p`O>M;{nUoCM#CA`i)ksZgsjwJN;{xrEYcFtxmhuX}3DuhO^7)3?kE zyBmk>iI2Kk8jl>#d-V};Bpi#=9#29(CH!6Dw+q6vj$a6i9lwMuF2#RWI=|xh)sC$S z*Fp{I&e$YN3W}F2FSxcBW{J^8EQkPC3g1vun_G;VtjNtmO?V{O;ec1=ysQPXqO?o(=T znVMRwmTGE=CBK-XmX@iZIoaOyMy!=t#d0NHQ{puxJVy!7QNnYSZscG!B{)Z^)s$3C zDbr!m1U_clY=fY&_xEi$Uv9z z^ExunrS5yAysYkHTkciIUFxz+J$9+X*tUDsVQk;M>aIt<^{BHR^;J<xTVTOw52+}H5?B^k;Mqnh zY$Fv%T22olQk$67F)KI ziV|C%O%_gtw}B@iN^m)wN<|4SPpxo*z`wvVB^Dz>{} zHLQUutOfVY72shMVAKxD~z!KY$;? zkKo7fQ@GDHeh&}ATzD98H};|mdr^hGsKQ=UVK1uW>XY;=BR$JV&oa`pJhkS!Ft+G* z#qx%xFs5M`G?R_#VX8Km;K$%rE*$MQ_sEFNiPEX64$!;szZ6&*{uhS^8S1FUl zRwY&@hpkGjoHuwsVUrLqCX1~D+Qq*8?DKYXr5Ae%J%oLy+CCl3dCD(!{N?a9xT;u1 z25A`kzXjJh=6bjRX2VTzGu#T_gCD>T;YaXe_$e%Nj}`C|B(3B+HP@}XZm)2iu-`it zS=Y%|t6C|WskJ*h)(j=Q7n|j?;`7B93ZGB*%T?h8+e?gBdyG5+?5t1U0}j{Ef0B)| z1Q!%9k*Sx+)JtTlQz=E;Wwcv%lBrcnr;|)|E18$b)G8$tpOSTwr&Z)>6?y6=Pu=9H zn>?)|Ppk6lu8t1OHzrR>TrdBl!D^|-vC#l+9N@t|huax>usb4AeE3}MG zsqajwXML4}HF6O9x|MRUQVwFuS4#OxDPJk2gQ;98l`(bEn%eog)OAW-r_`;HqLotA zDMg)9)G0-4JQu;Vm1Uo@Tq7m%Zk~^0%O#A@vsZUp<8Et|Q=fA3ZWL{Z?ME5KHn&FE z^eLM@dGC|=K6&qRr#150=UM;Z`tc*+NH|`rdO%o#ZoTGe+g0(kP=h-3!aD!07h(z6 zS`AyPVQaN4gGSndts16P!=P#yQw?LP={@V% zQVmqGc^w(FQGOgVwWwxde5iur>KN{r}f2XK(#{f0I>h;Q4+%{K&X?lyUJW zGhFIQyoXJxhfS%+tg}(!AYruXA0^ymOX&$W+rAf~E#*<+W6)jPq5a=wY}~`v)I$&4 zHDGl&?wz8ZJ^Rk4v6E~i+#L=8*Jpp~$v*Cw!{BJ~GuTQ#2WP@ra5nr`Y%6+Kvs>z= zM$p5Slyph2SQ-v3p1+TPkuVBI!<$+12kkCfJLjG7Zg{U_w}I`zGk-R)9yYKZHn1Kx zu%42g976fN@Gt4-G<0gFjM&qg~;&k#Gz zDC6@{#^#~PAk+CYpe?$7nxf)*xLg~dVkq%3O~JzmeI| zFiVfy;@zerU?hx!(ePf_2DXD8U?U${Dbl z)am^7$zsy2H;6r1k7r()B!Yq*=ws z)3ta`OubaUYxTQUziaioR_uxTU8~=<`n~w-4!z2qbo@p=ABJbQ6o0gLoxSVqT}S(` z%kDR8?)Pl94-`*ibBnoN+;@wsJwS_P^v=R`oz41-(Bgz>f)3VrtWxL$8&E{46UUGWxM_v?Yo#Ik2q+QU*THSxH)?mhg!qVrM;Yw0de zY|K|(TIw+^^5L0Vv_8jtCms6G>r2GHt!&d{*rvzCtC04A9BsNvEQ9B1`QwV8N%d^U zjCV|*T=#A)!I+c16`1{w&KY&PV>Y^ ztC;8hKVnn(gezUlel*Y3ma;3XGyeLD@z*@%cewOjrk#osW=A7uO^fGC@TWkKceNl>^ zx4lx%J_)xweoi`Spww0?m)|*WwR_b!vfniuEnd|mwQHm$GK^Ty@kxv)0G{j6*2R;` zuh-n(ql@L2n#x9q-U#qoC!e7K+EiP1Zmq_hp?*3gV zUZMuCP=nu9gO@0&->Ajgl>Of-`|m6Jvz2{WZJs4pH!8tjsnv_sYO9i*o$NZXjo*#~ z-BH}9KJHT=_mQ>xJQI3Ay+m!W)~t$d_Sh*(eTqCiCr@=xDA#VR|K;g<@>66&KS~zH z$wRkv|Eu);tF$bVmc`Q2CM{jOgpK0OFZNf9)BQJZ58X;Vmg{hJ_NZ(8!8M*GPhGCD zj0`OxLsfNooAVbKvmRZXqx7!}XT3T%T(semaM^|jl>YWg{}3rXSxRqE`mJHkhKkaE zdw68SDy9Fi@Wh5VOXRrl)P`5XybUiX{Ueorqm)0W^bZQ%8#XBY@lxTU?KUNy|R{FnG`V*D@-b#P8l%A>d50c}NQoC=q&xT$(ekhx{p{DfTuk=q+`YlSo zQR;V*`)J=du6S5V+ob9qcmG#+yw4qe?G6jw;YnAG^%={q(^Z~vm1ms&w6kmKtfJ1! z>TE!lG%Njyn8ZXS*6FNO($T5ZUQ%i+m0D+bcW92WJJMyda9GQC)-R><`I(%(%Eo=&>xDRUO) zY13a&_hy%ouYZ=-c4>WDTAwCgPm`@avQ-PEVc(?JLd&t^wlntIGKAvh;v2=YMO#rV z9yK%ezws$%{WlKY?9k#HgPU|~DEbSQ>*Ak^-xog-e^z@Rs}+wUOMmsF6|hLcQ=1=3 zBmI>R-uGYr=ud`x(mnYYB2VeV;<52le7{(&US9XnazljAgu%b$w>%jCPr`9S_YbC) zObvX-ij9pa@fka?Yw(xk{QvkAv`Nb0Yq^s8ZW6okQ~dO`*h8N7r*w$n?{QB)w$YwlNyVDoz$zI*k{871sj{KJqIsS{GZ~6;)lgeaGlcd z26Jc)V~QUazbhWoHbQY}@wa~88_y`F6l02d@tYL?mtrBk8t}Ny?ps7##E=gS=3{=@ zYo9{@7VCRsosS;;V`KdeZieFO7}d9K_DSg{OL$29q_c}P=~}T4$I`JIKQU#4AFblI zJX8G<9w!H5X^L^`d|dH@HoJ20C|BK)o(RP=g|XU3DU93kei1|DtzggFblw|dwDN!Y z(ObO5XHZVx>=5;w&Sh_U-6!tfyyRbZz*}y<@p_vMe-o+N^k>ko{trJG-+%fkdIzri z#;Yei66G}MgEzUx=50R7(2(22_7G<^`W>=&v#1`;r?`2uof|je{odrBZ+7VG_QjmM zF*mO}=nb32hl(AFj}*JY`$*I<2Mp8a4=WBSzEpg@xY0FRi;-IPMe)=Pr`TSAr3F3U zW}gk(B-R^n>hvK;sr@9Weys)vdVu2R#q8oidiX&tJU+7+_-qV4r4M=;8hXPy@sHx# zjsI^9-nxV~>A<096&DWO+B|NS>c-E24jbHZ=YA~j0zI+582cKIPmlI3(O_eYl-_0b zZv+1c)BUr%XSd^$tB0wq8V4F59PGO#p9n{qQ-8GYJ6Yk5bVb@Dpn>KN@~+J?4So7uIAR6#mh=%tONM)@B|S{;70C>4mxV{IRcZdmR`1^ z@Q>c1K0KIFm5&PD)^D~3Ys>O+VNG1e8LHl){*KV&o$2ohuXtnn4&g7}l)hV*#rL9T z%~ovgleJj2xoL&JXJlK%x1DG2wO;ew zY@6JA&1`!sHZRV0v}*IpY^V4>^XvoGYyL~NhqsUaHv5cqmXBvAd$;(r*|}C#F37%M zW#yvmi&j^5W?%9K@viKv)>d|B7g}GrCc7xEvCJ;^2Jt^-mw0#hzh_s(w})q6^Y-u! z*;UqCma?m@xopV3?%m{Gr|wV@OE zHHocgyjJu}?d?~-B`d1$l&ZUYOQ~H=P3>w@YFFb^yV^3ft6`~KMc*JV$jcJnty1e6 zmRi@a)Vj7zt!vBFy562z*A}UDjZdv>Ypv_g>?D8j9iQ6R)~Stco!VGQ8=DX&^Dr{e z*lv>cHdTAu#*y1*+lFbpiEJ0%p4#5<)b@s_wl^`gy|?rA@i!XnIa*+rT40u1U}I{5 zBdxpcQIoZ-#`aXt`a%;=7O#Y1yjZ+y6t$t0g=YRMXemA{hFe$L!Uj5l|BBIJqINsp z3fl=<@8syMB24A4;_actXT{cze1|8yCB7@(DgG``cuRa(yj%P|C9=Vn#d}@neI;6t zPm68Dw=Hcez8yO%pHaL$W@({Wj(3Gml|JQaCzMVQ|8(io;wP3)6z8`>{N&Qf(tJwk6eaW7 z(r3j_EuAWUTIn?L)9I$kNjFVNx@k(%O;eI?8kuy{$fTP_Cf(GWbW?NEP0dL+H7DKF zoODxj(oLi3CUYwIw75|*ur`#p7;%74Z5umv9$yE)iA6^QGZ} z`~vyaTll>szeKJtH6vmxUNJ6n$IJ7}#jo&;XetjGUlYI5{N1T~lyA7swfVKKbG;c7 zC4Msg&K++sQ=-IQ#<#_1n=w)1H{(X}oAR5)zmtDQ{AM#KO8jWtB7Uox6ea#NzAOGc zGb&2_YJ6Y(2WD23_}BP*@gJIDQQ~LgAH;uTrbUUr4PzcYH-0Ssll&**KQ;5B#Q(<6 z#DAXuT&{oNdB{Y*IQ~)mcF#p7^2zZ};&*sHGLdhNe-{6x=Oh#P==hcRot~FW*O{P*GyUYUgJmHjTu zmyvggxxe_QpJk+DwF|Gv}yO}&0dy?(Pke;fO^^=(eQen`Fk7W({M?2kQtbL!~> z4!W!OZoYw!un*{iW8XhK_5CfW?;nx+{*kHgpPc&sQK|2rf{*U+tjGZJ!~lZ+|3GP< z=^H!;JV^ZDaG3bvJc%_X9?+C{K%*ywNBKAM=)kwwC-vF0d;=e2$H-@7)~%iy9A{5t z3DXiwXiY3(4Bumy+kb^`GapmYUiEF?W=|YvyXK92js?D>zGKhLz70H+@i)%{*{!bp zUECrJ;TFvsxy9W)Rt3*0?{)ldeM4d!t?3OeA#sjIKF%Jo|4~me8`#I@z^?X52D z;?MdH=jZ7U?mORiEY82ce)Em(U&Lw|5=WVw-u4pklouQmnaafE%`EU|wiK6o(K}y4 z;wX)*oYAfo#{tv40fs#xRJ{kL(KFN@Jjr|)@pZl-@t6j0hgok=zwZS8)m|3o>B+y5 z*)%3*6TCO(Kg4;9vcK?cz;yy1tHqHJmvn|E9@{G5EPqv-xiDQ)E*)G}p z#iwV}-JSn6@egDlaOCcu0ypr)wufW(%sy=Ye%XGG**_z7iCs1D#`ZDsIKFA*k&VCY^;%beF ztA)hX8WUG*PF$@iakb{e)tVAlYffCPsdPr^Y}YxbbPmq+`O@dHpL0v+ihrT>1@SMI zz9{~s(wD@~E1f6)<*8^o+QPS)S&fMij^yL) zo30so;i$w5TN5uFm3ZNl#0$qHUN|N3!ZC>#PD#9QO!S53+*?YwDAij_J>r8*QF~7u zu{m+XQPJC&J%db9drus33XWLt_8oa)GhSE{kBo3sVuX_uBMgZVj^c}K7$(%5Yww92 zHu6i>B0eG?foYA**^3fiY~rVEw0In6M}K8w#K-1i9TUggO?;P)7oUJThQu9{_cHOw z9VfoV9p7zFWQxtvinTrzsKdr$uG}p{*4^8IdRa2#6d?T4%(W3CjX3oBL|(FM-DnIanLY?gXZ}r4w~nm z!$Cvhpbh!ixt5ZcXhUM6lk><#r{!PBzhwV;`FYaz<^0RyU&+5B9vNzL9vNyApKV_i zztF6ghWw)ZBJs#sn{qzf93MIB#Kc)!6K9>6IBRR-tP>MwZROqVD(vv;{A#TF>-pE6 zbxnSayF@NKIdR!(yuNuJ#`D{^!dTwluET~mvD1Eqz3-&o)VvDK2ifecTbBylRqQgp0|rf2HeE&9mc}< z-F*8a8*bqPZ=qHkIdKbLc+ZJHpJOk4;+dz;FCJ?*-*|r%e<6QCd`Z4U{NHj<`}b>LTTwRmLM!}-^% z+7o&9d@ok+uv|zUL!q9hpz84qt@vhHdf|9K! z{I7-eY}5XX3O4%20^^Fw(I;~f6J`zkp8YpxoE%Qv_{*D2I5ql|38#J`{+>DT8~x*i zFKqlhZNmd4+v^gB%Y#_18SYnapUVq;6= z{*ALz-dLH8KFXn;7k!L((Z~50eS(M4=wmc`8I68M2Rx0o^ELV`Z==!Q zX!JN5eU3)2ql@?*eU9hR=lLFu-bbVV(ddCR`XG&7NTVOp=!rD?B8}ciqd(H+Jd#GA zq$^7=`F1AHq|rBNnRn9YpEP^ay_7~jrE5#yB@N@1@Y_hkyNu*ojNfLm7$0nO zwH=SmR~pw{?faoL8rqjH;afdzcqn^3>=z{=%uKe+gR?S=;m5Jfy~7Q--yz}0rNc@m zvHG8kgFS?IeU*j&VivB&TK6hrl>w=24q0g-d5g04tL)!~NZ!Iw{=Xu5zj}jz*?%BuD!vIl4N@(XC02ewO6u-Xup)COLX0$&vY) zNsEyqSC4Y!{mI#lWcm=Xj3h@ro#cz&Xi0vspWws#Uk5x+M{m=Pjps)B-|W~o(>spM fQws>G1!SoO#Cs0hgHCntc;ACI+qZ4nckllP8pbT8 literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/Roboto-Regular.ttf b/examples/nanovg_res/Roboto-Regular.ttf new file mode 100755 index 0000000000000000000000000000000000000000..3e6e2e76134cd6040a49ccf9961d302f7d139fcd GIT binary patch literal 145348 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 +#include +#include +#include "nanovg.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + + +#ifdef _MSC_VER +#define snprintf _snprintf +#elif !defined(__MINGW32__) +#include +#endif + +#define ICON_SEARCH 0x1F50D +#define ICON_CIRCLED_CROSS 0x2716 +#define ICON_CHEVRON_RIGHT 0xE75E +#define ICON_CHECK 0x2713 +#define ICON_LOGIN 0xE740 +#define ICON_TRASH 0xE729 + +// Returns 1 if col.rgba is 0.0f,0.0f,0.0f,0.0f, 0 otherwise +int isBlack( struct NVGcolor col ) +{ + if( col.r == 0.0f && col.g == 0.0f && col.b == 0.0f && col.a == 0.0f ) + { + return 1; + } + return 0; +} + +static char* cpToUTF8(int cp, char* str) +{ + int n = 0; + if (cp < 0x80) n = 1; + else if (cp < 0x800) n = 2; + else if (cp < 0x10000) n = 3; + else if (cp < 0x200000) n = 4; + else if (cp < 0x4000000) n = 5; + else if (cp <= 0x7fffffff) n = 6; + str[n] = '\0'; + switch (n) { + case 6: str[5] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x4000000; + case 5: str[4] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x200000; + case 4: str[3] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x10000; + case 3: str[2] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x800; + case 2: str[1] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0xc0; + case 1: str[0] = cp; + } + return str; +} + + +void drawWindow(struct NVGcontext* vg, const char* title, float x, float y, float w, float h) +{ + float cornerRadius = 3.0f; + struct NVGpaint shadowPaint; + struct NVGpaint headerPaint; + + nvgSave(vg); +// nvgClearState(vg); + + // Window + nvgBeginPath(vg); + nvgRoundedRect(vg, x,y, w,h, cornerRadius); + nvgFillColor(vg, nvgRGBA(28,30,34,192)); +// nvgFillColor(vg, nvgRGBA(0,0,0,128)); + nvgFill(vg); + + // Drop shadow + shadowPaint = nvgBoxGradient(vg, x,y+2, w,h, cornerRadius*2, 10, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0)); + nvgBeginPath(vg); + nvgRect(vg, x-10,y-10, w+20,h+30); + nvgRoundedRect(vg, x,y, w,h, cornerRadius); + nvgPathWinding(vg, NVG_HOLE); + nvgFillPaint(vg, shadowPaint); + nvgFill(vg); + + // Header + headerPaint = nvgLinearGradient(vg, x,y,x,y+15, nvgRGBA(255,255,255,8), nvgRGBA(0,0,0,16)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x+1,y+1, w-2,30, cornerRadius-1); + nvgFillPaint(vg, headerPaint); + nvgFill(vg); + nvgBeginPath(vg); + nvgMoveTo(vg, x+0.5f, y+0.5f+30); + nvgLineTo(vg, x+0.5f+w-1, y+0.5f+30); + nvgStrokeColor(vg, nvgRGBA(0,0,0,32)); + nvgStroke(vg); + + nvgFontSize(vg, 18.0f); + nvgFontFace(vg, "sans-bold"); + nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE); + + nvgFontBlur(vg,2); + nvgFillColor(vg, nvgRGBA(0,0,0,128)); + nvgText(vg, x+w/2,y+16+1, title, NULL); + + nvgFontBlur(vg,0); + nvgFillColor(vg, nvgRGBA(220,220,220,160)); + nvgText(vg, x+w/2,y+16, title, NULL); + + nvgRestore(vg); +} + +void drawSearchBox(struct NVGcontext* vg, const char* text, float x, float y, float w, float h) +{ + struct NVGpaint bg; + char icon[8]; + float cornerRadius = h/2-1; + + // Edit + bg = nvgBoxGradient(vg, x,y+1.5f, w,h, h/2,5, nvgRGBA(0,0,0,16), nvgRGBA(0,0,0,92)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x,y, w,h, cornerRadius); + nvgFillPaint(vg, bg); + nvgFill(vg); + +/* nvgBeginPath(vg); + nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f); + nvgStrokeColor(vg, nvgRGBA(0,0,0,48)); + nvgStroke(vg);*/ + + nvgFontSize(vg, h*1.3f); + nvgFontFace(vg, "icons"); + nvgFillColor(vg, nvgRGBA(255,255,255,64)); + nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE); + nvgText(vg, x+h*0.55f, y+h*0.55f, cpToUTF8(ICON_SEARCH,icon), NULL); + + nvgFontSize(vg, 20.0f); + nvgFontFace(vg, "sans"); + nvgFillColor(vg, nvgRGBA(255,255,255,32)); + + nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE); + nvgText(vg, x+h*1.05f,y+h*0.5f,text, NULL); + + nvgFontSize(vg, h*1.3f); + nvgFontFace(vg, "icons"); + nvgFillColor(vg, nvgRGBA(255,255,255,32)); + nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE); + nvgText(vg, x+w-h*0.55f, y+h*0.55f, cpToUTF8(ICON_CIRCLED_CROSS,icon), NULL); +} + +void drawDropDown(struct NVGcontext* vg, const char* text, float x, float y, float w, float h) +{ + struct NVGpaint bg; + char icon[8]; + float cornerRadius = 4.0f; + + bg = nvgLinearGradient(vg, x,y,x,y+h, nvgRGBA(255,255,255,16), nvgRGBA(0,0,0,16)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x+1,y+1, w-2,h-2, cornerRadius-1); + nvgFillPaint(vg, bg); + nvgFill(vg); + + nvgBeginPath(vg); + nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f); + nvgStrokeColor(vg, nvgRGBA(0,0,0,48)); + nvgStroke(vg); + + nvgFontSize(vg, 20.0f); + nvgFontFace(vg, "sans"); + nvgFillColor(vg, nvgRGBA(255,255,255,160)); + nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE); + nvgText(vg, x+h*0.3f,y+h*0.5f,text, NULL); + + nvgFontSize(vg, h*1.3f); + nvgFontFace(vg, "icons"); + nvgFillColor(vg, nvgRGBA(255,255,255,64)); + nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE); + nvgText(vg, x+w-h*0.5f, y+h*0.5f, cpToUTF8(ICON_CHEVRON_RIGHT,icon), NULL); +} + +void drawLabel(struct NVGcontext* vg, const char* text, float x, float y, float w, float h) +{ + NVG_NOTUSED(w); + + nvgFontSize(vg, 18.0f); + nvgFontFace(vg, "sans"); + nvgFillColor(vg, nvgRGBA(255,255,255,128)); + + nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE); + nvgText(vg, x,y+h*0.5f,text, NULL); +} + +void drawEditBoxBase(struct NVGcontext* vg, float x, float y, float w, float h) +{ + struct NVGpaint bg; + // Edit + bg = nvgBoxGradient(vg, x+1,y+1+1.5f, w-2,h-2, 3,4, nvgRGBA(255,255,255,32), nvgRGBA(32,32,32,32)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x+1,y+1, w-2,h-2, 4-1); + nvgFillPaint(vg, bg); + nvgFill(vg); + + nvgBeginPath(vg); + nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, 4-0.5f); + nvgStrokeColor(vg, nvgRGBA(0,0,0,48)); + nvgStroke(vg); +} + +void drawEditBox(struct NVGcontext* vg, const char* text, float x, float y, float w, float h) +{ + + drawEditBoxBase(vg, x,y, w,h); + + nvgFontSize(vg, 20.0f); + nvgFontFace(vg, "sans"); + nvgFillColor(vg, nvgRGBA(255,255,255,64)); + nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE); + nvgText(vg, x+h*0.3f,y+h*0.5f,text, NULL); +} + +void drawEditBoxNum(struct NVGcontext* vg, + const char* text, const char* units, float x, float y, float w, float h) +{ + float uw; + + drawEditBoxBase(vg, x,y, w,h); + + uw = nvgTextBounds(vg, 0,0, units, NULL, NULL); + + nvgFontSize(vg, 18.0f); + nvgFontFace(vg, "sans"); + nvgFillColor(vg, nvgRGBA(255,255,255,64)); + nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_MIDDLE); + nvgText(vg, x+w-h*0.3f,y+h*0.5f,units, NULL); + + nvgFontSize(vg, 20.0f); + nvgFontFace(vg, "sans"); + nvgFillColor(vg, nvgRGBA(255,255,255,128)); + nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_MIDDLE); + nvgText(vg, x+w-uw-h*0.5f,y+h*0.5f,text, NULL); +} + +void drawCheckBox(struct NVGcontext* vg, const char* text, float x, float y, float w, float h) +{ + struct NVGpaint bg; + char icon[8]; + NVG_NOTUSED(w); + + nvgFontSize(vg, 18.0f); + nvgFontFace(vg, "sans"); + nvgFillColor(vg, nvgRGBA(255,255,255,160)); + + nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE); + nvgText(vg, x+28,y+h*0.5f,text, NULL); + + bg = nvgBoxGradient(vg, x+1,y+(int)(h*0.5f)-9+1, 18,18, 3,3, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,92)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x+1,y+(int)(h*0.5f)-9, 18,18, 3); + nvgFillPaint(vg, bg); + nvgFill(vg); + + nvgFontSize(vg, 40); + nvgFontFace(vg, "icons"); + nvgFillColor(vg, nvgRGBA(255,255,255,128)); + nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE); + nvgText(vg, x+9+2, y+h*0.5f, cpToUTF8(ICON_CHECK,icon), NULL); +} + +void drawButton(struct NVGcontext* vg, int preicon, const char* text, float x, float y, float w, float h, struct NVGcolor col) +{ + struct NVGpaint bg; + char icon[8]; + float cornerRadius = 4.0f; + float tw = 0, iw = 0; + + bg = nvgLinearGradient(vg, x,y,x,y+h, nvgRGBA(255,255,255,isBlack(col)?16:32), nvgRGBA(0,0,0,isBlack(col)?16:32)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x+1,y+1, w-2,h-2, cornerRadius-1); + if (!isBlack(col)) { + nvgFillColor(vg, col); + nvgFill(vg); + } + nvgFillPaint(vg, bg); + nvgFill(vg); + + nvgBeginPath(vg); + nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f); + nvgStrokeColor(vg, nvgRGBA(0,0,0,48)); + nvgStroke(vg); + + nvgFontSize(vg, 20.0f); + nvgFontFace(vg, "sans-bold"); + tw = nvgTextBounds(vg, 0,0, text, NULL, NULL); + if (preicon != 0) { + nvgFontSize(vg, h*1.3f); + nvgFontFace(vg, "icons"); + iw = nvgTextBounds(vg, 0,0, cpToUTF8(preicon,icon), NULL, NULL); + iw += h*0.15f; + } + + if (preicon != 0) { + nvgFontSize(vg, h*1.3f); + nvgFontFace(vg, "icons"); + nvgFillColor(vg, nvgRGBA(255,255,255,96)); + nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE); + nvgText(vg, x+w*0.5f-tw*0.5f-iw*0.75f, y+h*0.5f, cpToUTF8(preicon,icon), NULL); + } + + nvgFontSize(vg, 20.0f); + nvgFontFace(vg, "sans-bold"); + nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE); + nvgFillColor(vg, nvgRGBA(0,0,0,160)); + nvgText(vg, x+w*0.5f-tw*0.5f+iw*0.25f,y+h*0.5f-1,text, NULL); + nvgFillColor(vg, nvgRGBA(255,255,255,160)); + nvgText(vg, x+w*0.5f-tw*0.5f+iw*0.25f,y+h*0.5f,text, NULL); +} + +void drawSlider(struct NVGcontext* vg, float pos, float x, float y, float w, float h) +{ + struct NVGpaint bg, knob; + float cy = y+(int)(h*0.5f); + float kr = (int)(h*0.25f); + + nvgSave(vg); +// nvgClearState(vg); + + // Slot + bg = nvgBoxGradient(vg, x,cy-2+1, w,4, 2,2, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,128)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x,cy-2, w,4, 2); + nvgFillPaint(vg, bg); + nvgFill(vg); + + // Knob Shadow + bg = nvgRadialGradient(vg, x+(int)(pos*w),cy+1, kr-3,kr+3, nvgRGBA(0,0,0,64), nvgRGBA(0,0,0,0)); + nvgBeginPath(vg); + nvgRect(vg, x+(int)(pos*w)-kr-5,cy-kr-5,kr*2+5+5,kr*2+5+5+3); + nvgCircle(vg, x+(int)(pos*w),cy, kr); + nvgPathWinding(vg, NVG_HOLE); + nvgFillPaint(vg, bg); + nvgFill(vg); + + // Knob + knob = nvgLinearGradient(vg, x,cy-kr,x,cy+kr, nvgRGBA(255,255,255,16), nvgRGBA(0,0,0,16)); + nvgBeginPath(vg); + nvgCircle(vg, x+(int)(pos*w),cy, kr-1); + nvgFillColor(vg, nvgRGBA(40,43,48,255)); + nvgFill(vg); + nvgFillPaint(vg, knob); + nvgFill(vg); + + nvgBeginPath(vg); + nvgCircle(vg, x+(int)(pos*w),cy, kr-0.5f); + nvgStrokeColor(vg, nvgRGBA(0,0,0,92)); + nvgStroke(vg); + + nvgRestore(vg); +} + +void drawEyes(struct NVGcontext* vg, float x, float y, float w, float h, float mx, float my, float t) +{ + struct NVGpaint gloss, bg; + float ex = w *0.23f; + float ey = h * 0.5f; + float lx = x + ex; + float ly = y + ey; + float rx = x + w - ex; + float ry = y + ey; + float dx,dy,d; + float br = (ex < ey ? ex : ey) * 0.5f; + float blink = 1 - pow(sinf(t*0.5f),200)*0.8f; + + bg = nvgLinearGradient(vg, x,y+h*0.5f,x+w*0.1f,y+h, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,16)); + nvgBeginPath(vg); + nvgEllipse(vg, lx+3.0f,ly+16.0f, ex,ey); + nvgEllipse(vg, rx+3.0f,ry+16.0f, ex,ey); + nvgFillPaint(vg, bg); + nvgFill(vg); + + bg = nvgLinearGradient(vg, x,y+h*0.25f,x+w*0.1f,y+h, nvgRGBA(220,220,220,255), nvgRGBA(128,128,128,255)); + nvgBeginPath(vg); + nvgEllipse(vg, lx,ly, ex,ey); + nvgEllipse(vg, rx,ry, ex,ey); + nvgFillPaint(vg, bg); + nvgFill(vg); + + dx = (mx - rx) / (ex * 10); + dy = (my - ry) / (ey * 10); + d = sqrtf(dx*dx+dy*dy); + if (d > 1.0f) { + dx /= d; dy /= d; + } + dx *= ex*0.4f; + dy *= ey*0.5f; + nvgBeginPath(vg); + nvgEllipse(vg, lx+dx,ly+dy+ey*0.25f*(1-blink), br,br*blink); + nvgFillColor(vg, nvgRGBA(32,32,32,255)); + nvgFill(vg); + + dx = (mx - rx) / (ex * 10); + dy = (my - ry) / (ey * 10); + d = sqrtf(dx*dx+dy*dy); + if (d > 1.0f) { + dx /= d; dy /= d; + } + dx *= ex*0.4f; + dy *= ey*0.5f; + nvgBeginPath(vg); + nvgEllipse(vg, rx+dx,ry+dy+ey*0.25f*(1-blink), br,br*blink); + nvgFillColor(vg, nvgRGBA(32,32,32,255)); + nvgFill(vg); + + gloss = nvgRadialGradient(vg, lx-ex*0.25f,ly-ey*0.5f, ex*0.1f,ex*0.75f, nvgRGBA(255,255,255,128), nvgRGBA(255,255,255,0)); + nvgBeginPath(vg); + nvgEllipse(vg, lx,ly, ex,ey); + nvgFillPaint(vg, gloss); + nvgFill(vg); + + gloss = nvgRadialGradient(vg, rx-ex*0.25f,ry-ey*0.5f, ex*0.1f,ex*0.75f, nvgRGBA(255,255,255,128), nvgRGBA(255,255,255,0)); + nvgBeginPath(vg); + nvgEllipse(vg, rx,ry, ex,ey); + nvgFillPaint(vg, gloss); + nvgFill(vg); +} + +void drawGraph(struct NVGcontext* vg, float x, float y, float w, float h, float t) +{ + struct NVGpaint bg; + float samples[6]; + float sx[6], sy[6]; + float dx = w/5.0f; + int i; + + samples[0] = (1+sinf(t*1.2345f+cosf(t*0.33457f)*0.44f))*0.5f; + samples[1] = (1+sinf(t*0.68363f+cosf(t*1.3f)*1.55f))*0.5f; + samples[2] = (1+sinf(t*1.1642f+cosf(t*0.33457)*1.24f))*0.5f; + samples[3] = (1+sinf(t*0.56345f+cosf(t*1.63f)*0.14f))*0.5f; + samples[4] = (1+sinf(t*1.6245f+cosf(t*0.254f)*0.3f))*0.5f; + samples[5] = (1+sinf(t*0.345f+cosf(t*0.03f)*0.6f))*0.5f; + + for (i = 0; i < 6; i++) { + sx[i] = x+i*dx; + sy[i] = y+h*samples[i]*0.8f; + } + + // Graph background + bg = nvgLinearGradient(vg, x,y,x,y+h, nvgRGBA(0,160,192,0), nvgRGBA(0,160,192,64)); + nvgBeginPath(vg); + nvgMoveTo(vg, sx[0], sy[0]); + for (i = 1; i < 6; i++) + nvgBezierTo(vg, sx[i-1]+dx*0.5f,sy[i-1], sx[i]-dx*0.5f,sy[i], sx[i],sy[i]); + nvgLineTo(vg, x+w, y+h); + nvgLineTo(vg, x, y+h); + nvgFillPaint(vg, bg); + nvgFill(vg); + + // Graph line + nvgBeginPath(vg); + nvgMoveTo(vg, sx[0], sy[0]+2); + for (i = 1; i < 6; i++) + nvgBezierTo(vg, sx[i-1]+dx*0.5f,sy[i-1]+2, sx[i]-dx*0.5f,sy[i]+2, sx[i],sy[i]+2); + nvgStrokeColor(vg, nvgRGBA(0,0,0,32)); + nvgStrokeWidth(vg, 3.0f); + nvgStroke(vg); + + nvgBeginPath(vg); + nvgMoveTo(vg, sx[0], sy[0]); + for (i = 1; i < 6; i++) + nvgBezierTo(vg, sx[i-1]+dx*0.5f,sy[i-1], sx[i]-dx*0.5f,sy[i], sx[i],sy[i]); + nvgStrokeColor(vg, nvgRGBA(0,160,192,255)); + nvgStrokeWidth(vg, 3.0f); + nvgStroke(vg); + + // Graph sample pos + for (i = 0; i < 6; i++) { + bg = nvgRadialGradient(vg, sx[i],sy[i]+2, 3.0f,8.0f, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,0)); + nvgBeginPath(vg); + nvgRect(vg, sx[i]-10, sy[i]-10+2, 20,20); + nvgFillPaint(vg, bg); + nvgFill(vg); + } + + nvgBeginPath(vg); + for (i = 0; i < 6; i++) + nvgCircle(vg, sx[i], sy[i], 4.0f); + nvgFillColor(vg, nvgRGBA(0,160,192,255)); + nvgFill(vg); + nvgBeginPath(vg); + for (i = 0; i < 6; i++) + nvgCircle(vg, sx[i], sy[i], 2.0f); + nvgFillColor(vg, nvgRGBA(220,220,220,255)); + nvgFill(vg); + + nvgStrokeWidth(vg, 1.0f); +} + +void drawThumbnails(struct NVGcontext* vg, float x, float y, float w, float h, const int* images, int nimages, float t) +{ + float cornerRadius = 3.0f; + struct NVGpaint shadowPaint, imgPaint, fadePaint; + float ix,iy,iw,ih; + float thumb = 60.0f; + float arry = 30.5f; + int imgw, imgh; + float stackh = (nimages/2) * (thumb+10) + 10; + int i; + float u = (1+cosf(t*0.5f))*0.5f; + float scrollh; + + nvgSave(vg); +// nvgClearState(vg); + + // Drop shadow + shadowPaint = nvgBoxGradient(vg, x,y+4, w,h, cornerRadius*2, 20, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0)); + nvgBeginPath(vg); + nvgRect(vg, x-10,y-10, w+20,h+30); + nvgRoundedRect(vg, x,y, w,h, cornerRadius); + nvgPathWinding(vg, NVG_HOLE); + nvgFillPaint(vg, shadowPaint); + nvgFill(vg); + + // Window + nvgBeginPath(vg); + nvgRoundedRect(vg, x,y, w,h, cornerRadius); + nvgMoveTo(vg, x-10,y+arry); + nvgLineTo(vg, x+1,y+arry-11); + nvgLineTo(vg, x+1,y+arry+11); + nvgFillColor(vg, nvgRGBA(200,200,200,255)); + nvgFill(vg); + + nvgSave(vg); + nvgScissor(vg, x,y,w,h); + nvgTranslate(vg, 0, -(stackh - h)*u); + + for (i = 0; i < nimages; i++) { + float tx, ty; + tx = x+10; + ty = y+10; + tx += (i%2) * (thumb+10); + ty += (i/2) * (thumb+10); + nvgImageSize(vg, images[i], &imgw, &imgh); + if (imgw < imgh) { + iw = thumb; + ih = iw * (float)imgh/(float)imgw; + ix = 0; + iy = -(ih-thumb)*0.5f; + } else { + ih = thumb; + iw = ih * (float)imgw/(float)imgh; + ix = -(iw-thumb)*0.5f; + iy = 0; + } + imgPaint = nvgImagePattern(vg, tx+ix, ty+iy, iw,ih, 0.0f/180.0f*NVG_PI, images[i], 0); + nvgBeginPath(vg); + nvgRoundedRect(vg, tx,ty, thumb,thumb, 5); + nvgFillPaint(vg, imgPaint); + nvgFill(vg); + + shadowPaint = nvgBoxGradient(vg, tx-1,ty, thumb+2,thumb+2, 5, 3, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0)); + nvgBeginPath(vg); + nvgRect(vg, tx-5,ty-5, thumb+10,thumb+10); + nvgRoundedRect(vg, tx,ty, thumb,thumb, 6); + nvgPathWinding(vg, NVG_HOLE); + nvgFillPaint(vg, shadowPaint); + nvgFill(vg); + + nvgBeginPath(vg); + nvgRoundedRect(vg, tx+0.5f,ty+0.5f, thumb-1,thumb-1, 4-0.5f); + nvgStrokeWidth(vg,1.0f); + nvgStrokeColor(vg, nvgRGBA(255,255,255,192)); + nvgStroke(vg); + } + nvgRestore(vg); + + // Hide fades + fadePaint = nvgLinearGradient(vg, x,y,x,y+6, nvgRGBA(200,200,200,255), nvgRGBA(200,200,200,0)); + nvgBeginPath(vg); + nvgRect(vg, x+4,y,w-8,6); + nvgFillPaint(vg, fadePaint); + nvgFill(vg); + + fadePaint = nvgLinearGradient(vg, x,y+h,x,y+h-6, nvgRGBA(200,200,200,255), nvgRGBA(200,200,200,0)); + nvgBeginPath(vg); + nvgRect(vg, x+4,y+h-6,w-8,6); + nvgFillPaint(vg, fadePaint); + nvgFill(vg); + + // Scroll bar + shadowPaint = nvgBoxGradient(vg, x+w-12+1,y+4+1, 8,h-8, 3,4, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,92)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x+w-12,y+4, 8,h-8, 3); + nvgFillPaint(vg, shadowPaint); +// nvgFillColor(vg, nvgRGBA(255,0,0,128)); + nvgFill(vg); + + scrollh = (h/stackh) * (h-8); + shadowPaint = nvgBoxGradient(vg, x+w-12-1,y+4+(h-8-scrollh)*u-1, 8,scrollh, 3,4, nvgRGBA(220,220,220,255), nvgRGBA(128,128,128,255)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x+w-12+1,y+4+1 + (h-8-scrollh)*u, 8-2,scrollh-2, 2); + nvgFillPaint(vg, shadowPaint); +// nvgFillColor(vg, nvgRGBA(0,0,0,128)); + nvgFill(vg); + + nvgRestore(vg); +} + +void drawColorwheel(struct NVGcontext* vg, float x, float y, float w, float h, float t) +{ + int i; + float r0, r1, ax,ay, bx,by, cx,cy, aeps, r; + float hue = sinf(t * 0.12f); + struct NVGpaint paint; + + nvgSave(vg); + +/* nvgBeginPath(vg); + nvgRect(vg, x,y,w,h); + nvgFillColor(vg, nvgRGBA(255,0,0,128)); + nvgFill(vg);*/ + + cx = x + w*0.5f; + cy = y + h*0.5f; + r1 = (w < h ? w : h) * 0.5f - 5.0f; + r0 = r1 - 20.0f; + aeps = 0.5f / r1; // half a pixel arc length in radians (2pi cancels out). + + for (i = 0; i < 6; i++) { + float a0 = (float)i / 6.0f * NVG_PI * 2.0f - aeps; + float a1 = (float)(i+1.0f) / 6.0f * NVG_PI * 2.0f + aeps; + nvgBeginPath(vg); + nvgArc(vg, cx,cy, r0, a0, a1, NVG_CW); + nvgArc(vg, cx,cy, r1, a1, a0, NVG_CCW); + nvgClosePath(vg); + ax = cx + cosf(a0) * (r0+r1)*0.5f; + ay = cy + sinf(a0) * (r0+r1)*0.5f; + bx = cx + cosf(a1) * (r0+r1)*0.5f; + by = cy + sinf(a1) * (r0+r1)*0.5f; + paint = nvgLinearGradient(vg, ax,ay, bx,by, nvgHSLA(a0/(NVG_PI*2),1.0f,0.55f,255), nvgHSLA(a1/(NVG_PI*2),1.0f,0.55f,255)); + nvgFillPaint(vg, paint); + nvgFill(vg); + } + + nvgBeginPath(vg); + nvgCircle(vg, cx,cy, r0-0.5f); + nvgCircle(vg, cx,cy, r1+0.5f); + nvgStrokeColor(vg, nvgRGBA(0,0,0,64)); + nvgStrokeWidth(vg, 1.0f); + nvgStroke(vg); + + // Selector + nvgSave(vg); + nvgTranslate(vg, cx,cy); + nvgRotate(vg, hue*NVG_PI*2); + + // Marker on + nvgStrokeWidth(vg, 2.0f); + nvgBeginPath(vg); + nvgRect(vg, r0-1,-3,r1-r0+2,6); + nvgStrokeColor(vg, nvgRGBA(255,255,255,192)); + nvgStroke(vg); + + paint = nvgBoxGradient(vg, r0-3,-5,r1-r0+6,10, 2,4, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0)); + nvgBeginPath(vg); + nvgRect(vg, r0-2-10,-4-10,r1-r0+4+20,8+20); + nvgRect(vg, r0-2,-4,r1-r0+4,8); + nvgPathWinding(vg, NVG_HOLE); + nvgFillPaint(vg, paint); + nvgFill(vg); + + // Center triangle + r = r0 - 6; + ax = cosf(120.0f/180.0f*NVG_PI) * r; + ay = sinf(120.0f/180.0f*NVG_PI) * r; + bx = cosf(-120.0f/180.0f*NVG_PI) * r; + by = sinf(-120.0f/180.0f*NVG_PI) * r; + nvgBeginPath(vg); + nvgMoveTo(vg, r,0); + nvgLineTo(vg, ax,ay); + nvgLineTo(vg, bx,by); + nvgClosePath(vg); + paint = nvgLinearGradient(vg, r,0, ax,ay, nvgHSLA(hue,1.0f,0.5f,255), nvgRGBA(255,255,255,255)); + nvgFillPaint(vg, paint); + nvgFill(vg); + paint = nvgLinearGradient(vg, (r+ax)*0.5f,(0+ay)*0.5f, bx,by, nvgRGBA(0,0,0,0), nvgRGBA(0,0,0,255)); + nvgFillPaint(vg, paint); + nvgFill(vg); + nvgStrokeColor(vg, nvgRGBA(0,0,0,64)); + nvgStroke(vg); + + // Select circle on triangle + ax = cosf(120.0f/180.0f*NVG_PI) * r*0.3f; + ay = sinf(120.0f/180.0f*NVG_PI) * r*0.4f; + nvgStrokeWidth(vg, 2.0f); + nvgBeginPath(vg); + nvgCircle(vg, ax,ay,5); + nvgStrokeColor(vg, nvgRGBA(255,255,255,192)); + nvgStroke(vg); + + paint = nvgRadialGradient(vg, ax,ay, 7,9, nvgRGBA(0,0,0,64), nvgRGBA(0,0,0,0)); + nvgBeginPath(vg); + nvgRect(vg, ax-20,ay-20,40,40); + nvgCircle(vg, ax,ay,7); + nvgPathWinding(vg, NVG_HOLE); + nvgFillPaint(vg, paint); + nvgFill(vg); + + nvgRestore(vg); + + nvgRestore(vg); +} + +void drawLines(struct NVGcontext* vg, float x, float y, float w, float h, float t) +{ + int i, j; + float pad = 5.0f, s = w/9.0f - pad*2; + float pts[4*2], fx, fy; + int joins[3] = {NVG_MITER, NVG_ROUND, NVG_BEVEL}; + int caps[3] = {NVG_BUTT, NVG_ROUND, NVG_SQUARE}; + NVG_NOTUSED(h); + + nvgSave(vg); + pts[0] = -s*0.25f + cosf(t*0.3f) * s*0.5f; + pts[1] = sinf(t*0.3f) * s*0.5f; + pts[2] = -s*0.25; + pts[3] = 0; + pts[4] = s*0.25f; + pts[5] = 0; + pts[6] = s*0.25f + cosf(-t*0.3f) * s*0.5f; + pts[7] = sinf(-t*0.3f) * s*0.5f; + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + fx = x + s*0.5f + (i*3+j)/9.0f*w + pad; + fy = y - s*0.5f + pad; + + nvgLineCap(vg, caps[i]); + nvgLineJoin(vg, joins[j]); + + nvgStrokeWidth(vg, s*0.3f); + nvgStrokeColor(vg, nvgRGBA(0,0,0,160)); + nvgBeginPath(vg); + nvgMoveTo(vg, fx+pts[0], fy+pts[1]); + nvgLineTo(vg, fx+pts[2], fy+pts[3]); + nvgLineTo(vg, fx+pts[4], fy+pts[5]); + nvgLineTo(vg, fx+pts[6], fy+pts[7]); + nvgStroke(vg); + + nvgLineCap(vg, NVG_BUTT); + nvgLineJoin(vg, NVG_BEVEL); + + nvgStrokeWidth(vg, 1.0f); + nvgStrokeColor(vg, nvgRGBA(0,192,255,255)); + nvgBeginPath(vg); + nvgMoveTo(vg, fx+pts[0], fy+pts[1]); + nvgLineTo(vg, fx+pts[2], fy+pts[3]); + nvgLineTo(vg, fx+pts[4], fy+pts[5]); + nvgLineTo(vg, fx+pts[6], fy+pts[7]); + nvgStroke(vg); + } + } + + + nvgRestore(vg); +} + +int loadDemoData(struct NVGcontext* vg, struct DemoData* data) +{ + int i; + + if (vg == NULL) + return -1; + + for (i = 0; i < 12; i++) { + char file[128]; + snprintf(file, 128, "./nanovg_res/images/image%d.jpg", i+1); + data->images[i] = nvgCreateImage(vg, file); + if (data->images[i] == 0) { + printf("Could not load %s.\n", file); + return -1; + } + } + + data->fontIcons = nvgCreateFont(vg, "icons", "./nanovg_res/entypo.ttf"); + if (data->fontIcons == -1) { + printf("Could not add font icons.\n"); + return -1; + } + data->fontNormal = nvgCreateFont(vg, "sans", "./nanovg_res/Roboto-Regular.ttf"); + if (data->fontNormal == -1) { + printf("Could not add font italic.\n"); + return -1; + } + data->fontBold = nvgCreateFont(vg, "sans-bold", "./nanovg_res/Roboto-Bold.ttf"); + if (data->fontBold == -1) { + printf("Could not add font bold.\n"); + return -1; + } + + return 0; +} + +void freeDemoData(struct NVGcontext* vg, struct DemoData* data) +{ + int i; + + if (vg == NULL) + return; + + for (i = 0; i < 12; i++) + nvgDeleteImage(vg, data->images[i]); +} + +void drawParagraph(struct NVGcontext* vg, float x, float y, float width, float height, float mx, float my) +{ + struct NVGtextRow rows[3]; + struct NVGglyphPosition glyphs[100]; + const char* text = "This is longer chunk of text.\n \n Would have used lorem ipsum but she was busy jumping over the lazy dog with the fox and all the men who came to the aid of the party."; + const char* start; + const char* end; + int nrows, i, nglyphs, j, lnum = 0; + float lineh; + float caretx, px; + float bounds[4]; + float gx,gy; + int gutter = 0; + NVG_NOTUSED(height); + + nvgSave(vg); + + nvgFontSize(vg, 18.0f); + nvgFontFace(vg, "sans"); + nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP); + nvgTextMetrics(vg, NULL, NULL, &lineh); + + // The text break API can be used to fill a large buffer of rows, + // or to iterate over the text just few lines (or just one) at a time. + // The "next" variable of the last returned item tells where to continue. + start = text; + end = text + strlen(text); + while ((nrows = nvgTextBreakLines(vg, start, end, width, rows, 3))) { + for (i = 0; i < nrows; i++) { + struct NVGtextRow* row = &rows[i]; + int hit = mx > x && mx < (x+width) && my >= y && my < (y+lineh); + + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGBA(255,255,255,hit?64:8)); + nvgRect(vg, x, y, row->width, lineh); + nvgFill(vg); + + nvgFillColor(vg, nvgRGBA(255,255,255,255)); + nvgText(vg, x, y, row->start, row->end); + + if (hit) { + caretx = (mx < x+row->width/2) ? x : x+row->width; + px = x; + nglyphs = nvgTextGlyphPositions(vg, x, y, row->start, row->end, glyphs, 100); + for (j = 0; j < nglyphs; j++) { + float x0 = glyphs[j].x; + float x1 = (j+1 < nglyphs) ? glyphs[j+1].x : x+row->width; + float gx = x0 * 0.3f + x1 * 0.7f; + if (mx >= px && mx < gx) + caretx = glyphs[j].x; + px = gx; + } + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGBA(255,192,0,255)); + nvgRect(vg, caretx, y, 1, lineh); + nvgFill(vg); + + gutter = lnum+1; + gx = x - 10; + gy = y + lineh/2; + } + lnum++; + y += lineh; + } + // Keep going... + start = rows[nrows-1].next; + } + + if (gutter) { + char txt[16]; + snprintf(txt, sizeof(txt), "%d", gutter); + nvgFontSize(vg, 13.0f); + nvgTextAlign(vg, NVG_ALIGN_RIGHT|NVG_ALIGN_MIDDLE); + + nvgTextBounds(vg, gx,gy, txt, NULL, bounds); + + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGBA(255,192,0,255)); + nvgRoundedRect(vg, (int)bounds[0]-4,(int)bounds[1]-2, (int)(bounds[2]-bounds[0])+8, (int)(bounds[3]-bounds[1])+4, ((int)(bounds[3]-bounds[1])+4)/2-1); + nvgFill(vg); + + nvgFillColor(vg, nvgRGBA(32,32,32,255)); + nvgText(vg, gx,gy, txt, NULL); + } + + y += 20.0f; + + nvgFontSize(vg, 13.0f); + nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP); + nvgTextLineHeight(vg, 1.2f); + + nvgTextBoxBounds(vg, x,y, 150, "Hover your mouse over the text to see calculated caret position.", NULL, bounds); + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGBA(220,220,220,255)); + nvgRoundedRect(vg, bounds[0]-2,bounds[1]-2, (int)(bounds[2]-bounds[0])+4, (int)(bounds[3]-bounds[1])+4, 3); + px = (int)((bounds[2]+bounds[0])/2); + nvgMoveTo(vg, px,bounds[1] - 10); + nvgLineTo(vg, px+7,bounds[1]+1); + nvgLineTo(vg, px-7,bounds[1]+1); + nvgFill(vg); + + nvgFillColor(vg, nvgRGBA(0,0,0,220)); + nvgTextBox(vg, x,y, 150, "Hover your mouse over the text to see calculated caret position.", NULL); + + nvgRestore(vg); +} + +void drawWidths(struct NVGcontext* vg, float x, float y, float width) +{ + int i; + + nvgSave(vg); + + nvgStrokeColor(vg, nvgRGBA(0,0,0,255)); + + for (i = 0; i < 20; i++) { + float w = (i+0.5f)*0.1f; + nvgStrokeWidth(vg, w); + nvgBeginPath(vg); + nvgMoveTo(vg, x,y); + nvgLineTo(vg, x+width,y+width*0.3f); + nvgStroke(vg); + y += 10; + } + + nvgRestore(vg); +} + +void renderDemo(struct NVGcontext* vg, float mx, float my, float width, float height, + float t, int blowup, struct DemoData* data) +{ + float x,y,popy; + + drawEyes(vg, width - 250, 50, 150, 100, mx, my, t); + drawParagraph(vg, width - 450, 50, 150, 100, mx, my); + drawGraph(vg, 0, height/2, width, height/2, t); + drawColorwheel(vg, width - 300, height - 300, 250.0f, 250.0f, t); + + // Line joints + drawLines(vg, 50, height-50, 600, 50, t); + + // Line width; + drawWidths(vg, 10, 50, 30); + + nvgSave(vg); + if (blowup) { + nvgRotate(vg, sinf(t*0.3f)*5.0f/180.0f*NVG_PI); + nvgScale(vg, 2.0f, 2.0f); + } + + // Widgets + drawWindow(vg, "Widgets `n Stuff", 50, 50, 300, 400); + x = 60; y = 95; + drawSearchBox(vg, "Search", x,y,280,25); + y += 40; + drawDropDown(vg, "Effects", x,y,280,28); + popy = y + 14; + y += 45; + + // Form + drawLabel(vg, "Login", x,y, 280,20); + y += 25; + drawEditBox(vg, "Email", x,y, 280,28); + y += 35; + drawEditBox(vg, "Password", x,y, 280,28); + y += 38; + drawCheckBox(vg, "Remember me", x,y, 140,28); + drawButton(vg, ICON_LOGIN, "Sign in", x+138, y, 140, 28, nvgRGBA(0,96,128,255)); + y += 45; + + // Slider + drawLabel(vg, "Diameter", x,y, 280,20); + y += 25; + drawEditBoxNum(vg, "123.00", "px", x+180,y, 100,28); + drawSlider(vg, 0.4f, x,y, 170,28); + y += 55; + + drawButton(vg, ICON_TRASH, "Delete", x, y, 160, 28, nvgRGBA(128,16,8,255)); + drawButton(vg, 0, "Cancel", x+170, y, 110, 28, nvgRGBA(0,0,0,0)); + + // Thumbnails box + drawThumbnails(vg, 365, popy-30, 160, 300, data->images, 12, t); + + nvgRestore(vg); +} + +static int mini(int a, int b) { return a < b ? a : b; } + +static void unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) +{ + int x,y; + + // Unpremultiply + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = row[0], g = row[1], b = row[2], a = row[3]; + if (a != 0) { + row[0] = (int)mini(r*255/a, 255); + row[1] = (int)mini(g*255/a, 255); + row[2] = (int)mini(b*255/a, 255); + } + row += 4; + } + } + + // Defringe + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = 0, g = 0, b = 0, a = row[3], n = 0; + if (a == 0) { + if (x-1 > 0 && row[-1] != 0) { + r += row[-4]; + g += row[-3]; + b += row[-2]; + n++; + } + if (x+1 < w && row[7] != 0) { + r += row[4]; + g += row[5]; + b += row[6]; + n++; + } + if (y-1 > 0 && row[-stride+3] != 0) { + r += row[-stride]; + g += row[-stride+1]; + b += row[-stride+2]; + n++; + } + if (y+1 < h && row[stride+3] != 0) { + r += row[stride]; + g += row[stride+1]; + b += row[stride+2]; + n++; + } + if (n > 0) { + row[0] = r/n; + row[1] = g/n; + row[2] = b/n; + } + } + row += 4; + } + } +} + +static void setAlpha(unsigned char* image, int w, int h, int stride, unsigned char a) +{ + int x, y; + for (y = 0; y < h; y++) { + unsigned char* row = &image[y*stride]; + for (x = 0; x < w; x++) + row[x*4+3] = a; + } +} + +static void flipHorizontal(unsigned char* image, int w, int h, int stride) +{ + int i = 0, j = h-1, k; + while (i < j) { + unsigned char* ri = &image[i * stride]; + unsigned char* rj = &image[j * stride]; + for (k = 0; k < w*4; k++) { + unsigned char t = ri[k]; + ri[k] = rj[k]; + rj[k] = t; + } + i++; + j--; + } +} + +void saveScreenShot(int w, int h, int premult, const char* name) +{ + unsigned char* image = (unsigned char*)malloc(w*h*4); + if (image == NULL) + return; + glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image); + if (premult) + unpremultiplyAlpha(image, w, h, w*4); + else + setAlpha(image, w, h, w*4, 255); + flipHorizontal(image, w, h, w*4); + stbi_write_png(name, w, h, 4, image, w*4); + free(image); +} + diff --git a/examples/nanovg_res/demo.h b/examples/nanovg_res/demo.h new file mode 100644 index 0000000..999bb31 --- /dev/null +++ b/examples/nanovg_res/demo.h @@ -0,0 +1,25 @@ +#ifndef DEMO_H +#define DEMO_H + +#include "nanovg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct DemoData { + int fontNormal, fontBold, fontIcons; + int images[12]; +}; + +int loadDemoData(struct NVGcontext* vg, struct DemoData* data); +void freeDemoData(struct NVGcontext* vg, struct DemoData* data); +void renderDemo(struct NVGcontext* vg, float mx, float my, float width, float height, float t, int blowup, struct DemoData* data); + +void saveScreenShot(int w, int h, int premult, const char* name); + +#ifdef __cplusplus +} +#endif + +#endif // DEMO_H \ No newline at end of file diff --git a/examples/nanovg_res/entypo.ttf b/examples/nanovg_res/entypo.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fc305d2a9ab50a0e4862a144ef9e4b9bf00dce4d GIT binary patch literal 35392 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 FirMxiNxRrNX8#X1F5=oA^hRozV~ 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 literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/images.txt b/examples/nanovg_res/images.txt new file mode 100644 index 0000000..96ad626 --- /dev/null +++ b/examples/nanovg_res/images.txt @@ -0,0 +1,13 @@ +Image credits +http://cuteoverload.com/2013/11/05/mom-taxi-xvi-birthday-party/ +http://cuteoverload.com/2013/11/05/benson-hedges-private-eye-in-the-case-of-the-crafty-craftsman/ +http://cuteoverload.com/2013/11/05/no-underwater-ballets/ +http://cuteoverload.com/2013/11/05/every-nose-has-a-story/ +http://cuteoverload.com/2013/11/04/nosevember-nozzle-nose/ +http://cuteoverload.com/2013/11/04/this-just-in-super-strength-cute/ +http://cuteoverload.com/2013/11/03/have-a-bunderful-sunday/ +http://cuteoverload.com/2013/11/02/caturday-sense-a-common-theme-here/ +http://cuteoverload.com/2013/11/01/nosevember-1st-24-hours-of-noses-1148pm-pt/ +http://cuteoverload.com/2013/04/02/there-might-be-something-cuter-than-this/ +http://cuteoverload.com/2013/07/17/snorting-micro-peeg-gets-belleh-rubs-interwebs-explode/ +http://cuteoverload.com/2013/08/07/bark-in-the-park-v3-0/ \ No newline at end of file diff --git a/examples/nanovg_res/images/image1.jpg b/examples/nanovg_res/images/image1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c2ce39b736b573c2cb303f5dfda4e2fc6480582c GIT binary patch literal 25760 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 Z>xIt?4!_lDDyqtkd+Gd=Z8Nj&{{YK~!&(3U literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/images/image10.jpg b/examples/nanovg_res/images/image10.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d443fb0cbf4e0ffa7a190f54472a53774718d7c1 GIT binary patch literal 3439 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 literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/images/image11.jpg b/examples/nanovg_res/images/image11.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7429fe52ab750bde420e3ee8f18b87196208200d GIT binary patch literal 3818 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 aQ&nyhlyZOV@8%ng!#RgSK*!;1^1lH3hx#c1 literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/images/image2.jpg b/examples/nanovg_res/images/image2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1db15ab2490d971d8cb197a6608dcebf70101a2a GIT binary patch literal 24091 zcmeHv2Ut_fw(t%Ky^5ex#E2*sTIiuli*%4GsCYsM5C{pR(2j~?K`bD}0#-U=2azVC z2Lw@6Ito}oP*AYYL=p1O4&CE9_x}I;-v92s&z)~i)~s2xX00`|XZFmjJ*=m!SCEJ$ z&H@J^I1rFI_(7}=foo==ByR|^v4Pe?5X1vm!MqX4;BHG@U%AnGHsexz{ zM>Vt!oN;ww5QIFBm@JpYf}rypGyJ{IBWGYDC=0&0 z5XdPRx#sGDkeSs3v1+a!z$R1jB9u60c=Lx(vV*M!TV(Q|HP@Cf4z^`KB*4~bn~4pC zV10mu*fGd!7o3yT2JM9eczAetc?5WQ1s3r0@h=b;77!2?U$Sta_`-!t76`DfslUm{ zk0C;kpI=Z&P((;bL`+CXNDK~y#3r*W_-7cf?m!E8AR^?2M94xM3lPW!2-b6;HaDvR zVF;w;M6fR)Fnl3FLqTzJar5x<@q^|tKM?_OAg2>WAOs45&7eO#2xN>s;Sc=>rA*;#yfuM_)MT{EK1GYv&uBV$;<4I zG=}Eldgo5!K;GW+2IlKQoP1*Xz8l9X8oP!v^6xcu54(5=??0Y-v$FZkh}H&IpOB~% zSp`)sZ@(;nI5>c`D7H-8T$~zg5td^_Q9yzq*@c{{yW_-Q3GO=7eNvP2?4U))n>dOS z=<1(Wa=`*{uhRIV9te@P*vrgWlR@m95#g^Tke?NS)d~q9*<~((457E{;*Gd$i&OjQ z-eIqBX;r;D2bR?ZA?_q(4KTlVy?I{MX0W4rMXz{f?V9&={ykgx-6~%p)Y^I@)|d<8 zK52XqIr;W_Yoydu9mL)pHG#UODm~nCvWvvq`!Ay(7eA2lx^c#i`{A3c9s3`yY;)gQ z&B5K?s0o=bTv+|-tK501-EGOdwO>Bj3hR7b-i+SRZ6-w4UQ~^_R*lGH1~;=HY*E>* z(~HD{do5bdJ~WI9;Ep{#5!{p#q$KHUha6pdq97m!qD#}i^qgAx3D?G}dnxG*)EI9V zUY2^ka?jBoM1yMYF7@`0-|yPgob=z(AX%9=cDq~CsW#+zZ~76u=84w9iJp2r|TGS$uZRrO*$w z86bQU+;6Nel^W_QuG)XXDtiA{#Y?u)=X@^}?Yo?dfBbfj@Ux*(jp`;sa{r_LFMg%M zS;KzM_g0-M-GDv+bhN2z@DvN;YM$tfQX*9diSX+9*M(L|X)0a_oO&jDx8+6ohJNnL zA*zcC`00LCI<#(C>ed(*l)HH2?^cI4dDLx=Rg_Zvk~g+as5w%OrXjO-i(ATZxym7R z(|wqS(%0|vx|AN-f2Bc0C`Oi>r!UHN=_uyHGPy(io75Zq?DWPGJv^QSev|Y2$cZ*< z)8EtZcI>MKf6EB&xvW;0T+D$~7R0LSmRWzfow76eGpEz-+?MrC*U?Mu#>I&rR=g6o z+G6$iIks!Wb%5L$`6#d!v8+_*^A|+{|1A=|kN4PfC7*lon)VnUgZ=EXPR`}Mo`!rv zip@c$3%iM!~c|E!LEgd`T{~!hnw4Ls| zDWM?Wa0P1rp#ONH{^(-r=-*<6U*+WO@CokWHhC4du7Y88 z>stx>!#A}77E~>l_RhK3iv@Y;hW1b7m~TravFf`eLrgXptj)B-ckMASu#b7G&GR+q zWQa|((Rwi1}J%a8aRYB%wxj-2h!xxBJ|{r7(1K<97L zLC?2#iIT1#Ule1P`R17*3)0YBVbYZ(Gyc-Pcd7EScOPxW&utQ}{#|nHBM1NGz%PXp zAq7266Gsk=J4&8tE_XhA@xDx6CezT2sCLDvkKXI@MZaz7<{E9I=I0+s-*m<`w1x?U zP4$mtYTNjeHw&MO$b7S7RgQe}VPeM9_7)dvNfL*RRNEb`t9P>2WkqQmsan#hl~D8q zy+h)AbI$PDi~?!CB*Gd(sj@xp&F&8}>0>unZ#@^;wl#L^sr{!ss?iBuJI3m3Y6{Rz zR#FO=v%*jVh8xoGSNsx|sR|`8{n8SQ9T>-**evsqDRlPn<(#A zgWA5P)SJB8`M&kCz-c{=OJ0x2wy*RQ?1=s>b>d75`ev8Rv4L|L81rl(I*@-~4dR4idl#6ng>*B1WL>q`11IX8Ry zc}BMz#$URPGMB}w>E(QAAHZD;b?s_Aq<~BK@MC$MenZYx>t@KE!p-tfT{7;oAMj;>g6MZ8Y?%YVea9jeJ@U}84Il`xZJp+N<}&1 z)#uT(rLIy^gk27IT`E`7whCP->))@kY3xgL|JImw=%YS8 zA3}OHFR8MijUU1yza#}o$44YyG`J&x%SwEFz5j+F3hKdRuiGGG=qI}h=TqcY^ZD#r z$67k zcX@Ugs&DxGRbgq}MX)$T7arUFjhZPg3`_ z-|m0X382YsvHm^MpwXj%YSD1J&@1)oZs+Jcofqdj*$_-xfTlHaoVx5#bF(l77n4Yv;aMwtj&i%aggSe005 z9gW&%lo6(CrJn6y5HXfjcj?{J!vcjKwJTb!O811tQa_X`#9VKnJ>A@x6dNGjSIBcb z_R;gNA6jp*pr93w`jPwgzSAAC)QjciZr5RO99ccMXx(1_&O@u-6n}YI`AuWfTZ7`l zr!AtJ9vRkM>$CU2+s1A5@{xSK$8)CMk%*5Mp5FHjSvs_|DOs~BLlblP3;DCFBMb5 z+22EV@}INVrnr9S(t+>Uk@90K=wNreDZ||(r?lkO_qtOeg+mFQH|@Oj5_IId3L6uS z4!=A#Xw`-c5iJsnU-NFbzpekJo?zZsSrXsyMxCJaE4*@Pg`2f%Siq$rRd9IlvHHVL<)iGMWXoV%Q`#Z6t!gy4fynyo7$N;x!Iec z1IZ*h18whYY;9qJmQ_?%HVOW&X4LjNvo$kmBz>Ww=78vVt^5ih7X%%iie{>B#?1hsK4OxkJW;i0HMhS zTB>@=Yyjr_w-V7Acm@$i#{0}j1n7Ygi9SS{fr_5;G{_cEd0Ij}s@DrtAi%**kGv9*$Dplb~z>`5!{?Dm~9~7E-5P<#v<(>l< z(94lP1^h?#M?X<&rV{=od!uQAe`q=X{yoxPC^l0Y|00hBo$2W`InD;+!MMMTMDeC> zn;uPnHcH#u*xQ@h!?Y$;FQP3zfH>WK%^5lWZ^n^-c0m8zPW#UzI1XM5ij^|A%e;W3A46%`@`yW6~K!FBs`SBryIRoBaQqf^aR(d5}PC`km^> z7X9~0lkdaYYI9!%Kn^xqLjyYxJ?DY=|NqSWlV$?_-+y>R_))_@w*+?ODPpweRo+yc z{smfF6+Gs%uYxlzKuc>L`d_RC08?0iaS=>o|E;^?KR4~aQ|w>9f1VZInS%e1?4bWC z4gdUH{;%tye{Ls${m<#5|2Y-^+!p@V_0fTWUjMoM^j|4Ar?38>p0oTo$V|>XTmv^=f^VHqcl8XPfl&;@Sd4={oM@p&$3QW z2!9my@3_pBbUOc^1)W|Z|54IuLG_f`3-1Pe?0o}d|Kz^Gl*bRp^gnw~VHG<~h#j&6 z!(CIm4T9__ybmG6rj(c;M-H;%yzCGJh{-()K6V`V6~TFcZxnp-vHgr-h=aXXF${x1 z3T1C^kU|C_@Mr=TVhWRzlstn0PEr8x4#H^~4K_w`7Q^N_3gQqH2)veldLet@L0w6r( z12B`C!>RKx*k^0{g7ZxKhfU*PA^vnt2?ygTOfnh#!MjRao>V5qi*D!XM*y*@d=3N# z2E^DKP)@*&jZCHZfH?cICVDgG#*G=&fT=iJ32$U_bTWxRq`Q!<=^#5|UOAlX zG=LTbH#a+CVLFF5w-1fV44jAJ0^318Bnpwj3;=lq%p76vU=bhSKwJ>dWKcH{DMT9B z6@g2KoWNs0Ma(xz3}fIFivS-q`1|kHAIYSVoy;~lfug323mbnv#)j@goQL7UlNnBU zpLsYT0ukgRhB7SZ=1w-&o>VFsX2>&*o=42FQPY| zNoD|$HyEsG|AZV~i)5qclkpHK_^Z( z3^wm(umYY`253-!!t;YF_WhBZ{p78NnLz^JMi_o#!;?=L?5{c5gb{2ym;&NI z4g%P0Q%?;-0fK_J0DL&K1o$)l@__v10r_U+mj~o856E90kiR@2e|bRu@__v10r|@V z@|OqXf4c`H`we|L{DuxWgBtc|Gh=-jC(Vj#w z!_=4kSW+g7CVA=0x@y>{*aVsoeM#owG@@g;trH>KpMdoOo|S05P@T|#zyKlxj|QHY z6uM5RzAT%$4uD~_vMicS!tmFZWq(hF-ehBsHlfmpXmurwB0)t>6RobPq^hNW>$<7oKu)OM2K3m{+8B8=K`4H(aRzp)kRYMEh+Tg}2sA{SzsA{Q$8{o9n z6*SaTz>NVgz-y~1sA4r0)KoAEY8Y*BV-+-1!L6nWaDb1N3b+BS797_A>EKpVS5wu+ zn&M1x>c*OyI1P-7y1KTyrUqcHX{@PYW^9U6)c{__pel9jNufmYCQ}lqgDHn&Sliho zw|PmxQ0yf3GJzv_xQP0)iqpvXTB>PkYpN@#t6()%*ddT#3!^$2sgHgb!Dqz$UFzO1RegP3iO93 zhM9`0raHzHXQr*LWujuFp>Ar7HBrH-fd>sAsAB@Qi5NsvaD!F}zHU{-U=&p}oiJD( zRjiI0cAbi<4%`}GQXAla?j80g(&-Oj=aGUw(hJYP|5LjDBm&xD zgE4vvIhWKN_`g$WVeDQLA82EvV?hBPUK9e+!c6ke%)n!0tg5bUriQ_2 z{CWC(>K|hKUSKK-=H2kVIMs_z0K2t_ zPzd5A69Q>q$C0ZG;I%^-fdJ-~2Cx_n2nb*m81|WjO<~w`65hn7adI#LG_b5f1YbPZ z!{go#U`)>? zeQD+nQ}B5L-pmoeasYonkYNntR{^+>>F;a=U~py)hp0CV2d{*Hyc~MrzD}+HUIbt- z62ro23Lj4Kw6y~;$jg!HPql*iz%=&>bQ9QHLKeU;e2Ert9RqogkQdR^48RHimhokp zPw_`u(t{mgJeyCruZb;y0UspI4{vD?VBoKSJV2v5z-0lMk*P%T2DmK1A6Xg5u%D6v z*+U`Q!es%OQ9?vITSveX*~9R4GM~(g0%uk@P31+ody{Y$Q}o*d$?UcWWJ9IXm=3UP zfIq4hPcz#9V8918K%_Xsb^|cy5-+?dY+EG&8$d1yJXj=}Tq+_!6lfeQ7&$;2AY5prH#TuXp|vpJ<0%O3b0s|?u-QXAkVa%XyE9bpSee(QmdM=G3S+<*4Lr3Tz2tlH+_GhNGO0UL2kfS(^ma?S@07; z0_&?35QYF+8n7yCVG0xmxX!WabQvfa6b5DSL#x$>+wwF8ml>BSml1^KTFZsy!f;u_ z@br_%B@fcDAT*nie)7qgp_r!uaSj!*$_yVxcB_NiDx5nQ$V~=v6B)o020Z_e2{kZ` z?He+=HfjWal#aHb5R??qcrqEyPNJh}L^_ccO!QKM;BgSn2SI}l?C}k;=*cXOVFN+> zA3&c~JBxE?LeM4PytG(-7PlJoiQ?xVs8Ex@qyw#hkAhvAz%#prWY*M zAn32S;~{7~j>Q^FWwFN70RMIfx5fNGPUp`L@96&XDo|YguPb(G3$vyWtEeOSl zC#2nshY zW%YG%M#{c0Pd1W1NH`QI4Y}WF>le|YlzhjoTDg(C0=3I&qE`0Iy6aLqCD34x6xchw*3caOQXJ}ZW>SXJ+YT+JrESAz;X z$WFN{Z{v5IsUCBlARn+iY%ckAG^hH(`6Bdch$go*>RXan8dg~Fn?uwQ0~R#m#>_Wl z)pn&uFGrMPQX7{=rt@8s6UxebR$CljdgV~T-G|@vJgjS0FG(KjtUT5k<{5E7{BS~r zK4L{hbDHQqNtrdsKo6Bt4n>P~1NyWe{{+_-LG|zFBGrb`LGBKTsbikTo+%;%&S4Ag z$JiLJM^Ov5AmXeqw;ad{7i>*EMIn7W4%J$&^g1ASu#V;f8MmKQ@RB^I~hy@QLdN(Q}mr<&LYi(esBKZt&2)}eTB#l=R1 zyNt8kcC^aaw$BO(xg0b9B~+)B^RG9SNgixWaQ_&!kIVAhhbvoLPG7J~dmE3Zvg*1p zH9eiq&W;tk?UyV)pdsi{R3`h}byde{cL#gzYBYyVV~pI66pvdao`M+JmezNoPtdsV z`{q10Yk9Lj4wiTJKO*vZ>uiYoewT5x@6(dBqO06A6NkM~s|}V*j4i)#vc8G1OSY~< zLV3_9?&^@CrJJ1S-L=S?T>><|1gTw{3!YKC%d?tB59w`J@Lv7U61pnM5k9WJV^B#q z|DpHB6yK2K^h?fr1D9`e?|8Ic&h-F9@illX(Y1i1)uw6HSC`;Mmqro|!Ov7tz81>yCe#L2p?)gGCc73ZqEfJ?ar8{dWn5iOLe)O{ zMnx^Ln8tHrW{1P_Y9h8o_r(=mI-GtUeQ;H0V%wE~8=-~Ldbyp1KzZ@@?B95a8mNYH z%6Oh9r}~+Oe#08t)3%j`6R)_kkrZn`i>!mgHHt26N6Nhs#N@A5gM9q{6+X@?6<1o$ z=8Jpn+ns%^ORlgpBBbD}L`tIsFS4nv1G$I6brIj?|45Ubkwj)Lc~-Mg@AKtb>5|zU zigI>Q9n!`uC@#sJQvayaX*|%dDN6Wh2eIz;fuZAqj9ur0MvfnjDqKoWQ{T$aTphCO zlGU~Bi;JF)r)_L#g9HvAH*YW-yEoAn8~tU}qV01Vg{pG}M*=^u*v)F_ ztQWavXy;&adu=V3j6s!FE_1iyt-Tj(ozu`NT!(Rua>vBppG=@0JWX4Dp5qL1;DYSA zV906jd%MCQ-l(`vLF0p0`hFjhS4G#UB)8RCE;-v4P)e6YFP7PQzxGyQ8QQnqT#j}D zb)`U6&Fzy%X6dT7(Ah;MrHjTCi_Wy0GJcX%GZ->#0bj8tNP@R@{qYBPHzu75mV#;!?W2tewE-tyS3$vos!59b-S^n-Pc8jj@E+IZ1vl^Q z;&HpeQ6|}EBu;7UwM)y?vhGy0#O}R*KPI237d)o%PH@A`^-?}gyM{NvH#oQONuO)a z`!8J~CK_ML zif*i3w8?|o%^CePc~8fQ$VNAxgd1CZYKoId#m+?~x6lhK>K54b-)BL@6;e&PFGY52 zNU(l=ZK=oI)SWgxL9Ojomprh|zscQDsC@A<@~T$KvIW`WnTCC|qPJQ>-`q{ZVscum z;&1IC_}WEck#KGq?T{Yn&aVnm8nc|Y{i{M*FdA5 z2jzXaXR+J8c^q6WosLy9$*Ry-)=Y^72zE9qyO< zS|lX#`SSFC{CLxO=_%WHWt(meudBY;(3IKu`E^U*$Xz0d)zo>vy(i6^n=tW$8uQ^A z`S{D_ZksM-k6(Udwi=ShsPa)_-fdiAFr0eOdEa_*DUNVzn=Df4!kHVY)#T<+^4MK!vJ8xl zz|%z#YZC@GE4~&E9^RcVtP&P1tP$qnbmJJ6si?Uq`N7vM9AQ}( zht(ExJVI* ztmcjnAH-rsg&+EPe~fN!%J_mBZ2wqa;&AN2s(@l$rJFt%_#K~kt~Hk}5#{cPJK#pQ zy+qVhZF>ly?Q}3f;9%DpU1}A%EULC)XI5?C*MX)RZ@=Z=%C(bEQ@ruy53|lY z5@EUV+Phq0Pgkb7X~!>(QPi^$_m9;stq3&zv~lGTLSe|^3K#pv2#d6~7 zqOB9sFMg}Bmb0sFBvzFh$|r@zkCfXEQZlmK)|Q?>>%tRq*jzda?b=s7@raHTTYvJZ z1w9>gK0a^2@9deXe0r#~vqXohT+Y$-*4^yEE5fXTuJg^#zgP z(Ocb$^vpUTAsY_|%zZl{^WEET3taEs8x>>{t6jdND0%21Dxaj@wCiHxZ&e)%RZX1+ z_sOlnjNAm19&p-Yd9yK!{D;go51+Vf;;}=z5bw%p`G~U&S@lTz# zT#N-#wM>h<77iSZD_iKGg$!?4qqtkbM-o>WjoLG`Ou9s-K2uazKW`%F?6=2l+nV*Q z7{}lF`^K+1c~$N}N%(nW8JC{GxjwbctziNy7Jj7GZ@S=9@SEC`a6qNS9lK8v@1Z zd{ah~DUyk8nXkG7PnaI)J9$sVOVFrnD3fTae)LjSzK&x=GbuqqBw~}H$Lopr8EfJ$ zx9J$N>N?_c!Qq9jZ_A1v)iFDSxIYRH?&vJDb&-(09PIeALMSnFGneR^w~0qyjvlNI zSeka`S-hsP(aOXajb)D#-w{IZZ?ZTM7BRTTsUUAr_z|0?gk#@Ylu)_X9V|9F2U*wq z>FRAu`#e(gsZsK|dVPdOU;3c_qwb0b*McI#W2U(B3QjHy<60S`&_!9#z_@oJLf*Xc z^>D=E&Q_tc9nYRuow||j!hio`os%Mgux98)v-+sb1_>U1o58IPG6|2Whl+ab0CjSj)`QXls5U~?^PXuty%_Nyk ziWluHwd7I*CdN$4b+F`D6|3b^B7<}lLt2l0EPS~nG0tM&zN)I;gprGO=OW5fQ&uJ2 z(~RzRkWssLBBkv@e__`4f>RshxEBr=2ef2uU+VVa39G)t;Y@LVV`xJ@owha9Q7R9+ z%s;d3d*ymn*FD5e)u-3;al;U&pA=of!Kf=UA0=YM{Zer2UfKKw8QJ>@kXfczv-p!=+|rF(o*u*~UfbwkTb}j2 zZ51c~Zy9RWvOnf)F0&owc_+$w-s>hw#I;jHKnrD0wPZf94!9vnSzUNO++pvTsPO*v zK_VHdH%uu#?{PWFPH{FD-KifF4)Mw#@2?IWX+0l;%=CU8lvz@5xVhVIe_{rw{}*QU zsPF4%tcDKZ25NaG#cJ0g#o(-{2tF@|1BYUlUTn>O-y+Y&=@vRLQC785Oerc#?RA;Q zSr#-%v0S~naP%_^(iwVa;^u6-|J>W#HV3QMIJ!5V>O8i~p2|Fw~2m)qj)(yk{g xKXyb~F3u9((|Fle+x2UN_t~ei8QM29cFLTh^;+MrcwH?q7NbA7@&fDG{{i^CsdfMW literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/images/image3.jpg b/examples/nanovg_res/images/image3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..884f9f2bbe847fe0d7523cd085170484a72efb43 GIT binary patch literal 29282 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 literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/images/image4.jpg b/examples/nanovg_res/images/image4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f6e103912dbf56537371d36bb069e7067f6b4a93 GIT binary patch literal 23830 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 AVE_OC literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/images/image5.jpg b/examples/nanovg_res/images/image5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d952d16119f9906330dd31f4fdeadeb4eccba27c GIT binary patch literal 27131 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 literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/images/image7.jpg b/examples/nanovg_res/images/image7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..623b4cb91d1951c56b97d3df8659373c73ca6ba4 GIT binary patch literal 25590 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 F{|EKqH$wmb literal 0 HcmV?d00001 diff --git a/examples/nanovg_res/perf.c b/examples/nanovg_res/perf.c new file mode 100644 index 0000000..434ba02 --- /dev/null +++ b/examples/nanovg_res/perf.c @@ -0,0 +1,164 @@ +#include "perf.h" +#include +#include +#include +#include "nanovg.h" + +#ifdef _MSC_VER +#define snprintf _snprintf +#elif !defined(__MINGW32__) +#include +#endif + +// timer query support +#ifndef GL_ARB_timer_query +#define GL_TIME_ELAPSED 0x88BF +//typedef void (APIENTRY *pfnGLGETQUERYOBJECTUI64V)(GLuint id, GLenum pname, GLuint64* params); +//pfnGLGETQUERYOBJECTUI64V glGetQueryObjectui64v = 0; +#endif + +void initGPUTimer(struct GPUtimer* timer) +{ + memset(timer, 0, sizeof(*timer)); + +/* timer->supported = glfwExtensionSupported("GL_ARB_timer_query"); + if (timer->supported) { +#ifndef GL_ARB_timer_query + glGetQueryObjectui64v = (pfnGLGETQUERYOBJECTUI64V)glfwGetProcAddress("glGetQueryObjectui64v"); + printf("glGetQueryObjectui64v=%p\n", glGetQueryObjectui64v); + if (!glGetQueryObjectui64v) { + timer->supported = GL_FALSE; + return; + } +#endif + glGenQueries(GPU_QUERY_COUNT, timer->queries); + }*/ +} + +void startGPUTimer(struct GPUtimer* timer) +{ + if (!timer->supported) + return; + glBeginQuery(GL_TIME_ELAPSED, timer->queries[timer->cur % GPU_QUERY_COUNT] ); + timer->cur++; +} + +int stopGPUTimer(struct GPUtimer* timer, float* times, int maxTimes) +{ + GLint available = 1; + int n = 0; + if (!timer->supported) + return 0; + + glEndQuery(GL_TIME_ELAPSED); + while (available && timer->ret <= timer->cur) { + // check for results if there are any + glGetQueryObjectiv(timer->queries[timer->ret % GPU_QUERY_COUNT], GL_QUERY_RESULT_AVAILABLE, &available); + if (available) { +/* GLuint64 timeElapsed = 0; + glGetQueryObjectui64v(timer->queries[timer->ret % GPU_QUERY_COUNT], GL_QUERY_RESULT, &timeElapsed); + timer->ret++; + if (n < maxTimes) { + times[n] = (float)((double)timeElapsed * 1e-9); + n++; + }*/ + } + } + return n; +} + + +void initGraph(struct PerfGraph* fps, int style, const char* name) +{ + memset(fps, 0, sizeof(struct PerfGraph)); + fps->style = style; + strncpy(fps->name, name, sizeof(fps->name)); + fps->name[sizeof(fps->name)-1] = '\0'; +} + +void updateGraph(struct PerfGraph* fps, float frameTime) +{ + fps->head = (fps->head+1) % GRAPH_HISTORY_COUNT; + fps->values[fps->head] = frameTime; +} + +float getGraphAverage(struct PerfGraph* fps) +{ + int i; + float avg = 0; + for (i = 0; i < GRAPH_HISTORY_COUNT; i++) { + avg += fps->values[i]; + } + return avg / (float)GRAPH_HISTORY_COUNT; +} + +void renderGraph(struct NVGcontext* vg, float x, float y, struct PerfGraph* fps) +{ + int i; + float avg, w, h; + char str[64]; + + avg = getGraphAverage(fps); + + w = 200; + h = 35; + + nvgBeginPath(vg); + nvgRect(vg, x,y, w,h); + nvgFillColor(vg, nvgRGBA(0,0,0,128)); + nvgFill(vg); + + nvgBeginPath(vg); + nvgMoveTo(vg, x, y+h); + if (fps->style == GRAPH_RENDER_FPS) { + for (i = 0; i < GRAPH_HISTORY_COUNT; i++) { + float v = 1.0f / (0.00001f + fps->values[(fps->head+i) % GRAPH_HISTORY_COUNT]); + float vx, vy; + if (v > 80.0f) v = 80.0f; + vx = x + ((float)i/(GRAPH_HISTORY_COUNT-1)) * w; + vy = y + h - ((v / 80.0f) * h); + nvgLineTo(vg, vx, vy); + } + } else { + for (i = 0; i < GRAPH_HISTORY_COUNT; i++) { + float v = fps->values[(fps->head+i) % GRAPH_HISTORY_COUNT] * 1000.0f; + float vx, vy; + if (v > 20.0f) v = 20.0f; + vx = x + ((float)i/(GRAPH_HISTORY_COUNT-1)) * w; + vy = y + h - ((v / 20.0f) * h); + nvgLineTo(vg, vx, vy); + } + } + nvgLineTo(vg, x+w, y+h); + nvgFillColor(vg, nvgRGBA(255,192,0,128)); + nvgFill(vg); + + nvgFontFace(vg, "sans"); + + if (fps->name[0] != '\0') { + nvgFontSize(vg, 14.0f); + nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP); + nvgFillColor(vg, nvgRGBA(240,240,240,192)); + nvgText(vg, x+3,y+1, fps->name, NULL); + } + + if (fps->style == GRAPH_RENDER_FPS) { + nvgFontSize(vg, 18.0f); + nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_TOP); + nvgFillColor(vg, nvgRGBA(240,240,240,255)); + sprintf(str, "%.2f FPS", 1.0f / avg); + nvgText(vg, x+w-3,y+1, str, NULL); + + nvgFontSize(vg, 15.0f); + nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_BOTTOM); + nvgFillColor(vg, nvgRGBA(240,240,240,160)); + sprintf(str, "%.2f ms", avg * 1000.0f); + nvgText(vg, x+w-3,y+h-1, str, NULL); + } else { + nvgFontSize(vg, 18.0f); + nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_TOP); + nvgFillColor(vg, nvgRGBA(240,240,240,255)); + sprintf(str, "%.2f ms", avg * 1000.0f); + nvgText(vg, x+w-3,y+1, str, NULL); + } +} diff --git a/examples/nanovg_res/perf.h b/examples/nanovg_res/perf.h new file mode 100644 index 0000000..86c530b --- /dev/null +++ b/examples/nanovg_res/perf.h @@ -0,0 +1,43 @@ +#ifndef PERF_H +#define PERF_H + +#include "nanovg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum GraphrenderStyle { + GRAPH_RENDER_FPS, + GRAPH_RENDER_MS, +}; + +#define GRAPH_HISTORY_COUNT 100 +struct PerfGraph { + int style; + char name[32]; + float values[GRAPH_HISTORY_COUNT]; + int head; +}; + +void initGraph(struct PerfGraph* fps, int style, const char* name); +void updateGraph(struct PerfGraph* fps, float frameTime); +void renderGraph(struct NVGcontext* vg, float x, float y, struct PerfGraph* fps); +float getGraphAverage(struct PerfGraph* fps); + +#define GPU_QUERY_COUNT 5 +struct GPUtimer { + int supported; + int cur, ret; + unsigned int queries[GPU_QUERY_COUNT]; +}; + +void initGPUTimer(struct GPUtimer* timer); +void startGPUTimer(struct GPUtimer* timer); +int stopGPUTimer(struct GPUtimer* timer, float* times, int maxTimes); + +#ifdef __cplusplus +} +#endif + +#endif // PERF_H \ No newline at end of file diff --git a/examples/nanovg_res/stb_image_write.h b/examples/nanovg_res/stb_image_write.h new file mode 100644 index 0000000..5de3159 --- /dev/null +++ b/examples/nanovg_res/stb_image_write.h @@ -0,0 +1,511 @@ +/* stbiw-0.92 - public domain - http://nothings.org/stb/stb_image_write.h + writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010 + no warranty implied; use at your own risk + + +Before including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + +in the file that you want to have the implementation. + + +ABOUT: + + This header file is a library for writing images to C stdio. It could be + adapted to write to memory or a general streaming interface; let me know. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation. This library is designed + for source code compactness and simplicitly, not optimal image file size + or run-time performance. + +USAGE: + + There are three functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP and TGA formats expand Y to RGB in the file format. BMP does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +extern int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +extern int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + +#ifdef __cplusplus +} +#endif + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#include +#include +#include +#include +#include + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void writefv(FILE *f, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = (unsigned char) va_arg(v, int); fputc(x,f); break; } + case '2': { int x = va_arg(v,int); unsigned char b[2]; + b[0] = (unsigned char) x; b[1] = (unsigned char) (x>>8); + fwrite(b,2,1,f); break; } + case '4': { stbiw_uint32 x = va_arg(v,int); unsigned char b[4]; + b[0]=(unsigned char)x; b[1]=(unsigned char)(x>>8); + b[2]=(unsigned char)(x>>16); b[3]=(unsigned char)(x>>24); + fwrite(b,4,1,f); break; } + default: + assert(0); + return; + } + } +} + +static void write3(FILE *f, unsigned char a, unsigned char b, unsigned char c) +{ + unsigned char arr[3]; + arr[0] = a, arr[1] = b, arr[2] = c; + fwrite(arr, 3, 1, f); +} + +static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + stbiw_uint32 zero = 0; + int i,j,k, j_end; + + if (y <= 0) + return; + + if (vdir < 0) + j_end = -1, j = y-1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + if (write_alpha < 0) + fwrite(&d[comp-1], 1, 1, f); + switch (comp) { + case 1: + case 2: write3(f, d[0],d[0],d[0]); + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k=0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3])/255; + write3(f, px[1-rgb_dir],px[1],px[1+rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + write3(f, d[1-rgb_dir],d[1],d[1+rgb_dir]); + break; + } + if (write_alpha > 0) + fwrite(&d[comp-1], 1, 1, f); + } + fwrite(&zero,scanline_pad,1,f); + } +} + +static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, const char *fmt, ...) +{ + FILE *f; + if (y < 0 || x < 0) return 0; + f = fopen(filename, "wb"); + if (f) { + va_list v; + va_start(v, fmt); + writefv(f, fmt, v); + va_end(v); + write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad); + fclose(f); + } + return f != NULL; +} + +int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + int pad = (-x*3) & 3; + return outfile(filename,-1,-1,x,y,comp,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + int has_alpha = !(comp & 1); + return outfile(filename, -1,-1, x, y, comp, (void *) data, has_alpha, 0, + "111 221 2222 11", 0,0,2, 0,0,0, 0,0,x,y, 24+8*has_alpha, 8*has_alpha); +} + +// stretchy buffer; stbi__sbpush() == vector<>::push_back() -- stbi__sbcount() == vector<>::size() +#define stbi__sbraw(a) ((int *) (a) - 2) +#define stbi__sbm(a) stbi__sbraw(a)[0] +#define stbi__sbn(a) stbi__sbraw(a)[1] + +#define stbi__sbneedgrow(a,n) ((a)==0 || stbi__sbn(a)+n >= stbi__sbm(a)) +#define stbi__sbmaybegrow(a,n) (stbi__sbneedgrow(a,(n)) ? stbi__sbgrow(a,n) : 0) +#define stbi__sbgrow(a,n) stbi__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbi__sbpush(a, v) (stbi__sbmaybegrow(a,1), (a)[stbi__sbn(a)++] = (v)) +#define stbi__sbcount(a) ((a) ? stbi__sbn(a) : 0) +#define stbi__sbfree(a) ((a) ? free(stbi__sbraw(a)),0 : 0) + +static void *stbi__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbi__sbm(*arr)+increment : increment+1; + void *p = realloc(*arr ? stbi__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2); + assert(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbi__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbi__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbi__sbpush(data, (unsigned char) *bitbuffer); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbi__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbi__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbi__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbi__zlib_flush() (out = stbi__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbi__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbi__zlib_flush()) +#define stbi__zlib_huffa(b,c) stbi__zlib_add(stbi__zlib_bitrev(b,c),c) +// default huffman tables +#define stbi__zlib_huff1(n) stbi__zlib_huffa(0x30 + (n), 8) +#define stbi__zlib_huff2(n) stbi__zlib_huffa(0x190 + (n)-144, 9) +#define stbi__zlib_huff3(n) stbi__zlib_huffa(0 + (n)-256,7) +#define stbi__zlib_huff4(n) stbi__zlib_huffa(0xc0 + (n)-280,8) +#define stbi__zlib_huff(n) ((n) <= 143 ? stbi__zlib_huff1(n) : (n) <= 255 ? stbi__zlib_huff2(n) : (n) <= 279 ? stbi__zlib_huff3(n) : stbi__zlib_huff4(n)) +#define stbi__zlib_huffb(n) ((n) <= 143 ? stbi__zlib_huff1(n) : stbi__zlib_huff2(n)) + +#define stbi__ZHASH 16384 + +unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ + static unsigned short lengthc[] = { 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, 259 }; + static unsigned char lengtheb[]= { 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 }; + static unsigned short distc[] = { 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, 32768 }; + static unsigned char disteb[] = { 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 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char **hash_table[stbi__ZHASH]; // 64KB on the stack! + if (quality < 5) quality = 5; + + stbi__sbpush(out, 0x78); // DEFLATE 32K window + stbi__sbpush(out, 0x5e); // FLEVEL = 1 + stbi__zlib_add(1,1); // BFINAL = 1 + stbi__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbi__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbi__zhash(data+i)&(stbi__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbi__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbi__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) best=d,bestloc=hlist[j]; + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbi__sbn(hash_table[h]) == 2*quality) { + memcpy(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbi__sbn(hash_table[h]) = quality; + } + stbi__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbi__zhash(data+i+1)&(stbi__ZHASH-1); + hlist = hash_table[h]; + n = stbi__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbi__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = data+i - bestloc; // distance back + assert(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbi__zlib_huff(j+257); + if (lengtheb[j]) stbi__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbi__zlib_add(stbi__zlib_bitrev(j,5),5); + if (disteb[j]) stbi__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbi__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbi__zlib_huffb(data[i]); + stbi__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbi__zlib_add(0,1); + + for (i=0; i < stbi__ZHASH; ++i) + (void) stbi__sbfree(hash_table[i]); + + { + // compute adler32 on input + unsigned int i=0, s1=1, s2=0, blocklen = data_len % 5552; + int j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbi__sbpush(out, (unsigned char) (s2 >> 8)); + stbi__sbpush(out, (unsigned char) s2); + stbi__sbpush(out, (unsigned char) (s1 >> 8)); + stbi__sbpush(out, (unsigned char) s1); + } + *out_len = stbi__sbn(out); + // make returned pointer freeable + memmove(stbi__sbraw(out), out, *out_len); + return (unsigned char *) stbi__sbraw(out); +} + +unsigned int stbi__crc32(unsigned char *buffer, int len) +{ + static unsigned int crc_table[256]; + unsigned int crc = ~0u; + int i,j; + if (crc_table[1] == 0) + for(i=0; i < 256; i++) + for (crc_table[i]=i, j=0; j < 8; ++j) + crc_table[i] = (crc_table[i] >> 1) ^ (crc_table[i] & 1 ? 0xedb88320 : 0); + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +#define stbi__wpng4(o,a,b,c,d) ((o)[0]=(unsigned char)(a),(o)[1]=(unsigned char)(b),(o)[2]=(unsigned char)(c),(o)[3]=(unsigned char)(d),(o)+=4) +#define stbi__wp32(data,v) stbi__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbi__wptag(data,s) stbi__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbi__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbi__crc32(*data - len - 4, len+4); + stbi__wp32(*data, crc); +} + +static unsigned char stbi__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return (unsigned char) a; + if (pb <= pc) return (unsigned char) b; + return (unsigned char) c; +} + +unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int i,j,k,p,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + filt = (unsigned char *) malloc((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) malloc(x * n); if (!line_buffer) { free(filt); return 0; } + for (j=0; j < y; ++j) { + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = j ? mapping : firstmap; + int best = 0, bestval = 0x7fffffff; + for (p=0; p < 2; ++p) { + for (k= p?best:0; k < 5; ++k) { + int type = mymap[k],est=0; + unsigned char *z = pixels + stride_bytes*j; + for (i=0; i < n; ++i) + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbi__paeth(0,z[i-stride_bytes],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + for (i=n; i < x*n; ++i) { + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i] - z[i-n]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; + case 4: line_buffer[i] = z[i] - stbi__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; + case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: line_buffer[i] = z[i] - stbi__paeth(z[i-n], 0,0); break; + } + } + if (p) break; + for (i=0; i < x*n; ++i) + est += abs((signed char) line_buffer[i]); + if (est < bestval) { bestval = est; best = k; } + } + } + // when we get here, best contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) best; + memcpy(filt+j*(x*n+1)+1, line_buffer, x*n); + } + free(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory + free(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) malloc(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + memcpy(o,sig,8); o+= 8; + stbi__wp32(o, 13); // header length + stbi__wptag(o, "IHDR"); + stbi__wp32(o, x); + stbi__wp32(o, y); + *o++ = 8; + *o++ = (unsigned char) ctype[n]; + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbi__wpcrc(&o,13); + + stbi__wp32(o, zlen); + stbi__wptag(o, "IDAT"); + memcpy(o, zlib, zlen); o += zlen; free(zlib); + stbi__wpcrc(&o, zlen); + + stbi__wp32(o,0); + stbi__wptag(o, "IEND"); + stbi__wpcrc(&o,0); + + assert(o == out + *out_len); + + return out; +} + +int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (!png) return 0; + f = fopen(filename, "wb"); + if (!f) { free(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + free(png); + return 1; +} +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ diff --git a/examples/nanovg_src/LICENSE.txt b/examples/nanovg_src/LICENSE.txt new file mode 100644 index 0000000..2a03a1a --- /dev/null +++ b/examples/nanovg_src/LICENSE.txt @@ -0,0 +1,18 @@ +Copyright (c) 2013 Mikko Mononen memon@inside.org + +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/examples/nanovg_src/README.md b/examples/nanovg_src/README.md new file mode 100644 index 0000000..5ee5184 --- /dev/null +++ b/examples/nanovg_src/README.md @@ -0,0 +1,104 @@ +/work in progress.../ + + +NanoVG +========== + +NanoVG is small antialiased vector graphics rendering library for OpenGL. It has lean API modeled after HTML5 canvas API. It is aimed to be a practical and fun toolset for building scalable user interfaces and visualizations. + +## Screenshot + +![screenshot of some text rendered witht the sample program](/example/screenshot-01.png?raw=true) + +Usage +===== + +The NanoVG API is modeled loosely on HTML5 canvas API. If you know canvas, you're up to speed with NanoVG in no time. + +## Creating drawing context + +The drawing context is created using platform specific constructor function. If you're using the a OpenGL 2.0 back-end the context is created as follows: +```C +#define NANOVG_GL2_IMPLEMENTATION // Use GL2 implementation. +#include "nanovg_gl.h" +... +struct NVGcontext* vg = nvgCreateGL2(512, 512, NVG_ANTIALIAS); +``` +The first two values passed to the constructor define the size of the texture atlas used for text rendering, 512x512 is a good starting point. If you're rendering retina sized text or plan to use a lot of different fonts, 1024x1024 is better choice. The third parameter defines if anti-aliasing should be used, passing 0 means no AA (useful when you're using MSAA). + +Currently there is an OpenGL back-end for NanoVG: [nanovg_gl.h](/src/nanovg_gl.h) for OpenGL 2.0, OpenGL ES 2.0, OpenGL 3.2 core profile and OpenGL ES 3. The implementation can be chosen using a define as in above example. See the header file and examples for further info. + +## Drawing shapes with NanoVG + +Drawing a simple shape using NanoVG consists of four steps: 1) begin a new shape, 2) define the path to draw, 3) set fill or stroke, 4) and finally fill or stroke the path. + +```C +nvgBeginPath(vg); +nvgRect(vg, 100,100, 120,30); +nvgFillColor(vg, nvgRGBA(255,192,0,255)); +nvgFill(vg); +``` + +Calling `nvgBeginPath()` will clear any existing paths and start drawing from blank slate. There are number of number of functions to define the path to draw, such as rectangle, rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and arcTo API to compose the paths step by step. + +## Understanding Composite Paths + +Because of the way the rendering backend is build in NanoVG, drawing a composite path, that is path consisting from multiple paths defining holes and fills, is a bit more involved. NanoVG uses even-odd filling rule and by default the paths are wound in counter clockwise order. Keep that in mind when drawing using the low level draw API. In order to wind one of the predefined shapes as a hole, you should call nvgPathWinding(vg, `NVG_HOLE`), or `nvgPathWinding(vg, NVG_CV)` _after_ defining the path. + +``` C +nvgBeginPath(vg); +nvgRect(vg, 100,100, 120,30); +nvgCircle(vg, 120,120, 5); +nvgPathWinding(vg, NVG_HOLE); // Mark circle as a hole. +nvgFillColor(vg, nvgRGBA(255,192,0,255)); +nvgFill(vg); +``` + +## Rendering is wrong, what to do? + +- make sure you have created NanoVG context using one of the `nvgCreatexxx()` calls +- make sure you have initialised OpenGL with stencil buffer +- make sure you have cleared stencil buffer +- make sure all rendering calls happen between `nvgBeginFrame()` and `nvgEndFrame()` +- if the problem still persists, please report an issue! + +## OpenGL state touched by the backend + +The OpenGL back-end touches following states: + +When textures are uploaded or updated, the following pixel store is set to defaults: `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`. Texture binding is also affected. Texture updates can happen when the user loads images, or when new font glyphs are added. Glyphs are added as needed between calls to `nvgBeginFrame()` and `nvgEndFrame()`. + +The data for the whole frame is buffered and flushed in `nvgEndFrame()`. The following code illustrates the OpenGL state touched by the rendering code: +```C + glUseProgram(prog); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CCW); + glEnable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glStencilMask(0xffffffff); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilFunc(GL_ALWAYS, 0, 0xffffffff); + glActiveTexture(GL_TEXTURE0); + glBindBuffer(GL_UNIFORM_BUFFER, buf); + glBindVertexArray(arr); + glBindBuffer(GL_ARRAY_BUFFER, buf); + glBindTexture(GL_TEXTURE_2D, tex); +``` + +## API Reference + +See the header file [nanovg.h](/src/nanovg.h) for API reference. + +## License +The library is licensed under [zlib license](LICENSE.txt) + +## Discussions +[NanoVG mailing list](https://groups.google.com/forum/#!forum/nanovg) + +## Links +Uses [stb_truetype](http://nothings.org) (or, optionally, [freetype](http://freetype.org)) for font rendering. +Uses [stb_image](http://nothings.org) for image loading. diff --git a/examples/nanovg_src/fontstash.h b/examples/nanovg_src/fontstash.h new file mode 100644 index 0000000..dc61bab --- /dev/null +++ b/examples/nanovg_src/fontstash.h @@ -0,0 +1,1670 @@ +// +// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org +// +// 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. +// + +#ifndef FONS_H +#define FONS_H + +#define FONS_INVALID -1 + +enum FONSflags { + FONS_ZERO_TOPLEFT = 1, + FONS_ZERO_BOTTOMLEFT = 2, +}; + +enum FONSalign { + // Horizontal align + FONS_ALIGN_LEFT = 1<<0, // Default + FONS_ALIGN_CENTER = 1<<1, + FONS_ALIGN_RIGHT = 1<<2, + // Vertical align + FONS_ALIGN_TOP = 1<<3, + FONS_ALIGN_MIDDLE = 1<<4, + FONS_ALIGN_BOTTOM = 1<<5, + FONS_ALIGN_BASELINE = 1<<6, // Default +}; + +enum FONSerrorCode { + // Font atlas is full. + FONS_ATLAS_FULL = 1, + // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. + FONS_SCRATCH_FULL = 2, + // Calls to fonsPushState has craeted too large stack, if you need deep state stack bump up FONS_MAX_STATES. + FONS_STATES_OVERFLOW = 3, + // Trying to pop too many states fonsPopState(). + FONS_STATES_UNDERFLOW = 4, +}; + +struct FONSparams { + int width, height; + unsigned char flags; + void* userPtr; + int (*renderCreate)(void* uptr, int width, int height); + int (*renderResize)(void* uptr, int width, int height); + void (*renderUpdate)(void* uptr, int* rect, const unsigned char* data); + void (*renderDraw)(void* uptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts); + void (*renderDelete)(void* uptr); +}; + +struct FONSquad +{ + float x0,y0,s0,t0; + float x1,y1,s1,t1; +}; + +struct FONStextIter { + float x, y, nextx, nexty, scale, spacing; + unsigned int codepoint; + short isize, iblur; + struct FONSfont* font; + struct FONSglyph* prevGlyph; + const char* str; + const char* next; + const char* end; + unsigned int utf8state; +}; + +// Contructor and destructor. +struct FONScontext* fonsCreateInternal(struct FONSparams* params); +void fonsDeleteInternal(struct FONScontext* s); + +void fonsSetErrorCallback(struct FONScontext* s, void (*callback)(void* uptr, int error, int val), void* uptr); +// Returns current atlas size. +void fonsGetAtlasSize(struct FONScontext* s, int* width, int* height); +// Expands the atlas size. +int fonsExpandAtlas(struct FONScontext* s, int width, int height); +// Reseta the whole stash. +int fonsResetAtlas(struct FONScontext* stash, int width, int height); + +// Add fonts +int fonsAddFont(struct FONScontext* s, const char* name, const char* path); +int fonsAddFontMem(struct FONScontext* s, const char* name, unsigned char* data, int ndata, int freeData); +int fonsGetFontByName(struct FONScontext* s, const char* name); + +// State handling +void fonsPushState(struct FONScontext* s); +void fonsPopState(struct FONScontext* s); +void fonsClearState(struct FONScontext* s); + +// State setting +void fonsSetSize(struct FONScontext* s, float size); +void fonsSetColor(struct FONScontext* s, unsigned int color); +void fonsSetSpacing(struct FONScontext* s, float spacing); +void fonsSetBlur(struct FONScontext* s, float blur); +void fonsSetAlign(struct FONScontext* s, int align); +void fonsSetFont(struct FONScontext* s, int font); + +// Draw text +float fonsDrawText(struct FONScontext* s, float x, float y, const char* string, const char* end); + +// Measure text +float fonsTextBounds(struct FONScontext* s, float x, float y, const char* string, const char* end, float* bounds); +void fonsLineBounds(struct FONScontext* s, float y, float* miny, float* maxy); +void fonsVertMetrics(struct FONScontext* s, float* ascender, float* descender, float* lineh); + +// Text iterator +int fonsTextIterInit(struct FONScontext* stash, struct FONStextIter* iter, float x, float y, const char* str, const char* end); +int fonsTextIterNext(struct FONScontext* stash, struct FONStextIter* iter, struct FONSquad* quad); + +// Pull texture changes +const unsigned char* fonsGetTextureData(struct FONScontext* stash, int* width, int* height); +int fonsValidateTexture(struct FONScontext* s, int* dirty); + +// Draws the stash texture for debugging +void fonsDrawDebug(struct FONScontext* s, float x, float y); + +#endif // FONS_H + + +#ifdef FONTSTASH_IMPLEMENTATION + +#define FONS_NOTUSED(v) (void)sizeof(v) + +#ifdef FONS_USE_FREETYPE + +#include +#include FT_FREETYPE_H +#include FT_ADVANCES_H +#include + +struct FONSttFontImpl { + FT_Face font; +}; + +static FT_Library ftLibrary; + +int fons__tt_init() +{ + FT_Error ftError; + ftError = FT_Init_FreeType(&ftLibrary); + return ftError == 0; +} + +int fons__tt_loadFont(struct FONScontext *context, struct FONSttFontImpl *font, unsigned char *data, int dataSize) +{ + FT_Error ftError; + FONS_NOTUSED(context); + + //font->font.userdata = stash; + ftError = FT_New_Memory_Face(ftLibrary, (const FT_Byte*)data, dataSize, 0, &font->font); + return ftError == 0; +} + +void fons__tt_getFontVMetrics(struct FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + *ascent = font->font->ascender; + *descent = font->font->descender; + *lineGap = font->font->height - (*ascent - *descent); +} + +float fons__tt_getPixelHeightScale(struct FONSttFontImpl *font, float size) +{ + return size / (font->font->ascender - font->font->descender); +} + +int fons__tt_getGlyphIndex(struct FONSttFontImpl *font, int codepoint) +{ + return FT_Get_Char_Index(font->font, codepoint); +} + +int fons__tt_buildGlyphBitmap(struct FONSttFontImpl *font, int glyph, float size, float scale, int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FT_Error ftError; + FT_GlyphSlot ftGlyph; + FONS_NOTUSED(scale); + + ftError = FT_Set_Pixel_Sizes(font->font, 0, (FT_UInt)(size * (float)font->font->units_per_EM / (float)(font->font->ascender - font->font->descender))); + if (ftError) return 0; + ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER); + if (ftError) return 0; + ftError = FT_Get_Advance(font->font, glyph, FT_LOAD_NO_SCALE, (FT_Fixed*)advance); + if (ftError) return 0; + ftGlyph = font->font->glyph; + *lsb = ftGlyph->metrics.horiBearingX; + *x0 = ftGlyph->bitmap_left; + *x1 = *x0 + ftGlyph->bitmap.width; + *y0 = -ftGlyph->bitmap_top; + *y1 = *y0 + ftGlyph->bitmap.rows; + return 1; +} + +void fons__tt_renderGlyphBitmap(struct FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) +{ + FT_GlyphSlot ftGlyph = font->font->glyph; + int ftGlyphOffset = 0; + int x, y; + FONS_NOTUSED(outWidth); + FONS_NOTUSED(outHeight); + FONS_NOTUSED(scaleX); + FONS_NOTUSED(scaleY); + FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap + + for ( y = 0; y < ftGlyph->bitmap.rows; y++ ) { + for ( x = 0; x < ftGlyph->bitmap.width; x++ ) { + output[(y * outStride) + x] = ftGlyph->bitmap.buffer[ftGlyphOffset++]; + } + } +} + +int fons__tt_getGlyphKernAdvance(struct FONSttFontImpl *font, int glyph1, int glyph2) +{ + FT_Vector ftKerning; + FT_Get_Kerning(font->font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); + return ftKerning.x; +} + +#else + +#define STB_TRUETYPE_IMPLEMENTATION +static void* fons__tmpalloc(size_t size, void* up); +static void fons__tmpfree(void* ptr, void* up); +#define STBTT_malloc(x,u) fons__tmpalloc(x,u) +#define STBTT_free(x,u) fons__tmpfree(x,u) +#include "stb_truetype.h" + +struct FONSttFontImpl { + stbtt_fontinfo font; +}; + +int fons__tt_init(struct FONScontext *context) +{ + FONS_NOTUSED(context); + return 1; +} + +int fons__tt_loadFont(struct FONScontext *context, struct FONSttFontImpl *font, unsigned char *data, int dataSize) +{ + int stbError; + FONS_NOTUSED(dataSize); + + font->font.userdata = context; + stbError = stbtt_InitFont(&font->font, data, 0); + return stbError; +} + +void fons__tt_getFontVMetrics(struct FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + stbtt_GetFontVMetrics(&font->font, ascent, descent, lineGap); +} + +float fons__tt_getPixelHeightScale(struct FONSttFontImpl *font, float size) +{ + return stbtt_ScaleForPixelHeight(&font->font, size); +} + +int fons__tt_getGlyphIndex(struct FONSttFontImpl *font, int codepoint) +{ + return stbtt_FindGlyphIndex(&font->font, codepoint); +} + +int fons__tt_buildGlyphBitmap(struct FONSttFontImpl *font, int glyph, float size, float scale, int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FONS_NOTUSED(size); + stbtt_GetGlyphHMetrics(&font->font, glyph, advance, lsb); + stbtt_GetGlyphBitmapBox(&font->font, glyph, scale, scale, x0, y0, x1, y1); + return 1; +} + +void fons__tt_renderGlyphBitmap(struct FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) +{ + stbtt_MakeGlyphBitmap(&font->font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); +} + +int fons__tt_getGlyphKernAdvance(struct FONSttFontImpl *font, int glyph1, int glyph2) +{ + return stbtt_GetGlyphKernAdvance(&font->font, glyph1, glyph2); +} + +#endif + +#ifndef FONS_SCRATCH_BUF_SIZE +# define FONS_SCRATCH_BUF_SIZE 16000 +#endif +#ifndef FONS_HASH_LUT_SIZE +# define FONS_HASH_LUT_SIZE 256 +#endif +#ifndef FONS_INIT_FONTS +# define FONS_INIT_FONTS 4 +#endif +#ifndef FONS_INIT_GLYPHS +# define FONS_INIT_GLYPHS 256 +#endif +#ifndef FONS_INIT_ATLAS_NODES +# define FONS_INIT_ATLAS_NODES 256 +#endif +#ifndef FONS_VERTEX_COUNT +# define FONS_VERTEX_COUNT 1024 +#endif +#ifndef FONS_MAX_STATES +# define FONS_MAX_STATES 20 +#endif + +static unsigned int fons__hashint(unsigned int a) +{ + a += ~(a<<15); + a ^= (a>>10); + a += (a<<3); + a ^= (a>>6); + a += ~(a<<11); + a ^= (a>>16); + return a; +} + +static int fons__mini(int a, int b) +{ + return a < b ? a : b; +} + +static int fons__maxi(int a, int b) +{ + return a > b ? a : b; +} + +struct FONSglyph +{ + unsigned int codepoint; + int index; + int next; + short size, blur; + short x0,y0,x1,y1; + short xadv,xoff,yoff; +}; + +struct FONSfont +{ + struct FONSttFontImpl font; + char name[64]; + unsigned char* data; + int dataSize; + unsigned char freeData; + float ascender; + float descender; + float lineh; + struct FONSglyph* glyphs; + int cglyphs; + int nglyphs; + int lut[FONS_HASH_LUT_SIZE]; +}; + +struct FONSstate +{ + int font; + int align; + float size; + unsigned int color; + float blur; + float spacing; +}; + +struct FONSatlasNode { + short x, y, width; +}; + +struct FONSatlas +{ + int width, height; + struct FONSatlasNode* nodes; + int nnodes; + int cnodes; +}; + +struct FONScontext +{ + struct FONSparams params; + float itw,ith; + unsigned char* texData; + int dirtyRect[4]; + struct FONSfont** fonts; + struct FONSatlas* atlas; + int cfonts; + int nfonts; + float verts[FONS_VERTEX_COUNT*2]; + float tcoords[FONS_VERTEX_COUNT*2]; + unsigned int colors[FONS_VERTEX_COUNT]; + int nverts; + unsigned char *scratch; + int nscratch; + struct FONSstate states[FONS_MAX_STATES]; + int nstates; + void (*handleError)(void* uptr, int error, int val); + void* errorUptr; +}; + +static void* fons__tmpalloc(size_t size, void* up) +{ + unsigned char* ptr; + struct FONScontext* stash = (struct FONScontext*)up; + + // 16-byte align the returned pointer + size = (size + 0xf) & ~0xf; + + if (stash->nscratch+(int)size > FONS_SCRATCH_BUF_SIZE) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_SCRATCH_FULL, stash->nscratch+(int)size); + return NULL; + } + ptr = stash->scratch + stash->nscratch; + stash->nscratch += (int)size; + return ptr; +} + +static void fons__tmpfree(void* ptr, void* up) +{ + (void)ptr; + (void)up; + // empty +} + +// Copyright (c) 2008-2010 Bjoern Hoehrmann +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +#define FONS_UTF8_ACCEPT 0 +#define FONS_UTF8_REJECT 12 + +static unsigned int fons__decutf8(unsigned int* state, unsigned int* codep, unsigned int byte) +{ + static const unsigned char utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 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,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, + }; + + unsigned int type = utf8d[byte]; + + *codep = (*state != FONS_UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +// Atlas based on Skyline Bin Packer by Jukka Jylänki + +static void fons__deleteAtlas(struct FONSatlas* atlas) +{ + if (atlas == NULL) return; + if (atlas->nodes != NULL) free(atlas->nodes); + free(atlas); +} + +static struct FONSatlas* fons__allocAtlas(int w, int h, int nnodes) +{ + struct FONSatlas* atlas = NULL; + + // Allocate memory for the font stash. + atlas = (struct FONSatlas*)malloc(sizeof(struct FONSatlas)); + if (atlas == NULL) goto error; + memset(atlas, 0, sizeof(struct FONSatlas)); + + atlas->width = w; + atlas->height = h; + + // Allocate space for skyline nodes + atlas->nodes = (struct FONSatlasNode*)malloc(sizeof(struct FONSatlasNode) * nnodes); + if (atlas->nodes == NULL) goto error; + memset(atlas->nodes, 0, sizeof(struct FONSatlasNode) * nnodes); + atlas->nnodes = 0; + atlas->cnodes = nnodes; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = (short)w; + atlas->nnodes++; + + return atlas; + +error: + if (atlas) fons__deleteAtlas(atlas); + return NULL; +} + +static int fons__atlasInsertNode(struct FONSatlas* atlas, int idx, int x, int y, int w) +{ + int i; + // Insert node + if (atlas->nnodes+1 > atlas->cnodes) { + atlas->cnodes = atlas->cnodes == 0 ? 8 : atlas->cnodes * 2; + atlas->nodes = (struct FONSatlasNode*)realloc(atlas->nodes, sizeof(struct FONSatlasNode) * atlas->cnodes); + if (atlas->nodes == NULL) + return 0; + } + for (i = atlas->nnodes; i > idx; i--) + atlas->nodes[i] = atlas->nodes[i-1]; + atlas->nodes[idx].x = (short)x; + atlas->nodes[idx].y = (short)y; + atlas->nodes[idx].width = (short)w; + atlas->nnodes++; + + return 1; +} + +static void fons__atlasRemoveNode(struct FONSatlas* atlas, int idx) +{ + int i; + if (atlas->nnodes == 0) return; + for (i = idx; i < atlas->nnodes-1; i++) + atlas->nodes[i] = atlas->nodes[i+1]; + atlas->nnodes--; +} + +static void fons__atlasExpand(struct FONSatlas* atlas, int w, int h) +{ + // Insert node for empty space + if (w > atlas->width) + fons__atlasInsertNode(atlas, atlas->nnodes, atlas->width, 0, w - atlas->width); + atlas->width = w; + atlas->height = h; +} + +static void fons__atlasReset(struct FONSatlas* atlas, int w, int h) +{ + atlas->width = w; + atlas->height = h; + atlas->nnodes = 0; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = (short)w; + atlas->nnodes++; +} + +static int fons__atlasAddSkylineLevel(struct FONSatlas* atlas, int idx, int x, int y, int w, int h) +{ + int i; + + // Insert new node + if (fons__atlasInsertNode(atlas, idx, x, y+h, w) == 0) + return 0; + + // Delete skyline segments that fall under the shaodw of the new segment. + for (i = idx+1; i < atlas->nnodes; i++) { + if (atlas->nodes[i].x < atlas->nodes[i-1].x + atlas->nodes[i-1].width) { + int shrink = atlas->nodes[i-1].x + atlas->nodes[i-1].width - atlas->nodes[i].x; + atlas->nodes[i].x += (short)shrink; + atlas->nodes[i].width -= (short)shrink; + if (atlas->nodes[i].width <= 0) { + fons__atlasRemoveNode(atlas, i); + i--; + } else { + break; + } + } else { + break; + } + } + + // Merge same height skyline segments that are next to each other. + for (i = 0; i < atlas->nnodes-1; i++) { + if (atlas->nodes[i].y == atlas->nodes[i+1].y) { + atlas->nodes[i].width += atlas->nodes[i+1].width; + fons__atlasRemoveNode(atlas, i+1); + i--; + } + } + + return 1; +} + +static int fons__atlasRectFits(struct FONSatlas* atlas, int i, int w, int h) +{ + // Checks if there is enough space at the location of skyline span 'i', + // and return the max height of all skyline spans under that at that location, + // (think tetris block being dropped at that position). Or -1 if no space found. + int x = atlas->nodes[i].x; + int y = atlas->nodes[i].y; + int spaceLeft; + if (x + w > atlas->width) + return -1; + spaceLeft = w; + while (spaceLeft > 0) { + if (i == atlas->nnodes) return -1; + y = fons__maxi(y, atlas->nodes[i].y); + if (y + h > atlas->height) return -1; + spaceLeft -= atlas->nodes[i].width; + ++i; + } + return y; +} + +static int fons__atlasAddRect(struct FONSatlas* atlas, int rw, int rh, int* rx, int* ry) +{ + int besth = atlas->height, bestw = atlas->width, besti = -1; + int bestx = -1, besty = -1, i; + + // Bottom left fit heuristic. + for (i = 0; i < atlas->nnodes; i++) { + int y = fons__atlasRectFits(atlas, i, rw, rh); + if (y != -1) { + if (y + rh < besth || (y + rh == besth && atlas->nodes[i].width < bestw)) { + besti = i; + bestw = atlas->nodes[i].width; + besth = y + rh; + bestx = atlas->nodes[i].x; + besty = y; + } + } + } + + if (besti == -1) + return 0; + + // Perform the actual packing. + if (fons__atlasAddSkylineLevel(atlas, besti, bestx, besty, rw, rh) == 0) + return 0; + + *rx = bestx; + *ry = besty; + + return 1; +} + +static void fons__addWhiteRect(struct FONScontext* stash, int w, int h) +{ + int x, y, gx, gy; + unsigned char* dst; + if (fons__atlasAddRect(stash->atlas, w, h, &gx, &gy) == 0) + return; + + // Rasterize + dst = &stash->texData[gx + gy * stash->params.width]; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) + dst[x] = 0xff; + dst += stash->params.width; + } + + stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], gx); + stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], gy); + stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], gx+w); + stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], gy+h); +} + +struct FONScontext* fonsCreateInternal(struct FONSparams* params) +{ + struct FONScontext* stash = NULL; + + // Allocate memory for the font stash. + stash = (struct FONScontext*)malloc(sizeof(struct FONScontext)); + if (stash == NULL) goto error; + memset(stash, 0, sizeof(struct FONScontext)); + + stash->params = *params; + + // Allocate scratch buffer. + stash->scratch = malloc(FONS_SCRATCH_BUF_SIZE); + if (stash->scratch == NULL) goto error; + + // Initialize implementation library + if (!fons__tt_init(stash)) goto error; + + if (stash->params.renderCreate != NULL) { + if (stash->params.renderCreate(stash->params.userPtr, stash->params.width, stash->params.height) == 0) + goto error; + } + + stash->atlas = fons__allocAtlas(stash->params.width, stash->params.height, FONS_INIT_ATLAS_NODES); + if (stash->atlas == NULL) goto error; + + // Allocate space for fonts. + stash->fonts = (struct FONSfont**)malloc(sizeof(struct FONSfont*) * FONS_INIT_FONTS); + if (stash->fonts == NULL) goto error; + memset(stash->fonts, 0, sizeof(struct FONSfont*) * FONS_INIT_FONTS); + stash->cfonts = FONS_INIT_FONTS; + stash->nfonts = 0; + + // Create texture for the cache. + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + stash->texData = (unsigned char*)malloc(stash->params.width * stash->params.height); + if (stash->texData == NULL) goto error; + memset(stash->texData, 0, stash->params.width * stash->params.height); + + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + + // Add white rect at 0,0 for debug drawing. + fons__addWhiteRect(stash, 2,2); + + fonsPushState(stash); + fonsClearState(stash); + + return stash; + +error: + fonsDeleteInternal(stash); + return NULL; +} + +static struct FONSstate* fons__getState(struct FONScontext* stash) +{ + return &stash->states[stash->nstates-1]; +} + +void fonsSetSize(struct FONScontext* stash, float size) +{ + fons__getState(stash)->size = size; +} + +void fonsSetColor(struct FONScontext* stash, unsigned int color) +{ + fons__getState(stash)->color = color; +} + +void fonsSetSpacing(struct FONScontext* stash, float spacing) +{ + fons__getState(stash)->spacing = spacing; +} + +void fonsSetBlur(struct FONScontext* stash, float blur) +{ + fons__getState(stash)->blur = blur; +} + +void fonsSetAlign(struct FONScontext* stash, int align) +{ + fons__getState(stash)->align = align; +} + +void fonsSetFont(struct FONScontext* stash, int font) +{ + fons__getState(stash)->font = font; +} + +void fonsPushState(struct FONScontext* stash) +{ + if (stash->nstates >= FONS_MAX_STATES) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_STATES_OVERFLOW, 0); + return; + } + if (stash->nstates > 0) + memcpy(&stash->states[stash->nstates], &stash->states[stash->nstates-1], sizeof(struct FONSstate)); + stash->nstates++; +} + +void fonsPopState(struct FONScontext* stash) +{ + if (stash->nstates <= 1) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_STATES_UNDERFLOW, 0); + return; + } + stash->nstates--; +} + +void fonsClearState(struct FONScontext* stash) +{ + struct FONSstate* state = fons__getState(stash); + state->size = 12.0f; + state->color = 0xffffffff; + state->font = 0; + state->blur = 0; + state->spacing = 0; + state->align = FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE; +} + +static void fons__freeFont(struct FONSfont* font) +{ + if (font == NULL) return; + if (font->glyphs) free(font->glyphs); + if (font->freeData && font->data) free(font->data); + free(font); +} + +static int fons__allocFont(struct FONScontext* stash) +{ + struct FONSfont* font = NULL; + if (stash->nfonts+1 > stash->cfonts) { + stash->cfonts = stash->cfonts == 0 ? 8 : stash->cfonts * 2; + stash->fonts = (struct FONSfont**)realloc(stash->fonts, sizeof(struct FONSfont*) * stash->cfonts); + if (stash->fonts == NULL) + return -1; + } + font = (struct FONSfont*)malloc(sizeof(struct FONSfont)); + if (font == NULL) goto error; + memset(font, 0, sizeof(struct FONSfont)); + + font->glyphs = (struct FONSglyph*)malloc(sizeof(struct FONSglyph) * FONS_INIT_GLYPHS); + if (font->glyphs == NULL) goto error; + font->cglyphs = FONS_INIT_GLYPHS; + font->nglyphs = 0; + + stash->fonts[stash->nfonts++] = font; + return stash->nfonts-1; + +error: + fons__freeFont(font); + + return FONS_INVALID; +} + +int fonsAddFont(struct FONScontext* stash, const char* name, const char* path) +{ + FILE* fp = 0; + int dataSize = 0; + unsigned char* data = NULL; + + // Read in the font data. + fp = fopen(path, "rb"); + if (!fp) goto error; + fseek(fp,0,SEEK_END); + dataSize = (int)ftell(fp); + fseek(fp,0,SEEK_SET); + data = (unsigned char*)malloc(dataSize); + if (data == NULL) goto error; + fread(data, 1, dataSize, fp); + fclose(fp); + fp = 0; + + return fonsAddFontMem(stash, name, data, dataSize, 1); + +error: + if (data) free(data); + if (fp) fclose(fp); + return FONS_INVALID; +} + +int fonsAddFontMem(struct FONScontext* stash, const char* name, unsigned char* data, int dataSize, int freeData) +{ + int i, ascent, descent, fh, lineGap; + struct FONSfont* font; + + int idx = fons__allocFont(stash); + if (idx == FONS_INVALID) + return FONS_INVALID; + + font = stash->fonts[idx]; + + strncpy(font->name, name, sizeof(font->name)); + font->name[sizeof(font->name)-1] = '\0'; + + // Init hash lookup. + for (i = 0; i < FONS_HASH_LUT_SIZE; ++i) + font->lut[i] = -1; + + // Read in the font data. + font->dataSize = dataSize; + font->data = data; + font->freeData = (unsigned char)freeData; + + // Init font + stash->nscratch = 0; + if (!fons__tt_loadFont(stash, &font->font, data, dataSize)) goto error; + + // Store normalized line height. The real line height is got + // by multiplying the lineh by font size. + fons__tt_getFontVMetrics( &font->font, &ascent, &descent, &lineGap); + fh = ascent - descent; + font->ascender = (float)ascent / (float)fh; + font->descender = (float)descent / (float)fh; + font->lineh = (float)(fh + lineGap) / (float)fh; + + return idx; + +error: + fons__freeFont(font); + stash->nfonts--; + return FONS_INVALID; +} + +int fonsGetFontByName(struct FONScontext* s, const char* name) +{ + int i; + for (i = 0; i < s->nfonts; i++) { + if (strcmp(s->fonts[i]->name, name) == 0) + return i; + } + return FONS_INVALID; +} + + +static struct FONSglyph* fons__allocGlyph(struct FONSfont* font) +{ + if (font->nglyphs+1 > font->cglyphs) { + font->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2; + font->glyphs = (struct FONSglyph*)realloc(font->glyphs, sizeof(struct FONSglyph) * font->cglyphs); + if (font->glyphs == NULL) return NULL; + } + font->nglyphs++; + return &font->glyphs[font->nglyphs-1]; +} + + +// Based on Exponential blur, Jani Huhtanen, 2006 + +#define APREC 16 +#define ZPREC 7 + +static void fons__blurCols(unsigned char* dst, int w, int h, int dstStride, int alpha) +{ + int x, y; + for (y = 0; y < h; y++) { + int z = 0; // force zero border + for (x = 1; x < w; x++) { + z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; + dst[x] = (unsigned char)(z >> ZPREC); + } + dst[w-1] = 0; // force zero border + z = 0; + for (x = w-2; x >= 0; x--) { + z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; + dst[x] = (unsigned char)(z >> ZPREC); + } + dst[0] = 0; // force zero border + dst += dstStride; + } +} + +static void fons__blurRows(unsigned char* dst, int w, int h, int dstStride, int alpha) +{ + int x, y; + for (x = 0; x < w; x++) { + int z = 0; // force zero border + for (y = dstStride; y < h*dstStride; y += dstStride) { + z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; + dst[y] = (unsigned char)(z >> ZPREC); + } + dst[(h-1)*dstStride] = 0; // force zero border + z = 0; + for (y = (h-2)*dstStride; y >= 0; y -= dstStride) { + z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; + dst[y] = (unsigned char)(z >> ZPREC); + } + dst[0] = 0; // force zero border + dst++; + } +} + + +static void fons__blur(struct FONScontext* stash, unsigned char* dst, int w, int h, int dstStride, int blur) +{ + int alpha; + float sigma; + (void)stash; + + if (blur < 1) + return; + // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) + sigma = (float)blur * 0.57735f; // 1 / sqrt(3) + alpha = (int)((1< 20) iblur = 20; + pad = iblur+2; + + // Reset allocator. + stash->nscratch = 0; + + // Find code point and size. + h = fons__hashint(codepoint) & (FONS_HASH_LUT_SIZE-1); + i = font->lut[h]; + while (i != -1) { + if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur) + return &font->glyphs[i]; + i = font->glyphs[i].next; + } + + // Could not find glyph, create it. + scale = fons__tt_getPixelHeightScale(&font->font, size); + g = fons__tt_getGlyphIndex(&font->font, codepoint); + fons__tt_buildGlyphBitmap(&font->font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); + gw = x1-x0 + pad*2; + gh = y1-y0 + pad*2; + + // Find free spot for the rect in the atlas + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + if (added == 0 && stash->handleError != NULL) { + // Atlas is full, let the user to resize the atlas (or not), and try again. + stash->handleError(stash->errorUptr, FONS_ATLAS_FULL, 0); + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + } + if (added == 0) return NULL; + + // Init glyph. + glyph = fons__allocGlyph(font); + glyph->codepoint = codepoint; + glyph->size = isize; + glyph->blur = iblur; + glyph->index = g; + glyph->x0 = (short)gx; + glyph->y0 = (short)gy; + glyph->x1 = (short)(glyph->x0+gw); + glyph->y1 = (short)(glyph->y0+gh); + glyph->xadv = (short)(scale * advance * 10.0f); + glyph->xoff = (short)(x0 - pad); + glyph->yoff = (short)(y0 - pad); + glyph->next = 0; + + // Insert char to hash lookup. + glyph->next = font->lut[h]; + font->lut[h] = font->nglyphs-1; + + // Rasterize + dst = &stash->texData[(glyph->x0+pad) + (glyph->y0+pad) * stash->params.width]; + fons__tt_renderGlyphBitmap(&font->font, dst, gw-pad*2,gh-pad*2, stash->params.width, scale,scale, g); + + // Make sure there is one pixel empty border. + dst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + for (y = 0; y < gh; y++) { + dst[y*stash->params.width] = 0; + dst[gw-1 + y*stash->params.width] = 0; + } + for (x = 0; x < gw; x++) { + dst[x] = 0; + dst[x + (gh-1)*stash->params.width] = 0; + } + + // Debug code to color the glyph background +/* unsigned char* fdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + for (y = 0; y < gh; y++) { + for (x = 0; x < gw; x++) { + int a = (int)fdst[x+y*stash->params.width] + 20; + if (a > 255) a = 255; + fdst[x+y*stash->params.width] = a; + } + }*/ + + // Blur + if (iblur > 0) { + stash->nscratch = 0; + bdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + fons__blur(stash, bdst, gw,gh, stash->params.width, iblur); + } + + stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], glyph->x0); + stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], glyph->y0); + stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], glyph->x1); + stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], glyph->y1); + + return glyph; +} + +static void fons__getQuad(struct FONScontext* stash, struct FONSfont* font, + struct FONSglyph* prevGlyph, struct FONSglyph* glyph, + float scale, float spacing, float* x, float* y, struct FONSquad* q) +{ + float rx,ry,xoff,yoff,x0,y0,x1,y1; + + if (prevGlyph) { + float adv = fons__tt_getGlyphKernAdvance(&font->font, prevGlyph->index, glyph->index) * scale; + *x += (int)(adv + spacing + 0.5f); + } + + // Each glyph has 2px border to allow good interpolation, + // one pixel to prevent leaking, and one to allow good interpolation for rendering. + // Inset the texture region by one pixel for corret interpolation. + xoff = (short)(glyph->xoff+1); + yoff = (short)(glyph->yoff+1); + x0 = (float)(glyph->x0+1); + y0 = (float)(glyph->y0+1); + x1 = (float)(glyph->x1-1); + y1 = (float)(glyph->y1-1); + + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + rx = (float)(int)(*x + xoff); + ry = (float)(int)(*y + yoff); + + q->x0 = rx; + q->y0 = ry; + q->x1 = rx + x1 - x0; + q->y1 = ry + y1 - y0; + + q->s0 = x0 * stash->itw; + q->t0 = y0 * stash->ith; + q->s1 = x1 * stash->itw; + q->t1 = y1 * stash->ith; + } else { + rx = (float)(int)(*x + xoff); + ry = (float)(int)(*y - yoff); + + q->x0 = rx; + q->y0 = ry; + q->x1 = rx + x1 - x0; + q->y1 = ry - y1 + y0; + + q->s0 = x0 * stash->itw; + q->t0 = y0 * stash->ith; + q->s1 = x1 * stash->itw; + q->t1 = y1 * stash->ith; + } + + *x += (int)(glyph->xadv / 10.0f + 0.5f); +} + +static void fons__flush(struct FONScontext* stash) +{ + // Flush texture + if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { + if (stash->params.renderUpdate != NULL) + stash->params.renderUpdate(stash->params.userPtr, stash->dirtyRect, stash->texData); + // Reset dirty rect + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + } + + // Flush triangles + if (stash->nverts > 0) { + if (stash->params.renderDraw != NULL) + stash->params.renderDraw(stash->params.userPtr, stash->verts, stash->tcoords, stash->colors, stash->nverts); + stash->nverts = 0; + } +} + +static __inline void fons__vertex(struct FONScontext* stash, float x, float y, float s, float t, unsigned int c) +{ + stash->verts[stash->nverts*2+0] = x; + stash->verts[stash->nverts*2+1] = y; + stash->tcoords[stash->nverts*2+0] = s; + stash->tcoords[stash->nverts*2+1] = t; + stash->colors[stash->nverts] = c; + stash->nverts++; +} + +static float fons__getVertAlign(struct FONScontext* stash, struct FONSfont* font, int align, short isize) +{ + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + if (align & FONS_ALIGN_TOP) { + return font->ascender * (float)isize/10.0f; + } else if (align & FONS_ALIGN_MIDDLE) { + return (font->ascender + font->descender) / 2.0f * (float)isize/10.0f; + } else if (align & FONS_ALIGN_BASELINE) { + return 0.0f; + } else if (align & FONS_ALIGN_BOTTOM) { + return font->descender * (float)isize/10.0f; + } + } else { + if (align & FONS_ALIGN_TOP) { + return -font->ascender * (float)isize/10.0f; + } else if (align & FONS_ALIGN_MIDDLE) { + return -(font->ascender + font->descender) / 2.0f * (float)isize/10.0f; + } else if (align & FONS_ALIGN_BASELINE) { + return 0.0f; + } else if (align & FONS_ALIGN_BOTTOM) { + return -font->descender * (float)isize/10.0f; + } + } + return 0.0; +} + +float fonsDrawText(struct FONScontext* stash, + float x, float y, + const char* str, const char* end) +{ + struct FONSstate* state = fons__getState(stash); + unsigned int codepoint; + unsigned int utf8state = 0; + struct FONSglyph* glyph = NULL; + struct FONSglyph* prevGlyph = NULL; + struct FONSquad q; + short isize = (short)(state->size*10.0f); + short iblur = (short)state->blur; + float scale; + struct FONSfont* font; + float width; + + if (stash == NULL) return x; + if (state->font < 0 || state->font >= stash->nfonts) return x; + font = stash->fonts[state->font]; + if (!font->data) return x; + + scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); + + if (end == NULL) + end = str + strlen(str); + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width; + } else if (state->align & FONS_ALIGN_CENTER) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width * 0.5f; + } + // Align vertically. + y += fons__getVertAlign(stash, font, state->align, isize); + + for (; str != end; ++str) { + if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) + continue; + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); + if (glyph) { + fons__getQuad(stash, font, prevGlyph, glyph, scale, state->spacing, &x, &y, &q); + + if (stash->nverts+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); + fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); + fons__vertex(stash, q.x1, q.y0, q.s1, q.t0, state->color); + + fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); + fons__vertex(stash, q.x0, q.y1, q.s0, q.t1, state->color); + fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); + } + prevGlyph = glyph; + } + fons__flush(stash); + + return x; +} + +int fonsTextIterInit(struct FONScontext* stash, struct FONStextIter* iter, + float x, float y, const char* str, const char* end) +{ + struct FONSstate* state = fons__getState(stash); + float width; + + memset(iter, 0, sizeof(*iter)); + + if (stash == NULL) return 0; + if (state->font < 0 || state->font >= stash->nfonts) return 0; + iter->font = stash->fonts[state->font]; + if (!iter->font->data) return 0; + + iter->isize = (short)(state->size*10.0f); + iter->iblur = (short)state->blur; + iter->scale = fons__tt_getPixelHeightScale(&iter->font->font, (float)iter->isize/10.0f); + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width; + } else if (state->align & FONS_ALIGN_CENTER) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width * 0.5f; + } + // Align vertically. + y += fons__getVertAlign(stash, iter->font, state->align, iter->isize); + + if (end == NULL) + end = str + strlen(str); + + iter->x = iter->nextx = x; + iter->y = iter->nexty = y; + iter->spacing = state->spacing; + iter->str = str; + iter->next = str; + iter->end = end; + iter->codepoint = 0; + + return 1; +} + +int fonsTextIterNext(struct FONScontext* stash, struct FONStextIter* iter, struct FONSquad* quad) +{ + struct FONSglyph* glyph = NULL; + const char* str = iter->next; + iter->str = iter->next; + + if (str == iter->end) + return 0; + + for (; str != iter->end; str++) { + if (fons__decutf8(&iter->utf8state, &iter->codepoint, *(const unsigned char*)str)) + continue; + str++; + // Get glyph and quad + iter->x = iter->nextx; + iter->y = iter->nexty; + glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur); + if (glyph != NULL) + fons__getQuad(stash, iter->font, iter->prevGlyph, glyph, iter->scale, iter->spacing, &iter->nextx, &iter->nexty, quad); + iter->prevGlyph = glyph; + break; + } + iter->next = str; + + return 1; +} + +void fonsDrawDebug(struct FONScontext* stash, float x, float y) +{ + int i; + int w = stash->params.width; + int h = stash->params.height; + float u = w == 0 ? 0 : (1.0f / w); + float v = h == 0 ? 0 : (1.0f / h); + + if (stash->nverts+6+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + // Draw background + fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+0, u, v, 0x0fffffff); + + fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); + fons__vertex(stash, x+0, y+h, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); + + // Draw texture + fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); + fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); + fons__vertex(stash, x+w, y+0, 1, 0, 0xffffffff); + + fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); + fons__vertex(stash, x+0, y+h, 0, 1, 0xffffffff); + fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); + + // Drawbug draw atlas + for (i = 0; i < stash->atlas->nnodes; i++) { + struct FONSatlasNode* n = &stash->atlas->nodes[i]; + + if (stash->nverts+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+0, u, v, 0xc00000ff); + + fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+0, y+n->y+1, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); + } + + fons__flush(stash); +} + +float fonsTextBounds(struct FONScontext* stash, + float x, float y, + const char* str, const char* end, + float* bounds) +{ + struct FONSstate* state = fons__getState(stash); + unsigned int codepoint; + unsigned int utf8state = 0; + struct FONSquad q; + struct FONSglyph* glyph = NULL; + struct FONSglyph* prevGlyph = NULL; + short isize = (short)(state->size*10.0f); + short iblur = (short)state->blur; + float scale; + struct FONSfont* font; + float startx, advance; + float minx, miny, maxx, maxy; + + if (stash == NULL) return 0; + if (state->font < 0 || state->font >= stash->nfonts) return 0; + font = stash->fonts[state->font]; + if (!font->data) return 0; + + scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); + + // Align vertically. + y += fons__getVertAlign(stash, font, state->align, isize); + + minx = maxx = x; + miny = maxy = y; + startx = x; + + if (end == NULL) + end = str + strlen(str); + + for (; str != end; ++str) { + if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) + continue; + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); + if (glyph) { + fons__getQuad(stash, font, prevGlyph, glyph, scale, state->spacing, &x, &y, &q); + if (q.x0 < minx) minx = q.x0; + if (q.x1 > maxx) maxx = q.x1; + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + if (q.y0 < miny) miny = q.y0; + if (q.y1 > maxy) maxy = q.y1; + } else { + if (q.y1 < miny) miny = q.y1; + if (q.y0 > maxy) maxy = q.y0; + } + } + prevGlyph = glyph; + } + + advance = x - startx; + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + minx -= advance; + maxx -= advance; + } else if (state->align & FONS_ALIGN_CENTER) { + minx -= advance * 0.5f; + maxx -= advance * 0.5f; + } + + if (bounds) { + bounds[0] = minx; + bounds[1] = miny; + bounds[2] = maxx; + bounds[3] = maxy; + } + + return advance; +} + +void fonsVertMetrics(struct FONScontext* stash, + float* ascender, float* descender, float* lineh) +{ + struct FONSfont* font; + struct FONSstate* state = fons__getState(stash); + short isize; + + if (stash == NULL) return; + if (state->font < 0 || state->font >= stash->nfonts) return; + font = stash->fonts[state->font]; + isize = (short)(state->size*10.0f); + if (!font->data) return; + + if (ascender) + *ascender = font->ascender*isize/10.0f; + if (descender) + *descender = font->descender*isize/10.0f; + if (lineh) + *lineh = font->lineh*isize/10.0f; +} + +void fonsLineBounds(struct FONScontext* stash, float y, float* miny, float* maxy) +{ + struct FONSfont* font; + struct FONSstate* state = fons__getState(stash); + short isize; + + if (stash == NULL) return; + if (state->font < 0 || state->font >= stash->nfonts) return; + font = stash->fonts[state->font]; + isize = (short)(state->size*10.0f); + if (!font->data) return; + + y += fons__getVertAlign(stash, font, state->align, isize); + + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + *miny = y - font->ascender * (float)isize/10.0f; + *maxy = *miny + font->lineh*isize/10.0f; + } else { + *maxy = y + font->descender * (float)isize/10.0f; + *miny = *maxy - font->lineh*isize/10.0f; + } +} + +const unsigned char* fonsGetTextureData(struct FONScontext* stash, int* width, int* height) +{ + if (width != NULL) + *width = stash->params.width; + if (height != NULL) + *height = stash->params.height; + return stash->texData; +} + +int fonsValidateTexture(struct FONScontext* stash, int* dirty) +{ + if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { + dirty[0] = stash->dirtyRect[0]; + dirty[1] = stash->dirtyRect[1]; + dirty[2] = stash->dirtyRect[2]; + dirty[3] = stash->dirtyRect[3]; + // Reset dirty rect + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + return 1; + } + return 0; +} + +void fonsDeleteInternal(struct FONScontext* stash) +{ + int i; + if (stash == NULL) return; + + if (stash->params.renderDelete) + stash->params.renderDelete(stash->params.userPtr); + + for (i = 0; i < stash->nfonts; ++i) + fons__freeFont(stash->fonts[i]); + + if (stash->atlas) fons__deleteAtlas(stash->atlas); + if (stash->fonts) free(stash->fonts); + if (stash->texData) free(stash->texData); + if (stash->scratch) free(stash->scratch); + free(stash); +} + +void fonsSetErrorCallback(struct FONScontext* stash, void (*callback)(void* uptr, int error, int val), void* uptr) +{ + if (stash == NULL) return; + stash->handleError = callback; + stash->errorUptr = uptr; +} + +void fonsGetAtlasSize(struct FONScontext* stash, int* width, int* height) +{ + if (stash == NULL) return; + *width = stash->params.width; + *height = stash->params.height; +} + +int fonsExpandAtlas(struct FONScontext* stash, int width, int height) +{ + int i, maxy = 0; + unsigned char* data = NULL; + if (stash == NULL) return 0; + + width = fons__maxi(width, stash->params.width); + height = fons__maxi(height, stash->params.height); + + if (width == stash->params.width && height == stash->params.height) + return 1; + + // Flush pending glyphs. + fons__flush(stash); + + // Create new texture + if (stash->params.renderResize != NULL) { + if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) + return 0; + } + // Copy old texture data over. + data = (unsigned char*)malloc(width * height); + if (data == NULL) + return 0; + for (i = 0; i < stash->params.height; i++) { + unsigned char* dst = &data[i*width]; + unsigned char* src = &stash->texData[i*stash->params.width]; + memcpy(dst, src, stash->params.width); + if (width > stash->params.width) + memset(dst+stash->params.width, 0, width - stash->params.width); + } + if (height > stash->params.height) + memset(&data[stash->params.height * width], 0, (height - stash->params.height) * width); + + free(stash->texData); + stash->texData = data; + + // Increase atlas size + fons__atlasExpand(stash->atlas, width, height); + + // Add axisting data as dirty. + for (i = 0; i < stash->atlas->nnodes; i++) + maxy = fons__maxi(maxy, stash->atlas->nodes[i].y); + stash->dirtyRect[0] = 0; + stash->dirtyRect[1] = 0; + stash->dirtyRect[2] = stash->params.width; + stash->dirtyRect[3] = maxy; + + stash->params.width = width; + stash->params.height = height; + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + + return 1; +} + +int fonsResetAtlas(struct FONScontext* stash, int width, int height) +{ + int i, j; + if (stash == NULL) return 0; + + // Flush pending glyphs. + fons__flush(stash); + + // Create new texture + if (stash->params.renderResize != NULL) { + if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) + return 0; + } + + // Reset atlas + fons__atlasReset(stash->atlas, width, height); + + // Clear texture data. + stash->texData = (unsigned char*)realloc(stash->texData, width * height); + if (stash->texData == NULL) return 0; + memset(stash->texData, 0, width * height); + + // Reset dirty rect + stash->dirtyRect[0] = width; + stash->dirtyRect[1] = height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + + // Reset cached glyphs + for (i = 0; i < stash->nfonts; i++) { + struct FONSfont* font = stash->fonts[i]; + font->nglyphs = 0; + for (j = 0; j < FONS_HASH_LUT_SIZE; j++) + font->lut[j] = -1; + } + + stash->params.width = width; + stash->params.height = height; + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + + // Add white rect at 0,0 for debug drawing. + fons__addWhiteRect(stash, 2,2); + + return 1; +} + + +#endif diff --git a/examples/nanovg_src/nanovg.c b/examples/nanovg_src/nanovg.c new file mode 100644 index 0000000..aec8ef9 --- /dev/null +++ b/examples/nanovg_src/nanovg.c @@ -0,0 +1,2474 @@ +// +// Copyright (c) 2013 Mikko Mononen memon@inside.org +// +// 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. +// + +#include +#include +#include "nanovg.h" +#define FONTSTASH_IMPLEMENTATION +#include "fontstash.h" +#define STBI_HEADER_FILE_ONLY +#include "stb_image.c" + + +#define NVG_INIT_COMMANDS_SIZE 256 +#define NVG_INIT_POINTS_SIZE 128 +#define NVG_INIT_PATHS_SIZE 16 +#define NVG_INIT_VERTS_SIZE 256 +#define NVG_MAX_STATES 32 + +#define NVG_KAPPA90 0.5522847493f // Lenght proportional to radius of a cubic bezier handle for 90deg arcs. + +#define NVG_COUNTOF(arr) (sizeof(arr) / sizeof(0[arr])) + + +enum NVGcommands { + NVG_MOVETO = 0, + NVG_LINETO = 1, + NVG_BEZIERTO = 2, + NVG_CLOSE = 3, + NVG_WINDING = 4, +}; + +enum NVGpointFlags +{ + NVG_PT_CORNER = 0x01, + NVG_PT_LEFT = 0x02, + NVG_PT_BEVEL = 0x04, + NVG_PR_INNERBEVEL = 0x08, +}; + +enum NVGexpandFeatures { + NVG_FILL = 0x01, + NVG_STROKE = 0x02, + NVG_CAPS = 0x04, +}; + +struct NVGstate { + struct NVGpaint fill; + struct NVGpaint stroke; + float strokeWidth; + float miterLimit; + int lineJoin; + int lineCap; + float xform[6]; + struct NVGscissor scissor; + float fontSize; + float letterSpacing; + float lineHeight; + float fontBlur; + int textAlign; + int fontId; +}; + +struct NVGpoint { + float x,y; + float dx, dy; + float len; + float dmx, dmy; + unsigned char flags; +}; + +struct NVGpathCache { + struct NVGpoint* points; + int npoints; + int cpoints; + struct NVGpath* paths; + int npaths; + int cpaths; + struct NVGvertex* verts; + int nverts; + int cverts; + float bounds[4]; +}; + +struct NVGcontext { + struct NVGparams params; + float* commands; + int ccommands; + int ncommands; + float commandx, commandy; + struct NVGstate states[NVG_MAX_STATES]; + int nstates; + struct NVGpathCache* cache; + float tessTol; + float distTol; + float fringeWidth; + float devicePxRatio; + struct FONScontext* fs; + int fontImage; + int alphaBlend; + int drawCallCount; + int fillTriCount; + int strokeTriCount; + int textTriCount; +}; + +static float nvg__sqrtf(float a) { return sqrtf(a); } +static float nvg__modf(float a, float b) { return fmodf(a, b); } +static float nvg__sinf(float a) { return sinf(a); } +static float nvg__cosf(float a) { return cosf(a); } +static float nvg__tanf(float a) { return tanf(a); } +static float nvg__atan2f(float a,float b) { return atan2f(a, b); } +static float nvg__acosf(float a) { return acosf(a); } + +static int nvg__mini(int a, int b) { return a < b ? a : b; } +static int nvg__maxi(int a, int b) { return a > b ? a : b; } +static int nvg__clampi(int a, int mn, int mx) { return a < mn ? mn : (a > mx ? mx : a); } +static float nvg__minf(float a, float b) { return a < b ? a : b; } +static float nvg__maxf(float a, float b) { return a > b ? a : b; } +static float nvg__absf(float a) { return a >= 0.0f ? a : -a; } +static float nvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } +static float nvg__cross(float dx0, float dy0, float dx1, float dy1) { return dx1*dy0 - dx0*dy1; } + +static float nvg__normalize(float *x, float* y) +{ + float d = nvg__sqrtf((*x)*(*x) + (*y)*(*y)); + if (d > 1e-6f) { + float id = 1.0f / d; + *x *= id; + *y *= id; + } + return d; +} + + +static void nvg__deletePathCache(struct NVGpathCache* c) +{ + if (c == NULL) return; + if (c->points != NULL) free(c->points); + if (c->paths != NULL) free(c->paths); + if (c->verts != NULL) free(c->verts); + free(c); +} + +static struct NVGpathCache* nvg__allocPathCache() +{ + struct NVGpathCache* c = (struct NVGpathCache*)malloc(sizeof(struct NVGpathCache)); + if (c == NULL) goto error; + memset(c, 0, sizeof(struct NVGpathCache)); + + c->points = (struct NVGpoint*)malloc(sizeof(struct NVGpoint)*NVG_INIT_POINTS_SIZE); + if (!c->points) goto error; + c->npoints = 0; + c->cpoints = NVG_INIT_POINTS_SIZE; + + c->paths = (struct NVGpath*)malloc(sizeof(struct NVGpath)*NVG_INIT_PATHS_SIZE); + if (!c->paths) goto error; + c->npaths = 0; + c->cpaths = NVG_INIT_PATHS_SIZE; + + c->verts = (struct NVGvertex*)malloc(sizeof(struct NVGvertex)*NVG_INIT_VERTS_SIZE); + if (!c->verts) goto error; + c->nverts = 0; + c->cverts = NVG_INIT_VERTS_SIZE; + + return c; +error: + nvg__deletePathCache(c); + return NULL; +} + +static void nvg__setDevicePixelRatio(struct NVGcontext* ctx, float ratio) +{ + ctx->tessTol = 1.0f / ratio; + ctx->distTol = 0.01f / ratio; + ctx->fringeWidth = 1.0f / ratio; + ctx->devicePxRatio = ratio; +} + +struct NVGcontext* nvgCreateInternal(struct NVGparams* params) +{ + struct FONSparams fontParams; + struct NVGcontext* ctx = (struct NVGcontext*)malloc(sizeof(struct NVGcontext)); + if (ctx == NULL) goto error; + memset(ctx, 0, sizeof(struct NVGcontext)); + + ctx->params = *params; + + ctx->commands = (float*)malloc(sizeof(float)*NVG_INIT_COMMANDS_SIZE); + if (!ctx->commands) goto error; + ctx->ncommands = 0; + ctx->ccommands = NVG_INIT_COMMANDS_SIZE; + + ctx->alphaBlend = NVG_STRAIGHT_ALPHA; + + ctx->cache = nvg__allocPathCache(); + if (ctx->cache == NULL) goto error; + + nvgSave(ctx); + nvgReset(ctx); + + nvg__setDevicePixelRatio(ctx, 1.0f); + + if (ctx->params.renderCreate(ctx->params.userPtr) == 0) goto error; + + // Init font rendering + memset(&fontParams, 0, sizeof(fontParams)); + fontParams.width = params->atlasWidth; + fontParams.height = params->atlasHeight; + fontParams.flags = FONS_ZERO_TOPLEFT; + fontParams.renderCreate = NULL; + fontParams.renderUpdate = NULL; + fontParams.renderDraw = NULL; + fontParams.renderDelete = NULL; + fontParams.userPtr = NULL; + ctx->fs = fonsCreateInternal(&fontParams); + if (ctx->fs == NULL) goto error; + + // Create font texture + ctx->fontImage = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, fontParams.width, fontParams.height, NULL); + if (ctx->fontImage == 0) goto error; + + return ctx; + +error: + nvgDeleteInternal(ctx); + return 0; +} + +void nvgDeleteInternal(struct NVGcontext* ctx) +{ + if (ctx == NULL) return; + if (ctx->commands != NULL) free(ctx->commands); + if (ctx->cache != NULL) nvg__deletePathCache(ctx->cache); + + if (ctx->fs) + fonsDeleteInternal(ctx->fs); + + if (ctx->params.renderDelete != NULL) + ctx->params.renderDelete(ctx->params.userPtr); + + free(ctx); +} + +void nvgBeginFrame(struct NVGcontext* ctx, int windowWidth, int windowHeight, float devicePixelRatio, int alphaBlend) +{ +/* printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", + ctx->drawCallCount, ctx->fillTriCount, ctx->strokeTriCount, ctx->textTriCount, + ctx->fillTriCount+ctx->strokeTriCount+ctx->textTriCount);*/ + + ctx->nstates = 0; + nvgSave(ctx); + nvgReset(ctx); + + nvg__setDevicePixelRatio(ctx, devicePixelRatio); + ctx->alphaBlend = alphaBlend; + + ctx->params.renderViewport(ctx->params.userPtr, windowWidth, windowHeight, ctx->alphaBlend); + + ctx->drawCallCount = 0; + ctx->fillTriCount = 0; + ctx->strokeTriCount = 0; + ctx->textTriCount = 0; +} + +void nvgEndFrame(struct NVGcontext* ctx) +{ + ctx->params.renderFlush(ctx->params.userPtr, ctx->alphaBlend); +} + +struct NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b) +{ + return nvgRGBA(r,g,b,255); +} + +struct NVGcolor nvgRGBf(float r, float g, float b) +{ + return nvgRGBAf(r,g,b,1.0f); +} + +struct NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + struct NVGcolor color; + // Use longer initialization to suppress warning. + color.r = r / 255.0f; + color.g = g / 255.0f; + color.b = b / 255.0f; + color.a = a / 255.0f; + return color; +} + +struct NVGcolor nvgRGBAf(float r, float g, float b, float a) +{ + struct NVGcolor color; + // Use longer initialization to suppress warning. + color.r = r; + color.g = g; + color.b = b; + color.a = a; + return color; +} + +struct NVGcolor nvgTransRGBA(struct NVGcolor c, unsigned char a) +{ + c.a = a / 255.0f; + return c; +} + +struct NVGcolor nvgTransRGBAf(struct NVGcolor c, float a) +{ + c.a = a; + return c; +} + +struct NVGcolor nvgLerpRGBA(struct NVGcolor c0, struct NVGcolor c1, float u) +{ + int i; + float oneminu; + struct NVGcolor cint; + + u = nvg__clampf(u, 0.0f, 1.0f); + oneminu = 1.0f - u; + for( i = 0; i <4; ++i ) + { + cint.rgba[i] = c0.rgba[i] * oneminu + c1.rgba[i] * u; + } + + return cint; +} + +struct NVGcolor nvgHSL(float h, float s, float l) +{ + return nvgHSLA(h,s,l,255); +} + +static float nvg__hue(float h, float m1, float m2) +{ + if (h < 0) h += 1; + if (h > 1) h -= 1; + if (h < 1.0f/6.0f) + return m1 + (m2 - m1) * h * 6.0f; + else if (h < 3.0f/6.0f) + return m2; + else if (h < 4.0f/6.0f) + return m1 + (m2 - m1) * (2.0f/3.0f - h) * 6.0f; + return m1; +} + +struct NVGcolor nvgHSLA(float h, float s, float l, unsigned char a) +{ + float m1, m2; + struct NVGcolor col; + h = nvg__modf(h, 1.0f); + if (h < 0.0f) h += 1.0f; + s = nvg__clampf(s, 0.0f, 1.0f); + l = nvg__clampf(l, 0.0f, 1.0f); + m2 = l <= 0.5f ? (l * (1 + s)) : (l + s - l * s); + m1 = 2 * l - m2; + col.r = nvg__clampf(nvg__hue(h + 1.0f/3.0f, m1, m2), 0.0f, 1.0f); + col.g = nvg__clampf(nvg__hue(h, m1, m2), 0.0f, 1.0f); + col.b = nvg__clampf(nvg__hue(h - 1.0f/3.0f, m1, m2), 0.0f, 1.0f); + col.a = a/255.0f; + return col; +} + + +static struct NVGstate* nvg__getState(struct NVGcontext* ctx) +{ + return &ctx->states[ctx->nstates-1]; +} + +void nvgTransformIdentity(float* t) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +void nvgTransformTranslate(float* t, float tx, float ty) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = tx; t[5] = ty; +} + +void nvgTransformScale(float* t, float sx, float sy) +{ + t[0] = sx; t[1] = 0.0f; + t[2] = 0.0f; t[3] = sy; + t[4] = 0.0f; t[5] = 0.0f; +} + +void nvgTransformRotate(float* t, float a) +{ + float cs = nvg__cosf(a), sn = nvg__sinf(a); + t[0] = cs; t[1] = sn; + t[2] = -sn; t[3] = cs; + t[4] = 0.0f; t[5] = 0.0f; +} + +void nvgTransformSkewX(float* t, float a) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = nvg__tanf(a); t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +void nvgTransformSkewY(float* t, float a) +{ + t[0] = 1.0f; t[1] = nvg__tanf(a); + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +void nvgTransformMultiply(float* t, const float* s) +{ + float t0 = t[0] * s[0] + t[1] * s[2]; + float t2 = t[2] * s[0] + t[3] * s[2]; + float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; + t[1] = t[0] * s[1] + t[1] * s[3]; + t[3] = t[2] * s[1] + t[3] * s[3]; + t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; + t[0] = t0; + t[2] = t2; + t[4] = t4; +} + +void nvgTransformPremultiply(float* t, const float* s) +{ + float s2[6]; + memcpy(s2, s, sizeof(float)*6); + nvgTransformMultiply(s2, t); + memcpy(t, s2, sizeof(float)*6); +} + +int nvgTransformInverse(float* inv, const float* t) +{ + double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; + if (det > -1e-6 && det < 1e-6) { + nvgTransformIdentity(inv); + return 0; + } + invdet = 1.0 / det; + inv[0] = (float)(t[3] * invdet); + inv[2] = (float)(-t[2] * invdet); + inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); + inv[1] = (float)(-t[1] * invdet); + inv[3] = (float)(t[0] * invdet); + inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); + return 1; +} + +void nvgTransformPoint(float* dx, float* dy, const float* t, float sx, float sy) +{ + *dx = sx*t[0] + sy*t[2] + t[4]; + *dy = sx*t[1] + sy*t[3] + t[5]; +} + +float nvgDegToRad(float deg) +{ + return deg / 180.0f * NVG_PI; +} + +float nvgRadToDeg(float rad) +{ + return rad / NVG_PI * 180.0f; +} + +static void nvg__setPaintColor(struct NVGpaint* p, struct NVGcolor color) +{ + memset(p, 0, sizeof(*p)); + nvgTransformIdentity(p->xform); + p->radius = 0.0f; + p->feather = 1.0f; + p->innerColor = color; + p->outerColor = color; +} + + +// State handling +void nvgSave(struct NVGcontext* ctx) +{ + if (ctx->nstates >= NVG_MAX_STATES) + return; + if (ctx->nstates > 0) + memcpy(&ctx->states[ctx->nstates], &ctx->states[ctx->nstates-1], sizeof(struct NVGstate)); + ctx->nstates++; +} + +void nvgRestore(struct NVGcontext* ctx) +{ + if (ctx->nstates <= 1) + return; + ctx->nstates--; +} + +void nvgReset(struct NVGcontext* ctx) +{ + struct NVGstate* state = nvg__getState(ctx); + memset(state, 0, sizeof(*state)); + + nvg__setPaintColor(&state->fill, nvgRGBA(255,255,255,255)); + nvg__setPaintColor(&state->stroke, nvgRGBA(0,0,0,255)); + state->strokeWidth = 1.0f; + state->miterLimit = 10.0f; + state->lineCap = NVG_BUTT; + state->lineJoin = NVG_MITER; + nvgTransformIdentity(state->xform); + + state->scissor.extent[0] = 0.0f; + state->scissor.extent[1] = 0.0f; + + state->fontSize = 16.0f; + state->letterSpacing = 0.0f; + state->lineHeight = 0.0f; + state->fontBlur = 0.0f; + state->textAlign = NVG_ALIGN_LEFT | NVG_ALIGN_BASELINE; + state->fontId = 0; +} + +// State setting +void nvgStrokeWidth(struct NVGcontext* ctx, float width) +{ + struct NVGstate* state = nvg__getState(ctx); + state->strokeWidth = width; +} + +void nvgMiterLimit(struct NVGcontext* ctx, float limit) +{ + struct NVGstate* state = nvg__getState(ctx); + state->miterLimit = limit; +} + +void nvgLineCap(struct NVGcontext* ctx, int cap) +{ + struct NVGstate* state = nvg__getState(ctx); + state->lineCap = cap; +} + +void nvgLineJoin(struct NVGcontext* ctx, int join) +{ + struct NVGstate* state = nvg__getState(ctx); + state->lineJoin = join; +} + + +void nvgTransform(struct NVGcontext* ctx, float a, float b, float c, float d, float e, float f) +{ + struct NVGstate* state = nvg__getState(ctx); + float t[6] = { a, b, c, d, e, f }; + nvgTransformPremultiply(state->xform, t); +} + +void nvgResetTransform(struct NVGcontext* ctx) +{ + struct NVGstate* state = nvg__getState(ctx); + nvgTransformIdentity(state->xform); +} + +void nvgTranslate(struct NVGcontext* ctx, float x, float y) +{ + struct NVGstate* state = nvg__getState(ctx); + float t[6]; + nvgTransformTranslate(t, x,y); + nvgTransformPremultiply(state->xform, t); +} + +void nvgRotate(struct NVGcontext* ctx, float angle) +{ + struct NVGstate* state = nvg__getState(ctx); + float t[6]; + nvgTransformRotate(t, angle); + nvgTransformPremultiply(state->xform, t); +} + +void nvgSkewX(struct NVGcontext* ctx, float angle) +{ + struct NVGstate* state = nvg__getState(ctx); + float t[6]; + nvgTransformSkewX(t, angle); + nvgTransformPremultiply(state->xform, t); +} + +void nvgSkewY(struct NVGcontext* ctx, float angle) +{ + struct NVGstate* state = nvg__getState(ctx); + float t[6]; + nvgTransformSkewY(t, angle); + nvgTransformPremultiply(state->xform, t); +} + +void nvgScale(struct NVGcontext* ctx, float x, float y) +{ + struct NVGstate* state = nvg__getState(ctx); + float t[6]; + nvgTransformScale(t, x,y); + nvgTransformPremultiply(state->xform, t); +} + +void nvgCurrentTransform(struct NVGcontext* ctx, float* xform) +{ + struct NVGstate* state = nvg__getState(ctx); + if (xform == NULL) return; + memcpy(xform, state->xform, sizeof(float)*6); +} + +void nvgStrokeColor(struct NVGcontext* ctx, struct NVGcolor color) +{ + struct NVGstate* state = nvg__getState(ctx); + nvg__setPaintColor(&state->stroke, color); +} + +void nvgStrokePaint(struct NVGcontext* ctx, struct NVGpaint paint) +{ + struct NVGstate* state = nvg__getState(ctx); + state->stroke = paint; + nvgTransformMultiply(state->stroke.xform, state->xform); +} + +void nvgFillColor(struct NVGcontext* ctx, struct NVGcolor color) +{ + struct NVGstate* state = nvg__getState(ctx); + nvg__setPaintColor(&state->fill, color); +} + +void nvgFillPaint(struct NVGcontext* ctx, struct NVGpaint paint) +{ + struct NVGstate* state = nvg__getState(ctx); + state->fill = paint; + nvgTransformMultiply(state->fill.xform, state->xform); +} + +int nvgCreateImage(struct NVGcontext* ctx, const char* filename) +{ + int w, h, n, image; + unsigned char* img = stbi_load(filename, &w, &h, &n, 4); + if (img == NULL) { +// printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); + return 0; + } + image = nvgCreateImageRGBA(ctx, w, h, img); + stbi_image_free(img); + return image; +} + +int nvgCreateImageMem(struct NVGcontext* ctx, unsigned char* data, int ndata) +{ + int w, h, n, image; + unsigned char* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); + if (img == NULL) { +// printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); + return 0; + } + image = nvgCreateImageRGBA(ctx, w, h, img); + stbi_image_free(img); + return image; +} + +int nvgCreateImageRGBA(struct NVGcontext* ctx, int w, int h, const unsigned char* data) +{ + return ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_RGBA, w, h, data); +} + +void nvgUpdateImage(struct NVGcontext* ctx, int image, const unsigned char* data) +{ + int w, h; + ctx->params.renderGetTextureSize(ctx->params.userPtr, image, &w, &h); + ctx->params.renderUpdateTexture(ctx->params.userPtr, image, 0,0, w,h, data); +} + +void nvgImageSize(struct NVGcontext* ctx, int image, int* w, int* h) +{ + ctx->params.renderGetTextureSize(ctx->params.userPtr, image, w, h); +} + +void nvgDeleteImage(struct NVGcontext* ctx, int image) +{ + ctx->params.renderDeleteTexture(ctx->params.userPtr, image); +} + +struct NVGpaint nvgLinearGradient(struct NVGcontext* ctx, + float sx, float sy, float ex, float ey, + struct NVGcolor icol, struct NVGcolor ocol) +{ + struct NVGpaint p; + float dx, dy, d; + const float large = 1e5; + NVG_NOTUSED(ctx); + memset(&p, 0, sizeof(p)); + + // Calculate transform aligned to the line + dx = ex - sx; + dy = ey - sy; + d = sqrtf(dx*dx + dy*dy); + if (d > 0.0001f) { + dx /= d; + dy /= d; + } else { + dx = 0; + dy = 1; + } + + p.xform[0] = dy; p.xform[1] = -dx; + p.xform[2] = dx; p.xform[3] = dy; + p.xform[4] = sx - dx*large; p.xform[5] = sy - dy*large; + + p.extent[0] = large; + p.extent[1] = large + d*0.5f; + + p.radius = 0.0f; + + p.feather = nvg__maxf(1.0f, d); + + p.innerColor = icol; + p.outerColor = ocol; + + return p; +} + +struct NVGpaint nvgRadialGradient(struct NVGcontext* ctx, + float cx, float cy, float inr, float outr, + struct NVGcolor icol, struct NVGcolor ocol) +{ + struct NVGpaint p; + float r = (inr+outr)*0.5f; + float f = (outr-inr); + NVG_NOTUSED(ctx); + memset(&p, 0, sizeof(p)); + + nvgTransformIdentity(p.xform); + p.xform[4] = cx; + p.xform[5] = cy; + + p.extent[0] = r; + p.extent[1] = r; + + p.radius = r; + + p.feather = nvg__maxf(1.0f, f); + + p.innerColor = icol; + p.outerColor = ocol; + + return p; +} + +struct NVGpaint nvgBoxGradient(struct NVGcontext* ctx, + float x, float y, float w, float h, float r, float f, + struct NVGcolor icol, struct NVGcolor ocol) +{ + struct NVGpaint p; + NVG_NOTUSED(ctx); + memset(&p, 0, sizeof(p)); + + nvgTransformIdentity(p.xform); + p.xform[4] = x+w*0.5f; + p.xform[5] = y+h*0.5f; + + p.extent[0] = w*0.5f; + p.extent[1] = h*0.5f; + + p.radius = r; + + p.feather = nvg__maxf(1.0f, f); + + p.innerColor = icol; + p.outerColor = ocol; + + return p; +} + + +struct NVGpaint nvgImagePattern(struct NVGcontext* ctx, + float cx, float cy, float w, float h, float angle, + int image, int repeat) +{ + struct NVGpaint p; + NVG_NOTUSED(ctx); + memset(&p, 0, sizeof(p)); + + nvgTransformRotate(p.xform, angle); + p.xform[4] = cx; + p.xform[5] = cy; + + p.extent[0] = w; + p.extent[1] = h; + + p.image = image; + p.repeat = repeat; + + return p; +} + +// Scissoring +void nvgScissor(struct NVGcontext* ctx, float x, float y, float w, float h) +{ + struct NVGstate* state = nvg__getState(ctx); + + nvgTransformIdentity(state->scissor.xform); + state->scissor.xform[4] = x+w*0.5f; + state->scissor.xform[5] = y+h*0.5f; + nvgTransformMultiply(state->scissor.xform, state->xform); + + state->scissor.extent[0] = w*0.5f; + state->scissor.extent[1] = h*0.5f; +} + +void nvgResetScissor(struct NVGcontext* ctx) +{ + struct NVGstate* state = nvg__getState(ctx); + memset(state->scissor.xform, 0, sizeof(state->scissor.xform)); + state->scissor.extent[0] = 0; + state->scissor.extent[1] = 0; +} + +static int nvg__ptEquals(float x1, float y1, float x2, float y2, float tol) +{ + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy < tol*tol; +} + +static float nvg__distPtSeg(float x, float y, float px, float py, float qx, float qy) +{ + float pqx, pqy, dx, dy, d, t; + pqx = qx-px; + pqy = qy-py; + dx = x-px; + dy = y-py; + d = pqx*pqx + pqy*pqy; + t = pqx*dx + pqy*dy; + if (d > 0) t /= d; + if (t < 0) t = 0; + else if (t > 1) t = 1; + dx = px + t*pqx - x; + dy = py + t*pqy - y; + return dx*dx + dy*dy; +} + +static void nvg__appendCommands(struct NVGcontext* ctx, float* vals, int nvals) +{ + struct NVGstate* state = nvg__getState(ctx); + int i; + + if (ctx->ncommands+nvals > ctx->ccommands) { + float* commands; + int ccommands = ctx->ncommands+nvals + ctx->ccommands/2; + commands = (float*)realloc(ctx->commands, sizeof(float)*ccommands); + if (commands == NULL) return; + ctx->commands = commands; + ctx->ccommands = ccommands; + } + + // transform commands + i = 0; + while (i < nvals) { + int cmd = (int)vals[i]; + switch (cmd) { + case NVG_MOVETO: + nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]); + i += 3; + break; + case NVG_LINETO: + nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]); + i += 3; + break; + case NVG_BEZIERTO: + nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]); + nvgTransformPoint(&vals[i+3],&vals[i+4], state->xform, vals[i+3],vals[i+4]); + nvgTransformPoint(&vals[i+5],&vals[i+6], state->xform, vals[i+5],vals[i+6]); + i += 7; + break; + case NVG_CLOSE: + i++; + break; + case NVG_WINDING: + i += 2; + break; + default: + i++; + } + } + + memcpy(&ctx->commands[ctx->ncommands], vals, nvals*sizeof(float)); + + ctx->ncommands += nvals; + + if ((int)vals[0] != NVG_CLOSE && (int)vals[0] != NVG_WINDING) { + ctx->commandx = vals[nvals-2]; + ctx->commandy = vals[nvals-1]; + } +} + + +static void nvg__clearPathCache(struct NVGcontext* ctx) +{ + ctx->cache->npoints = 0; + ctx->cache->npaths = 0; +} + +static struct NVGpath* nvg__lastPath(struct NVGcontext* ctx) +{ + if (ctx->cache->npaths > 0) + return &ctx->cache->paths[ctx->cache->npaths-1]; + return NULL; +} + +static void nvg__addPath(struct NVGcontext* ctx) +{ + struct NVGpath* path; + if (ctx->cache->npaths+1 > ctx->cache->cpaths) { + struct NVGpath* paths; + int cpaths = ctx->cache->npaths+1 + ctx->cache->cpaths/2; + paths = (struct NVGpath*)realloc(ctx->cache->paths, sizeof(struct NVGpath)*cpaths); + if (paths == NULL) return; + ctx->cache->paths = paths; + ctx->cache->cpaths = cpaths; + } + path = &ctx->cache->paths[ctx->cache->npaths]; + memset(path, 0, sizeof(*path)); + path->first = ctx->cache->npoints; + path->winding = NVG_CCW; + + ctx->cache->npaths++; +} + +static struct NVGpoint* nvg__lastPoint(struct NVGcontext* ctx) +{ + if (ctx->cache->npoints > 0) + return &ctx->cache->points[ctx->cache->npoints-1]; + return NULL; +} + +static void nvg__addPoint(struct NVGcontext* ctx, float x, float y, int flags) +{ + struct NVGpath* path = nvg__lastPath(ctx); + struct NVGpoint* pt; + if (path == NULL) return; + + if (ctx->cache->npoints > 0) { + pt = nvg__lastPoint(ctx); + if (nvg__ptEquals(pt->x,pt->y, x,y, ctx->distTol)) { + pt->flags |= flags; + return; + } + } + + if (ctx->cache->npoints+1 > ctx->cache->cpoints) { + struct NVGpoint* points; + int cpoints = ctx->cache->npoints+1 + ctx->cache->cpoints/2; + points = (struct NVGpoint*)realloc(ctx->cache->points, sizeof(struct NVGpoint)*cpoints); + if (points == NULL) return; + ctx->cache->points = points; + ctx->cache->cpoints = cpoints; + } + + pt = &ctx->cache->points[ctx->cache->npoints]; + memset(pt, 0, sizeof(*pt)); + pt->x = x; + pt->y = y; + pt->flags = flags; + + ctx->cache->npoints++; + path->count++; +} + +static void nvg__closePath(struct NVGcontext* ctx) +{ + struct NVGpath* path = nvg__lastPath(ctx); + if (path == NULL) return; + path->closed = 1; +} + +static void nvg__pathWinding(struct NVGcontext* ctx, int winding) +{ + struct NVGpath* path = nvg__lastPath(ctx); + if (path == NULL) return; + path->winding = winding; +} + +static float nvg__getAverageScale(float *t) +{ + float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); + float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); + return (sx + sy) * 0.5f; +} + +static struct NVGvertex* nvg__allocTempVerts(struct NVGcontext* ctx, int nverts) +{ + if (nverts > ctx->cache->cverts) { + struct NVGvertex* verts; + int cverts = (nverts + 0xff) & ~0xff; // Round up to prevent allocations when things change just slightly. + verts = (struct NVGvertex*)realloc(ctx->cache->verts, sizeof(struct NVGvertex)*cverts); + if (verts == NULL) return NULL; + ctx->cache->verts = verts; + ctx->cache->cverts = cverts; + } + + return ctx->cache->verts; +} + +static float nvg__triarea2(float ax, float ay, float bx, float by, float cx, float cy) +{ + float abx = bx - ax; + float aby = by - ay; + float acx = cx - ax; + float acy = cy - ay; + return acx*aby - abx*acy; +} + +static float nvg__polyArea(struct NVGpoint* pts, int npts) +{ + int i; + float area = 0; + for (i = 2; i < npts; i++) { + struct NVGpoint* a = &pts[0]; + struct NVGpoint* b = &pts[i-1]; + struct NVGpoint* c = &pts[i]; + area += nvg__triarea2(a->x,a->y, b->x,b->y, c->x,c->y); + } + return area * 0.5f; +} + +static void nvg__polyReverse(struct NVGpoint* pts, int npts) +{ + struct NVGpoint tmp; + int i = 0, j = npts-1; + while (i < j) { + tmp = pts[i]; + pts[i] = pts[j]; + pts[j] = tmp; + i++; + j--; + } +} + + +static void nvg__vset(struct NVGvertex* vtx, float x, float y, float u, float v) +{ + vtx->x = x; + vtx->y = y; + vtx->u = u; + vtx->v = v; +} + +static void nvg__tesselateBezier(struct NVGcontext* ctx, + float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4, + int level, int type) +{ + float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; + float dx,dy,d2,d3; + + if (level > 10) return; + + x12 = (x1+x2)*0.5f; + y12 = (y1+y2)*0.5f; + x23 = (x2+x3)*0.5f; + y23 = (y2+y3)*0.5f; + x34 = (x3+x4)*0.5f; + y34 = (y3+y4)*0.5f; + x123 = (x12+x23)*0.5f; + y123 = (y12+y23)*0.5f; + + dx = x3 - x1; + dy = y3 - y1; + d2 = nvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); + d3 = nvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); + + if ((d2 + d3)*(d2 + d3) < ctx->tessTol * (dx*dx + dy*dy)) { + nvg__addPoint(ctx, x4, y4, type); + return; + } + +/* if (nvg__absf(x1+x3-x2-x2) + nvg__absf(y1+y3-y2-y2) + nvg__absf(x2+x4-x3-x3) + nvg__absf(y2+y4-y3-y3) < ctx->tessTol) { + nvg__addPoint(ctx, x4, y4, type); + return; + }*/ + + x234 = (x23+x34)*0.5f; + y234 = (y23+y34)*0.5f; + x1234 = (x123+x234)*0.5f; + y1234 = (y123+y234)*0.5f; + + nvg__tesselateBezier(ctx, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); + nvg__tesselateBezier(ctx, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); +} + +static void nvg__flattenPaths(struct NVGcontext* ctx) +{ + struct NVGpathCache* cache = ctx->cache; +// struct NVGstate* state = nvg__getState(ctx); + struct NVGpoint* last; + struct NVGpoint* p0; + struct NVGpoint* p1; + struct NVGpoint* pts; + struct NVGpath* path; + int i, j; + float* cp1; + float* cp2; + float* p; + float area; + + if (cache->npaths > 0) + return; + + // Flatten + i = 0; + while (i < ctx->ncommands) { + int cmd = (int)ctx->commands[i]; + switch (cmd) { + case NVG_MOVETO: + nvg__addPath(ctx); + p = &ctx->commands[i+1]; + nvg__addPoint(ctx, p[0], p[1], NVG_PT_CORNER); + i += 3; + break; + case NVG_LINETO: + p = &ctx->commands[i+1]; + nvg__addPoint(ctx, p[0], p[1], NVG_PT_CORNER); + i += 3; + break; + case NVG_BEZIERTO: + last = nvg__lastPoint(ctx); + if (last != NULL) { + cp1 = &ctx->commands[i+1]; + cp2 = &ctx->commands[i+3]; + p = &ctx->commands[i+5]; + nvg__tesselateBezier(ctx, last->x,last->y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, NVG_PT_CORNER); + } + i += 7; + break; + case NVG_CLOSE: + nvg__closePath(ctx); + i++; + break; + case NVG_WINDING: + nvg__pathWinding(ctx, (int)ctx->commands[i+1]); + i += 2; + break; + default: + i++; + } + } + + cache->bounds[0] = cache->bounds[1] = 1e6f; + cache->bounds[2] = cache->bounds[3] = -1e6f; + + // Calculate the direction and length of line segments. + for (j = 0; j < cache->npaths; j++) { + path = &cache->paths[j]; + pts = &cache->points[path->first]; + + // If the first and last points are the same, remove the last, mark as closed path. + p0 = &pts[path->count-1]; + p1 = &pts[0]; + if (nvg__ptEquals(p0->x,p0->y, p1->x,p1->y, ctx->distTol)) { + path->count--; + p0 = &pts[path->count-1]; + path->closed = 1; + } + + // Enforce winding. + if (path->count > 2) { + area = nvg__polyArea(pts, path->count); + if (path->winding == NVG_CCW && area < 0.0f) + nvg__polyReverse(pts, path->count); + if (path->winding == NVG_CW && area > 0.0f) + nvg__polyReverse(pts, path->count); + } + + for(i = 0; i < path->count; ++i) { + // Calculate segment direction and length + p0->dx = p1->x - p0->x; + p0->dy = p1->y - p0->y; + p0->len = nvg__normalize(&p0->dx, &p0->dy); + // Update bounds + cache->bounds[0] = nvg__minf(cache->bounds[0], p0->x); + cache->bounds[1] = nvg__minf(cache->bounds[1], p0->y); + cache->bounds[2] = nvg__maxf(cache->bounds[2], p0->x); + cache->bounds[3] = nvg__maxf(cache->bounds[3], p0->y); + // Advance + p0 = p1++; + } + } +} + +static int nvg__curveDivs(float r, float arc, float tol) +{ + float da = acosf(r / (r + tol)) * 2.0f; + return nvg__maxi(2, (int)ceilf(arc / da)); +} + +static void nvg__chooseBevel(int bevel, struct NVGpoint* p0, struct NVGpoint* p1, float w, + float* x0, float* y0, float* x1, float* y1) +{ + if (bevel) { + *x0 = p1->x + p0->dy * w; + *y0 = p1->y - p0->dx * w; + *x1 = p1->x + p1->dy * w; + *y1 = p1->y - p1->dx * w; + } else { + *x0 = p1->x + p1->dmx * w; + *y0 = p1->y + p1->dmy * w; + *x1 = p1->x + p1->dmx * w; + *y1 = p1->y + p1->dmy * w; + } +} + +static struct NVGvertex* nvg__roundJoin(struct NVGvertex* dst, struct NVGpoint* p0, struct NVGpoint* p1, + float lw, float rw, float lu, float ru, int ncap, float fringe) +{ + int i, n; + float dlx0 = p0->dy; + float dly0 = -p0->dx; + float dlx1 = p1->dy; + float dly1 = -p1->dx; + NVG_NOTUSED(fringe); + + if (p1->flags & NVG_PT_LEFT) { + float lx0,ly0,lx1,ly1,a0,a1; + nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, lw, &lx0,&ly0, &lx1,&ly1); + a0 = atan2f(-dly0, -dlx0); + a1 = atan2f(-dly1, -dlx1); + if (a1 > a0) a1 -= NVG_PI*2; + + nvg__vset(dst, lx0, ly0, lu,1); dst++; + nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; + + n = nvg__clampi((int)ceilf(((a0 - a1) / NVG_PI) * ncap), 2, ncap); + for (i = 0; i < n; i++) { + float u = i/(float)(n-1); + float a = a0 + u*(a1-a0); + float rx = p1->x + cosf(a) * rw; + float ry = p1->y + sinf(a) * rw; + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + nvg__vset(dst, rx, ry, ru,1); dst++; + } + + nvg__vset(dst, lx1, ly1, lu,1); dst++; + nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; + + } else { + float rx0,ry0,rx1,ry1,a0,a1; + nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1); + a0 = atan2f(dly0, dlx0); + a1 = atan2f(dly1, dlx1); + if (a1 < a0) a1 += NVG_PI*2; + + nvg__vset(dst, p1->x + dlx0*rw, p1->y + dly0*rw, lu,1); dst++; + nvg__vset(dst, rx0, ry0, ru,1); dst++; + + n = nvg__clampi((int)ceilf(((a1 - a0) / NVG_PI) * ncap), 2, ncap); + for (i = 0; i < n; i++) { + float u = i/(float)(n-1); + float a = a0 + u*(a1-a0); + float lx = p1->x + cosf(a) * lw; + float ly = p1->y + sinf(a) * lw; + nvg__vset(dst, lx, ly, lu,1); dst++; + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + } + + nvg__vset(dst, p1->x + dlx1*rw, p1->y + dly1*rw, lu,1); dst++; + nvg__vset(dst, rx1, ry1, ru,1); dst++; + + } + return dst; +} + +static struct NVGvertex* nvg__bevelJoin(struct NVGvertex* dst, struct NVGpoint* p0, struct NVGpoint* p1, + float lw, float rw, float lu, float ru, float fringe) +{ + float rx0,ry0,rx1,ry1; + float lx0,ly0,lx1,ly1; + float mx,my,len,mu; + float dlx0 = p0->dy; + float dly0 = -p0->dx; + float dlx1 = p1->dy; + float dly1 = -p1->dx; + NVG_NOTUSED(fringe); + + if (p1->flags & NVG_PT_LEFT) { + nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, lw, &lx0,&ly0, &lx1,&ly1); + + nvg__vset(dst, lx0, ly0, lu,1); dst++; + nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; + + if (p1->flags & NVG_PT_BEVEL) { + // TODO: this needs more work. + mx = (dlx0 + dlx1) * 0.5f; + my = (dly0 + dly1) * 0.5f; + len = sqrtf(mx*mx + my*my); + mu = ru + len*(lu-ru)*0.5f; + + nvg__vset(dst, lx0, ly0, lu,1); dst++; + nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; + + nvg__vset(dst, lx1, ly1, lu,1); dst++; + nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; + } else { + rx0 = p1->x - p1->dmx * rw; + ry0 = p1->y - p1->dmy * rw; + + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; + + nvg__vset(dst, rx0, ry0, ru,1); dst++; + nvg__vset(dst, rx0, ry0, ru,1); dst++; + + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; + } + + nvg__vset(dst, lx1, ly1, lu,1); dst++; + nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; + + } else { + nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1); + + nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++; + nvg__vset(dst, rx0, ry0, ru,1); dst++; + + if (p1->flags & NVG_PT_BEVEL) { + // TODO: this needs more work. + mx = (dlx0 + dlx1) * 0.5f; + my = (dly0 + dly1) * 0.5f; + len = sqrtf(mx*mx + my*my); + mu = lu + len*(ru-lu)*0.5f; + + nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++; + nvg__vset(dst, rx0, ry0, ru,1); dst++; + + nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++; + nvg__vset(dst, rx1, ry1, ru,1); dst++; + } else { + lx0 = p1->x + p1->dmx * lw; + ly0 = p1->y + p1->dmy * lw; + + nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++; + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + + nvg__vset(dst, lx0, ly0, lu,1); dst++; + nvg__vset(dst, lx0, ly0, lu,1); dst++; + + nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++; + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + } + + nvg__vset(dst, p1->x + dlx1*rw, p1->y + dly1*rw, lu,1); dst++; + nvg__vset(dst, rx1, ry1, ru,1); dst++; + } + + return dst; +} + +static int nvg__expandStrokeAndFill(struct NVGcontext* ctx, int feats, float w, int lineCap, int lineJoin, float miterLimit) +{ + struct NVGpathCache* cache = ctx->cache; + struct NVGpath* path; + struct NVGpoint* pts; + struct NVGvertex* verts; + struct NVGvertex* dst; + struct NVGpoint* p0; + struct NVGpoint* p1; + int cverts, convex, i, j, s, e; + float wo = 0, iw = 0, aa = ctx->fringeWidth; + int ncap = nvg__curveDivs(w, NVG_PI, ctx->tessTol / 4.0f); + int nleft = 0; + + if (w > 0.0f) iw = 1.0f / w; + + // Calculate which joins needs extra vertices to append, and gather vertex count. + for (i = 0; i < cache->npaths; i++) { + path = &cache->paths[i]; + pts = &cache->points[path->first]; + path->nbevel = 0; + nleft = 0; + + p0 = &pts[path->count-1]; + p1 = &pts[0]; + for (j = 0; j < path->count; j++) { + float dlx0, dly0, dlx1, dly1, dmr2, cross, limit; + dlx0 = p0->dy; + dly0 = -p0->dx; + dlx1 = p1->dy; + dly1 = -p1->dx; + // Calculate extrusions + p1->dmx = (dlx0 + dlx1) * 0.5f; + p1->dmy = (dly0 + dly1) * 0.5f; + dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; + if (dmr2 > 0.000001f) { + float scale = 1.0f / dmr2; + if (scale > 600.0f) { + scale = 600.0f; + } + p1->dmx *= scale; + p1->dmy *= scale; + } + + // Clear flags, but keep the corner. + p1->flags = (p1->flags & NVG_PT_CORNER) ? NVG_PT_CORNER : 0; + + // Keep track of left turns. + cross = p1->dx * p0->dy - p0->dx * p1->dy; + if (cross > 0.0f) { + nleft++; + p1->flags |= NVG_PT_LEFT; + } + + // Calculate if we should use bevel or miter for inner join. + limit = nvg__maxf(1.01f, nvg__minf(p0->len, p1->len) * iw); + if ((dmr2 * limit*limit) < 1.0f) + p1->flags |= NVG_PR_INNERBEVEL; + + // Check to see if the corner needs to be beveled. + if (p1->flags & NVG_PT_CORNER) { + if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NVG_BEVEL || lineJoin == NVG_ROUND) { + p1->flags |= NVG_PT_BEVEL; + } + } + + if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) + path->nbevel++; + + p0 = p1++; + } + + path->convex = (nleft == path->count) ? 1 : 0; + } + + // Calculate max vertex usage. + cverts = 0; + for (i = 0; i < cache->npaths; i++) { + path = &cache->paths[i]; + if (feats & NVG_FILL) + cverts += path->count + path->nbevel + 1; + if (feats & NVG_STROKE) { + int loop = ((feats & NVG_CAPS) && path->closed == 0) ? 0 : 1; + if (lineCap == NVG_ROUND) + cverts += (path->count + path->nbevel*(ncap+2) + 1) * 2; // plus one for loop + else + cverts += (path->count + path->nbevel*5 + 1) * 2; // plus one for loop + if (loop == 0) { + // space for caps + if (lineCap == NVG_ROUND) { + cverts += (ncap*2 + 2)*2; + } else { + cverts += (3+3)*2; + } + } + } + } + + verts = nvg__allocTempVerts(ctx, cverts); + if (verts == NULL) return 0; + + if ((feats & NVG_FILL) && cache->npaths == 1 && cache->paths[0].convex) + convex = 1; + else + convex = 0; + + for (i = 0; i < cache->npaths; i++) { + path = &cache->paths[i]; + pts = &cache->points[path->first]; + + // Calculate shape vertices. + if (feats & NVG_FILL) { + wo = 0.5f*aa; + dst = verts; + path->fill = dst; + + if (w == 0.0f) { + for (j = 0; j < path->count; ++j) { + nvg__vset(dst, pts[j].x, pts[j].y, 0.5f,1); + dst++; + } + } else { + // Looping + p0 = &pts[path->count-1]; + p1 = &pts[0]; + for (j = 0; j < path->count; ++j) { + if (p1->flags & NVG_PT_BEVEL) { + float dlx0 = p0->dy; + float dly0 = -p0->dx; + float dlx1 = p1->dy; + float dly1 = -p1->dx; + if (p1->flags & NVG_PT_LEFT) { + float lx = p1->x + p1->dmx * wo; + float ly = p1->y + p1->dmy * wo; + nvg__vset(dst, lx, ly, 0.5f,1); dst++; + } else { + float lx0 = p1->x + dlx0 * wo; + float ly0 = p1->y + dly0 * wo; + float lx1 = p1->x + dlx1 * wo; + float ly1 = p1->y + dly1 * wo; + nvg__vset(dst, lx0, ly0, 0.5f,1); dst++; + nvg__vset(dst, lx1, ly1, 0.5f,1); dst++; + } + } else { + nvg__vset(dst, p1->x + (p1->dmx * wo), p1->y + (p1->dmy * wo), 0.5f,1); dst++; + } + p0 = p1++; + } + } + + path->nfill = (int)(dst - verts); + verts = dst; + } else { + wo = 0.0f; + path->fill = 0; + path->nfill = 0; + } + + // Calculate fringe or stroke + if (feats & NVG_STROKE) { + float lw = w + wo, rw = w - wo; + float lu = 0, ru = 1; + int loop = ((feats & NVG_CAPS) && path->closed == 0) ? 0 : 1; + dst = verts; + path->stroke = dst; + + // Create only half a fringe for convex shapes so that + // the shape can be rendered without stenciling. + if (convex) { + lw = wo; // This should generate the same vertex as fill inset above. + lu = 0.5f; // Set outline fade at middle. + } + + if (loop) { + // Looping + p0 = &pts[path->count-1]; + p1 = &pts[0]; + s = 0; + e = path->count; + } else { + // Add cap + p0 = &pts[0]; + p1 = &pts[1]; + s = 1; + e = path->count-1; + } + + if (loop == 0) { + // Add cap + float dx, dy, dlx, dly, px, py; + dx = p1->x - p0->x; + dy = p1->y - p0->y; + nvg__normalize(&dx, &dy); + dlx = dy; + dly = -dx; + if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE) { + if (lineCap == NVG_BUTT) { + px = p0->x; + py = p0->y; + } else /*if (lineCap == NVG_SQUARE)*/ { + px = p0->x - dx*w; + py = p0->y - dy*w; + } + nvg__vset(dst, px + dlx*lw - dx*aa, py + dly*lw - dy*aa, lu,0); dst++; + nvg__vset(dst, px - dlx*rw - dx*aa, py - dly*rw - dy*aa, ru,0); dst++; + nvg__vset(dst, px + dlx*lw, py + dly * lw, lu,1); dst++; + nvg__vset(dst, px - dlx*rw, py - dly * rw, ru,1); dst++; + } else if (lineCap == NVG_ROUND) { + px = p0->x; + py = p0->y; + for (j = 0; j < ncap; j++) { + float a = j/(float)(ncap-1)*NVG_PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + nvg__vset(dst, px - dlx*ax - dx*ay, py - dly*ax - dy*ay, lu,1); dst++; + nvg__vset(dst, px, py, 0.5f,1); dst++; + } + nvg__vset(dst, px + dlx*lw, py + dly * lw, lu,1); dst++; + nvg__vset(dst, px - dlx*rw, py - dly * rw, ru,1); dst++; + } + } + + for (j = s; j < e; ++j) { + if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) { + if (lineJoin == NVG_ROUND) { + dst = nvg__roundJoin(dst, p0, p1, lw, rw, lu, ru, ncap, ctx->fringeWidth); + } else { + dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx->fringeWidth); + } + } else { + nvg__vset(dst, p1->x + (p1->dmx * lw), p1->y + (p1->dmy * lw), lu,1); dst++; + nvg__vset(dst, p1->x - (p1->dmx * rw), p1->y - (p1->dmy * rw), ru,1); dst++; + } + p0 = p1++; + } + + if (loop) { + // Loop it + nvg__vset(dst, verts[0].x, verts[0].y, lu,1); dst++; + nvg__vset(dst, verts[1].x, verts[1].y, ru,1); dst++; + } else { + // Add cap + float dx, dy, dlx, dly, px, py; + dx = p1->x - p0->x; + dy = p1->y - p0->y; + nvg__normalize(&dx, &dy); + dlx = dy; + dly = -dx; + if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE) { + if (lineCap == NVG_BUTT) { + px = p1->x; + py = p1->y; + } else /*if (lineCap == NVG_SQUARE)*/ { + px = p1->x + dx*w; + py = p1->y + dy*w; + } + nvg__vset(dst, px + dlx*lw, py + dly * lw, lu,1); dst++; + nvg__vset(dst, px - dlx*rw, py - dly * rw, ru,1); dst++; + nvg__vset(dst, px + dlx*lw + dx*aa, py + dly*lw + dy*aa, lu,0); dst++; + nvg__vset(dst, px - dlx*rw + dx*aa, py - dly*rw + dy*aa, ru,0); dst++; + } else if (lineCap == NVG_ROUND) { + px = p1->x; + py = p1->y; + nvg__vset(dst, px + dlx*lw, py + dly * lw, lu,1); dst++; + nvg__vset(dst, px - dlx*rw, py - dly * rw, ru,1); dst++; + for (j = 0; j < ncap; j++) { + float a = j/(float)(ncap-1)*NVG_PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + nvg__vset(dst, px, py, 0.5f,1); dst++; + nvg__vset(dst, px - dlx*ax + dx*ay, py - dly*ax + dy*ay, lu,1); dst++; + } + } + } + + path->nstroke = (int)(dst - verts); + + verts = dst; + } else { + path->stroke = 0; + path->nstroke = 0; + } + } + + return 1; +} + + +// Draw +void nvgBeginPath(struct NVGcontext* ctx) +{ + ctx->ncommands = 0; + nvg__clearPathCache(ctx); +} + +void nvgMoveTo(struct NVGcontext* ctx, float x, float y) +{ + float vals[] = { NVG_MOVETO, x, y }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgLineTo(struct NVGcontext* ctx, float x, float y) +{ + float vals[] = { NVG_LINETO, x, y }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgBezierTo(struct NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y) +{ + float vals[] = { NVG_BEZIERTO, c1x, c1y, c2x, c2y, x, y }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgArcTo(struct NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius) +{ + float x0 = ctx->commandx; + float y0 = ctx->commandy; + float dx0,dy0, dx1,dy1, a, d, cx,cy, a0,a1; + int dir; + + if (ctx->ncommands == 0) { + return; + } + + // Handle degenerate cases. + if (nvg__ptEquals(x0,y0, x1,y1, ctx->distTol) || + nvg__ptEquals(x1,y1, x2,y2, ctx->distTol) || + nvg__distPtSeg(x1,y1, x0,y0, x2,y2) < ctx->distTol*ctx->distTol || + radius < ctx->distTol) { + nvgLineTo(ctx, x1,y1); + return; + } + + // Calculate tangential circle to lines (x0,y0)-(x1,y1) and (x1,y1)-(x2,y2). + dx0 = x0-x1; + dy0 = y0-y1; + dx1 = x2-x1; + dy1 = y2-y1; + nvg__normalize(&dx0,&dy0); + nvg__normalize(&dx1,&dy1); + a = nvg__acosf(dx0*dx1 + dy0*dy1); + d = radius / nvg__tanf(a/2.0f); + +// printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d); + + if (d > 10000.0f) { + nvgLineTo(ctx, x1,y1); + return; + } + + if (nvg__cross(dx0,dy0, dx1,dy1) > 0.0f) { + cx = x1 + dx0*d + dy0*radius; + cy = y1 + dy0*d + -dx0*radius; + a0 = nvg__atan2f(dx0, -dy0); + a1 = nvg__atan2f(-dx1, dy1); + dir = NVG_CW; +// printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); + } else { + cx = x1 + dx0*d + -dy0*radius; + cy = y1 + dy0*d + dx0*radius; + a0 = nvg__atan2f(-dx0, dy0); + a1 = nvg__atan2f(dx1, -dy1); + dir = NVG_CCW; +// printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); + } + + nvgArc(ctx, cx, cy, radius, a0, a1, dir); +} + +void nvgClosePath(struct NVGcontext* ctx) +{ + float vals[] = { NVG_CLOSE }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgPathWinding(struct NVGcontext* ctx, int dir) +{ + float vals[] = { NVG_WINDING, (float)dir }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgArc(struct NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir) +{ + float a = 0, da = 0, hda = 0, kappa = 0; + float dx = 0, dy = 0, x = 0, y = 0, tanx = 0, tany = 0; + float px = 0, py = 0, ptanx = 0, ptany = 0; + float vals[3 + 5*7 + 100]; + int i, ndivs, nvals; + int move = ctx->ncommands > 0 ? NVG_LINETO : NVG_MOVETO; + + // Clamp angles + da = a1 - a0; + if (dir == NVG_CW) { + if (nvg__absf(da) >= NVG_PI*2) { + da = NVG_PI*2; + } else { + while (da < 0.0f) da += NVG_PI*2; + } + } else { + if (nvg__absf(da) >= NVG_PI*2) { + da = -NVG_PI*2; + } else { + while (da > 0.0f) da -= NVG_PI*2; + } + } + + // Split arc into max 90 degree segments. + ndivs = nvg__maxi(1, nvg__mini((int)(nvg__absf(da) / (NVG_PI*0.5f) + 0.5f), 5)); + hda = (da / (float)ndivs) / 2.0f; + kappa = nvg__absf(4.0f / 3.0f * (1.0f - nvg__cosf(hda)) / nvg__sinf(hda)); + + if (dir == NVG_CCW) + kappa = -kappa; + + nvals = 0; + for (i = 0; i <= ndivs; i++) { + a = a0 + da * (i/(float)ndivs); + dx = nvg__cosf(a); + dy = nvg__sinf(a); + x = cx + dx*r; + y = cy + dy*r; + tanx = -dy*r*kappa; + tany = dx*r*kappa; + + if (i == 0) { + vals[nvals++] = (float)move; + vals[nvals++] = x; + vals[nvals++] = y; + } else { + vals[nvals++] = NVG_BEZIERTO; + vals[nvals++] = px+ptanx; + vals[nvals++] = py+ptany; + vals[nvals++] = x-tanx; + vals[nvals++] = y-tany; + vals[nvals++] = x; + vals[nvals++] = y; + } + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + nvg__appendCommands(ctx, vals, nvals); +} + +void nvgRect(struct NVGcontext* ctx, float x, float y, float w, float h) +{ + float vals[] = { + NVG_MOVETO, x,y, + NVG_LINETO, x+w,y, + NVG_LINETO, x+w,y+h, + NVG_LINETO, x,y+h, + NVG_CLOSE + }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgRoundedRect(struct NVGcontext* ctx, float x, float y, float w, float h, float r) +{ + if (r < 0.1f) { + nvgRect(ctx, x,y,w,h); + return; + } + else { + float vals[] = { + NVG_MOVETO, x+r, y, + NVG_LINETO, x+w-r, y, + NVG_BEZIERTO, x+w-r*(1-NVG_KAPPA90), y, x+w, y+r*(1-NVG_KAPPA90), x+w, y+r, + NVG_LINETO, x+w, y+h-r, + NVG_BEZIERTO, x+w, y+h-r*(1-NVG_KAPPA90), x+w-r*(1-NVG_KAPPA90), y+h, x+w-r, y+h, + NVG_LINETO, x+r, y+h, + NVG_BEZIERTO, x+r*(1-NVG_KAPPA90), y+h, x, y+h-r*(1-NVG_KAPPA90), x, y+h-r, + NVG_LINETO, x, y+r, + NVG_BEZIERTO, x, y+r*(1-NVG_KAPPA90), x+r*(1-NVG_KAPPA90), y, x+r, y, + NVG_CLOSE + }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); + } +} + +void nvgEllipse(struct NVGcontext* ctx, float cx, float cy, float rx, float ry) +{ + float vals[] = { + NVG_MOVETO, cx+rx, cy, + NVG_BEZIERTO, cx+rx, cy+ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy+ry, cx, cy+ry, + NVG_BEZIERTO, cx-rx*NVG_KAPPA90, cy+ry, cx-rx, cy+ry*NVG_KAPPA90, cx-rx, cy, + NVG_BEZIERTO, cx-rx, cy-ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy-ry, cx, cy-ry, + NVG_BEZIERTO, cx+rx*NVG_KAPPA90, cy-ry, cx+rx, cy-ry*NVG_KAPPA90, cx+rx, cy, + NVG_CLOSE + }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgCircle(struct NVGcontext* ctx, float cx, float cy, float r) +{ + nvgEllipse(ctx, cx,cy, r,r); +} + +void nvgDebugDumpPathCache(struct NVGcontext* ctx) +{ + const struct NVGpath* path; + int i, j; + + printf("Dumping %d cached paths\n", ctx->cache->npaths); + for (i = 0; i < ctx->cache->npaths; i++) { + path = &ctx->cache->paths[i]; + printf(" - Path %d\n", i); + if (path->nfill) { + printf(" - fill: %d\n", path->nfill); + for (j = 0; j < path->nfill; j++) + printf("%f\t%f\n", path->fill[j].x, path->fill[j].y); + } + if (path->nstroke) { + printf(" - stroke: %d\n", path->nstroke); + for (j = 0; j < path->nstroke; j++) + printf("%f\t%f\n", path->stroke[j].x, path->stroke[j].y); + } + } +} + +void nvgFill(struct NVGcontext* ctx) +{ + struct NVGstate* state = nvg__getState(ctx); + const struct NVGpath* path; + int i; + + nvg__flattenPaths(ctx); + if (ctx->params.edgeAntiAlias) + nvg__expandStrokeAndFill(ctx, NVG_FILL|NVG_STROKE, ctx->fringeWidth, NVG_BUTT, NVG_MITER, 3.6f); + else + nvg__expandStrokeAndFill(ctx, NVG_FILL, 0.0f, NVG_BUTT, NVG_MITER, 1.2f); + + ctx->params.renderFill(ctx->params.userPtr, &state->fill, &state->scissor, ctx->fringeWidth, + ctx->cache->bounds, ctx->cache->paths, ctx->cache->npaths); + + // Count triangles + for (i = 0; i < ctx->cache->npaths; i++) { + path = &ctx->cache->paths[i]; + ctx->fillTriCount += path->nfill-2; + ctx->fillTriCount += path->nstroke-2; + ctx->drawCallCount += 2; + } +} + +void nvgStroke(struct NVGcontext* ctx) +{ + struct NVGstate* state = nvg__getState(ctx); + float scale = nvg__getAverageScale(state->xform); + float strokeWidth = nvg__clampf(state->strokeWidth * scale, 0.0f, 20.0f); + struct NVGpaint strokePaint = state->stroke; + const struct NVGpath* path; + int i; + + if (strokeWidth < ctx->fringeWidth) { + // If the stroke width is less than pixel size, use alpha to emulate coverate. + // Since coverage is area, scale by alpha*alpha. + float alpha = nvg__clampf(strokeWidth / ctx->fringeWidth, 0.0f, 1.0f); + strokePaint.innerColor.a *= alpha*alpha; + strokePaint.outerColor.a *= alpha*alpha; + strokeWidth = ctx->fringeWidth; + } + + nvg__flattenPaths(ctx); + if (ctx->params.edgeAntiAlias) + nvg__expandStrokeAndFill(ctx, NVG_STROKE|NVG_CAPS, strokeWidth*0.5f + ctx->fringeWidth*0.5f, state->lineCap, state->lineJoin, state->miterLimit); + else + nvg__expandStrokeAndFill(ctx, NVG_STROKE|NVG_CAPS, strokeWidth*0.5f, state->lineCap, state->lineJoin, state->miterLimit); + + ctx->params.renderStroke(ctx->params.userPtr, &strokePaint, &state->scissor, ctx->fringeWidth, + strokeWidth, ctx->cache->paths, ctx->cache->npaths); + + // Count triangles + for (i = 0; i < ctx->cache->npaths; i++) { + path = &ctx->cache->paths[i]; + ctx->strokeTriCount += path->nstroke-2; + ctx->drawCallCount++; + } +} + +// Add fonts +int nvgCreateFont(struct NVGcontext* ctx, const char* name, const char* path) +{ + return fonsAddFont(ctx->fs, name, path); +} + +int nvgCreateFontMem(struct NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData) +{ + return fonsAddFontMem(ctx->fs, name, data, ndata, freeData); +} + +int nvgFindFont(struct NVGcontext* ctx, const char* name) +{ + if (name == NULL) return -1; + return fonsGetFontByName(ctx->fs, name); +} + +// State setting +void nvgFontSize(struct NVGcontext* ctx, float size) +{ + struct NVGstate* state = nvg__getState(ctx); + state->fontSize = size; +} + +void nvgFontBlur(struct NVGcontext* ctx, float blur) +{ + struct NVGstate* state = nvg__getState(ctx); + state->fontBlur = blur; +} + +void nvgTextLetterSpacing(struct NVGcontext* ctx, float spacing) +{ + struct NVGstate* state = nvg__getState(ctx); + state->letterSpacing = spacing; +} + +void nvgTextLineHeight(struct NVGcontext* ctx, float lineHeight) +{ + struct NVGstate* state = nvg__getState(ctx); + state->lineHeight = lineHeight; +} + +void nvgTextAlign(struct NVGcontext* ctx, int align) +{ + struct NVGstate* state = nvg__getState(ctx); + state->textAlign = align; +} + +void nvgFontFaceId(struct NVGcontext* ctx, int font) +{ + struct NVGstate* state = nvg__getState(ctx); + state->fontId = font; +} + +void nvgFontFace(struct NVGcontext* ctx, const char* font) +{ + struct NVGstate* state = nvg__getState(ctx); + state->fontId = fonsGetFontByName(ctx->fs, font); +} + +static float nvg__quantize(float a, float d) +{ + return ((int)(a / d + 0.5f)) * d; +} + +static float nvg__getFontScale(struct NVGstate* state) +{ + return nvg__minf(nvg__quantize(nvg__getAverageScale(state->xform), 0.01f), 4.0f); +} + +float nvgText(struct NVGcontext* ctx, float x, float y, const char* string, const char* end) +{ + struct NVGstate* state = nvg__getState(ctx); + struct NVGpaint paint; + struct FONStextIter iter; + struct FONSquad q; + struct NVGvertex* verts; + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + int dirty[4]; + int cverts = 0; + int nverts = 0; + + if (end == NULL) + end = string + strlen(string); + + if (state->fontId == FONS_INVALID) return x; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + + cverts = nvg__maxi(2, (int)(end - string)) * 6; // conservative estimate. + verts = nvg__allocTempVerts(ctx, cverts); + if (verts == NULL) return x; + + fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end); + while (fonsTextIterNext(ctx->fs, &iter, &q)) { + // Trasnform corners. + float c[4*2]; + nvgTransformPoint(&c[0],&c[1], state->xform, q.x0*invscale, q.y0*invscale); + nvgTransformPoint(&c[2],&c[3], state->xform, q.x1*invscale, q.y0*invscale); + nvgTransformPoint(&c[4],&c[5], state->xform, q.x1*invscale, q.y1*invscale); + nvgTransformPoint(&c[6],&c[7], state->xform, q.x0*invscale, q.y1*invscale); + // Create triangles + if (nverts+6 <= cverts) { + nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++; + nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++; + nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); nverts++; + nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++; + nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); nverts++; + nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++; + } + } + + // TODO: add back-end bit to do this just once per frame. + if (fonsValidateTexture(ctx->fs, dirty)) { + // Update texture + if (ctx->fontImage != 0) { + int iw, ih; + const unsigned char* data = fonsGetTextureData(ctx->fs, &iw, &ih); + int x = dirty[0]; + int y = dirty[1]; + int w = dirty[2] - dirty[0]; + int h = dirty[3] - dirty[1]; + ctx->params.renderUpdateTexture(ctx->params.userPtr, ctx->fontImage, x,y, w,h, data); + } + } + + // Render triangles. + paint = state->fill; + paint.image = ctx->fontImage; + ctx->params.renderTriangles(ctx->params.userPtr, &paint, &state->scissor, verts, nverts); + + ctx->drawCallCount++; + ctx->textTriCount += nverts/3; + + return iter.x; +} + +void nvgTextBox(struct NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end) +{ + struct NVGstate* state = nvg__getState(ctx); + struct NVGtextRow rows[2]; + int nrows = 0, i; + int oldAlign = state->textAlign; + int haling = state->textAlign & (NVG_ALIGN_LEFT | NVG_ALIGN_CENTER | NVG_ALIGN_RIGHT); + int valign = state->textAlign & (NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE | NVG_ALIGN_BOTTOM | NVG_ALIGN_BASELINE); + float lineh = 0; + + if (state->fontId == FONS_INVALID) return; + + nvgTextMetrics(ctx, NULL, NULL, &lineh); + + state->textAlign = NVG_ALIGN_LEFT | valign; + + while ((nrows = nvgTextBreakLines(ctx, string, end, breakRowWidth, rows, 2))) { + for (i = 0; i < nrows; i++) { + struct NVGtextRow* row = &rows[i]; + if (haling & NVG_ALIGN_LEFT) + nvgText(ctx, x, y, row->start, row->end); + else if (haling & NVG_ALIGN_CENTER) + nvgText(ctx, x + breakRowWidth*0.5f - row->width*0.5f, y, row->start, row->end); + else if (haling & NVG_ALIGN_RIGHT) + nvgText(ctx, x + breakRowWidth - row->width, y, row->start, row->end); + y += lineh * state->lineHeight; + } + string = rows[nrows-1].next; + } + + state->textAlign = oldAlign; +} + +int nvgTextGlyphPositions(struct NVGcontext* ctx, float x, float y, const char* string, const char* end, struct NVGglyphPosition* positions, int maxPositions) +{ + struct NVGstate* state = nvg__getState(ctx); + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + struct FONStextIter iter; + struct FONSquad q; + int npos = 0; + + if (state->fontId == FONS_INVALID) return 0; + + if (end == NULL) + end = string + strlen(string); + + if (string == end) + return 0; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + + fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end); + while (fonsTextIterNext(ctx->fs, &iter, &q)) { + positions[npos].str = iter.str; + positions[npos].x = iter.x * invscale; + positions[npos].minx = q.x0 * invscale; + positions[npos].maxx = q.x1 * invscale; + npos++; + if (npos >= maxPositions) + break; + } + + return npos; +} + +enum NVGcodepointType { + NVG_SPACE, + NVG_NEWLINE, + NVG_CHAR, +}; + +int nvgTextBreakLines(struct NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, struct NVGtextRow* rows, int maxRows) +{ + struct NVGstate* state = nvg__getState(ctx); + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + struct FONStextIter iter; + struct FONSquad q; + int nrows = 0; + float rowStartX = 0; + float rowWidth = 0; + float rowMinX = 0; + float rowMaxX = 0; + const char* rowStart = NULL; + const char* rowEnd = NULL; + const char* wordStart = NULL; + float wordStartX = 0; + float wordMinX = 0; + const char* breakEnd = NULL; + float breakWidth = 0; + float breakMaxX = 0; + int type = NVG_SPACE, ptype = NVG_SPACE; + unsigned int pcodepoint = 0; + + if (maxRows == 0) return 0; + if (state->fontId == FONS_INVALID) return 0; + + if (end == NULL) + end = string + strlen(string); + + if (string == end) return 0; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + + breakRowWidth *= scale; + + fonsTextIterInit(ctx->fs, &iter, 0, 0, string, end); + while (fonsTextIterNext(ctx->fs, &iter, &q)) { + switch (iter.codepoint) { + case 9: // \t + case 11: // \v + case 12: // \f + case 32: // space + case 0x00a0: // NBSP + type = NVG_SPACE; + break; + case 10: // \n + type = pcodepoint == 13 ? NVG_SPACE : NVG_NEWLINE; + break; + case 13: // \r + type = pcodepoint == 10 ? NVG_SPACE : NVG_NEWLINE; + break; + case 0x0085: // NEL + type = NVG_NEWLINE; + break; + default: + type = NVG_CHAR; + break; + } + + if (type == NVG_NEWLINE) { + // Always handle new lines. + rows[nrows].start = rowStart != NULL ? rowStart : iter.str; + rows[nrows].end = rowEnd != NULL ? rowEnd : iter.str; + rows[nrows].width = rowWidth * invscale; + rows[nrows].minx = rowMinX * invscale; + rows[nrows].maxx = rowMaxX * invscale; + rows[nrows].next = iter.next; + nrows++; + if (nrows >= maxRows) + return nrows; + // Set null break point + breakEnd = rowStart; + breakWidth = 0.0; + breakMaxX = 0.0; + // Indicate to skip the white space at the beginning of the row. + rowStart = NULL; + rowEnd = NULL; + rowWidth = 0; + rowMinX = rowMaxX = 0; + } else { + if (rowStart == NULL) { + // Skip white space until the beginning of the line + if (type == NVG_CHAR) { + // The current char is the row so far + rowStartX = iter.x; + rowStart = iter.str; + rowEnd = iter.next; + rowWidth = iter.nextx - rowStartX; // q.x1 - rowStartX; + rowMinX = q.x0 - rowStartX; + rowMaxX = q.x1 - rowStartX; + wordStart = iter.str; + wordStartX = iter.x; + wordMinX = q.x0 - rowStartX; + // Set null break point + breakEnd = rowStart; + breakWidth = 0.0; + breakMaxX = 0.0; + } + } else { + float nextWidth = iter.nextx - rowStartX; //q.x1 - rowStartX; + + if (nextWidth > breakRowWidth) { + // The run length is too long, need to break to new line. + if (breakEnd == rowStart) { + // The current word is longer than the row length, just break it from here. + rows[nrows].start = rowStart; + rows[nrows].end = iter.str; + rows[nrows].width = rowWidth * invscale; + rows[nrows].minx = rowMinX * invscale; + rows[nrows].maxx = rowMaxX * invscale; + rows[nrows].next = iter.str; + nrows++; + if (nrows >= maxRows) + return nrows; + rowStartX = iter.x; + rowStart = iter.str; + rowEnd = iter.next; + rowWidth = iter.nextx - rowStartX; + rowMinX = q.x0 - rowStartX; + rowMaxX = q.x1 - rowStartX; + wordStart = iter.str; + wordStartX = iter.x; + wordMinX = q.x0 - rowStartX; + } else { + // Break the line from the end of the last word, and start new line from the begining of the new. + rows[nrows].start = rowStart; + rows[nrows].end = breakEnd; + rows[nrows].width = breakWidth * invscale; + rows[nrows].minx = rowMinX * invscale; + rows[nrows].maxx = breakMaxX * invscale; + rows[nrows].next = wordStart; + nrows++; + if (nrows >= maxRows) + return nrows; + rowStartX = wordStartX; + rowStart = wordStart; + rowEnd = iter.next; + rowWidth = iter.nextx - rowStartX; // q.x1 - rowStartX; + rowMinX = wordMinX; + rowMaxX = q.x1 - rowStartX; + // No change to the word start + } + // Set null break point + breakEnd = rowStart; + breakWidth = 0.0; + breakMaxX = 0.0; + } + + // track last non-white space character + if (type == NVG_CHAR) { + rowEnd = iter.next; + rowWidth = iter.nextx - rowStartX; // q.x1 - rowStartX; + rowMaxX = q.x1 - rowStartX; + } + // track last end of a word + if (ptype == NVG_CHAR && (type == NVG_SPACE || type == NVG_SPACE)) { + breakEnd = iter.str; + breakWidth = rowWidth; + breakMaxX = rowMaxX; + } + // track last beginning of a word + if ((ptype == NVG_SPACE || ptype == NVG_SPACE) && type == NVG_CHAR) { + wordStart = iter.str; + wordStartX = iter.x; + wordMinX = q.x0 - rowStartX; + } + } + } + + pcodepoint = iter.codepoint; + ptype = type; + } + + // Break the line from the end of the last word, and start new line from the begining of the new. + if (rowStart != NULL) { + rows[nrows].start = rowStart; + rows[nrows].end = rowEnd; + rows[nrows].width = rowWidth * invscale; + rows[nrows].minx = rowMinX * invscale; + rows[nrows].maxx = rowMaxX * invscale; + rows[nrows].next = end; + nrows++; + } + + return nrows; +} + +float nvgTextBounds(struct NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds) +{ + struct NVGstate* state = nvg__getState(ctx); + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + float width; + + if (state->fontId == FONS_INVALID) return 0; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + + width = fonsTextBounds(ctx->fs, x, y, string, end, bounds); + if (bounds != NULL) { + bounds[0] *= invscale; + bounds[1] *= invscale; + bounds[2] *= invscale; + bounds[3] *= invscale; + } + return width * invscale; +} + +void nvgTextBoxBounds(struct NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds) +{ + struct NVGstate* state = nvg__getState(ctx); + struct NVGtextRow rows[2]; + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + int nrows = 0, i; + int oldAlign = state->textAlign; + int haling = state->textAlign & (NVG_ALIGN_LEFT | NVG_ALIGN_CENTER | NVG_ALIGN_RIGHT); + int valign = state->textAlign & (NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE | NVG_ALIGN_BOTTOM | NVG_ALIGN_BASELINE); + float lineh = 0, rminy = 0, rmaxy = 0; + float minx, miny, maxx, maxy; + + if (state->fontId == FONS_INVALID) { + if (bounds != NULL) + bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0f; + return; + } + + nvgTextMetrics(ctx, NULL, NULL, &lineh); + + nvgTextMetrics(ctx, NULL, NULL, &lineh); + + state->textAlign = NVG_ALIGN_LEFT | valign; + + minx = maxx = x; + miny = maxy = y; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + fonsLineBounds(ctx->fs, 0, &rminy, &rmaxy); + rminy *= invscale; + rmaxy *= invscale; + + while ((nrows = nvgTextBreakLines(ctx, string, end, breakRowWidth, rows, 2))) { + for (i = 0; i < nrows; i++) { + struct NVGtextRow* row = &rows[i]; + float rminx, rmaxx, dx = 0; + // Horizontal bounds + if (haling & NVG_ALIGN_LEFT) + dx = 0; + else if (haling & NVG_ALIGN_CENTER) + dx = breakRowWidth*0.5f - row->width*0.5f; + else if (haling & NVG_ALIGN_RIGHT) + dx = breakRowWidth - row->width; + rminx = x + row->minx + dx; + rmaxx = x + row->maxx + dx; + minx = nvg__minf(minx, rminx); + maxx = nvg__maxf(maxx, rmaxx); + // Vertical bounds. + miny = nvg__minf(miny, y + rminy); + maxy = nvg__maxf(maxy, y + rmaxy); + + y += lineh * state->lineHeight; + } + string = rows[nrows-1].next; + } + + state->textAlign = oldAlign; + + if (bounds != NULL) { + bounds[0] = minx; + bounds[1] = miny; + bounds[2] = maxx; + bounds[3] = maxy; + } +} + +void nvgTextMetrics(struct NVGcontext* ctx, float* ascender, float* descender, float* lineh) +{ + struct NVGstate* state = nvg__getState(ctx); + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + + if (state->fontId == FONS_INVALID) return; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + + fonsVertMetrics(ctx->fs, ascender, descender, lineh); + if (ascender != NULL) + *ascender *= invscale; + if (descender != NULL) + *descender *= invscale; + if (lineh != NULL) + *lineh *= invscale; +} diff --git a/examples/nanovg_src/nanovg.h b/examples/nanovg_src/nanovg.h new file mode 100644 index 0000000..1328627 --- /dev/null +++ b/examples/nanovg_src/nanovg.h @@ -0,0 +1,590 @@ +// +// Copyright (c) 2013 Mikko Mononen memon@inside.org +// +// 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. +// + +#ifndef NANOVG_H +#define NANOVG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define NVG_PI 3.14159265358979323846264338327f + +struct NVGcontext; + +struct NVGcolor { + union { + float rgba[4]; + struct { + float r,g,b,a; + }; + }; +}; + +struct NVGpaint { + float xform[6]; + float extent[2]; + float radius; + float feather; + struct NVGcolor innerColor; + struct NVGcolor outerColor; + int image; + int repeat; +}; + +enum NVGwinding { + NVG_CCW = 1, // Winding for solid shapes + NVG_CW = 2, // Winding for holes +}; + +enum NVGsolidity { + NVG_SOLID = 1, // CCW + NVG_HOLE = 2, // CW +}; + +enum NVGlineCap { + NVG_BUTT, + NVG_ROUND, + NVG_SQUARE, + NVG_BEVEL, + NVG_MITER, +}; + +enum NVGpatternRepeat { + NVG_REPEATX = 0x01, // Repeat image pattern in X direction + NVG_REPEATY = 0x02, // Repeat image pattern in Y direction +}; + +enum NVGalign { + // Horizontal align + NVG_ALIGN_LEFT = 1<<0, // Default, align text horizontally to left. + NVG_ALIGN_CENTER = 1<<1, // Align text horizontally to center. + NVG_ALIGN_RIGHT = 1<<2, // Align text horizontally to right. + // Vertical align + NVG_ALIGN_TOP = 1<<3, // Align text vertically to top. + NVG_ALIGN_MIDDLE = 1<<4, // Align text vertically to middle. + NVG_ALIGN_BOTTOM = 1<<5, // Align text vertically to bottom. + NVG_ALIGN_BASELINE = 1<<6, // Default, align text vertically to baseline. +}; + +enum NVGalpha { + NVG_STRAIGHT_ALPHA, + NVG_PREMULTIPLIED_ALPHA, +}; + +struct NVGglyphPosition { + const char* str; // Position of the glyph in the input string. + float x; // The x-coordinate of the logical glyph position. + float minx, maxx; // The bounds of the glyph shape. +}; + +struct NVGtextRow { + const char* start; // Pointer to the input text where the row starts. + const char* end; // Pointer to the input text where the row ends (one past the last character). + const char* next; // Pointer to the beginning of the next row. + float width; // Logical width of the row. + float minx, maxx; // Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. +}; + + +// Begin drawing a new frame +// Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame() +// nvgBeginFrame() defines the size of the window to render to in relation currently +// set viewport (i.e. glViewport on GL backends). Device pixel ration allows to +// control the rendering on Hi-DPI devices. +// For example, GLFW returns two dimension for an opened window: window size and +// frame buffer size. In that case you would set windowWidth/Height to the window size +// devicePixelRatio to: frameBufferWidth / windowWidth. +// AlphaBlend controls if drawing the shapes to the render target should be done using straight or +// premultiplied alpha. If rendering directly to framebuffer you probably want to use NVG_STRAIGHT_ALPHA, +// if rendering to texture which should contain transparent regions NVG_PREMULTIPLIED_ALPHA is the +// right choice. +void nvgBeginFrame(struct NVGcontext* ctx, int windowWidth, int windowHeight, float devicePixelRatio, int alphaBlend); + +// Ends drawing flushing remaining render state. +void nvgEndFrame(struct NVGcontext* ctx); + +// +// Color utils +// +// Colors in NanoVG are stored as unsigned ints in ABGR format. + +// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). +struct NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b); + +// Returns a color value from red, green, blue values. Alpha will be set to 1.0f. +struct NVGcolor nvgRGBf(float r, float g, float b); + + +// Returns a color value from red, green, blue and alpha values. +struct NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +// Returns a color value from red, green, blue and alpha values. +struct NVGcolor nvgRGBAf(float r, float g, float b, float a); + + +// Linearly interpoaltes from color c0 to c1, and returns resulting color value. +struct NVGcolor nvgLerpRGBA(struct NVGcolor c0, struct NVGcolor c1, float u); + +// Sets transparency of a color value. +struct NVGcolor nvgTransRGBA(struct NVGcolor c0, unsigned char a); + +// Sets transparency of a color value. +struct NVGcolor nvgTransRGBAf(struct NVGcolor c0, float a); + +// Returns color value specified by hue, saturation and lightness. +// HSL values are all in range [0..1], alpha will be set to 255. +struct NVGcolor nvgHSL(float h, float s, float l); + +// Returns color value specified by hue, saturation and lightness and alpha. +// HSL values are all in range [0..1], alpha in range [0..255] +struct NVGcolor nvgHSLA(float h, float s, float l, unsigned char a); + +// +// State Handling +// +// NanoVG contains state which represents how paths will be rendered. +// The state contains transform, fill and stroke styles, text and font styles, +// and scissor clipping. + +// Pushes and saves the current render state into a state stack. +// A matching nvgRestore() must be used to restore the state. +void nvgSave(struct NVGcontext* ctx); + +// Pops and restores current render state. +void nvgRestore(struct NVGcontext* ctx); + +// Resets current render state to default values. Does not affect the render state stack. +void nvgReset(struct NVGcontext* ctx); + +// +// Render styles +// +// Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern. +// Solid color is simply defined as a color value, different kinds of paints can be created +// using nvgLinearGradient(), nvgBoxGradient(), nvgRadialGradient() and nvgImagePattern(). +// +// Current render style can be saved and restored using nvgSave() and nvgRestore(). + +// Sets current stroke style to a solid color. +void nvgStrokeColor(struct NVGcontext* ctx, struct NVGcolor color); + +// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. +void nvgStrokePaint(struct NVGcontext* ctx, struct NVGpaint paint); + +// Sets current fill cstyle to a solid color. +void nvgFillColor(struct NVGcontext* ctx, struct NVGcolor color); + +// Sets current fill style to a paint, which can be a one of the gradients or a pattern. +void nvgFillPaint(struct NVGcontext* ctx, struct NVGpaint paint); + +// Sets the miter limit of the stroke style. +// Miter limit controls when a sharp corner is beveled. +void nvgMiterLimit(struct NVGcontext* ctx, float limit); + +// Sets the stroke witdth of the stroke style. +void nvgStrokeWidth(struct NVGcontext* ctx, float size); + +// Sets how the end of the line (cap) is drawn, +// Can be one of: NVG_BUTT (default), NVG_ROUND, NVG_SQUARE. +void nvgLineCap(struct NVGcontext* ctx, int cap); + +// Sets how sharp path corners are drawn. +// Can be one of NVG_MITER (default), NVG_ROUND, NVG_BEVEL. +void nvgLineJoin(struct NVGcontext* ctx, int join); + +// +// Transforms +// +// The paths, gradients, patterns and scissor region are transformed by an transformation +// matrix at the time when they are passed to the API. +// The current transformation matrix is a affine matrix: +// [sx kx tx] +// [ky sy ty] +// [ 0 0 1] +// Where: sx,sy define scaling, kx,ky skewing, and tx,ty translation. +// The last row is assumed to be 0,0,1 and is not stored. +// +// Apart from nvgResetTransform(), each transformation function first creates +// specific transformation matrix and pre-multiplies the current transformation by it. +// +// Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore(). + +// Resets current transform to a identity matrix. +void nvgResetTransform(struct NVGcontext* ctx); + +// Premultiplies current coordinate system by specified matrix. +// The parameters are interpreted as matrix as follows: +// [a c e] +// [b d f] +// [0 0 1] +void nvgTransform(struct NVGcontext* ctx, float a, float b, float c, float d, float e, float f); + +// Translates current coordinate system. +void nvgTranslate(struct NVGcontext* ctx, float x, float y); + +// Rotates current coordinate system. Angle is specifid in radians. +void nvgRotate(struct NVGcontext* ctx, float angle); + +// Skews the current coordinate system along X axis. Angle is specifid in radians. +void nvgSkewX(struct NVGcontext* ctx, float angle); + +// Skews the current coordinate system along Y axis. Angle is specifid in radians. +void nvgSkewY(struct NVGcontext* ctx, float angle); + +// Scales the current coordinat system. +void nvgScale(struct NVGcontext* ctx, float x, float y); + +// Stores the top part (a-f) of the current transformation matrix in to the specified buffer. +// [a c e] +// [b d f] +// [0 0 1] +// There should be space for 6 floats in the return buffer for the values a-f. +void nvgCurrentTransform(struct NVGcontext* ctx, float* xform); + + +// The following functions can be used to make calculations on 2x3 transformation matrices. +// A 2x3 matrix is representated as float[6]. + +// Sets the transform to identity matrix. +void nvgTransformIdentity(float* dst); + +// Sets the transform to translation matrix matrix. +void nvgTransformTranslate(float* dst, float tx, float ty); + +// Sets the transform to scale matrix. +void nvgTransformScale(float* dst, float sx, float sy); + +// Sets the transform to rotate matrix. Angle is specifid in radians. +void nvgTransformRotate(float* dst, float a); + +// Sets the transform to skew-x matrix. Angle is specifid in radians. +void nvgTransformSkewX(float* dst, float a); + +// Sets the transform to skew-y matrix. Angle is specifid in radians. +void nvgTransformSkewY(float* dst, float a); + +// Sets the transform to the result of multiplication of two transforms, of A = A*B. +void nvgTransformMultiply(float* dst, const float* src); + +// Sets the transform to the result of multiplication of two transforms, of A = B*A. +void nvgTransformPremultiply(float* dst, const float* src); + +// Sets the destination to inverse of specified transform. +// Returns 1 if the inverse could be calculated, else 0. +int nvgTransformInverse(float* dst, const float* src); + +// Transform a point by given transform. +void nvgTransformPoint(float* dstx, float* dsty, const float* xform, float srcx, float srcy); + +// Converts degress to radians and vice versa. +float nvgDegToRad(float deg); +float nvgRadToDeg(float rad); + +// +// Images +// +// NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering. +// In addition you can upload your own image. The image loading is provided by stb_image. + +// Creates image by loading it from the disk from specified file name. +// Returns handle to the image. +int nvgCreateImage(struct NVGcontext* ctx, const char* filename); + +// Creates image by loading it from the specified chunk of memory. +// Returns handle to the image. +int nvgCreateImageMem(struct NVGcontext* ctx, unsigned char* data, int ndata); + +// Creates image from specified image data. +// Returns handle to the image. +int nvgCreateImageRGBA(struct NVGcontext* ctx, int w, int h, const unsigned char* data); + +// Updates image data specified by image handle. +void nvgUpdateImage(struct NVGcontext* ctx, int image, const unsigned char* data); + +// Returns the domensions of a created image. +void nvgImageSize(struct NVGcontext* ctx, int image, int* w, int* h); + +// Deletes created image. +void nvgDeleteImage(struct NVGcontext* ctx, int image); + +// +// Paints +// +// NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. +// These can be used as paints for strokes and fills. + +// Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates +// of the linear gradient, icol specifies the start color and ocol the end color. +// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +struct NVGpaint nvgLinearGradient(struct NVGcontext* ctx, float sx, float sy, float ex, float ey, + struct NVGcolor icol, struct NVGcolor ocol); + +// Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering +// drop shadows or hilights for boxes. Parameters (x,y) define the top-left corner of the rectangle, +// (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry +// the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. +// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +struct NVGpaint nvgBoxGradient(struct NVGcontext* ctx, float x, float y, float w, float h, + float r, float f, struct NVGcolor icol, struct NVGcolor ocol); + +// Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify +// the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. +// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +struct NVGpaint nvgRadialGradient(struct NVGcontext* ctx, float cx, float cy, float inr, float outr, + struct NVGcolor icol, struct NVGcolor ocol); + +// Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern, +// (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render, +// and repeat is combination of NVG_REPEATX and NVG_REPEATY which tells if the image should be repeated across x or y. +// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +struct NVGpaint nvgImagePattern(struct NVGcontext* ctx, float ox, float oy, float ex, float ey, + float angle, int image, int repeat); + +// +// Scissoring +// +// Scissoring allows you to clip the rendering into a rectangle. This is useful for varius +// user interface cases like rendering a text edit or a timeline. + +// Sets the current +// The scissor rectangle is transformed by the current transform. +void nvgScissor(struct NVGcontext* ctx, float x, float y, float w, float h); + +// Reset and disables scissoring. +void nvgResetScissor(struct NVGcontext* ctx); + +// +// Paths +// +// Drawing a new shape starts with nvgBeginPath(), it clears all the currently defined paths. +// Then you define one or more paths and sub-paths which describe the shape. The are functions +// to draw common shapes like rectangles and circles, and lower level step-by-step functions, +// which allow to define a path curve by curve. +// +// NanoVG uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise +// winding and holes should have counter clockwise order. To specify winding of a path you can +// call nvgPathWinding(). This is useful especially for the common shapes, which are drawn CCW. +// +// Finally you can fill the path using current fill style by calling nvgFill(), and stroke it +// with current stroke style by calling nvgStroke(). +// +// The curve segments and sub-paths are transformed by the current transform. + +// Clears the current path and sub-paths. +void nvgBeginPath(struct NVGcontext* ctx); + +// Starts new sub-path with specified point as first point. +void nvgMoveTo(struct NVGcontext* ctx, float x, float y); + +// Adds line segment from the last point in the path to the specified point. +void nvgLineTo(struct NVGcontext* ctx, float x, float y); + +// Adds bezier segment from last point in the path via two control points to the specified point. +void nvgBezierTo(struct NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y); + +// Adds an arc segment at the corner defined by the last path point, and two specified points. +void nvgArcTo(struct NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius); + +// Closes current sub-path with a line segment. +void nvgClosePath(struct NVGcontext* ctx); + +// Sets the current sub-path winding, see NVGwinding and NVGsolidity. +void nvgPathWinding(struct NVGcontext* ctx, int dir); + +// Creates new arc shaped sub-path. +void nvgArc(struct NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir); + +// Creates new rectangle shaped sub-path. +void nvgRect(struct NVGcontext* ctx, float x, float y, float w, float h); + +// Creates new rounded rectangle shaped sub-path. +void nvgRoundedRect(struct NVGcontext* ctx, float x, float y, float w, float h, float r); + +// Creates new ellipse shaped sub-path. +void nvgEllipse(struct NVGcontext* ctx, float cx, float cy, float rx, float ry); + +// Creates new circle shaped sub-path. +void nvgCircle(struct NVGcontext* ctx, float cx, float cy, float r); + +// Fills the current path with current fill style. +void nvgFill(struct NVGcontext* ctx); + +// Fills the current path with current stroke style. +void nvgStroke(struct NVGcontext* ctx); + + +// +// Text +// +// NanoVG allows you to load .ttf files and use the font to render text. +// +// The appearance of the text can be defined by setting the current text style +// and by specifying the fill color. Common text and font settings such as +// font size, letter spacing and text align are supported. Font blur allows you +// to create simple text effects such as drop shadows. +// +// At render time the font face can be set based on the font handles or name. +// +// Font measure functions return values in local space, the calculations are +// carried in the same resolution as the final rendering. This is done because +// the text glyph positions are snapped to the nearest pixels sharp rendering. +// +// The local space means that values are not rotated or scale as per the current +// transformation. For example if you set font size to 12, which would mean that +// line height is 16, then regardless of the current scaling and rotation, the +// returned line height is always 16. Some measures may vary because of the scaling +// since aforementioned pixel snapping. +// +// While this may sound a little odd, the setup allows you to always render the +// same way regardless of scaling. I.e. following works regardless of scaling: +// +// const char* txt = "Text me up."; +// nvgTextBounds(vg, x,y, txt, NULL, bounds); +// nvgBeginPath(vg); +// nvgRoundedRect(vg, bounds[0],bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); +// nvgFill(vg); +// +// Note: currently only solid color fill is supported for text. + +// Creates font by loading it from the disk from specified file name. +// Returns handle to the font. +int nvgCreateFont(struct NVGcontext* ctx, const char* name, const char* filename); + +// Creates image by loading it from the specified memory chunk. +// Returns handle to the font. +int nvgCreateFontMem(struct NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData); + +// Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found. +int nvgFindFont(struct NVGcontext* ctx, const char* name); + +// Sets the font size of current text style. +void nvgFontSize(struct NVGcontext* ctx, float size); + +// Sets the blur of current text style. +void nvgFontBlur(struct NVGcontext* ctx, float blur); + +// Sets the letter spacing of current text style. +void nvgTextLetterSpacing(struct NVGcontext* ctx, float spacing); + +// Sets the proportional line height of current text style. The line height is specified as multiple of font size. +void nvgTextLineHeight(struct NVGcontext* ctx, float lineHeight); + +// Sets the text align of current text style, see NVGaling for options. +void nvgTextAlign(struct NVGcontext* ctx, int align); + +// Sets the font face based on specified id of current text style. +void nvgFontFaceId(struct NVGcontext* ctx, int font); + +// Sets the font face based on specified name of current text style. +void nvgFontFace(struct NVGcontext* ctx, const char* font); + +// Draws text string at specified location. If end is specified only the sub-string up to the end is drawn. +float nvgText(struct NVGcontext* ctx, float x, float y, const char* string, const char* end); + +// Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn. +// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. +// Words longer than the max width are slit at nearest character (i.e. no hyphenation). +void nvgTextBox(struct NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end); + +// Measures the specified text string. Parameter bounds should be a pointer to float[4], +// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] +// Returns the horizontal advance of the measured text (i.e. where the next character should drawn). +// Measured values are returned in local coordinate space. +float nvgTextBounds(struct NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds); + +// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], +// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] +// Measured values are returned in local coordinate space. +void nvgTextBoxBounds(struct NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds); + +// Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used. +// Measured values are returned in local coordinate space. +int nvgTextGlyphPositions(struct NVGcontext* ctx, float x, float y, const char* string, const char* end, struct NVGglyphPosition* positions, int maxPositions); + +// Returns the vertical metrics based on the current text style. +// Measured values are returned in local coordinate space. +void nvgTextMetrics(struct NVGcontext* ctx, float* ascender, float* descender, float* lineh); + +// Breaks the specified text into lines. If end is specified only the sub-string will be used. +// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. +// Words longer than the max width are slit at nearest character (i.e. no hyphenation). +int nvgTextBreakLines(struct NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, struct NVGtextRow* rows, int maxRows); + +// +// Internal Render API +// +enum NVGtexture { + NVG_TEXTURE_ALPHA = 0x01, + NVG_TEXTURE_RGBA = 0x02, +}; + +struct NVGscissor +{ + float xform[6]; + float extent[2]; +}; + +struct NVGvertex { + float x,y,u,v; +}; + +struct NVGpath { + int first; + int count; + unsigned char closed; + int nbevel; + struct NVGvertex* fill; + int nfill; + struct NVGvertex* stroke; + int nstroke; + int winding; + int convex; +}; + +struct NVGparams { + void* userPtr; + int atlasWidth, atlasHeight; + int edgeAntiAlias; + int (*renderCreate)(void* uptr); + int (*renderCreateTexture)(void* uptr, int type, int w, int h, const unsigned char* data); + int (*renderDeleteTexture)(void* uptr, int image); + int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data); + int (*renderGetTextureSize)(void* uptr, int image, int* w, int* h); + void (*renderViewport)(void* uptr, int width, int height, int alphaBlend); + void (*renderFlush)(void* uptr, int alphaBlend); + void (*renderFill)(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor, float fringe, const float* bounds, const struct NVGpath* paths, int npaths); + void (*renderStroke)(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor, float fringe, float strokeWidth, const struct NVGpath* paths, int npaths); + void (*renderTriangles)(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor, const struct NVGvertex* verts, int nverts); + void (*renderDelete)(void* uptr); +}; + +// Contructor and destructor, called by the render back-end. +struct NVGcontext* nvgCreateInternal(struct NVGparams* params); +void nvgDeleteInternal(struct NVGcontext* ctx); + +// Debug function to dump cached path data. +void nvgDebugDumpPathCache(struct NVGcontext* ctx); + +#define NVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) + +#ifdef __cplusplus +} +#endif + +#endif // NANOVG_H diff --git a/examples/nanovg_src/nanovg_gl.h b/examples/nanovg_src/nanovg_gl.h new file mode 100644 index 0000000..28be0d0 --- /dev/null +++ b/examples/nanovg_src/nanovg_gl.h @@ -0,0 +1,1311 @@ +// +// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org +// +// 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. +// +#ifndef NANOVG_GL3_H +#define NANOVG_GL3_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define NVG_ANTIALIAS 1 + +#if defined NANOVG_GL2_IMPLEMENTATION +# define NANOVG_GL2 1 +# define NANOVG_GL_IMPLEMENTATION 1 +#elif defined NANOVG_GL3_IMPLEMENTATION +# define NANOVG_GL3 1 +# define NANOVG_GL_IMPLEMENTATION 1 +# define NANOVG_GL_USE_UNIFORMBUFFER 1 +#elif defined NANOVG_GLES2_IMPLEMENTATION +# define NANOVG_GLES2 1 +# define NANOVG_GL_IMPLEMENTATION 1 +#elif defined NANOVG_GLES3_IMPLEMENTATION +# define NANOVG_GLES3 1 +# define NANOVG_GL_IMPLEMENTATION 1 +#endif + + +#if defined NANOVG_GL2 + +struct NVGcontext* nvgCreateGL2(int atlasw, int atlash, int edgeaa); +void nvgDeleteGL2(struct NVGcontext* ctx); + +#elif defined NANOVG_GL3 + +struct NVGcontext* nvgCreateGL3(int atlasw, int atlash, int edgeaa); +void nvgDeleteGL3(struct NVGcontext* ctx); + +#elif defined NANOVG_GLES2 + +struct NVGcontext* nvgCreateGLES2(int atlasw, int atlash, int edgeaa); +void nvgDeleteGLES2(struct NVGcontext* ctx); + +#elif defined NANOVG_GLES3 + +struct NVGcontext* nvgCreateGLES3(int atlasw, int atlash, int edgeaa); +void nvgDeleteGLES3(struct NVGcontext* ctx); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif + +#ifdef NANOVG_GL_IMPLEMENTATION + +#include +#include +#include +#include +#include "nanovg.h" + +enum GLNVGuniformLoc { +#if NANOVG_GL_USE_UNIFORMBUFFER + GLNVG_LOC_VIEW, + GLNVG_LOC_FRAG, +#else + GLNVG_LOC_VIEWSIZE, + GLNVG_LOC_SCISSORMAT, + GLNVG_LOC_SCISSOREXT, + GLNVG_LOC_SCISSORSCALE, + GLNVG_LOC_PAINTMAT, + GLNVG_LOC_EXTENT, + GLNVG_LOC_RADIUS, + GLNVG_LOC_FEATHER, + GLNVG_LOC_INNERCOL, + GLNVG_LOC_OUTERCOL, + GLNVG_LOC_STROKEMULT, + GLNVG_LOC_TEX, + GLNVG_LOC_TEXTYPE, + GLNVG_LOC_TYPE, +#endif + GLNVG_MAX_LOCS +}; + +enum GLNVGshaderType { + NSVG_SHADER_FILLGRAD, + NSVG_SHADER_FILLIMG, + NSVG_SHADER_SIMPLE, + NSVG_SHADER_IMG +}; + +#if NANOVG_GL_USE_UNIFORMBUFFER +enum GLNVGuniformBindings { + GLNVG_VIEW_BINDING = 0, + GLNVG_FRAG_BINDING = 1, +}; +#endif + +struct GLNVGshader { + GLuint prog; + GLuint frag; + GLuint vert; + GLint loc[GLNVG_MAX_LOCS]; +}; + +struct GLNVGtexture { + int id; + GLuint tex; + int width, height; + int type; +}; + +enum GLNVGcallType { + GLNVG_NONE = 0, + GLNVG_FILL, + GLNVG_CONVEXFILL, + GLNVG_STROKE, + GLNVG_TRIANGLES, +}; + +struct GLNVGcall { + int type; + int image; + int pathOffset; + int pathCount; + int triangleOffset; + int triangleCount; + int uniformOffset; +}; + +struct GLNVGpath { + int fillOffset; + int fillCount; + int strokeOffset; + int strokeCount; +}; + +struct GLNVGfragUniforms { + float scissorMat[12]; // matrices are actually 3 vec4s + float paintMat[12]; + struct NVGcolor innerCol; + struct NVGcolor outerCol; + float scissorExt[2]; + float scissorScale[2]; + float extent[2]; + float radius; + float feather; + float strokeMult; + int texType; + int type; +}; + +struct GLNVGcontext { + struct GLNVGshader shader; + struct GLNVGtexture* textures; + float view[2]; + int ntextures; + int ctextures; + int textureId; + GLuint vertBuf; +#if defined NANOVG_GL3 + GLuint vertArr; +#endif +#if NANOVG_GL_USE_UNIFORMBUFFER + GLuint viewBuf; + GLuint fragBuf; +#endif + int fragSize; + int edgeAntiAlias; + + // Per frame buffers + struct GLNVGcall* calls; + int ccalls; + int ncalls; + struct GLNVGpath* paths; + int cpaths; + int npaths; + struct NVGvertex* verts; + int cverts; + int nverts; + unsigned char* uniforms; + int cuniforms; + int nuniforms; +}; + +static int glnvg__maxi(int a, int b) { return a > b ? a : b; } + +static struct GLNVGtexture* glnvg__allocTexture(struct GLNVGcontext* gl) +{ + struct GLNVGtexture* tex = NULL; + int i; + + for (i = 0; i < gl->ntextures; i++) { + if (gl->textures[i].id == 0) { + tex = &gl->textures[i]; + break; + } + } + if (tex == NULL) { + if (gl->ntextures+1 > gl->ctextures) { + struct GLNVGtexture* textures; + int ctextures = glnvg__maxi(gl->ntextures+1, 4) + gl->ctextures/2; // 1.5x Overallocate + textures = (struct GLNVGtexture*)realloc(gl->textures, sizeof(struct GLNVGtexture)*ctextures); + if (textures == NULL) return NULL; + gl->textures = textures; + gl->ctextures = ctextures; + } + tex = &gl->textures[gl->ntextures++]; + } + + memset(tex, 0, sizeof(*tex)); + tex->id = ++gl->textureId; + + return tex; +} + +static struct GLNVGtexture* glnvg__findTexture(struct GLNVGcontext* gl, int id) +{ + int i; + for (i = 0; i < gl->ntextures; i++) + if (gl->textures[i].id == id) + return &gl->textures[i]; + return NULL; +} + +static int glnvg__deleteTexture(struct GLNVGcontext* gl, int id) +{ + int i; + for (i = 0; i < gl->ntextures; i++) { + if (gl->textures[i].id == id) { + if (gl->textures[i].tex != 0) + glDeleteTextures(1, &gl->textures[i].tex); + memset(&gl->textures[i], 0, sizeof(gl->textures[i])); + return 1; + } + } + return 0; +} + +static void glnvg__dumpShaderError(GLuint shader, const char* name, const char* type) +{ + char str[512+1]; + int len = 0; + glGetShaderInfoLog(shader, 512, &len, str); + if (len > 512) len = 512; + str[len] = '\0'; + printf("Shader %s/%s error:\n%s\n", name, type, str); +} + +static void glnvg__dumpProgramError(GLuint prog, const char* name) +{ + char str[512+1]; + int len = 0; + glGetProgramInfoLog(prog, 512, &len, str); + if (len > 512) len = 512; + str[len] = '\0'; + printf("Program %s error:\n%s\n", name, str); +} + +static int glnvg__checkError(const char* str) +{ + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + printf("Error %08x after %s\n", err, str); + return 1; + } + return 0; +} + +static int glnvg__createShader(struct GLNVGshader* shader, const char* name, const char* header, const char* opts, const char* vshader, const char* fshader) +{ + GLint status; + GLuint prog, vert, frag; + const char* str[3]; + str[0] = header; + str[1] = opts != NULL ? opts : ""; + + memset(shader, 0, sizeof(*shader)); + + prog = glCreateProgram(); + vert = glCreateShader(GL_VERTEX_SHADER); + frag = glCreateShader(GL_FRAGMENT_SHADER); + str[2] = vshader; + glShaderSource(vert, 3, str, 0); + str[2] = fshader; + glShaderSource(frag, 3, str, 0); + + glCompileShader(vert); + glGetShaderiv(vert, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) { + glnvg__dumpShaderError(vert, name, "vert"); + return 0; + } + + glCompileShader(frag); + glGetShaderiv(frag, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) { + glnvg__dumpShaderError(frag, name, "frag"); + return 0; + } + + glAttachShader(prog, vert); + glAttachShader(prog, frag); + + glBindAttribLocation(prog, 0, "vertex"); + glBindAttribLocation(prog, 1, "tcoord"); + + glLinkProgram(prog); + glGetProgramiv(prog, GL_LINK_STATUS, &status); + if (status != GL_TRUE) { + glnvg__dumpProgramError(prog, name); + return 0; + } + + shader->prog = prog; + shader->vert = vert; + shader->frag = frag; + + return 1; +} + +static void glnvg__deleteShader(struct GLNVGshader* shader) +{ + if (shader->prog != 0) + glDeleteProgram(shader->prog); + if (shader->vert != 0) + glDeleteShader(shader->vert); + if (shader->frag != 0) + glDeleteShader(shader->frag); +} + +static void glnvg__getUniforms(struct GLNVGshader* shader) +{ +#if NANOVG_GL_USE_UNIFORMBUFFER + shader->loc[GLNVG_LOC_VIEW] = glGetUniformBlockIndex(shader->prog, "view"); + shader->loc[GLNVG_LOC_FRAG] = glGetUniformBlockIndex(shader->prog, "frag"); +#else + shader->loc[GLNVG_LOC_VIEWSIZE] = glGetUniformLocation(shader->prog, "viewSize"); + shader->loc[GLNVG_LOC_SCISSORMAT] = glGetUniformLocation(shader->prog, "scissorMat"); + shader->loc[GLNVG_LOC_SCISSOREXT] = glGetUniformLocation(shader->prog, "scissorExt"); + shader->loc[GLNVG_LOC_SCISSORSCALE] = glGetUniformLocation(shader->prog, "scissorScale"); + shader->loc[GLNVG_LOC_PAINTMAT] = glGetUniformLocation(shader->prog, "paintMat"); + shader->loc[GLNVG_LOC_EXTENT] = glGetUniformLocation(shader->prog, "extent"); + shader->loc[GLNVG_LOC_RADIUS] = glGetUniformLocation(shader->prog, "radius"); + shader->loc[GLNVG_LOC_FEATHER] = glGetUniformLocation(shader->prog, "feather"); + shader->loc[GLNVG_LOC_INNERCOL] = glGetUniformLocation(shader->prog, "innerCol"); + shader->loc[GLNVG_LOC_OUTERCOL] = glGetUniformLocation(shader->prog, "outerCol"); + shader->loc[GLNVG_LOC_STROKEMULT] = glGetUniformLocation(shader->prog, "strokeMult"); + shader->loc[GLNVG_LOC_TEX] = glGetUniformLocation(shader->prog, "tex"); + shader->loc[GLNVG_LOC_TEXTYPE] = glGetUniformLocation(shader->prog, "texType"); + shader->loc[GLNVG_LOC_TYPE] = glGetUniformLocation(shader->prog, "type"); +#endif +} + +static int glnvg__renderCreate(void* uptr) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + int align = 4; + + // TODO: mediump float may not be enough for GLES2 in iOS. + // see the following discussion: https://github.com/memononen/nanovg/issues/46 + static const char* shaderHeader = +#if defined NANOVG_GL2 + "#define NANOVG_GL2 1\n"; +#elif defined NANOVG_GL3 + "#version 150 core\n" +#if NANOVG_GL_USE_UNIFORMBUFFER + "#define USE_UNIFORMBUFFER 1\n" +#endif + "#define NANOVG_GL3 1\n"; +#elif defined NANOVG_GLES2 + "#version 100\n" + "precision mediump float;\n" + "#define NANOVG_GL2 1\n"; +#elif defined NANOVG_GLES3 + "#version 300 es\n" + "precision mediump float;\n" + "#define NANOVG_GL3 1\n"; +#endif + + static const char* fillVertShader = + "#ifdef NANOVG_GL3\n" + "#ifdef USE_UNIFORMBUFFER\n" + " layout(std140) uniform view {\n" + " vec2 viewSize;\n" + " };\n" + "#else\n" + " uniform vec2 viewSize;\n" + "#endif\n" + " in vec2 vertex;\n" + " in vec2 tcoord;\n" + " out vec2 ftcoord;\n" + " out vec2 fpos;\n" + "#else\n" + " uniform vec2 viewSize;\n" + " attribute vec2 vertex;\n" + " attribute vec2 tcoord;\n" + " varying vec2 ftcoord;\n" + " varying vec2 fpos;\n" + "#endif\n" + "void main(void) {\n" + " ftcoord = tcoord;\n" + " fpos = vertex;\n" + " gl_Position = vec4(2.0*vertex.x/viewSize.x - 1.0, 1.0 - 2.0*vertex.y/viewSize.y, 0, 1);\n" + "}\n"; + + static const char* fillFragShader = + "#ifdef NANOVG_GL3\n" + "#ifdef USE_UNIFORMBUFFER\n" + " layout(std140) uniform frag {\n" + " mat3 scissorMat;\n" + " mat3 paintMat;\n" + " vec4 innerCol;\n" + " vec4 outerCol;\n" + " vec2 scissorExt;\n" + " vec2 scissorScale;\n" + " vec2 extent;\n" + " float radius;\n" + " float feather;\n" + " float strokeMult;\n" + " int texType;\n" + " int type;\n" + " };\n" + "#else\n" + " uniform mat3 scissorMat;\n" + " uniform mat3 paintMat;\n" + " uniform vec4 innerCol;\n" + " uniform vec4 outerCol;\n" + " uniform vec2 scissorExt;\n" + " uniform vec2 scissorScale;\n" + " uniform vec2 extent;\n" + " uniform float radius;\n" + " uniform float feather;\n" + " uniform float strokeMult;\n" + " uniform int texType;\n" + " uniform int type;\n" + "#endif\n" + " uniform sampler2D tex;\n" + " in vec2 ftcoord;\n" + " in vec2 fpos;\n" + " out vec4 outColor;\n" + "#else\n" + " uniform mat3 scissorMat;\n" + " uniform mat3 paintMat;\n" + " uniform vec4 innerCol;\n" + " uniform vec4 outerCol;\n" + " uniform vec2 scissorExt;\n" + " uniform vec2 scissorScale;\n" + " uniform vec2 extent;\n" + " uniform float radius;\n" + " uniform float feather;\n" + " uniform float strokeMult;\n" + " uniform int texType;\n" + " uniform int type;\n" + " uniform sampler2D tex;\n" + " varying vec2 ftcoord;\n" + " varying vec2 fpos;\n" + "#endif\n" + "\n" + "float sdroundrect(vec2 pt, vec2 ext, float rad) {\n" + " vec2 ext2 = ext - vec2(rad,rad);\n" + " vec2 d = abs(pt) - ext2;\n" + " return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;\n" + "}\n" + "\n" + "// Scissoring\n" + "float scissorMask(vec2 p) {\n" + " vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);\n" + " sc = vec2(0.5,0.5) - sc * scissorScale;\n" + " return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);\n" + "}\n" + "#ifdef EDGE_AA\n" + "// Stroke - from [0..1] to clipped pyramid, where the slope is 1px.\n" + "float strokeMask() {\n" + " return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y);\n" + "}\n" + "#endif\n" + "\n" + "void main(void) {\n" + " vec4 result;\n" + " float scissor = scissorMask(fpos);\n" + "#ifdef EDGE_AA\n" + " float strokeAlpha = strokeMask();\n" + "#else\n" + " float strokeAlpha = 1.0;\n" + "#endif\n" + " if (type == 0) { // Gradient\n" + " // Calculate gradient color using box gradient\n" + " vec2 pt = (paintMat * vec3(fpos,1.0)).xy;\n" + " float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);\n" + " vec4 color = mix(innerCol,outerCol,d);\n" + " // Combine alpha\n" + " color.w *= strokeAlpha * scissor;\n" + " result = color;\n" + " } else if (type == 1) { // Image\n" + " // Calculate color fron texture\n" + " vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;\n" + "#ifdef NANOVG_GL3\n" + " vec4 color = texture(tex, pt);\n" + "#else\n" + " vec4 color = texture2D(tex, pt);\n" + "#endif\n" + " color = texType == 0 ? color : vec4(1,1,1,color.x);\n" + " // Combine alpha\n" + " color.w *= strokeAlpha * scissor;\n" + " result = color;\n" + " } else if (type == 2) { // Stencil fill\n" + " result = vec4(1,1,1,1);\n" + " } else if (type == 3) { // Textured tris\n" + "#ifdef NANOVG_GL3\n" + " vec4 color = texture(tex, ftcoord);\n" + "#else\n" + " vec4 color = texture2D(tex, ftcoord);\n" + "#endif\n" + " color = texType == 0 ? color : vec4(1,1,1,color.x);\n" + " color.w *= scissor;\n" + " result = color * innerCol;\n" + " }\n" + "#ifdef NANOVG_GL3\n" + " outColor = result;\n" + "#else\n" + " gl_FragColor = result;\n" + "#endif\n" + "}\n"; + + glnvg__checkError("init"); + + if (gl->edgeAntiAlias) { + if (glnvg__createShader(&gl->shader, "shader", shaderHeader, "#define EDGE_AA 1\n", fillVertShader, fillFragShader) == 0) + return 0; + } else { + if (glnvg__createShader(&gl->shader, "shader", shaderHeader, NULL, fillVertShader, fillFragShader) == 0) + return 0; + } + + glnvg__checkError("uniform locations"); + glnvg__getUniforms(&gl->shader); + + // Create dynamic vertex array +#if defined NANOVG_GL3 + glGenVertexArrays(1, &gl->vertArr); +#endif + glGenBuffers(1, &gl->vertBuf); + +#if NANOVG_GL_USE_UNIFORMBUFFER + // Create UBOs + glUniformBlockBinding(gl->shader.prog, gl->shader.loc[GLNVG_LOC_VIEW], GLNVG_VIEW_BINDING); + glGenBuffers(1, &gl->viewBuf); + glUniformBlockBinding(gl->shader.prog, gl->shader.loc[GLNVG_LOC_FRAG], GLNVG_FRAG_BINDING); + glGenBuffers(1, &gl->fragBuf); + glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &align); +#endif + gl->fragSize = sizeof(struct GLNVGfragUniforms) + align - sizeof(struct GLNVGfragUniforms) % align; + + glnvg__checkError("create done"); + + glFinish(); + + return 1; +} + +static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, const unsigned char* data) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + struct GLNVGtexture* tex = glnvg__allocTexture(gl); + + if (tex == NULL) return 0; + + glGenTextures(1, &tex->tex); + tex->width = w; + tex->height = h; + tex->type = type; + glBindTexture(GL_TEXTURE_2D, tex->tex); + + glPixelStorei(GL_UNPACK_ALIGNMENT,1); +#ifndef NANOVG_GLES2 + glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); +#endif + + if (type == NVG_TEXTURE_RGBA) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + else +#if defined(NANOVG_GLES2) + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); +#elif defined(NANOVG_GLES3) + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data); +#endif + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); +#ifndef NANOVG_GLES2 + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); +#endif + + if (glnvg__checkError("create tex")) + return 0; + + glBindTexture(GL_TEXTURE_2D, 0); + + return tex->id; +} + + +static int glnvg__renderDeleteTexture(void* uptr, int image) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + return glnvg__deleteTexture(gl, image); +} + +static int glnvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + struct GLNVGtexture* tex = glnvg__findTexture(gl, image); + + if (tex == NULL) return 0; + glBindTexture(GL_TEXTURE_2D, tex->tex); + + glPixelStorei(GL_UNPACK_ALIGNMENT,1); + +#ifndef NANOVG_GLES2 + glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); + glPixelStorei(GL_UNPACK_SKIP_ROWS, y); +#else + // No support for all of skip, need to update a whole row at a time. + if (tex->type == NVG_TEXTURE_RGBA) + data += y*tex->width*4; + else + data += y*tex->width; + x = 0; + w = tex->width; +#endif + + if (tex->type == NVG_TEXTURE_RGBA) + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGBA, GL_UNSIGNED_BYTE, data); + else +#ifdef NANOVG_GLES2 + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); +#else + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RED, GL_UNSIGNED_BYTE, data); +#endif + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); +#ifndef NANOVG_GLES2 + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); +#endif + + glBindTexture(GL_TEXTURE_2D, 0); + + return 1; +} + +static int glnvg__renderGetTextureSize(void* uptr, int image, int* w, int* h) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + struct GLNVGtexture* tex = glnvg__findTexture(gl, image); + if (tex == NULL) return 0; + *w = tex->width; + *h = tex->height; + return 1; +} + +static void glnvg__xformToMat3x4(float* m3, float* t) +{ + m3[0] = t[0]; + m3[1] = t[1]; + m3[2] = 0.0f; + m3[3] = 0.0f; + m3[4] = t[2]; + m3[5] = t[3]; + m3[6] = 0.0f; + m3[7] = 0.0f; + m3[8] = t[4]; + m3[9] = t[5]; + m3[10] = 1.0f; + m3[11] = 0.0f; +} + +static int glnvg__convertPaint(struct GLNVGcontext* gl, struct GLNVGfragUniforms* frag, struct NVGpaint* paint, + struct NVGscissor* scissor, float width, float fringe) +{ + struct GLNVGtexture* tex = NULL; + float invxform[6]; + + memset(frag, 0, sizeof(*frag)); + + frag->innerCol = paint->innerColor; + frag->outerCol = paint->outerColor; + + nvgTransformInverse(invxform, paint->xform); + glnvg__xformToMat3x4(frag->paintMat, invxform); + + if (scissor->extent[0] < 0.5f || scissor->extent[1] < 0.5f) { + memset(frag->scissorMat, 0, sizeof(frag->scissorMat)); + frag->scissorExt[0] = 1.0f; + frag->scissorExt[1] = 1.0f; + frag->scissorScale[0] = 1.0f; + frag->scissorScale[1] = 1.0f; + } else { + nvgTransformInverse(invxform, scissor->xform); + glnvg__xformToMat3x4(frag->scissorMat, invxform); + frag->scissorExt[0] = scissor->extent[0]; + frag->scissorExt[1] = scissor->extent[1]; + frag->scissorScale[0] = sqrtf(scissor->xform[0]*scissor->xform[0] + scissor->xform[2]*scissor->xform[2]) / fringe; + frag->scissorScale[1] = sqrtf(scissor->xform[1]*scissor->xform[1] + scissor->xform[3]*scissor->xform[3]) / fringe; + } + memcpy(frag->extent, paint->extent, sizeof(frag->extent)); + frag->strokeMult = (width*0.5f + fringe*0.5f) / fringe; + + if (paint->image != 0) { + tex = glnvg__findTexture(gl, paint->image); + if (tex == NULL) return 0; + frag->type = NSVG_SHADER_FILLIMG; + frag->texType = tex->type == NVG_TEXTURE_RGBA ? 0 : 1; + } else { + frag->type = NSVG_SHADER_FILLGRAD; + frag->radius = paint->radius; + frag->feather = paint->feather; + } + return 1; +} + +static struct GLNVGfragUniforms* nvg__fragUniformPtr(struct GLNVGcontext* gl, int i); + +#if !NANOVG_GL_USE_UNIFORMBUFFER +static void glnvg__mat3(float* dst, float* src) +{ + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + + dst[3] = src[4]; + dst[4] = src[5]; + dst[5] = src[6]; + + dst[6] = src[8]; + dst[7] = src[9]; + dst[8] = src[10]; +} +#endif + +static void glnvg__setUniforms(struct GLNVGcontext* gl, int uniformOffset, int image) +{ +#if NANOVG_GL_USE_UNIFORMBUFFER + glBindBufferRange(GL_UNIFORM_BUFFER, GLNVG_FRAG_BINDING, gl->fragBuf, uniformOffset, sizeof(struct GLNVGfragUniforms)); +#else + struct GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); + float tmp[9]; // Maybe there's a way to get rid of this... + glnvg__mat3(tmp, frag->scissorMat); + glUniformMatrix3fv(gl->shader.loc[GLNVG_LOC_SCISSORMAT], 1, GL_FALSE, tmp); + glnvg__mat3(tmp, frag->paintMat); + glUniformMatrix3fv(gl->shader.loc[GLNVG_LOC_PAINTMAT], 1, GL_FALSE, tmp); + glUniform4fv(gl->shader.loc[GLNVG_LOC_INNERCOL], 1, frag->innerCol.rgba); + glUniform4fv(gl->shader.loc[GLNVG_LOC_OUTERCOL], 1, frag->outerCol.rgba); + glUniform2fv(gl->shader.loc[GLNVG_LOC_SCISSOREXT], 1, frag->scissorExt); + glUniform2fv(gl->shader.loc[GLNVG_LOC_SCISSORSCALE], 1, frag->scissorScale); + glUniform2fv(gl->shader.loc[GLNVG_LOC_EXTENT], 1, frag->extent); + glUniform1f(gl->shader.loc[GLNVG_LOC_RADIUS], frag->radius); + glUniform1f(gl->shader.loc[GLNVG_LOC_FEATHER], frag->feather); + glUniform1f(gl->shader.loc[GLNVG_LOC_STROKEMULT], frag->strokeMult); + glUniform1i(gl->shader.loc[GLNVG_LOC_TEXTYPE], frag->texType); + glUniform1i(gl->shader.loc[GLNVG_LOC_TYPE], frag->type); +#endif + + if (image != 0) { + struct GLNVGtexture* tex = glnvg__findTexture(gl, image); + glBindTexture(GL_TEXTURE_2D, tex != NULL ? tex->tex : 0); + glnvg__checkError("tex paint tex"); + } else { + glBindTexture(GL_TEXTURE_2D, 0); + } +} + +static void glnvg__renderViewport(void* uptr, int width, int height, int alphaBlend) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + NVG_NOTUSED(alphaBlend); + gl->view[0] = (float)width; + gl->view[1] = (float)height; +} + +static void glnvg__fill(struct GLNVGcontext* gl, struct GLNVGcall* call) +{ + struct GLNVGpath* paths = &gl->paths[call->pathOffset]; + int i, npaths = call->pathCount; + + // Draw shapes + glEnable(GL_STENCIL_TEST); + glStencilMask(0xff); + glStencilFunc(GL_ALWAYS, 0, ~0L); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + // set bindpoint for solid loc + glnvg__setUniforms(gl, call->uniformOffset, 0); + glnvg__checkError("fill simple"); + + glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); + glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); + glDisable(GL_CULL_FACE); + for (i = 0; i < npaths; i++) + glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); + glEnable(GL_CULL_FACE); + + // Draw aliased off-pixels + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + glnvg__setUniforms(gl, call->uniformOffset + gl->fragSize, call->image); + glnvg__checkError("fill fill"); + + if (gl->edgeAntiAlias) { + glStencilFunc(GL_EQUAL, 0x00, 0xff); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + // Draw fringes + for (i = 0; i < npaths; i++) + glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); + } + + // Draw fill + glStencilFunc(GL_NOTEQUAL, 0x0, 0xff); + glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); + glDrawArrays(GL_TRIANGLES, call->triangleOffset, call->triangleCount); + + glDisable(GL_STENCIL_TEST); +} + +static void glnvg__convexFill(struct GLNVGcontext* gl, struct GLNVGcall* call) +{ + struct GLNVGpath* paths = &gl->paths[call->pathOffset]; + int i, npaths = call->pathCount; + + glnvg__setUniforms(gl, call->uniformOffset, call->image); + glnvg__checkError("convex fill"); + + for (i = 0; i < npaths; i++) + glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); + if (gl->edgeAntiAlias) { + // Draw fringes + for (i = 0; i < npaths; i++) + glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); + } +} + +static void glnvg__stroke(struct GLNVGcontext* gl, struct GLNVGcall* call) +{ + struct GLNVGpath* paths = &gl->paths[call->pathOffset]; + int npaths = call->pathCount, i; + + glnvg__setUniforms(gl, call->uniformOffset, call->image); + glnvg__checkError("stroke fill"); + + // Draw Strokes + for (i = 0; i < npaths; i++) + glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); +} + +static void glnvg__triangles(struct GLNVGcontext* gl, struct GLNVGcall* call) +{ + glnvg__setUniforms(gl, call->uniformOffset, call->image); + glnvg__checkError("triangles fill"); + + glDrawArrays(GL_TRIANGLES, call->triangleOffset, call->triangleCount); +} + +static void glnvg__renderFlush(void* uptr, int alphaBlend) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + int i; + + if (gl->ncalls > 0) { + + // Setup require GL state. + glUseProgram(gl->shader.prog); + + if (alphaBlend == NVG_PREMULTIPLIED_ALPHA) + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + else + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CCW); + glEnable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glStencilMask(0xffffffff); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilFunc(GL_ALWAYS, 0, 0xffffffff); + glActiveTexture(GL_TEXTURE0); + +#if NANOVG_GL_USE_UNIFORMBUFFER + // Upload ubo for frag shaders + glBindBuffer(GL_UNIFORM_BUFFER, gl->fragBuf); + glBufferData(GL_UNIFORM_BUFFER, gl->nuniforms * gl->fragSize, gl->uniforms, GL_STREAM_DRAW); +#endif + + // Upload vertex data +#if defined NANOVG_GL3 + glBindVertexArray(gl->vertArr); +#endif + glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf); + glBufferData(GL_ARRAY_BUFFER, gl->nverts * sizeof(struct NVGvertex), gl->verts, GL_STREAM_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(0 + 2*sizeof(float))); + +#if NANOVG_GL_USE_UNIFORMBUFFER + // once per frame set ubo for view + glBindBuffer(GL_UNIFORM_BUFFER, gl->viewBuf); + glBufferData(GL_UNIFORM_BUFFER, sizeof(gl->view), 0, GL_STREAM_DRAW); + glBufferData(GL_UNIFORM_BUFFER, sizeof(gl->view), gl->view, GL_STREAM_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, GLNVG_VIEW_BINDING, gl->viewBuf); + + glBindBuffer(GL_UNIFORM_BUFFER, gl->fragBuf); +#else + glUniform1i(gl->shader.loc[GLNVG_LOC_TEX], 0); + glUniform2fv(gl->shader.loc[GLNVG_LOC_VIEWSIZE], 1, gl->view); +#endif + + for (i = 0; i < gl->ncalls; i++) { + struct GLNVGcall* call = &gl->calls[i]; + if (call->type == GLNVG_FILL) + glnvg__fill(gl, call); + else if (call->type == GLNVG_CONVEXFILL) + glnvg__convexFill(gl, call); + else if (call->type == GLNVG_STROKE) + glnvg__stroke(gl, call); + else if (call->type == GLNVG_TRIANGLES) + glnvg__triangles(gl, call); + } + + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); +#if defined NANOVG_GL3 + glBindVertexArray(0); +#endif + glUseProgram(0); + glBindTexture(GL_TEXTURE_2D, 0); + } + + // Reset calls + gl->nverts = 0; + gl->npaths = 0; + gl->ncalls = 0; + gl->nuniforms = 0; +} + +static int glnvg__maxVertCount(const struct NVGpath* paths, int npaths) +{ + int i, count = 0; + for (i = 0; i < npaths; i++) { + count += paths[i].nfill; + count += paths[i].nstroke; + } + return count; +} + +static struct GLNVGcall* glnvg__allocCall(struct GLNVGcontext* gl) +{ + struct GLNVGcall* ret = NULL; + if (gl->ncalls+1 > gl->ccalls) { + struct GLNVGcall* calls; + int ccalls = glnvg__maxi(gl->ncalls+1, 128) + gl->ccalls; // 1.5x Overallocate + calls = (struct GLNVGcall*)realloc(gl->calls, sizeof(struct GLNVGcall) * ccalls); + if (calls == NULL) return NULL; + gl->calls = calls; + gl->ccalls = ccalls; + } + ret = &gl->calls[gl->ncalls++]; + memset(ret, 0, sizeof(struct GLNVGcall)); + return ret; +} + +static int glnvg__allocPaths(struct GLNVGcontext* gl, int n) +{ + int ret = 0; + if (gl->npaths+n > gl->cpaths) { + struct GLNVGpath* paths; + int cpaths = glnvg__maxi(gl->npaths + n, 128) + gl->cpaths; // 1.5x Overallocate + paths = (struct GLNVGpath*)realloc(gl->paths, sizeof(struct GLNVGpath) * cpaths); + if (paths == NULL) return -1; + gl->paths = paths; + gl->cpaths = cpaths; + } + ret = gl->npaths; + gl->npaths += n; + return ret; +} + +static int glnvg__allocVerts(struct GLNVGcontext* gl, int n) +{ + int ret = 0; + if (gl->nverts+n > gl->cverts) { + struct NVGvertex* verts; + int cverts = glnvg__maxi(gl->nverts + n, 4096) + gl->cverts/2; // 1.5x Overallocate + verts = (struct NVGvertex*)realloc(gl->verts, sizeof(struct NVGvertex) * cverts); + if (verts == NULL) return -1; + gl->verts = verts; + gl->cverts = cverts; + } + ret = gl->nverts; + gl->nverts += n; + return ret; +} + +static int glnvg__allocFragUniforms(struct GLNVGcontext* gl, int n) +{ + int ret = 0, structSize = gl->fragSize; + if (gl->nuniforms+n > gl->cuniforms) { + unsigned char* uniforms; + int cuniforms = glnvg__maxi(gl->nuniforms+n, 128) + gl->cuniforms/2; // 1.5x Overallocate + uniforms = (unsigned char*)realloc(gl->uniforms, structSize * cuniforms); + if (uniforms == NULL) return -1; + gl->uniforms = uniforms; + gl->cuniforms = cuniforms; + } + ret = gl->nuniforms * structSize; + gl->nuniforms += n; + return ret; +} + +static struct GLNVGfragUniforms* nvg__fragUniformPtr(struct GLNVGcontext* gl, int i) +{ + return (struct GLNVGfragUniforms*)&gl->uniforms[i]; +} + +static void glnvg__vset(struct NVGvertex* vtx, float x, float y, float u, float v) +{ + vtx->x = x; + vtx->y = y; + vtx->u = u; + vtx->v = v; +} + +static void glnvg__renderFill(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor, float fringe, + const float* bounds, const struct NVGpath* paths, int npaths) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + struct GLNVGcall* call = glnvg__allocCall(gl); + struct NVGvertex* quad; + struct GLNVGfragUniforms* frag; + int i, maxverts, offset; + + if (call == NULL) return; + + call->type = GLNVG_FILL; + call->pathOffset = glnvg__allocPaths(gl, npaths); + if (call->pathOffset == -1) goto error; + call->pathCount = npaths; + call->image = paint->image; + + if (npaths == 1 && paths[0].convex) + call->type = GLNVG_CONVEXFILL; + + // Allocate vertices for all the paths. + maxverts = glnvg__maxVertCount(paths, npaths) + 6; + offset = glnvg__allocVerts(gl, maxverts); + if (offset == -1) goto error; + + for (i = 0; i < npaths; i++) { + struct GLNVGpath* copy = &gl->paths[call->pathOffset + i]; + const struct NVGpath* path = &paths[i]; + memset(copy, 0, sizeof(struct GLNVGpath)); + if (path->nfill > 0) { + copy->fillOffset = offset; + copy->fillCount = path->nfill; + memcpy(&gl->verts[offset], path->fill, sizeof(struct NVGvertex) * path->nfill); + offset += path->nfill; + } + if (path->nstroke > 0) { + copy->strokeOffset = offset; + copy->strokeCount = path->nstroke; + memcpy(&gl->verts[offset], path->stroke, sizeof(struct NVGvertex) * path->nstroke); + offset += path->nstroke; + } + } + + // Quad + call->triangleOffset = offset; + call->triangleCount = 6; + quad = &gl->verts[call->triangleOffset]; + glnvg__vset(&quad[0], bounds[0], bounds[3], 0.5f, 1.0f); + glnvg__vset(&quad[1], bounds[2], bounds[3], 0.5f, 1.0f); + glnvg__vset(&quad[2], bounds[2], bounds[1], 0.5f, 1.0f); + + glnvg__vset(&quad[3], bounds[0], bounds[3], 0.5f, 1.0f); + glnvg__vset(&quad[4], bounds[2], bounds[1], 0.5f, 1.0f); + glnvg__vset(&quad[5], bounds[0], bounds[1], 0.5f, 1.0f); + + // Setup uniforms for draw calls + if (call->type == GLNVG_FILL) { + call->uniformOffset = glnvg__allocFragUniforms(gl, 2); + if (call->uniformOffset == -1) goto error; + // Simple shader for stencil + frag = nvg__fragUniformPtr(gl, call->uniformOffset); + memset(frag, 0, sizeof(*frag)); + frag->type = NSVG_SHADER_SIMPLE; + // Fill shader + glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset + gl->fragSize), paint, scissor, fringe, fringe); + } else { + call->uniformOffset = glnvg__allocFragUniforms(gl, 1); + if (call->uniformOffset == -1) goto error; + // Fill shader + glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, fringe, fringe); + } + + return; + +error: + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (gl->ncalls > 0) gl->ncalls--; +} + +static void glnvg__renderStroke(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor, float fringe, + float strokeWidth, const struct NVGpath* paths, int npaths) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + struct GLNVGcall* call = glnvg__allocCall(gl); + int i, maxverts, offset; + + if (call == NULL) return; + + call->type = GLNVG_STROKE; + call->pathOffset = glnvg__allocPaths(gl, npaths); + if (call->pathOffset == -1) goto error; + call->pathCount = npaths; + call->image = paint->image; + + // Allocate vertices for all the paths. + maxverts = glnvg__maxVertCount(paths, npaths); + offset = glnvg__allocVerts(gl, maxverts); + if (offset == -1) goto error; + + for (i = 0; i < npaths; i++) { + struct GLNVGpath* copy = &gl->paths[call->pathOffset + i]; + const struct NVGpath* path = &paths[i]; + memset(copy, 0, sizeof(struct GLNVGpath)); + if (path->nstroke) { + copy->strokeOffset = offset; + copy->strokeCount = path->nstroke; + memcpy(&gl->verts[offset], path->stroke, sizeof(struct NVGvertex) * path->nstroke); + offset += path->nstroke; + } + } + + // Fill shader + call->uniformOffset = glnvg__allocFragUniforms(gl, 1); + if (call->uniformOffset == -1) goto error; + glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, strokeWidth, fringe); + + return; + +error: + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (gl->ncalls > 0) gl->ncalls--; +} + +static void glnvg__renderTriangles(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor, + const struct NVGvertex* verts, int nverts) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + struct GLNVGcall* call = glnvg__allocCall(gl); + struct GLNVGfragUniforms* frag; + + if (call == NULL) return; + + call->type = GLNVG_TRIANGLES; + call->image = paint->image; + + // Allocate vertices for all the paths. + call->triangleOffset = glnvg__allocVerts(gl, nverts); + if (call->triangleOffset == -1) goto error; + call->triangleCount = nverts; + + memcpy(&gl->verts[call->triangleOffset], verts, sizeof(struct NVGvertex) * nverts); + + // Fill shader + call->uniformOffset = glnvg__allocFragUniforms(gl, 1); + if (call->uniformOffset == -1) goto error; + frag = nvg__fragUniformPtr(gl, call->uniformOffset); + glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f); + frag->type = NSVG_SHADER_IMG; + + return; + +error: + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (gl->ncalls > 0) gl->ncalls--; +} + +static void glnvg__renderDelete(void* uptr) +{ + struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr; + int i; + if (gl == NULL) return; + + glnvg__deleteShader(&gl->shader); + +#if NANOVG_GL3 +#if NANOVG_GL_USE_UNIFORMBUFFER + if (gl->viewBuf != 0) + glDeleteBuffers(1, &gl->viewBuf); + if (gl->fragBuf != 0) + glDeleteBuffers(1, &gl->fragBuf); +#endif + if (gl->vertArr != 0) + glDeleteVertexArrays(1, &gl->vertArr); +#endif + if (gl->vertBuf != 0) + glDeleteBuffers(1, &gl->vertBuf); + + for (i = 0; i < gl->ntextures; i++) { + if (gl->textures[i].tex != 0) + glDeleteTextures(1, &gl->textures[i].tex); + } + free(gl->textures); + + free(gl); +} + + +#if defined NANOVG_GL2 +struct NVGcontext* nvgCreateGL2(int atlasw, int atlash, int edgeaa) +#elif defined NANOVG_GL3 +struct NVGcontext* nvgCreateGL3(int atlasw, int atlash, int edgeaa) +#elif defined NANOVG_GLES2 +struct NVGcontext* nvgCreateGLES2(int atlasw, int atlash, int edgeaa) +#elif defined NANOVG_GLES3 +struct NVGcontext* nvgCreateGLES3(int atlasw, int atlash, int edgeaa) +#endif +{ + struct NVGparams params; + struct NVGcontext* ctx = NULL; + struct GLNVGcontext* gl = (struct GLNVGcontext*)malloc(sizeof(struct GLNVGcontext)); + if (gl == NULL) goto error; + memset(gl, 0, sizeof(struct GLNVGcontext)); + + memset(¶ms, 0, sizeof(params)); + params.renderCreate = glnvg__renderCreate; + params.renderCreateTexture = glnvg__renderCreateTexture; + params.renderDeleteTexture = glnvg__renderDeleteTexture; + params.renderUpdateTexture = glnvg__renderUpdateTexture; + params.renderGetTextureSize = glnvg__renderGetTextureSize; + params.renderViewport = glnvg__renderViewport; + params.renderFlush = glnvg__renderFlush; + params.renderFill = glnvg__renderFill; + params.renderStroke = glnvg__renderStroke; + params.renderTriangles = glnvg__renderTriangles; + params.renderDelete = glnvg__renderDelete; + params.userPtr = gl; + params.atlasWidth = atlasw; + params.atlasHeight = atlash; + params.edgeAntiAlias = edgeaa; + + gl->edgeAntiAlias = edgeaa; + + ctx = nvgCreateInternal(¶ms); + if (ctx == NULL) goto error; + + return ctx; + +error: + // 'gl' is freed by nvgDeleteInternal. + if (ctx != NULL) nvgDeleteInternal(ctx); + return NULL; +} + +#if NANOVG_GL2 +void nvgDeleteGL2(struct NVGcontext* ctx) +#elif NANOVG_GL3 +void nvgDeleteGL3(struct NVGcontext* ctx) +#elif NANOVG_GLES2 +void nvgDeleteGLES2(struct NVGcontext* ctx) +#elif NANOVG_GLES3 +void nvgDeleteGLES3(struct NVGcontext* ctx) +#endif +{ + nvgDeleteInternal(ctx); +} + +#endif diff --git a/examples/nanovg_src/stb_image.c b/examples/nanovg_src/stb_image.c new file mode 100644 index 0000000..e21855d --- /dev/null +++ b/examples/nanovg_src/stb_image.c @@ -0,0 +1,4673 @@ +/* stbi-1.33 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c + when you control the images you're loading + no warranty implied; use at your own risk + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline (no JPEG progressive) + PNG 8-bit-per-channel only + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - overridable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD) + + Latest revisions: + 1.33 (2011-07-14) minor fixes suggested by Dave Moore + 1.32 (2011-07-13) info support for all filetypes (SpartanJ) + 1.31 (2011-06-19) a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) added ability to load files via io callbacks (Ben Wenger) + 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-uint8 to fix warnings (Laurent Gomila) + allow trailing 0s at end of image data (Laurent Gomila) + 1.26 (2010-07-24) fix bug in file buffering for PNG reported by SpartanJ + + See end of file for full revision history. + + TODO: + stbi_info support for BMP,PSD,HDR,PIC + + + ============================ Contributors ========================= + + Image formats Optimizations & bugfixes + Sean Barrett (jpeg, png, bmp) Fabian "ryg" Giesen + Nicolas Schulz (hdr, psd) + Jonathan Dummer (tga) Bug fixes & warning fixes + Jean-Marc Lienher (gif) Marc LeBlanc + Tom Seddon (pic) Christpher Lloyd + Thatcher Ulrich (psd) Dave Moore + Won Chun + the Horde3D community + Extensions, features Janez Zemva + Jetro Lauha (stbi_info) Jonathan Blow + James "moose2000" Brown (iPhone PNG) Laurent Gomila + Ben "Disch" Wenger (io callbacks) Aruelien Pocheville + Martin "SpartanJ" Golini Ryamond Barbiero + David Woo + + + If your name should be here but isn't, let Sean know. + +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// To get a header file for this, either cut and paste the header, +// or create stb_image.h, #define STBI_HEADER_FILE_ONLY, and +// then include stb_image.c from it. + +//// begin header file //////////////////////////////////////////////////// +// +// Limitations: +// - no jpeg progressive support +// - non-HDR formats support 8-bit samples only (jpeg, png) +// - no delayed line count (jpeg) -- IJG doesn't support either +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below): +// 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 *comp -- outputs # of image components in image file +// int req_comp -- 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. 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 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to easily see if it's opaque. +// +// 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, *comp 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. +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB; nominally they +// would silently load as BGR, except the existing code should have just +// failed on such iPhone PNGs. But you can disable this conversion by +// by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through. +// +// 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). +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. 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); +// +// =========================================================================== +// +// 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). + + +#ifndef STBI_NO_STDIO + +#if defined(_MSC_VER) && _MSC_VER >= 0x1400 +#define _CRT_SECURE_NO_WARNINGS // suppress bogus warnings about fopen() +#endif + +#include +#endif + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +extern stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +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,unsigned n); // skip the next 'n' bytes + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +extern stbi_uc *stbi_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_HDR + extern float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + + #ifndef STBI_NO_STDIO + extern float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + extern float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + #endif + + extern float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + + extern void stbi_hdr_to_ldr_gamma(float gamma); + extern void stbi_hdr_to_ldr_scale(float scale); + + extern void stbi_ldr_to_hdr_gamma(float gamma); + extern void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_HDR + +// stbi_is_hdr is always defined +extern int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +extern int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +extern int stbi_is_hdr (char const *filename); +extern int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +extern const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +extern void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +extern int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +extern int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern int stbi_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#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. +extern 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" +extern void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + + +// ZLIB client - used by PNG, available for other purposes + +extern char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +extern char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +extern int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +extern char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +extern int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +// define faster low-level operations (typically SIMD support) +#ifdef STBI_SIMD +typedef void (*stbi_idct_8x8)(stbi_uc *out, int out_stride, short data[64], unsigned short *dequantize); +// compute an integer IDCT on "input" +// input[x] = data[x] * dequantize[x] +// write results to 'out': 64 samples, each run of 8 spaced by 'out_stride' +// CLAMP results to 0..255 +typedef void (*stbi_YCbCr_to_RGB_run)(stbi_uc *output, stbi_uc const *y, stbi_uc const *cb, stbi_uc const *cr, int count, int step); +// compute a conversion from YCbCr to RGB +// 'count' pixels +// write pixels to 'output'; each pixel is 'step' bytes (either 3 or 4; if 4, write '255' as 4th), order R,G,B +// y: Y input channel +// cb: Cb input channel; scale/biased to be 0..255 +// cr: Cr input channel; scale/biased to be 0..255 + +extern void stbi_install_idct(stbi_idct_8x8 func); +extern void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func); +#endif // STBI_SIMD + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifndef STBI_HEADER_FILE_ONLY + +#ifndef STBI_NO_HDR +#include // ldexp +#include // strcmp, strtok +#endif + +#ifndef STBI_NO_STDIO +#include +#endif +#include +#include +#include +#include + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +// implementation: +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef signed short int16; +typedef unsigned int uint32; +typedef signed int int32; +typedef unsigned int uint; + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(uint32)==4 ? 1 : -1]; + +#if defined(STBI_NO_STDIO) && !defined(STBI_NO_WRITE) +#define STBI_NO_WRITE +#endif + +#define STBI_NOTUSED(v) (void)sizeof(v) + +#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) >> (32 - (y)))) +#endif + +/////////////////////////////////////////////// +// +// stbi struct and start_xxx functions + +// stbi structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + 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; + uint8 buffer_start[128]; + + uint8 *img_buffer, *img_buffer_end; + uint8 *img_buffer_original; +} stbi; + + +static void refill_buffer(stbi *s); + +// initialize a memory-decode context +static void start_mem(stbi *s, uint8 const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (uint8 *) buffer; + s->img_buffer_end = (uint8 *) buffer+len; +} + +// initialize a callback-based context +static void start_callbacks(stbi *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->img_buffer_original = s->buffer_start; + refill_buffer(s); +} + +#ifndef STBI_NO_STDIO + +static int stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stdio_skip(void *user, unsigned n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi_stdio_callbacks = +{ + stdio_read, + stdio_skip, + stdio_eof, +}; + +static void start_file(stbi *s, FILE *f) +{ + start_callbacks(s, &stbi_stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi_rewind(stbi *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; +} + +static int stbi_jpeg_test(stbi *s); +static stbi_uc *stbi_jpeg_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_jpeg_info(stbi *s, int *x, int *y, int *comp); +static int stbi_png_test(stbi *s); +static stbi_uc *stbi_png_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_png_info(stbi *s, int *x, int *y, int *comp); +static int stbi_bmp_test(stbi *s); +static stbi_uc *stbi_bmp_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_tga_test(stbi *s); +static stbi_uc *stbi_tga_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_tga_info(stbi *s, int *x, int *y, int *comp); +static int stbi_psd_test(stbi *s); +static stbi_uc *stbi_psd_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_hdr_test(stbi *s); +static float *stbi_hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_pic_test(stbi *s); +static stbi_uc *stbi_pic_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_gif_test(stbi *s); +static stbi_uc *stbi_gif_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_gif_info(stbi *s, int *x, int *y, int *comp); + + +// this is not threadsafe +static const char *failure_reason; + +const char *stbi_failure_reason(void) +{ + return failure_reason; +} + +static int e(const char *str) +{ + failure_reason = str; + return 0; +} + +// e - error +// epf - error returning pointer to float +// epuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define e(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define e(x,y) e(y) +#else + #define e(x,y) e(x) +#endif + +#define epf(x,y) ((float *) (e(x,y)?NULL:NULL)) +#define epuc(x,y) ((unsigned char *) (e(x,y)?NULL:NULL)) + +void stbi_image_free(void *retval_from_stbi_load) +{ + free(retval_from_stbi_load); +} + +#ifndef STBI_NO_HDR +static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static unsigned char *stbi_load_main(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + if (stbi_jpeg_test(s)) return stbi_jpeg_load(s,x,y,comp,req_comp); + if (stbi_png_test(s)) return stbi_png_load(s,x,y,comp,req_comp); + if (stbi_bmp_test(s)) return stbi_bmp_load(s,x,y,comp,req_comp); + if (stbi_gif_test(s)) return stbi_gif_load(s,x,y,comp,req_comp); + if (stbi_psd_test(s)) return stbi_psd_load(s,x,y,comp,req_comp); + if (stbi_pic_test(s)) return stbi_pic_load(s,x,y,comp,req_comp); + + #ifndef STBI_NO_HDR + if (stbi_hdr_test(s)) { + float *hdr = stbi_hdr_load(s, x,y,comp,req_comp); + return hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + // test tga last because it's a crappy test! + if (stbi_tga_test(s)) + return stbi_tga_load(s,x,y,comp,req_comp); + return epuc("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +unsigned char *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = fopen(filename, "rb"); + unsigned char *result; + if (!f) return epuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s,f); + return stbi_load_main(&s,x,y,comp,req_comp); +} +#endif //!STBI_NO_STDIO + +unsigned char *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s,buffer,len); + return stbi_load_main(&s,x,y,comp,req_comp); +} + +unsigned char *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi_load_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_HDR + +float *stbi_loadf_main(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi_hdr_test(s)) + return stbi_hdr_load(s,x,y,comp,req_comp); + #endif + data = stbi_load_main(s, x, y, comp, req_comp); + if (data) + return ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return epf("unknown image type", "Image not of any known type, or corrupt"); +} + +float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s,buffer,len); + return stbi_loadf_main(&s,x,y,comp,req_comp); +} + +float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi_loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = fopen(filename, "rb"); + float *result; + if (!f) return epf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s,f); + return stbi_loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_HDR + +// these is-hdr-or-not is defined independent of whether STBI_NO_HDR is +// defined, for API simplicity; if STBI_NO_HDR is defined, it always +// reports false! + +int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi s; + start_mem(&s,buffer,len); + return stbi_hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +extern int stbi_is_hdr (char const *filename) +{ + FILE *f = fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +extern int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + stbi s; + start_file(&s,f); + return stbi_hdr_test(&s); + #else + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +extern int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi s; + start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi_hdr_test(&s); + #else + return 0; + #endif +} + +#ifndef STBI_NO_HDR +static float h2l_gamma_i=1.0f/2.2f, h2l_scale_i=1.0f; +static float l2h_gamma=2.2f, l2h_scale=1.0f; + +void stbi_hdr_to_ldr_gamma(float gamma) { h2l_gamma_i = 1/gamma; } +void stbi_hdr_to_ldr_scale(float scale) { h2l_scale_i = 1/scale; } + +void stbi_ldr_to_hdr_gamma(float gamma) { l2h_gamma = gamma; } +void stbi_ldr_to_hdr_scale(float scale) { l2h_scale = scale; } +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + SCAN_load=0, + SCAN_type, + SCAN_header +}; + +static void refill_buffer(stbi *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_end-1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static int get8(stbi *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int at_eof(stbi *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; +} + +stbi_inline static uint8 get8u(stbi *s) +{ + return (uint8) get8(s); +} + +static void skip(stbi *s, int n) +{ + if (s->io.read) { + int blen = 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; +} + +static int getn(stbi *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = 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; +} + +static int get16(stbi *s) +{ + int z = get8(s); + return (z << 8) + get8(s); +} + +static uint32 get32(stbi *s) +{ + uint32 z = get16(s); + return (z << 16) + get16(s); +} + +static int get16le(stbi *s) +{ + int z = get8(s); + return z + (get8(s) << 8); +} + +static uint32 get32le(stbi *s) +{ + uint32 z = get16le(s); + return z + (get16le(s) << 16); +} + +////////////////////////////////////////////////////////////////////////////// +// +// 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 uint8 compute_y(int r, int g, int b) +{ + return (uint8) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *convert_format(unsigned char *data, int img_n, int req_comp, uint x, uint y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + assert(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) malloc(req_comp * x * y); + if (good == NULL) { + free(data); + return epuc("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 COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case 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 (COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: assert(0); + } + #undef CASE + } + + free(data); + return good; +} + +#ifndef STBI_NO_HDR +static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output = (float *) malloc(x * y * comp * sizeof(float)); + if (output == NULL) { free(data); return epf("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, l2h_gamma) * l2h_scale; + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + free(data); + return output; +} + +#define float2int(x) ((int) (x)) +static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output = (stbi_uc *) malloc(x * y * comp); + if (output == NULL) { free(data); return epuc("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]*h2l_scale_i, h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (uint8) 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] = (uint8) float2int(z); + } + } + free(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder (not actually fully baseline implementation) +// +// simple implementation +// - channel subsampling of at most 2 in each dimension +// - 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 +// - uses a lot of intermediate memory, could cache poorly +// - load http://nothings.org/remote/anemones.jpg 3 times on 2.8Ghz P4 +// stb_jpeg: 1.34 seconds (MSVC6, default release build) +// stb_jpeg: 1.06 seconds (MSVC6, processor = Pentium Pro) +// IJL11.dll: 1.08 seconds (compiled by intel) +// IJG 1998: 0.98 seconds (MSVC6, makefile provided by IJG) +// IJG 1998: 0.95 seconds (MSVC6, makefile + proc=PPro) + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + uint8 fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + uint16 code[256]; + uint8 values[256]; + uint8 size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} huffman; + +typedef struct +{ + #ifdef STBI_SIMD + unsigned short dequant2[4][64]; + #endif + stbi *s; + huffman huff_dc[4]; + huffman huff_ac[4]; + uint8 dequant[4][64]; + +// 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; + uint8 *data; + void *raw_data; + uint8 *linebuf; + } img_comp[4]; + + 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 scan_n, order[4]; + int restart_interval, todo; +} jpeg; + +static int build_huffman(huffman *h, int *count) +{ + int i,j,k=0,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++] = (uint8) (i+1); + 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++] = (uint16) (code++); + if (code-1 >= (1 << j)) return e("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] = (uint8) i; + } + } + } + return 1; +} + +static void grow_buffer_unsafe(jpeg *j) +{ + do { + int b = j->nomore ? 0 : get8(j->s); + if (b == 0xff) { + int c = get8(j->s); + 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 uint32 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 decode(jpeg *j, huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) 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)) & bmask[k]) + h->delta[k]; + assert((((j->code_buffer) >> (32 - h->size[c])) & 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]; +} + +// combined JPEG 'receive' and JPEG 'extend', since baseline +// always extends everything it receives. +stbi_inline static int extend_receive(jpeg *j, int n) +{ + unsigned int m = 1 << (n-1); + unsigned int k; + if (j->code_bits < n) grow_buffer_unsafe(j); + + #if 1 + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~bmask[n]; + k &= bmask[n]; + j->code_bits -= n; + #else + k = (j->code_buffer >> (32 - n)) & bmask[n]; + j->code_bits -= n; + j->code_buffer <<= n; + #endif + // the following test is probably a random branch that won't + // predict well. I tried to table accelerate it but failed. + // maybe it's compiling as a conditional move? + if (k < m) + return (-1 << n) + k + 1; + else + return k; +} + +// 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 uint8 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 decode_block(jpeg *j, short data[64], huffman *hdc, huffman *hac, int b) +{ + int diff,dc,k; + int t = decode(j, hdc); + if (t < 0) return e("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 ? extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) dc; + + // decode AC components, see JPEG spec + k = 1; + do { + int r,s; + int rs = decode(j, hac); + if (rs < 0) return e("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 + data[dezigzag[k++]] = (short) extend_receive(j,s); + } + } while (k < 64); + return 1; +} + +// take a -128..127 value and clamp it and convert to 0..255 +stbi_inline static uint8 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 (uint8) x; +} + +#define f2f(x) (int) (((x) * 4096 + 0.5)) +#define fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define 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) * f2f(0.5411961f); \ + t2 = p1 + p3*f2f(-1.847759065f); \ + t3 = p1 + p2*f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = fsh(p2+p3); \ + t1 = 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)*f2f( 1.175875602f); \ + t0 = t0*f2f( 0.298631336f); \ + t1 = t1*f2f( 2.053119869f); \ + t2 = t2*f2f( 3.072711026f); \ + t3 = t3*f2f( 1.501321110f); \ + p1 = p5 + p1*f2f(-0.899976223f); \ + p2 = p5 + p2*f2f(-2.562915447f); \ + p3 = p3*f2f(-1.961570560f); \ + p4 = p4*f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +#ifdef STBI_SIMD +typedef unsigned short stbi_dequantize_t; +#else +typedef uint8 stbi_dequantize_t; +#endif + +// .344 seconds on 3*anemones.jpg +static void idct_block(uint8 *out, int out_stride, short data[64], stbi_dequantize_t *dequantize) +{ + int i,val[64],*v=val; + stbi_dequantize_t *dq = dequantize; + uint8 *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d,++dq, ++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] * dq[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], + d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[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 + 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] = clamp((x0+t3) >> 17); + o[7] = clamp((x0-t3) >> 17); + o[1] = clamp((x1+t2) >> 17); + o[6] = clamp((x1-t2) >> 17); + o[2] = clamp((x2+t1) >> 17); + o[5] = clamp((x2-t1) >> 17); + o[3] = clamp((x3+t0) >> 17); + o[4] = clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SIMD +static stbi_idct_8x8 stbi_idct_installed = idct_block; + +void stbi_install_idct(stbi_idct_8x8 func) +{ + stbi_idct_installed = func; +} +#endif + +#define 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 uint8 get_marker(jpeg *j) +{ + uint8 x; + if (j->marker != MARKER_none) { x = j->marker; j->marker = MARKER_none; return x; } + x = get8u(j->s); + if (x != 0xff) return MARKER_none; + while (x == 0xff) + x = get8u(j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, reset the entropy decoder and +// the dc prediction +static void reset(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 = 0; + j->marker = MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + // 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 parse_entropy_coded_data(jpeg *z) +{ + reset(z); + if (z->scan_n == 1) { + int i,j; + #ifdef STBI_SIMD + __declspec(align(16)) + #endif + 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) { + if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; + #ifdef STBI_SIMD + stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); + #else + idct_block(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); + #endif + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!RESTART(z->marker)) return 1; + reset(z); + } + } + } + } else { // interleaved! + int i,j,k,x,y; + 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; + if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; + #ifdef STBI_SIMD + stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); + #else + idct_block(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); + #endif + } + } + } + // 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) grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!RESTART(z->marker)) return 1; + reset(z); + } + } + } + } + return 1; +} + +static int process_marker(jpeg *z, int m) +{ + int L; + switch (m) { + case MARKER_none: // no marker found + return e("expected marker","Corrupt JPEG"); + + case 0xC2: // SOF - progressive + return e("progressive jpeg","JPEG format not supported (progressive)"); + + case 0xDD: // DRI - specify restart interval + if (get16(z->s) != 4) return e("bad DRI len","Corrupt JPEG"); + z->restart_interval = get16(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = get16(z->s)-2; + while (L > 0) { + int q = get8(z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return e("bad DQT type","Corrupt JPEG"); + if (t > 3) return e("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][dezigzag[i]] = get8u(z->s); + #ifdef STBI_SIMD + for (i=0; i < 64; ++i) + z->dequant2[t][i] = z->dequant[t][i]; + #endif + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = get16(z->s)-2; + while (L > 0) { + uint8 *v; + int sizes[16],i,m=0; + int q = get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return e("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = get8(z->s); + m += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < m; ++i) + v[i] = get8u(z->s); + L -= m; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + skip(z->s, get16(z->s)-2); + return 1; + } + return 0; +} + +// after we see SOS +static int process_scan_header(jpeg *z) +{ + int i; + int Ls = get16(z->s); + z->scan_n = get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return e("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return e("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = get8(z->s), which; + int q = 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; + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return e("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return e("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + if (get8(z->s) != 0) return e("bad SOS","Corrupt JPEG"); + get8(z->s); // should be 63, but might be 0 + if (get8(z->s) != 0) return e("bad SOS","Corrupt JPEG"); + + return 1; +} + +static int process_frame_header(jpeg *z, int scan) +{ + stbi *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = get16(s); if (Lf < 11) return e("bad SOF len","Corrupt JPEG"); // JPEG + p = get8(s); if (p != 8) return e("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = get16(s); if (s->img_y == 0) return e("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = get16(s); if (s->img_x == 0) return e("0 width","Corrupt JPEG"); // JPEG requires + c = get8(s); + if (c != 3 && c != 1) return e("bad component count","Corrupt JPEG"); // JFIF requires + 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 e("bad SOF len","Corrupt JPEG"); + + for (i=0; i < s->img_n; ++i) { + z->img_comp[i].id = get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! + return e("bad component ID","Corrupt JPEG"); + q = get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return e("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return e("bad V","Corrupt JPEG"); + z->img_comp[i].tq = get8(s); if (z->img_comp[i].tq > 3) return e("bad TQ","Corrupt JPEG"); + } + + if (scan != SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("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; + } + + // 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; + 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 + 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].raw_data = malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + free(z->img_comp[i].raw_data); + z->img_comp[i].data = NULL; + } + return e("outofmem", "Out of memory"); + } + // align blocks for installable-idct using mmx/sse + z->img_comp[i].data = (uint8*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define DNL(x) ((x) == 0xdc) +#define SOI(x) ((x) == 0xd8) +#define EOI(x) ((x) == 0xd9) +#define SOF(x) ((x) == 0xc0 || (x) == 0xc1) +#define SOS(x) ((x) == 0xda) + +static int decode_jpeg_header(jpeg *z, int scan) +{ + int m; + z->marker = MARKER_none; // initialize cached marker to empty + m = get_marker(z); + if (!SOI(m)) return e("no SOI","Corrupt JPEG"); + if (scan == SCAN_type) return 1; + m = get_marker(z); + while (!SOF(m)) { + if (!process_marker(z,m)) return 0; + m = get_marker(z); + while (m == MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (at_eof(z->s)) return e("no SOF", "Corrupt JPEG"); + m = get_marker(z); + } + } + if (!process_frame_header(z, scan)) return 0; + return 1; +} + +static int decode_jpeg_image(jpeg *j) +{ + int m; + j->restart_interval = 0; + if (!decode_jpeg_header(j, SCAN_load)) return 0; + m = get_marker(j); + while (!EOI(m)) { + if (SOS(m)) { + if (!process_scan_header(j)) return 0; + if (!parse_entropy_coded_data(j)) return 0; + if (j->marker == MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!at_eof(j->s)) { + int x = get8(j->s); + if (x == 255) { + j->marker = get8u(j->s); + break; + } else if (x != 0) { + return 0; + } + } + // if we reach eof without hitting a marker, get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!process_marker(j, m)) return 0; + } + m = get_marker(j); + } + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef uint8 *(*resample_row_func)(uint8 *out, uint8 *in0, uint8 *in1, + int w, int hs); + +#define div4(x) ((uint8) ((x) >> 2)) + +static uint8 *resample_row_1(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static uint8* resample_row_v_2(uint8 *out, uint8 *in_near, uint8 *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] = div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static uint8* resample_row_h_2(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + uint8 *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] = div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = div4(n+input[i-1]); + out[i*2+1] = div4(n+input[i+1]); + } + out[i*2+0] = 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 div16(x) ((uint8) ((x) >> 4)) + +static uint8 *resample_row_hv_2(uint8 *out, uint8 *in_near, uint8 *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] = div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = div16(3*t0 + t1 + 8); + out[i*2 ] = div16(3*t1 + t0 + 8); + } + out[w*2-1] = div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +static uint8 *resample_row_generic(uint8 *out, uint8 *in_near, uint8 *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; +} + +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) + +// 0.38 seconds on 3*anemones.jpg (0.25 with processor = Pro) +// VC6 without processor=Pro is generating multiple LEAs per multiply! +static void YCbCr_to_RGB_row(uint8 *out, const uint8 *y, const uint8 *pcb, const uint8 *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + 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] = (uint8)r; + out[1] = (uint8)g; + out[2] = (uint8)b; + out[3] = 255; + out += step; + } +} + +#ifdef STBI_SIMD +static stbi_YCbCr_to_RGB_run stbi_YCbCr_installed = YCbCr_to_RGB_row; + +void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func) +{ + stbi_YCbCr_installed = func; +} +#endif + + +// clean up the temporary component buffers +static void cleanup_jpeg(jpeg *j) +{ + int i; + for (i=0; i < j->s->img_n; ++i) { + if (j->img_comp[i].data) { + free(j->img_comp[i].raw_data); + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].linebuf) { + free(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } +} + +typedef struct +{ + resample_row_func resample; + uint8 *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; + +static uint8 *load_jpeg_image(jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + // validate req_comp + if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error"); + z->s->img_n = 0; + + // load a jpeg image from whichever source + if (!decode_jpeg_image(z)) { cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n; + + if (z->s->img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + uint i,j; + uint8 *output; + uint8 *coutput[4]; + + 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 = (uint8 *) malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { cleanup_jpeg(z); return epuc("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 = resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = resample_row_hv_2; + else r->resample = resample_row_generic; + } + + // can't error after this so, this is safe + output = (uint8 *) malloc(n * z->s->img_x * z->s->img_y + 1); + if (!output) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + uint8 *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) { + uint8 *y = coutput[0]; + if (z->s->img_n == 3) { + #ifdef STBI_SIMD + stbi_YCbCr_installed(out, y, coutput[1], coutput[2], z->s.img_x, n); + #else + YCbCr_to_RGB_row(out, y, coutput[1], coutput[2], z->s->img_x, n); + #endif + } 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 { + uint8 *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; + } + } + cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n; // report original components, not output + return output; + } +} + +static unsigned char *stbi_jpeg_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + jpeg j; + j.s = s; + return load_jpeg_image(&j, x,y,comp,req_comp); +} + +static int stbi_jpeg_test(stbi *s) +{ + int r; + jpeg j; + j.s = s; + r = decode_jpeg_header(&j, SCAN_type); + stbi_rewind(s); + return r; +} + +static int stbi_jpeg_info_raw(jpeg *j, int *x, int *y, int *comp) +{ + if (!decode_jpeg_header(j, 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; + return 1; +} + +static int stbi_jpeg_info(stbi *s, int *x, int *y, int *comp) +{ + jpeg j; + j.s = s; + return stbi_jpeg_info_raw(&j, x, y, comp); +} + +// 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 + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define ZFAST_BITS 9 // accelerate all cases in default tables +#define ZFAST_MASK ((1 << ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + uint16 fast[1 << ZFAST_BITS]; + uint16 firstcode[16]; + int maxcode[17]; + uint16 firstsymbol[16]; + uint8 size[288]; + uint16 value[288]; +} zhuffman; + +stbi_inline static int 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 bit_reverse(int v, int bits) +{ + assert(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return bitreverse16(v) >> (16-bits); +} + +static int zbuild_huffman(zhuffman *z, uint8 *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, 255, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + assert(sizes[i] <= (1 << i)); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (uint16) code; + z->firstsymbol[i] = (uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return e("bad codelengths","Corrupt JPEG"); + 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]; + z->size[c] = (uint8)s; + z->value[c] = (uint16)i; + if (s <= ZFAST_BITS) { + int k = bit_reverse(next_code[s],s); + while (k < (1 << ZFAST_BITS)) { + z->fast[k] = (uint16) c; + k += (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 +{ + uint8 *zbuffer, *zbuffer_end; + int num_bits; + uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + zhuffman z_length, z_distance; +} zbuf; + +stbi_inline static int zget8(zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void fill_bits(zbuf *z) +{ + do { + assert(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int zreceive(zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +stbi_inline static int zhuffman_decode(zbuf *a, zhuffman *z) +{ + int b,s,k; + if (a->num_bits < 16) fill_bits(a); + b = z->fast[a->code_buffer & ZFAST_MASK]; + if (b < 0xffff) { + s = z->size[b]; + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; + } + + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = bit_reverse(a->code_buffer, 16); + for (s=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]; + assert(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +static int expand(zbuf *z, int n) // need to make room for n bytes +{ + char *q; + int cur, limit; + if (!z->z_expandable) return e("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) realloc(z->zout_start, limit); + if (q == NULL) return e("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int length_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 int length_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 int dist_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 int dist_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 parse_huffman_block(zbuf *a) +{ + for(;;) { + int z = zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return e("bad huffman code","Corrupt PNG"); // error in huffman codes + if (a->zout >= a->zout_end) if (!expand(a, 1)) return 0; + *a->zout++ = (char) z; + } else { + uint8 *p; + int len,dist; + if (z == 256) return 1; + z -= 257; + len = length_base[z]; + if (length_extra[z]) len += zreceive(a, length_extra[z]); + z = zhuffman_decode(a, &a->z_distance); + if (z < 0) return e("bad huffman code","Corrupt PNG"); + dist = dist_base[z]; + if (dist_extra[z]) dist += zreceive(a, dist_extra[z]); + if (a->zout - a->zout_start < dist) return e("bad dist","Corrupt PNG"); + if (a->zout + len > a->zout_end) if (!expand(a, len)) return 0; + p = (uint8 *) (a->zout - dist); + while (len--) + *a->zout++ = *p++; + } + } +} + +static int compute_huffman_codes(zbuf *a) +{ + static uint8 length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + zhuffman z_codelength; + uint8 lencodes[286+32+137];//padding for maximum single op + uint8 codelength_sizes[19]; + int i,n; + + int hlit = zreceive(a,5) + 257; + int hdist = zreceive(a,5) + 1; + int hclen = zreceive(a,4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (uint8) s; + } + if (!zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = zhuffman_decode(a, &z_codelength); + assert(c >= 0 && c < 19); + if (c < 16) + lencodes[n++] = (uint8) c; + else if (c == 16) { + c = zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + assert(c == 18); + c = zreceive(a,7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return e("bad codelengths","Corrupt PNG"); + if (!zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int parse_uncompressed_block(zbuf *a) +{ + uint8 header[4]; + int len,nlen,k; + if (a->num_bits & 7) + zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (uint8) (a->code_buffer & 255); // wtf this warns? + a->code_buffer >>= 8; + a->num_bits -= 8; + } + assert(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = (uint8) zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return e("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return e("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!expand(a, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int parse_zlib_header(zbuf *a) +{ + int cmf = zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = zget8(a); + if ((cmf*256+flg) % 31 != 0) return e("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return e("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return e("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static uint8 default_length[288], default_distance[32]; +static void init_defaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) default_length[i] = 8; + for ( ; i <= 255; ++i) default_length[i] = 9; + for ( ; i <= 279; ++i) default_length[i] = 7; + for ( ; i <= 287; ++i) default_length[i] = 8; + + for (i=0; i <= 31; ++i) default_distance[i] = 5; +} + +int stbi_png_partial; // a quick hack to only allow decoding some of a PNG... I should implement real streaming support instead +static int parse_zlib(zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = zreceive(a,1); + type = zreceive(a,2); + if (type == 0) { + if (!parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!default_distance[31]) init_defaults(); + if (!zbuild_huffman(&a->z_length , default_length , 288)) return 0; + if (!zbuild_huffman(&a->z_distance, default_distance, 32)) return 0; + } else { + if (!compute_huffman_codes(a)) return 0; + } + if (!parse_huffman_block(a)) return 0; + } + if (stbi_png_partial && a->zout - a->zout_start > 65536) + break; + } while (!final); + return 1; +} + +static int do_zlib(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 parse_zlib(a, parse_header); +} + +char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + zbuf a; + char *p = (char *) malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (uint8 *) buffer; + a.zbuffer_end = (uint8 *) buffer + len; + if (do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + zbuf a; + char *p = (char *) malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (uint8 *) buffer; + a.zbuffer_end = (uint8 *) buffer + len; + if (do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + zbuf a; + a.zbuffer = (uint8 *) ibuffer; + a.zbuffer_end = (uint8 *) ibuffer + ilen; + if (do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + zbuf a; + char *p = (char *) malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (uint8 *) buffer; + a.zbuffer_end = (uint8 *) buffer+len; + if (do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + zbuf a; + a.zbuffer = (uint8 *) ibuffer; + a.zbuffer_end = (uint8 *) ibuffer + ilen; + if (do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +// 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 + + +typedef struct +{ + uint32 length; + uint32 type; +} chunk; + +#define PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static chunk get_chunk_header(stbi *s) +{ + chunk c; + c.length = get32(s); + c.type = get32(s); + return c; +} + +static int check_png_header(stbi *s) +{ + static uint8 png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (get8u(s) != png_sig[i]) return e("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi *s; + uint8 *idata, *expanded, *out; +} png; + + +enum { + F_none=0, F_sub=1, F_up=2, F_avg=3, F_paeth=4, + F_avg_first, F_paeth_first +}; + +static uint8 first_row_filter[5] = +{ + F_none, F_sub, F_none, F_avg_first, F_paeth_first +}; + +static int paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +// create the png data from post-deflated data +static int create_png_image_raw(png *a, uint8 *raw, uint32 raw_len, int out_n, uint32 x, uint32 y) +{ + stbi *s = a->s; + uint32 i,j,stride = x*out_n; + int k; + int img_n = s->img_n; // copy it into a local for later + assert(out_n == s->img_n || out_n == s->img_n+1); + if (stbi_png_partial) y = 1; + a->out = (uint8 *) malloc(x * y * out_n); + if (!a->out) return e("outofmem", "Out of memory"); + if (!stbi_png_partial) { + if (s->img_x == x && s->img_y == y) { + if (raw_len != (img_n * x + 1) * y) return e("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < (img_n * x + 1) * y) return e("not enough pixels","Corrupt PNG"); + } + } + for (j=0; j < y; ++j) { + uint8 *cur = a->out + stride*j; + uint8 *prior = cur - stride; + int filter = *raw++; + if (filter > 4) return e("invalid filter","Corrupt PNG"); + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + // handle first pixel explicitly + for (k=0; k < img_n; ++k) { + switch (filter) { + case F_none : cur[k] = raw[k]; break; + case F_sub : cur[k] = raw[k]; break; + case F_up : cur[k] = raw[k] + prior[k]; break; + case F_avg : cur[k] = raw[k] + (prior[k]>>1); break; + case F_paeth : cur[k] = (uint8) (raw[k] + paeth(0,prior[k],0)); break; + case F_avg_first : cur[k] = raw[k]; break; + case F_paeth_first: cur[k] = raw[k]; break; + } + } + if (img_n != out_n) cur[img_n] = 255; + raw += img_n; + cur += out_n; + prior += out_n; + // this is a little gross, so that we don't switch per-pixel or per-component + if (img_n == out_n) { + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, raw+=img_n,cur+=img_n,prior+=img_n) \ + for (k=0; k < img_n; ++k) + switch (filter) { + CASE(F_none) cur[k] = raw[k]; break; + CASE(F_sub) cur[k] = raw[k] + cur[k-img_n]; break; + CASE(F_up) cur[k] = raw[k] + prior[k]; break; + CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-img_n])>>1); break; + CASE(F_paeth) cur[k] = (uint8) (raw[k] + paeth(cur[k-img_n],prior[k],prior[k-img_n])); break; + CASE(F_avg_first) cur[k] = raw[k] + (cur[k-img_n] >> 1); break; + CASE(F_paeth_first) cur[k] = (uint8) (raw[k] + paeth(cur[k-img_n],0,0)); break; + } + #undef CASE + } else { + assert(img_n+1 == out_n); + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ + for (k=0; k < img_n; ++k) + switch (filter) { + CASE(F_none) cur[k] = raw[k]; break; + CASE(F_sub) cur[k] = raw[k] + cur[k-out_n]; break; + CASE(F_up) cur[k] = raw[k] + prior[k]; break; + CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-out_n])>>1); break; + CASE(F_paeth) cur[k] = (uint8) (raw[k] + paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; + CASE(F_avg_first) cur[k] = raw[k] + (cur[k-out_n] >> 1); break; + CASE(F_paeth_first) cur[k] = (uint8) (raw[k] + paeth(cur[k-out_n],0,0)); break; + } + #undef CASE + } + } + return 1; +} + +static int create_png_image(png *a, uint8 *raw, uint32 raw_len, int out_n, int interlaced) +{ + uint8 *final; + int p; + int save; + if (!interlaced) + return create_png_image_raw(a, raw, raw_len, out_n, a->s->img_x, a->s->img_y); + save = stbi_png_partial; + stbi_png_partial = 0; + + // de-interlacing + final = (uint8 *) malloc(a->s->img_x * a->s->img_y * out_n); + 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) { + if (!create_png_image_raw(a, raw, raw_len, out_n, x, y)) { + free(final); + return 0; + } + for (j=0; j < y; ++j) + for (i=0; i < x; ++i) + memcpy(final + (j*yspc[p]+yorig[p])*a->s->img_x*out_n + (i*xspc[p]+xorig[p])*out_n, + a->out + (j*x+i)*out_n, out_n); + free(a->out); + raw += (x*out_n+1)*y; + raw_len -= (x*out_n+1)*y; + } + } + a->out = final; + + stbi_png_partial = save; + return 1; +} + +static int compute_transparency(png *z, uint8 tc[3], int out_n) +{ + stbi *s = z->s; + uint32 i, pixel_count = s->img_x * s->img_y; + uint8 *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + 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 expand_palette(png *a, uint8 *palette, int len, int pal_img_n) +{ + uint32 i, pixel_count = a->s->img_x * a->s->img_y; + uint8 *p, *temp_out, *orig = a->out; + + p = (uint8 *) malloc(pixel_count * pal_img_n); + if (p == NULL) return e("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; + } + } + free(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi_unpremultiply_on_load = 0; +static int stbi_de_iphone_flag = 0; + +void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi_unpremultiply_on_load = flag_true_if_should_unpremultiply; +} +void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi_de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi_de_iphone(png *z) +{ + stbi *s = z->s; + uint32 i, pixel_count = s->img_x * s->img_y; + uint8 *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + uint8 t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + assert(s->img_out_n == 4); + if (stbi_unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + uint8 a = p[3]; + uint8 t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + uint8 t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +static int parse_png_file(png *z, int scan, int req_comp) +{ + uint8 palette[1024], pal_img_n=0; + uint8 has_trans=0, tc[3]; + uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, iphone=0; + stbi *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!check_png_header(s)) return 0; + + if (scan == SCAN_type) return 1; + + for (;;) { + chunk c = get_chunk_header(s); + switch (c.type) { + case PNG_TYPE('C','g','B','I'): + iphone = stbi_de_iphone_flag; + skip(s, c.length); + break; + case PNG_TYPE('I','H','D','R'): { + int depth,color,comp,filter; + if (!first) return e("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return e("bad IHDR len","Corrupt PNG"); + s->img_x = get32(s); if (s->img_x > (1 << 24)) return e("too large","Very large image (corrupt?)"); + s->img_y = get32(s); if (s->img_y > (1 << 24)) return e("too large","Very large image (corrupt?)"); + depth = get8(s); if (depth != 8) return e("8bit only","PNG not supported: 8-bit only"); + color = get8(s); if (color > 6) return e("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return e("bad ctype","Corrupt PNG"); + comp = get8(s); if (comp) return e("bad comp method","Corrupt PNG"); + filter= get8(s); if (filter) return e("bad filter method","Corrupt PNG"); + interlace = get8(s); if (interlace>1) return e("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return e("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 e("too large", "Image too large to decode"); + if (scan == SCAN_header) return 1; + } 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 e("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case PNG_TYPE('P','L','T','E'): { + if (first) return e("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return e("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return e("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = get8u(s); + palette[i*4+1] = get8u(s); + palette[i*4+2] = get8u(s); + palette[i*4+3] = 255; + } + break; + } + + case PNG_TYPE('t','R','N','S'): { + if (first) return e("first not IHDR", "Corrupt PNG"); + if (z->idata) return e("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return e("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return e("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = get8u(s); + } else { + if (!(s->img_n & 1)) return e("tRNS with alpha","Corrupt PNG"); + if (c.length != (uint32) s->img_n*2) return e("bad tRNS len","Corrupt PNG"); + has_trans = 1; + for (k=0; k < s->img_n; ++k) + tc[k] = (uint8) get16(s); // non 8-bit images will be larger + } + break; + } + + case PNG_TYPE('I','D','A','T'): { + if (first) return e("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return e("no PLTE","Corrupt PNG"); + if (scan == SCAN_header) { s->img_n = pal_img_n; return 1; } + if (ioff + c.length > idata_limit) { + uint8 *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + p = (uint8 *) realloc(z->idata, idata_limit); if (p == NULL) return e("outofmem", "Out of memory"); + z->idata = p; + } + if (!getn(s, z->idata+ioff,c.length)) return e("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case PNG_TYPE('I','E','N','D'): { + uint32 raw_len; + if (first) return e("first not IHDR", "Corrupt PNG"); + if (scan != SCAN_load) return 1; + if (z->idata == NULL) return e("no IDAT","Corrupt PNG"); + z->expanded = (uint8 *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, 16384, (int *) &raw_len, !iphone); + if (z->expanded == NULL) return 0; // zlib should set error + 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 (!create_png_image(z, z->expanded, raw_len, s->img_out_n, interlace)) return 0; + if (has_trans) + if (!compute_transparency(z, tc, s->img_out_n)) return 0; + if (iphone && 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 (!expand_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + free(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return e("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX chunk not known"; + invalid_chunk[0] = (uint8) (c.type >> 24); + invalid_chunk[1] = (uint8) (c.type >> 16); + invalid_chunk[2] = (uint8) (c.type >> 8); + invalid_chunk[3] = (uint8) (c.type >> 0); + #endif + return e(invalid_chunk, "PNG not supported: unknown chunk type"); + } + skip(s, c.length); + break; + } + // end of chunk, read and skip CRC + get32(s); + } +} + +static unsigned char *do_png(png *p, int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error"); + if (parse_png_file(p, SCAN_load, req_comp)) { + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + result = convert_format(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; + } + free(p->out); p->out = NULL; + free(p->expanded); p->expanded = NULL; + free(p->idata); p->idata = NULL; + + return result; +} + +static unsigned char *stbi_png_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + png p; + p.s = s; + return do_png(&p, x,y,comp,req_comp); +} + +static int stbi_png_test(stbi *s) +{ + int r; + r = check_png_header(s); + stbi_rewind(s); + return r; +} + +static int stbi_png_info_raw(png *p, int *x, int *y, int *comp) +{ + if (!parse_png_file(p, 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 *s, int *x, int *y, int *comp) +{ + png p; + p.s = s; + return stbi_png_info_raw(&p, x, y, comp); +} + +// Microsoft/Windows BMP image + +static int bmp_test(stbi *s) +{ + int sz; + if (get8(s) != 'B') return 0; + if (get8(s) != 'M') return 0; + get32le(s); // discard filesize + get16le(s); // discard reserved + get16le(s); // discard reserved + get32le(s); // discard data offset + sz = get32le(s); + if (sz == 12 || sz == 40 || sz == 56 || sz == 108) return 1; + return 0; +} + +static int stbi_bmp_test(stbi *s) +{ + int r = bmp_test(s); + stbi_rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int 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, z >>= 1; + return n; +} + +static int 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; +} + +static int shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +static stbi_uc *bmp_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + uint8 *out; + unsigned int mr=0,mg=0,mb=0,ma=0, fake_a=0; + stbi_uc pal[256][4]; + int psize=0,i,j,compress=0,width; + int bpp, flip_vertically, pad, target, offset, hsz; + if (get8(s) != 'B' || get8(s) != 'M') return epuc("not BMP", "Corrupt BMP"); + get32le(s); // discard filesize + get16le(s); // discard reserved + get16le(s); // discard reserved + offset = get32le(s); + hsz = get32le(s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108) return epuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = get16le(s); + s->img_y = get16le(s); + } else { + s->img_x = get32le(s); + s->img_y = get32le(s); + } + if (get16le(s) != 1) return epuc("bad BMP", "bad BMP"); + bpp = get16le(s); + if (bpp == 1) return epuc("monochrome", "BMP type not supported: 1-bit"); + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + if (hsz == 12) { + if (bpp < 24) + psize = (offset - 14 - 24) / 3; + } else { + compress = get32le(s); + if (compress == 1 || compress == 2) return epuc("BMP RLE", "BMP type not supported: RLE"); + get32le(s); // discard sizeof + get32le(s); // discard hres + get32le(s); // discard vres + get32le(s); // discard colorsused + get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + get32le(s); + get32le(s); + get32le(s); + get32le(s); + } + if (bpp == 16 || bpp == 32) { + mr = mg = mb = 0; + if (compress == 0) { + if (bpp == 32) { + mr = 0xffu << 16; + mg = 0xffu << 8; + mb = 0xffu << 0; + ma = 0xffu << 24; + fake_a = 1; // @TODO: check for cases like alpha value is all 0 and switch it to 255 + } else { + mr = 31u << 10; + mg = 31u << 5; + mb = 31u << 0; + } + } else if (compress == 3) { + mr = get32le(s); + mg = get32le(s); + mb = get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (mr == mg && mg == mb) { + // ?!?!? + return epuc("bad BMP", "bad BMP"); + } + } else + return epuc("bad BMP", "bad BMP"); + } + } else { + assert(hsz == 108); + mr = get32le(s); + mg = get32le(s); + mb = get32le(s); + ma = get32le(s); + get32le(s); // discard color space + for (i=0; i < 12; ++i) + get32le(s); // discard color space parameters + } + if (bpp < 16) + psize = (offset - 14 - hsz) >> 2; + } + 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 + out = (stbi_uc *) malloc(target * s->img_x * s->img_y); + if (!out) return epuc("outofmem", "Out of memory"); + if (bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { free(out); return epuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = get8u(s); + pal[i][1] = get8u(s); + pal[i][0] = get8u(s); + if (hsz != 12) get8(s); + pal[i][3] = 255; + } + skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4)); + if (bpp == 4) width = (s->img_x + 1) >> 1; + else if (bpp == 8) width = s->img_x; + else { free(out); return epuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=get8(s),v2=0; + if (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 = (bpp == 8) ? get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + 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; + skip(s, offset - 14 - hsz); + if (bpp == 24) width = 3 * s->img_x; + else if (bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (bpp == 24) { + easy = 1; + } else if (bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { free(out); return epuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = high_bit(mr)-7; rcount = bitcount(mr); + gshift = high_bit(mg)-7; gcount = bitcount(mr); + bshift = high_bit(mb)-7; bcount = bitcount(mr); + ashift = high_bit(ma)-7; acount = bitcount(mr); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + int a; + out[z+2] = get8u(s); + out[z+1] = get8u(s); + out[z+0] = get8u(s); + z += 3; + a = (easy == 2 ? get8(s) : 255); + if (target == 4) out[z++] = (uint8) a; + } + } else { + for (i=0; i < (int) s->img_x; ++i) { + uint32 v = (bpp == 16 ? get16le(s) : get32le(s)); + int a; + out[z++] = (uint8) shiftsigned(v & mr, rshift, rcount); + out[z++] = (uint8) shiftsigned(v & mg, gshift, gcount); + out[z++] = (uint8) shiftsigned(v & mb, bshift, bcount); + a = (ma ? shiftsigned(v & ma, ashift, acount) : 255); + if (target == 4) out[z++] = (uint8) a; + } + } + skip(s, pad); + } + } + 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 = convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} + +static stbi_uc *stbi_bmp_load(stbi *s,int *x, int *y, int *comp, int req_comp) +{ + return bmp_load(s, x,y,comp,req_comp); +} + + +// Targa Truevision - TGA +// by Jonathan Dummer + +static int tga_info(stbi *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp; + int sz; + get8u(s); // discard Offset + sz = get8u(s); // color type + if( sz > 1 ) { + stbi_rewind(s); + return 0; // only RGB or indexed allowed + } + sz = get8u(s); // image type + // only RGB or grey allowed, +/- RLE + if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0; + skip(s,9); + tga_w = get16le(s); + if( tga_w < 1 ) { + stbi_rewind(s); + return 0; // test width + } + tga_h = get16le(s); + if( tga_h < 1 ) { + stbi_rewind(s); + return 0; // test height + } + sz = get8(s); // bits per pixel + // only RGB or RGBA or grey allowed + if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32)) { + stbi_rewind(s); + return 0; + } + tga_comp = sz; + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp / 8; + return 1; // seems to have passed everything +} + +int stbi_tga_info(stbi *s, int *x, int *y, int *comp) +{ + return tga_info(s, x, y, comp); +} + +static int tga_test(stbi *s) +{ + int sz; + get8u(s); // discard Offset + sz = get8u(s); // color type + if ( sz > 1 ) return 0; // only RGB or indexed allowed + sz = get8u(s); // image type + if ( (sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11) ) return 0; // only RGB or grey allowed, +/- RLE + get16(s); // discard palette start + get16(s); // discard palette length + get8(s); // discard bits per palette color entry + get16(s); // discard x origin + get16(s); // discard y origin + if ( get16(s) < 1 ) return 0; // test width + if ( get16(s) < 1 ) return 0; // test height + sz = get8(s); // bits per pixel + if ( (sz != 8) && (sz != 16) && (sz != 24) && (sz != 32) ) return 0; // only RGB or RGBA or grey allowed + return 1; // seems to have passed everything +} + +static int stbi_tga_test(stbi *s) +{ + int res = tga_test(s); + stbi_rewind(s); + return res; +} + +static stbi_uc *tga_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = get8u(s); + int tga_indexed = get8u(s); + int tga_image_type = get8u(s); + int tga_is_RLE = 0; + int tga_palette_start = get16le(s); + int tga_palette_len = get16le(s); + int tga_palette_bits = get8u(s); + int tga_x_origin = get16le(s); + int tga_y_origin = get16le(s); + int tga_width = get16le(s); + int tga_height = get16le(s); + int tga_bits_per_pixel = get8u(s); + int tga_inverted = get8u(s); + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4]; + unsigned char trans_data[4]; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + /* int tga_alpha_bits = tga_inverted & 15; */ + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // error check + if ( //(tga_indexed) || + (tga_width < 1) || (tga_height < 1) || + (tga_image_type < 1) || (tga_image_type > 3) || + ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) && + (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32)) + ) + { + return NULL; // we don't report this as a bad TGA because we don't even know if it's TGA + } + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) + { + tga_bits_per_pixel = tga_palette_bits; + } + + // tga info + *x = tga_width; + *y = tga_height; + if ( (req_comp < 1) || (req_comp > 4) ) + { + // just use whatever the file was + req_comp = tga_bits_per_pixel / 8; + *comp = req_comp; + } else + { + // force a new number of components + *comp = tga_bits_per_pixel/8; + } + tga_data = (unsigned char*)malloc( tga_width * tga_height * req_comp ); + if (!tga_data) return epuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + skip(s, tga_offset ); + // do I need to load a palette? + if ( tga_indexed ) + { + // any data to skip? (offset usually = 0) + skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)malloc( tga_palette_len * tga_palette_bits / 8 ); + if (!tga_palette) return epuc("outofmem", "Out of memory"); + if (!getn(s, tga_palette, tga_palette_len * tga_palette_bits / 8 )) { + free(tga_data); + free(tga_palette); + return epuc("bad palette", "Corrupt TGA"); + } + } + // load the data + trans_data[0] = trans_data[1] = trans_data[2] = trans_data[3] = 0; + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE chunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = get8u(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 1 byte, then perform the lookup + int pal_idx = get8u(s); + if ( pal_idx >= tga_palette_len ) + { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_bits_per_pixel / 8; + for (j = 0; j*8 < tga_bits_per_pixel; ++j) + { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else + { + // read in the data raw + for (j = 0; j*8 < tga_bits_per_pixel; ++j) + { + raw_data[j] = get8u(s); + } + } + // convert raw to the intermediate format + switch (tga_bits_per_pixel) + { + case 8: + // Luminous => RGBA + trans_data[0] = raw_data[0]; + trans_data[1] = raw_data[0]; + trans_data[2] = raw_data[0]; + trans_data[3] = 255; + break; + case 16: + // Luminous,Alpha => RGBA + trans_data[0] = raw_data[0]; + trans_data[1] = raw_data[0]; + trans_data[2] = raw_data[0]; + trans_data[3] = raw_data[1]; + break; + case 24: + // BGR => RGBA + trans_data[0] = raw_data[2]; + trans_data[1] = raw_data[1]; + trans_data[2] = raw_data[0]; + trans_data[3] = 255; + break; + case 32: + // BGRA => RGBA + trans_data[0] = raw_data[2]; + trans_data[1] = raw_data[1]; + trans_data[2] = raw_data[0]; + trans_data[3] = raw_data[3]; + break; + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + // convert to final format + switch (req_comp) + { + case 1: + // RGBA => Luminance + tga_data[i*req_comp+0] = compute_y(trans_data[0],trans_data[1],trans_data[2]); + break; + case 2: + // RGBA => Luminance,Alpha + tga_data[i*req_comp+0] = compute_y(trans_data[0],trans_data[1],trans_data[2]); + tga_data[i*req_comp+1] = trans_data[3]; + break; + case 3: + // RGBA => RGB + tga_data[i*req_comp+0] = trans_data[0]; + tga_data[i*req_comp+1] = trans_data[1]; + tga_data[i*req_comp+2] = trans_data[2]; + break; + case 4: + // RGBA => RGBA + tga_data[i*req_comp+0] = trans_data[0]; + tga_data[i*req_comp+1] = trans_data[1]; + tga_data[i*req_comp+2] = trans_data[2]; + tga_data[i*req_comp+3] = trans_data[3]; + break; + } + // 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 * req_comp; + int index2 = (tga_height - 1 - j) * tga_width * req_comp; + for (i = tga_width * req_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 ) + { + free( tga_palette ); + } + // 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; + // OK, done + return tga_data; +} + +static stbi_uc *stbi_tga_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + return tga_load(s,x,y,comp,req_comp); +} + + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +static int psd_test(stbi *s) +{ + if (get32(s) != 0x38425053) return 0; // "8BPS" + else return 1; +} + +static int stbi_psd_test(stbi *s) +{ + int r = psd_test(s); + stbi_rewind(s); + return r; +} + +static stbi_uc *psd_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int w,h; + uint8 *out; + + // Check identifier + if (get32(s) != 0x38425053) // "8BPS" + return epuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (get16(s) != 1) + return epuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = get16(s); + if (channelCount < 0 || channelCount > 16) + return epuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = get32(s); + w = get32(s); + + // Make sure the depth is 8 bits. + if (get16(s) != 8) + return epuc("unsupported bit depth", "PSD bit depth is not 8 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 (get16(s) != 3) + return epuc("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.) + skip(s,get32(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + skip(s, get32(s) ); + + // Skip the reserved data. + skip(s, get32(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = get16(s); + if (compression > 1) + return epuc("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) malloc(4 * w*h); + if (!out) return epuc("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 preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + uint8 *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++) *p = (channel == 3 ? 255 : 0), p += 4; + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = get8u(s); + p += 4; + len--; + } + } else if (len > 128) { + uint8 val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = get8u(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } 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 value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + uint8 *p; + + p = out + channel; + if (channel > channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++) *p = channel == 3 ? 255 : 0, p += 4; + } else { + // Read the data. + for (i = 0; i < pixelCount; i++) + *p = get8u(s), p += 4; + } + } + } + + if (req_comp && req_comp != 4) { + out = convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // convert_format frees input on failure + } + + if (comp) *comp = channelCount; + *y = h; + *x = w; + + return out; +} + +static stbi_uc *stbi_psd_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + return psd_load(s,x,y,comp,req_comp); +} + +// ************************************************************************************************* +// 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/ + +static int pic_is4(stbi *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int pic_test(stbi *s) +{ + int i; + + if (!pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + get8(s); + + if (!pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} pic_packet_t; + +static stbi_uc *pic_readval(stbi *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (at_eof(s)) return epuc("bad file","PIC file too short"); + dest[i]=get8u(s); + } + } + + return dest; +} + +static void pic_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 *pic_load2(stbi *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + pic_packet_t packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + pic_packet_t *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return epuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = get8(s); + packet->size = get8u(s); + packet->type = get8u(s); + packet->channel = get8u(s); + + act_comp |= packet->channel; + + if (at_eof(s)) return epuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return epuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return epuc("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=get8u(s); + if (at_eof(s)) return epuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (uint8) left; + + if (!pic_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 = get8(s), i; + if (at_eof(s)) return epuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + int i; + + if (count==128) + count = get16(s); + else + count -= 127; + if (count > left) + return epuc("bad file","scanline overrun"); + + if (!pic_readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return epuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static stbi_uc *pic_load(stbi *s,int *px,int *py,int *comp,int req_comp) +{ + stbi_uc *result; + int i, x,y; + + for (i=0; i<92; ++i) + get8(s); + + x = get16(s); + y = get16(s); + if (at_eof(s)) return epuc("bad file","file too short (pic header)"); + if ((1 << 28) / x < y) return epuc("too large", "Image too large to decode"); + + get32(s); //skip `ratio' + get16(s); //skip `fields' + get16(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) malloc(x*y*4); + memset(result, 0xff, x*y*4); + + if (!pic_load2(s,x,y,comp, result)) { + free(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi_pic_test(stbi *s) +{ + int r = pic_test(s); + stbi_rewind(s); + return r; +} + +static stbi_uc *stbi_pic_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + return pic_load(s,x,y,comp,req_comp); +} + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb +typedef struct stbi_gif_lzw_struct { + int16 prefix; + uint8 first; + uint8 suffix; +} stbi_gif_lzw; + +typedef struct stbi_gif_struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags; + uint8 pal[256][4]; + uint8 lpal[256][4]; + stbi_gif_lzw codes[4096]; + uint8 *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; +} stbi_gif; + +static int gif_test(stbi *s) +{ + int sz; + if (get8(s) != 'G' || get8(s) != 'I' || get8(s) != 'F' || get8(s) != '8') return 0; + sz = get8(s); + if (sz != '9' && sz != '7') return 0; + if (get8(s) != 'a') return 0; + return 1; +} + +static int stbi_gif_test(stbi *s) +{ + int r = gif_test(s); + stbi_rewind(s); + return r; +} + +static void stbi_gif_parse_colortable(stbi *s, uint8 pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = get8u(s); + pal[i][1] = get8u(s); + pal[i][0] = get8u(s); + pal[i][3] = transp ? 0 : 255; + } +} + +static int stbi_gif_header(stbi *s, stbi_gif *g, int *comp, int is_info) +{ + uint8 version; + if (get8(s) != 'G' || get8(s) != 'I' || get8(s) != 'F' || get8(s) != '8') + return e("not GIF", "Corrupt GIF"); + + version = get8u(s); + if (version != '7' && version != '9') return e("not GIF", "Corrupt GIF"); + if (get8(s) != 'a') return e("not GIF", "Corrupt GIF"); + + failure_reason = ""; + g->w = get16le(s); + g->h = get16le(s); + g->flags = get8(s); + g->bgindex = get8(s); + g->ratio = get8(s); + g->transparent = -1; + + 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 *s, int *x, int *y, int *comp) +{ + stbi_gif g; + if (!stbi_gif_header(s, &g, comp, 1)) { + stbi_rewind( s ); + return 0; + } + if (x) *x = g.w; + if (y) *y = g.h; + return 1; +} + +static void stbi_out_gif_code(stbi_gif *g, uint16 code) +{ + uint8 *p, *c; + + // 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; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + 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 uint8 *stbi_process_gif_raster(stbi *s, stbi_gif *g) +{ + uint8 lzw_cs; + int32 len, code; + uint32 first; + int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi_gif_lzw *p; + + lzw_cs = get8u(s); + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (code = 0; code < clear; code++) { + g->codes[code].prefix = -1; + g->codes[code].first = (uint8) code; + g->codes[code].suffix = (uint8) code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (int32) get8(s) << valid_bits; + valid_bits += 8; + } else { + 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 + skip(s, len); + while ((len = get8(s)) > 0) + skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return epuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return epuc("too many codes", "Corrupt GIF"); + p->prefix = (int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return epuc("illegal code in raster", "Corrupt GIF"); + + stbi_out_gif_code(g, (uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return epuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi_fill_gif_background(stbi_gif *g) +{ + int i; + uint8 *c = g->pal[g->bgindex]; + // @OPTIMIZE: write a dword at a time + for (i = 0; i < g->w * g->h * 4; i += 4) { + uint8 *p = &g->out[i]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static uint8 *stbi_gif_load_next(stbi *s, stbi_gif *g, int *comp, int req_comp) +{ + int i; + uint8 *old_out = 0; + + if (g->out == 0) { + if (!stbi_gif_header(s, g, comp,0)) return 0; // failure_reason set by stbi_gif_header + g->out = (uint8 *) malloc(4 * g->w * g->h); + if (g->out == 0) return epuc("outofmem", "Out of memory"); + stbi_fill_gif_background(g); + } else { + // animated-gif-only path + if (((g->eflags & 0x1C) >> 2) == 3) { + old_out = g->out; + g->out = (uint8 *) malloc(4 * g->w * g->h); + if (g->out == 0) return epuc("outofmem", "Out of memory"); + memcpy(g->out, old_out, g->w*g->h*4); + } + } + + for (;;) { + switch (get8(s)) { + case 0x2C: /* Image Descriptor */ + { + int32 x, y, w, h; + uint8 *o; + + x = get16le(s); + y = get16le(s); + w = get16le(s); + h = get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return epuc("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; + + g->lflags = 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 = (uint8 *) g->lpal; + } else if (g->flags & 0x80) { + for (i=0; i < 256; ++i) // @OPTIMIZE: reset only the previous transparent + g->pal[i][3] = 255; + if (g->transparent >= 0 && (g->eflags & 0x01)) + g->pal[g->transparent][3] = 0; + g->color_table = (uint8 *) g->pal; + } else + return epuc("missing color table", "Corrupt GIF"); + + o = stbi_process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (req_comp && req_comp != 4) + o = convert_format(o, 4, req_comp, g->w, g->h); + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (get8(s) == 0xF9) { // Graphic Control Extension. + len = get8(s); + if (len == 4) { + g->eflags = get8(s); + get16le(s); // delay + g->transparent = get8(s); + } else { + skip(s, len); + break; + } + } + while ((len = get8(s)) != 0) + skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (uint8 *) 1; + + default: + return epuc("unknown code", "Corrupt GIF"); + } + } +} + +static stbi_uc *stbi_gif_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + uint8 *u = 0; + stbi_gif g={0}; + + u = stbi_gif_load_next(s, &g, comp, req_comp); + if (u == (void *) 1) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + } + + return u; +} + +static int stbi_gif_info(stbi *s, int *x, int *y, int *comp) +{ + return stbi_gif_info_raw(s,x,y,comp); +} + + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int hdr_test(stbi *s) +{ + const char *signature = "#?RADIANCE\n"; + int i; + for (i=0; signature[i]; ++i) + if (get8(s) != signature[i]) + return 0; + return 1; +} + +static int stbi_hdr_test(stbi* s) +{ + int r = hdr_test(s); + stbi_rewind(s); + return r; +} + +#define HDR_BUFLEN 1024 +static char *hdr_gettoken(stbi *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) get8(z); + + while (!at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == HDR_BUFLEN-1) { + // flush to end of line + while (!at_eof(z) && get8(z) != '\n') + ; + break; + } + c = (char) get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void 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 *hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[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; + + + // Check identifier + if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return epf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return epf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return epf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return epf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = strtol(token, NULL, 10); + + *x = width; + *y = height; + + *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) malloc(height * width * req_comp * sizeof(float)); + + // 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: + getn(s, rgbe, 4); + 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 = get8(s); + c2 = get8(s); + len = 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) + uint8 rgbe[4]; + rgbe[0] = (uint8) c1; + rgbe[1] = (uint8) c2; + rgbe[2] = (uint8) len; + rgbe[3] = (uint8) get8u(s); + hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + free(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= get8(s); + if (len != width) { free(hdr_data); free(scanline); return epf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) malloc(width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = get8u(s); + if (count > 128) { + // Run + value = get8u(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = get8u(s); + } + } + } + for (i=0; i < width; ++i) + hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + free(scanline); + } + + return hdr_data; +} + +static float *stbi_hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + return hdr_load(s,x,y,comp,req_comp); +} + +static int stbi_hdr_info(stbi *s, int *x, int *y, int *comp) +{ + char buffer[HDR_BUFLEN]; + char *token; + int valid = 0; + + if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0) { + stbi_rewind( s ); + return 0; + } + + for(;;) { + token = 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 = hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi_rewind( s ); + return 0; + } + token += 3; + *y = strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi_rewind( s ); + return 0; + } + token += 3; + *x = strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +static int stbi_bmp_info(stbi *s, int *x, int *y, int *comp) +{ + int hsz; + if (get8(s) != 'B' || get8(s) != 'M') { + stbi_rewind( s ); + return 0; + } + skip(s,12); + hsz = get32le(s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108) { + stbi_rewind( s ); + return 0; + } + if (hsz == 12) { + *x = get16le(s); + *y = get16le(s); + } else { + *x = get32le(s); + *y = get32le(s); + } + if (get16le(s) != 1) { + stbi_rewind( s ); + return 0; + } + *comp = get16le(s) / 8; + return 1; +} + +static int stbi_psd_info(stbi *s, int *x, int *y, int *comp) +{ + int channelCount; + if (get32(s) != 0x38425053) { + stbi_rewind( s ); + return 0; + } + if (get16(s) != 1) { + stbi_rewind( s ); + return 0; + } + skip(s, 6); + channelCount = get16(s); + if (channelCount < 0 || channelCount > 16) { + stbi_rewind( s ); + return 0; + } + *y = get32(s); + *x = get32(s); + if (get16(s) != 8) { + stbi_rewind( s ); + return 0; + } + if (get16(s) != 3) { + stbi_rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi_pic_info(stbi *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained; + pic_packet_t packets[10]; + + skip(s, 92); + + *x = get16(s); + *y = get16(s); + if (at_eof(s)) return 0; + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi_rewind( s ); + return 0; + } + + skip(s, 8); + + do { + pic_packet_t *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = get8(s); + packet->size = get8u(s); + packet->type = get8u(s); + packet->channel = get8u(s); + act_comp |= packet->channel; + + if (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; +} + +static int stbi_info_main(stbi *s, int *x, int *y, int *comp) +{ + if (stbi_jpeg_info(s, x, y, comp)) + return 1; + if (stbi_png_info(s, x, y, comp)) + return 1; + if (stbi_gif_info(s, x, y, comp)) + return 1; + if (stbi_bmp_info(s, x, y, comp)) + return 1; + if (stbi_psd_info(s, x, y, comp)) + return 1; + if (stbi_pic_info(s, x, y, comp)) + return 1; + #ifndef STBI_NO_HDR + if (stbi_hdr_info(s, x, y, comp)) + return 1; + #endif + // test tga last because it's a crappy test! + if (stbi_tga_info(s, x, y, comp)) + return 1; + return e("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = fopen(filename, "rb"); + int result; + if (!f) return e("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi s; + long pos = ftell(f); + start_file(&s, f); + r = stbi_info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi s; + start_mem(&s,buffer,len); + return stbi_info_main(&s,x,y,comp); +} + +int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi s; + start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi_info_main(&s,x,y,comp); +} + +#endif // STBI_HEADER_FILE_ONLY + +/* + revision history: + 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-uint8 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.e. Janez (U+017D)emva) + 1.21 fix use of 'uint8' 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 - 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 first released version +*/ diff --git a/examples/nanovg_src/stb_truetype.h b/examples/nanovg_src/stb_truetype.h new file mode 100644 index 0000000..3947a23 --- /dev/null +++ b/examples/nanovg_src/stb_truetype.h @@ -0,0 +1,2065 @@ +// stb_truetype.h - v0.6c - public domain +// authored from 2009-2012 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// +// Bug/warning reports: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// +// VERSION HISTORY +// +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (STB) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// +// LICENSE +// +// This software is in the public domain. Where that dedication is not +// recognized, you are granted a perpetual, irrevokable license to copy +// and modify this file as you see fit. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start, +// and you can cut and paste from it to move to more advanced) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- use for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLstbtt_uint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(data,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl,0=old d3d + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=0; + char *text = "Heljo World!"; + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_sort() to override this to avoid qsort + #ifndef STBTT_sort + #include + #define STBTT_sort(data,num_items,item_size,compare_func) qsort(data,num_items,item_size,compare_func) + #endif + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) malloc(x) + #define STBTT_free(x,u) free(x) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +extern void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +extern int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. You can just skip +// this step if you know it's that kind of font. + + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +typedef struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph +} stbtt_fontinfo; + +extern int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +extern float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +extern float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +extern void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +extern void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +extern void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +extern int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 +// @TODO; for now always returns 0! + +extern int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +extern void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +extern int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +extern int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy; + unsigned char type,padding; + } stbtt_vertex; +#endif + +extern int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +extern int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +extern int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates + +extern void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +extern void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +extern unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +extern unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +extern void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +extern void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +extern void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +extern void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +extern unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +extern unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +extern void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +extern void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +extern void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +extern void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +extern void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata); + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +extern int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +extern int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +extern const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) + + #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) + #define ttSHORT(p) (* (stbtt_int16 *) (p)) + #define ttULONG(p) (* (stbtt_uint32 *) (p)) + #define ttLONG(p) (* (stbtt_int32 *) (p)) + +#else + + stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#endif + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(const stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*14); + } + } + return -1; +} + +int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) +{ + stbtt_uint8 *data = (stbtt_uint8 *) data2; + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) + return 0; + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + stbtt_uint16 item, offset, start, end; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 start, end; + searchRange >>= 1; + start = ttUSHORT(data + search + 2 + segcount*2 + 2); + end = ttUSHORT(data + search + 2); + start = ttUSHORT(data + search + searchRange*2 + segcount*2 + 2); + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + end = ttUSHORT(data + index_map + 14 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + return 1; +} + +int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0,y0,x1,y1; + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) + x0=y0=x1=y1=0; // e.g. space character + // now move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor(x0 * scale_x + shift_x); + if (iy0) *iy0 = -STBTT_iceil (y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil (x1 * scale_x + shift_x); + if (iy1) *iy1 = -STBTT_ifloor(y0 * scale_y + shift_y); +} +void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + +typedef struct stbtt__active_edge +{ + int x,dx; + float ey; + struct stbtt__active_edge *next; + int valid; +} stbtt__active_edge; + +#define FIXSHIFT 10 +#define FIX (1 << FIXSHIFT) +#define FIXMASK (FIX-1) + +static stbtt__active_edge *new_active(stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) STBTT_malloc(sizeof(*z), userdata); // @TODO: make a pool of these!!! + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(e->y0 <= start_point); + if (!z) return z; + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = -STBTT_ifloor(FIX * -dxdy); + else + z->dx = STBTT_ifloor(FIX * dxdy); + z->x = STBTT_ifloor(FIX * (e->x0 + dxdy * (start_point - e->y0))); + z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->valid = e->invert ? 1 : -1; + return z; +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->valid; + } else { + int x1 = e->x; w += e->valid; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> FIXSHIFT; + int j = x1 >> FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((FIX - (x0 & FIXMASK)) * max_weight) >> FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & FIXMASK) * max_weight) >> FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->valid); + z->valid = 0; + STBTT_free(z, userdata); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = new_active(e, off_x, scan_y, userdata); + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + while (active) { + stbtt__active_edge *z = active; + active = active->next; + STBTT_free(z, userdata); + } + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +static int stbtt__edge_compare(const void *p, const void *q) +{ + stbtt__edge *a = (stbtt__edge *) p; + stbtt__edge *b = (stbtt__edge *) q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; + int vsubsample = result->h < 8 ? 15 : 5; + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = p[a].y * y_scale_inv * vsubsample + shift_y; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = p[b].y * y_scale_inv * vsubsample + shift_y; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +// returns number of contours +stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBox(info, glyph, scale_x, scale_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + stbtt_InitFont(&f, data, offset); + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 2; + if (y+gh+2 > bottom_y) + bottom_y = y+gh+2; + } + return bottom_y; +} + +void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8), off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + stbtt_int32 slen = ttUSHORT(fc+loc+12+8), off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#endif // STB_TRUETYPE_IMPLEMENTATION