From 2703bbd901631fa32a0c4dbe5fc73b775b5d0bb7 Mon Sep 17 00:00:00 2001 From: Max Amundsen Date: Tue, 3 Mar 2026 14:01:22 -0500 Subject: [PATCH] WIP: lunasvg implementation, things stopped working --- .../clangd/index/audio.h.EFD04539128CB066.idx | Bin 0 -> 1450 bytes .../audio_coreaudio.cpp.C30D9855B63A408E.idx | Bin 0 -> 7586 bytes .../index/base_arena.cpp.164995F241F3F880.idx | Bin 0 -> 1522 bytes .../index/base_arena.h.CBCA6374A6808DE1.idx | Bin 0 -> 1680 bytes .../index/base_core.h.7D5D690A43734B5E.idx | Bin 0 -> 2612 bytes .../index/base_inc.cpp.DA11457DD4910E5E.idx | Bin 0 -> 182 bytes .../index/base_inc.h.61DD50EE532D598A.idx | Bin 0 -> 214 bytes .../index/base_math.h.22213D7142116A00.idx | Bin 0 -> 7688 bytes .../base_strings.cpp.04AD22E410EE497F.idx | Bin 0 -> 1292 bytes .../index/base_strings.h.1662805A1C0C0BA6.idx | Bin 0 -> 2076 bytes .../clangd/index/clay.h.FBF47F0B8BF4850E.idx | Bin 0 -> 158018 bytes .../index/lunasvg.h.F0A538DDD7F84C21.idx | Bin 0 -> 12758 bytes .../index/main.cpp.A6515BFFE4CD0D5B.idx | Bin 0 -> 32200 bytes .../index/menus.cpp.9A9C873D20897FB5.idx | Bin 0 -> 1420 bytes .../clangd/index/midi.h.FE65C080BD6B4278.idx | Bin 0 -> 1682 bytes .../midi_coremidi.cpp.1405C3B7583C7F77.idx | Bin 0 -> 6260 bytes .../index/platform.h.0F95EF7BB057FB07.idx | Bin 0 -> 4532 bytes .../platform_macos.mm.592FED4BC31D10F6.idx | Bin 0 -> 13612 bytes .../index/renderer.h.72DA7B7C6BAF6E31.idx | Bin 0 -> 1800 bytes .../renderer_metal.mm.4A5862252690E2BA.idx | Bin 0 -> 23630 bytes .../index/ui_core.cpp.700D288EBBE4E64E.idx | Bin 0 -> 6180 bytes .../index/ui_core.h.08A48D201816FC6A.idx | Bin 0 -> 5792 bytes .../index/ui_icons.cpp.E120C1FAB06B49CD.idx | Bin 0 -> 1684 bytes .../index/ui_icons.h.C42E196812AFE48E.idx | Bin 0 -> 1344 bytes .../index/ui_widgets.cpp.95BDB5FDC10F055B.idx | Bin 0 -> 39670 bytes .../index/ui_widgets.h.FA28EA93ABE86503.idx | Bin 0 -> 5038 bytes .vscode/settings.json | 3 + build.c | 254 + compile_commands.json | 24 + src/main.cpp | 12 + src/renderer/renderer.h | 3 + src/renderer/renderer_dx12.cpp | 153 +- src/renderer/renderer_metal.mm | 53 + src/ui/ui_core.h | 7 + src/ui/ui_icons.cpp | 76 + src/ui/ui_icons.h | 23 + src/ui/ui_widgets.cpp | 36 +- src/ui/ui_widgets.h | 4 + vendor/lunasvg/LICENSE | 21 + vendor/lunasvg/include/lunasvg.h | 791 ++ vendor/lunasvg/plutovg/include/plutovg.h | 2548 ++++++ vendor/lunasvg/plutovg/source/plutovg-blend.c | 1144 +++ .../lunasvg/plutovg/source/plutovg-canvas.c | 759 ++ vendor/lunasvg/plutovg/source/plutovg-font.c | 1065 +++ .../lunasvg/plutovg/source/plutovg-ft-math.c | 441 + .../lunasvg/plutovg/source/plutovg-ft-math.h | 436 + .../plutovg/source/plutovg-ft-raster.c | 1889 ++++ .../plutovg/source/plutovg-ft-raster.h | 420 + .../plutovg/source/plutovg-ft-stroker.c | 1873 ++++ .../plutovg/source/plutovg-ft-stroker.h | 320 + .../lunasvg/plutovg/source/plutovg-ft-types.h | 176 + .../lunasvg/plutovg/source/plutovg-matrix.c | 229 + vendor/lunasvg/plutovg/source/plutovg-paint.c | 489 + vendor/lunasvg/plutovg/source/plutovg-path.c | 918 ++ .../lunasvg/plutovg/source/plutovg-private.h | 180 + .../plutovg/source/plutovg-rasterize.c | 394 + .../plutovg/source/plutovg-stb-image-write.h | 1724 ++++ .../plutovg/source/plutovg-stb-image.h | 7988 +++++++++++++++++ .../plutovg/source/plutovg-stb-truetype.h | 5077 +++++++++++ .../lunasvg/plutovg/source/plutovg-surface.c | 295 + vendor/lunasvg/plutovg/source/plutovg-utils.h | 211 + vendor/lunasvg/source/graphics.cpp | 693 ++ vendor/lunasvg/source/graphics.h | 562 ++ vendor/lunasvg/source/lunasvg.cpp | 536 ++ vendor/lunasvg/source/svgelement.cpp | 1225 +++ vendor/lunasvg/source/svgelement.h | 498 + vendor/lunasvg/source/svggeometryelement.cpp | 324 + vendor/lunasvg/source/svggeometryelement.h | 139 + vendor/lunasvg/source/svglayoutstate.cpp | 607 ++ vendor/lunasvg/source/svglayoutstate.h | 129 + vendor/lunasvg/source/svgpaintelement.cpp | 364 + vendor/lunasvg/source/svgpaintelement.h | 288 + vendor/lunasvg/source/svgparser.cpp | 956 ++ vendor/lunasvg/source/svgparserutils.h | 210 + vendor/lunasvg/source/svgproperty.cpp | 705 ++ vendor/lunasvg/source/svgproperty.h | 570 ++ vendor/lunasvg/source/svgrenderstate.cpp | 61 + vendor/lunasvg/source/svgrenderstate.h | 69 + vendor/lunasvg/source/svgtextelement.cpp | 579 ++ vendor/lunasvg/source/svgtextelement.h | 155 + 80 files changed, 38694 insertions(+), 12 deletions(-) create mode 100644 .cache/clangd/index/audio.h.EFD04539128CB066.idx create mode 100644 .cache/clangd/index/audio_coreaudio.cpp.C30D9855B63A408E.idx create mode 100644 .cache/clangd/index/base_arena.cpp.164995F241F3F880.idx create mode 100644 .cache/clangd/index/base_arena.h.CBCA6374A6808DE1.idx create mode 100644 .cache/clangd/index/base_core.h.7D5D690A43734B5E.idx create mode 100644 .cache/clangd/index/base_inc.cpp.DA11457DD4910E5E.idx create mode 100644 .cache/clangd/index/base_inc.h.61DD50EE532D598A.idx create mode 100644 .cache/clangd/index/base_math.h.22213D7142116A00.idx create mode 100644 .cache/clangd/index/base_strings.cpp.04AD22E410EE497F.idx create mode 100644 .cache/clangd/index/base_strings.h.1662805A1C0C0BA6.idx create mode 100644 .cache/clangd/index/clay.h.FBF47F0B8BF4850E.idx create mode 100644 .cache/clangd/index/lunasvg.h.F0A538DDD7F84C21.idx create mode 100644 .cache/clangd/index/main.cpp.A6515BFFE4CD0D5B.idx create mode 100644 .cache/clangd/index/menus.cpp.9A9C873D20897FB5.idx create mode 100644 .cache/clangd/index/midi.h.FE65C080BD6B4278.idx create mode 100644 .cache/clangd/index/midi_coremidi.cpp.1405C3B7583C7F77.idx create mode 100644 .cache/clangd/index/platform.h.0F95EF7BB057FB07.idx create mode 100644 .cache/clangd/index/platform_macos.mm.592FED4BC31D10F6.idx create mode 100644 .cache/clangd/index/renderer.h.72DA7B7C6BAF6E31.idx create mode 100644 .cache/clangd/index/renderer_metal.mm.4A5862252690E2BA.idx create mode 100644 .cache/clangd/index/ui_core.cpp.700D288EBBE4E64E.idx create mode 100644 .cache/clangd/index/ui_core.h.08A48D201816FC6A.idx create mode 100644 .cache/clangd/index/ui_icons.cpp.E120C1FAB06B49CD.idx create mode 100644 .cache/clangd/index/ui_icons.h.C42E196812AFE48E.idx create mode 100644 .cache/clangd/index/ui_widgets.cpp.95BDB5FDC10F055B.idx create mode 100644 .cache/clangd/index/ui_widgets.h.FA28EA93ABE86503.idx create mode 100644 .vscode/settings.json create mode 100644 compile_commands.json create mode 100644 src/ui/ui_icons.cpp create mode 100644 src/ui/ui_icons.h create mode 100644 vendor/lunasvg/LICENSE create mode 100644 vendor/lunasvg/include/lunasvg.h create mode 100644 vendor/lunasvg/plutovg/include/plutovg.h create mode 100644 vendor/lunasvg/plutovg/source/plutovg-blend.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-canvas.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-font.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-ft-math.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-ft-math.h create mode 100644 vendor/lunasvg/plutovg/source/plutovg-ft-raster.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-ft-raster.h create mode 100644 vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-ft-stroker.h create mode 100644 vendor/lunasvg/plutovg/source/plutovg-ft-types.h create mode 100644 vendor/lunasvg/plutovg/source/plutovg-matrix.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-paint.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-path.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-private.h create mode 100644 vendor/lunasvg/plutovg/source/plutovg-rasterize.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-stb-image-write.h create mode 100644 vendor/lunasvg/plutovg/source/plutovg-stb-image.h create mode 100644 vendor/lunasvg/plutovg/source/plutovg-stb-truetype.h create mode 100644 vendor/lunasvg/plutovg/source/plutovg-surface.c create mode 100644 vendor/lunasvg/plutovg/source/plutovg-utils.h create mode 100644 vendor/lunasvg/source/graphics.cpp create mode 100644 vendor/lunasvg/source/graphics.h create mode 100644 vendor/lunasvg/source/lunasvg.cpp create mode 100644 vendor/lunasvg/source/svgelement.cpp create mode 100644 vendor/lunasvg/source/svgelement.h create mode 100644 vendor/lunasvg/source/svggeometryelement.cpp create mode 100644 vendor/lunasvg/source/svggeometryelement.h create mode 100644 vendor/lunasvg/source/svglayoutstate.cpp create mode 100644 vendor/lunasvg/source/svglayoutstate.h create mode 100644 vendor/lunasvg/source/svgpaintelement.cpp create mode 100644 vendor/lunasvg/source/svgpaintelement.h create mode 100644 vendor/lunasvg/source/svgparser.cpp create mode 100644 vendor/lunasvg/source/svgparserutils.h create mode 100644 vendor/lunasvg/source/svgproperty.cpp create mode 100644 vendor/lunasvg/source/svgproperty.h create mode 100644 vendor/lunasvg/source/svgrenderstate.cpp create mode 100644 vendor/lunasvg/source/svgrenderstate.h create mode 100644 vendor/lunasvg/source/svgtextelement.cpp create mode 100644 vendor/lunasvg/source/svgtextelement.h diff --git a/.cache/clangd/index/audio.h.EFD04539128CB066.idx b/.cache/clangd/index/audio.h.EFD04539128CB066.idx new file mode 100644 index 0000000000000000000000000000000000000000..239691268227629e5958bc6523cbf360b4970a0c GIT binary patch literal 1450 zcmYk4eN0nV7{<@N<+i=$wD-1MZ{?#sv{VLK5tsrSfkjvp9F7zhk!f%Wx*&EE${drK zVLDLQgt1}5hp>RoSr>7C(4d1tgv|gUd^QviG_0ifiJJJVLs7sZcc%s8Fdzoy*X z{{4+R#=PppE7zvBrnfh)+m`(85$&H}?D=3aNK(_E?3Q!#pZ~*-hdyIG*y3Ry}RB$ZR>vies}W7$-DVi!P-dnCmahTf9H^)<7bY`y?S|u1v^DXbh^HwmM(* zJzea*-}haG?Si2``klhx(=E0QTQX0DKBqjD8_mG+nSGxJPrTLrVMFb>1VW$#Af%sS z`l&#q;#I2w%TT})Od)FhHfdRtS{X(n&L& zv>s`Bt$8I!PDb5{r1qmp5U8-_GNrss1t7>nTt)5&U;u8^b<}OG8hxCl0--pn9Ljm| zB2ncYWjouVZiRry6^1GGFl9nK&xZh3CIO07xI%2%WYYt1vJ(O=wv1EEI3*wruQ7=h zeGs->tGOE=yylHz2smu

hGtASJIftVC@{J+!aMFh3;)EC5I*lL_MG{f7^(@eex> zrar&=00IrJF-kL|G(rL|tPyL7ZL*q5gKF-TJcQsiY`IP|*XaPH<8_vmHlS$#0f&KJ zp-8by?8G(z5OS14Bt}vZV%RNm^0^5mEQ@S>G9iOyk%vzvG_Wi(@X3;*(u6nr@4W2? z5$nhLlLQcPfRmBN5b?2m;xY}SiPI#J!4rfyvzH&q{B*i%lm45Fh5jsJWDFkK(t!v9 z=ufghq%DX}c>j!!1vODmQ(#wX*60iGirNPqmUxHCQz s35jKKPkb`ol0$pF&yJ#8N2vF~;JucPkv!l%O&&YeV1JT*oHiQ3|6pO9mjD0& literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/audio_coreaudio.cpp.C30D9855B63A408E.idx b/.cache/clangd/index/audio_coreaudio.cpp.C30D9855B63A408E.idx new file mode 100644 index 0000000000000000000000000000000000000000..e2c5fb5d21bc29f0218b214876661ecb1e8e7cb5 GIT binary patch literal 7586 zcmYjV2V7Lg*1u=Gz-1X=>97mz(xtkWA|N0SK|zyPBO1FHMS}*F*!^N9A}A#`2%w?q z1C1yaR01d%6~ziMA&FQcSbpedR7{@b%`R_t?*5XI|NYO*IdkUBIcH|<@L|KgbKcP-oHfB4Ue|tdF)YjhUTzND)^3HCb;-*-;hCrW+`){Z1`#s}EXl}8? zt;Mr$o=kjin7d$9`GjH9t&<*U+HtG>n*4;a;(d8b_K(P4F(z@zjn0fk@hxv_zLZTK zWZIM5-)=_P{lCs#|K2Wf`!)9uH-gg?HTda{rodm%oP1t7_;hyLvteFGt$I8@?3`kM zbWBy~pghl)jjsx>O{gfi)>VZMm8cio?V9w|;&t`aO$lWK2Yyu1KQlddS@pszGb`Lj zUn}8#A6w<+?>#eM$(-(dT<$)(#_R`om*jIzR<8rFTXpikGtIMwPZt?PZXM}7-Na%^ z*sksFLETYhuvjgh~0E=H0yeR4Ht~yE$Ub9@~FfejBsq{;HJ! ze^>Ts%zSm&rR_V1Cai18G1~WL_Wu7%lE9j_}Tc+Ta-sTu6#ak=#Fhmhgvx%2c(sR zSZ5z~b6V)I%`>DrzIcAm;v)%$|Jq>^dQ|ycbKxXe#gmlj&sM##Q{O!}j7wXwAZbBA zj@#d!-sJybkIW2t$~n7i>VU2<$jLEiO# z7<0t^#bz*Q28G5;w|8|iW} zHMJvVV}E-yi>!f>cLZvh2RAh!WWYC5zG7x`xj1`c7tyU2fGvLu5u~urji;)HR%O-#!dG z`|WkL>Ctb@xc-)$D7Ta=bqyW8rh5IKqayyZ$qZSO$rTEPzpkP5+nK$+p^B_5Gvq~R zTZOHsuA%hK?w5C$S#f&|wNXnnH!@e~8Y)|;H$7;)+oA+>_JnQ)i&k*a$mMc1XDsyN zOkK$0t|We8+Uu_>GeaRnfTP0EQ`b-dyC#|Ws}m0%Hbdcrc2T%^>l#WAT-#pwl*_2a zoRSE*4hGl3R%0o*bkPY=yAuJ?zvK;_+fvtH$_0`xUe;c&x<;#_iMcCxCZ{XR(I==C zb9RKT2ZMUB)R@Xmo!ABoAH|iWB-Q?cIcL(vTkh?lYdB+>0cYjLJ^G}}H6r80Ei)8G zR8%QcKDvgg_}usf*SY%SQ_LyI=&Q)!DpF}|<+eUL0Xh{Y6s}U{bT9A0oFnOS4jG(7 z4jLP|jXTqUTReMC8YU_}j~j9}KIIz@uvmtKGUPx_Fy-E!P0t?Q;1Cvoh=83~(_ z6(wi@=qJ-1eCmf-+zax(pr8`5coRHtf`$&Gfq|Fl0s2&W@EItYsw zQNTqMPEAF_2gv>xJNlfR;8X!Bx~PA5oGLp#`ka<{55jx!rz37fRrf4B z@Uff_S?JR&G@lYQnh}lrLKzi4M3ywh(O_yI(*;qsF{R*N3c+;gSS*E6rI5&Q1;{JF zj^Qg{eFc>C@IP!)eD0V1PDw0MiG)gIP1pDSL)yxoU#B_};x*vc;7SP^wu!0wVm37u zA3ok#`BjNphQ%qu6l?k!i&KTE5_AmsV<2bjao~?j@nOUtMiRaT@ikI>3h}3;xDN4k zQhW*Vm!!BA@vRKgj3UT%nS_}!);~6Z`S+xOlg2PK)p=?-L&rgJ976P|L99WF8Wcj! zgT*?es6!!i->`TIDK4Q9dN$Hbl27k3_x2`s^_BVB(`kEb?V>lNNBfb_y8!P3ce-J8 z9v+Jgz&C)cJ|>E1fjXq_uK>kL`7b8_MlIXh|$#)}(er3qD40$nmT65kg7`Be?OA%m-at_EKVR>>WNsaNA~r|iQxvMZa^LkHzH*t zQZsxS;nT>1;q%DiJhG)H;;W(qZd0v(izIF_$#Ie&^}j#v_!Q4~KNUqjXCmuNvARwlFE*LR(=ljp~?GTYlFeh?-2Bp!ti) zgqmE7oh~AGihu5INFDf~Up%pO87Rv@Lj_f7nl_{qULQ`%0b{|KRQi2CELyu-2Qg&h zVKb1S)zPa*Q*`~im7@w$MrE6-&c@?(4<@+d_5G6{=k5I%$Ks81)i@v98aR!=4REEE<>B33%! zbnv0t1WoT+zV*qSsigi1!6BAx>!Wa#1RVkX2*{}Ww!F*wC$rf&iA2X>-(X21iu8?? zpm?wN5H?{Bc;|q=&dgZR(4gv2rw zrHqnrQ4vuRF2OS)fc4CUfLs_z*ZcjH3vLfDpPxhIuEA@9=;sfA|91G#{GG{UMf3Q1 z&h#@DzZSlhME{*2>;wsZ2mCwGM|+`34Bl(AH$`hp?+}R!QQlD!ln|VtH`0@$FLUzZ zlI9y=QO?UP=;aHGX__>B4NfX_eyIi1X{pQ70ETV?e-renle&{!)_1GZJF%CMmnFU1 zU@?#nG-oE7<1t5nzA%YQTs4@`y~bh<@HJpA#m+V0&2T+f)q^uD0-8bD44yP8V^Y4V zE`X0TFu1mYzcet|U1!A{CPjjMEpnrNjL8K@aSnOVz<|k}NPQLg>hDRU=Cf==PP7PI zoR?f{_ebqAvcUz&sDK@B+3#g*T<@=5Lx@|Tz6JXFC|77SwD?lKo)Fnc$VT?`h-J*} z}DM>O^}zrgvKHJx0A3ylFS>JtpiX#L(Gj6eod8du^s0g~g+Q zkAjNXvl`)Qq@u&H*o1HsQc-&Z7x^w(y6<$R_FQ`{jAqi7xh;#Jh~^I?neO_{WF_Nd zd)f($t2L`587cwi5*8S+*aB89q)ec%D&l4EybOVC-`c^m9RjJI{(j%q=3S`RN{qDy z30v4ztiOBlg(aN{IfTfCLAfxTN_?6)ep&sp*?EMx4gGIJ9G&V&-0gAsTPpI%5<`ZE zBr-pZ8Xl!bf5w;2{>{~YI~iRLLOJNq$CB@3Ui$HW?;uBczA)d5O2gth!0W)5;R3(~ zQoI%kYgx?_zCyONx9c_OL;`W(E0{>Nqx((Lg!Tc0CI&3p@HTezj!UgX5`Et@2JL3E z|1-3wy|RRi#sVg<5Pd&w`is1XMp4{3(&e9+p9)J&u;|D8$(RZJd4CBCHVw97T%>8F z1jQT2OOE&m(-9Ibl~0v1Lu>?LBbYIq1Ex7(#c(kQ#ZrC);x`}}wFj+7$cnVjG0fM< z{^ha5v~T6Uceg_hR_S(Iew!7ev0VjESDeT@NQipG*CTW4=|5lkx-0dK(IG#bHzOsLM0_3L7Nn#x1}3iCHf@mR z2$7tHge-Oi4RUJu>y2g1QQ}+6h2@x;C=+ld$QjN9f%rAUmB3emK=Ir9g-_aY&5x7$ zeRv;ds^~)2v0CH*9zHQNoNJdZu_NR)F7z}(ZShASZ2tuRuqSpwnji8}1Kj6bR z<4Z0+Xe83QLFfkklh5RuC4Na8{hA4J4}^Q{D!9NWI4t1_K1T>|BX7N*mJfF-8=GKv zo@CHhAiM%cx^ivx!*65Wq1y~)vqy*XI^MN-+JR^lzDf~n*ZKg4N> z#QY z3M5BQ`^-qy(%y2d@=_As=>1Tals_`Os5}#cN1?4QXg4P>?V`snF>~CrKOD zytNg5p3>e#xK=QyA^C^)J>#qL4TM9_2p%%yJTg#NXTXRD|XB6kp&yF z9}=Py0{6c2USXGTwoAFr_yM@$hn2zaRhgPL9Qa<^!#nwFjFImY^9X zGvth0IBVft8jr{$N6NB=45h>Dbohed&5*JgB>ZleyBiiTz5_fvz?UvYd)9~EgAojO zgJ-vt?}5Y~n9TUsF#I))WVi?oDnii=mm$A0Br;ryyeipEXhzlhF6F(fcf@#W0k37| z!r})IK7jriV)5+xGqqchws=eE6v)_r02blINc z^}*xo@A#^%BdO7%wii0jowAipi?8O#+V46-1-S~eQ!kRed2~;xn!njePK#x$Mk1SU&g;V{Lq1= zz0b$KR)1A^+~ul=(QO8FtX+K%`PCW8lBh63qk1+TcddFUtM z``a5J&|`u}pUOqSpf@SYR05O4LD30eD9v`8hwJ9dNLv}EB%Q$NpfWUwW zE|rVr9gM>Sv})yA4^=kn#>8vUe<4W41Xj(a^65-EE4u)p!0vDh1;CD}p6L9qlRyCx zZKiGH=s?TqP0htij1nS_o==UFARM4xNkeAP zLqfFgMR(V1r$0WAwPuYu=>nomO=c1i%UCPT57AN|Ihn&fv6v1bjZSm_{TNLgv4SsH zwC2kQ>5xH2f>Gc|Vh}k+XTFTGb=d|P<#BmwBo>I=X>LTif~de)z{$v~^BPFpUi+1W zyIjeK_>v4pgNX!48o-96c_)T?CSt{3bWg8fLlmG?Br+Up7M&|2HLcd(hs4iLtSK@UKfU=*9muqi7ZnrRW$q#+E`D1(LJAK35S-QU`s;rwxa-{0?? z^F5!XFfS*kMFp_wnY{X{KwYT<0C@6OTUYbk{S1Iv4p85DqHyE3@%6@kz8_qA^Sypw zvaxJwN6y{_;q>(_hlF32)7xfVyF2;w)(_RjY~#9{zop;IbdC0HeP}thY3IQc?G44l zgXI-_x}Kj~S!+Ge9A){*UE023(|m*X9esP&M}JL@KXSSv^XQ$G4c6m^`Pp+{f0Cc@ zO5?zmfnfEEm0e#Ie{k*WxUzKdoYJZnMs9uKTdcryy9@mrJ$2v}jq(F}YlWS?Z^DD6koB&>z$`y?TRI8`w-ZOK+eid z*v{HXlNzPg;2|$~tA=hE7Hk6uqKP0`napE1>ox<&%0g4fN8;JG?x7()BoZGHhmaJg zkJk&AW{1lTEb75(0&LQnj8dS-?c)Z+w%o&iliTFjtTpSUK#z+ZQ5$~U-@J;zL8|jA z-iV#7lfSox?x~^7>0{T{RG=poX;=-Cg6JWVJgf=qtp)NMqBJlXi2{0$Qb5uo0+IB- zI*;tw+q{|#qd+X3kszYbvGfkYW`&uh6Ci9i+iej)u`|XQA88*8NrFFu6n2429@!ms z$GI`Jxvgz2|C@!EV@N-KwVwBBTj3W)g80w94BD!8Ofkb7v0_{el39%TNz@#v7v^v2B>`LkZ!Vas$ zLH$G6X>vx<*AW&Rf`<}-u#0!u=mHRSTikYP8Nwd3Cz@`bm=Wa_s2MeZ%2KhB+M1_p Y$^RqrV-ZHCQq7eqrD+40)6aAz)f4 zM-;K-5t@R5;v*}K!p9Lq6ibXuDKlRwzDnh&&9`>%chA|`J-_)gckbNz=HsolxZR3*`b=6I^X`VLDwl4C-@#npZqLdRJUAre%27Y^X zmu`R6&1(UjqctHrZKinZuK5&fNnKEJ%=6}z>WZn4m)tq5E`2COdbJ)U8OzfX(kLOT zQ!{lIo6ALk2!v=HLjqLk^ql@NgANEysAw*@i? z1@{E7Wmh=Um>Uh;K#0?Z0r~`9EP7KH{ zUuXR?I;O!it`}|kC-}O?xFencGwhiS61i3)7+zS5l~? zFEIyeTXaZ0%W%r$=fk^C)yzV?i^49s1i7o~)wg1W+32XDP$N4gfBL?0dHl9H=rDkR z$Dcc+wf*3C8{@DDPT-_sr*xmV82XgS@rYf(MaB9uDJ=NgmCf}Dh#kSvh9&p?kuTIw zmDPzDI}YM_≻RR8G5FlMss_@-5#B`q<54S;->Ap%BW|#;h^5V}~tDMO;Fmg!0;m z5nT~q+g2_`M>vG@611;wNLkw*v<&fLSj;P*nW#{fJ$p49aRG$_YRgJit?Jko>vUgo zxP!Y5!`nTd?!J@|nvd8I{CLl_muy{F9;8}!Xviecm^{AQu7Ghm& z!B)j4#k>lyJ^gomi%|UOQR0m!84{iivw4C$K0oAZti7`aaUcZp#yEkk6Su`gPJ{vulS{39CiN8CRC?GIu5EA%dY9euQIwr zHzW1|A0Fh=pT$Kj#<5!wS5T;+c1-`K+oPWgw8h)d5ebpJxa?qvrTNFy;k623X0o9U zaR`KPwf8rQwl|uyzd+nfp;>+iEVOT@JlYAL^rkA1hF`y{Jda)dz`&%O57-PVO6hnocOVgR*xTrZ>{wVF-kGP&fyG5BA<&xcSl#h)XGyQl93v+%-#| zoXl@S$9NdeD>>BL@h(`Z*J!N`2TmY<9bV@hW^$PkIDBBhS;U(tY@&SI z;x!EpS4~;#rZKT9U{DWD5duaY<2T5Rl#t#1(Vqp5D#dV8FwAz~{*8o6S-) zPb^qaqhr6>KFH0Z`0mo(A$J*NLe!%;&KzHD$?-EOU{)xr?PH-=PeiWkMVonR@BXr< zMl$Z#;h*)tx?PqOSLP-G4P_`wO)G}bIf)>;xG1?;8N^`fia$1i>C7sACProkMvw>u JFvA!O3;?EvI57YK literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/base_inc.h.61DD50EE532D598A.idx b/.cache/clangd/index/base_inc.h.61DD50EE532D598A.idx new file mode 100644 index 0000000000000000000000000000000000000000..8d642084b4d1153219eee33e541d1e35f849e905 GIT binary patch literal 214 zcmWIYbaOk$z`)>~;#rZKT9U{DWD5duaY<2TB#_o-WMHV6Gxzjv-Ub66)&o8*)!!Dl z9WzqVu>U#puVc@HXS=-9SMCzJ$YvT)ye#u^SfPNu-GSUN)fpEKUYI}E*IM7wbsf*; z1l`!C%^RO5v_`)a-}jek)8bqQhT_WHB%lQhMX71U5IQFjL>Ct&7h8iE%sc1a*xcIY bc9MmOk%g53h9Uff@L&>!W;D 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 LiDnYRz@PsDAcXC+ literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/base_strings.cpp.04AD22E410EE497F.idx b/.cache/clangd/index/base_strings.cpp.04AD22E410EE497F.idx new file mode 100644 index 0000000000000000000000000000000000000000..a5736732a82b7817c1904f7e918b789be071d960 GIT binary patch literal 1292 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 aak1z09!`r*KhO>xy}T72WQmvjpZ^0xrPIX# literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/main.cpp.A6515BFFE4CD0D5B.idx b/.cache/clangd/index/main.cpp.A6515BFFE4CD0D5B.idx new file mode 100644 index 0000000000000000000000000000000000000000..a19a6fe1369d082b20ffc0514cbf8db89e7f5a9e GIT binary patch literal 32200 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 literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/menus.cpp.9A9C873D20897FB5.idx b/.cache/clangd/index/menus.cpp.9A9C873D20897FB5.idx new file mode 100644 index 0000000000000000000000000000000000000000..66db5f43be86ae84ec533226aa38a02c9a258c0b GIT binary patch literal 1420 zcmYk43rtg29L7)EbLoRy4!w_ZA(jk5KwNC&gBgr2(F_40h$cw1aTdf(2N|{y$}~ly z#);8wnM4;*nP~78Hw_r0@fD}wE1AP+M#c;qGn$b!qGl2x+y9>A-)ob8&As36oSyUl zzLkaf`8x$dmTo8v*H+gDC?UiJ-%x#U(+l`LZ6YMx(e}-v(px^|lc`l&UXR#YF?aqv z_U8DV{T<%iSw()A^0a)k$MWa(?;>$oxYa{mPu#^2o=^K@HaX~xfYPfB%dmh*hur1W$f)sa@rQHVIa53^kdJWX%qqDBehv)3x*u@%orAKzg_aq-ts= zLYOi@dc+2ls-z}TGC8}FmZtNxD6;PCCi5p<_?e^x&dWq88Z;V^a0YN zHz2p>jvX93U~=&65$a#`az|fJw9zEWk|xDy+!|=T4-hl|;3{~n_W)lX8gHmFl{X)= zh_Xy&1s99*G&=3Qr!<-td&1Vhnf1!x#-f~=k{O#Lb_XE6I0mF>O6>O5k)p%b!ln1% z201h*5&s;E1#9(I8EdC&(_h@GJy1k8IVWw#CE#-^{tiPM?;fv l3Lo{UcnWA>O+X)0C|DKZV^nZxP}rJ*{Gr`H?dmis?fTu4KSw;feoXQ6o__FCMWNv<)6~GG-}?cbZV%$LbuZJ; zMHN0x_O0r;tsm{YwKq28J?{Y3sZsW=UZY55^8V;m=K6U1=_C8)TuV$|luPrj=c2wv zV`E15*WWxkJKQnPEPUx&5fXSlpuW5Nal*aLx#F-DC%11ut-TyE9yZ?4b|f2tyzlJn z%EuQv3NCSf6nuW#3x8K?C~@eTZoTM(le?xSa>BLce{t0%SLz;C$MWy|8mT*aX;6}p z8alhx95t{|zxm1GXC87FbME|6TZcQ+Cu^@wN$1%6&$l*K_oRAW8_5h@ZSo(R2@F~6 zoGw$Ok4!$v9_~AJV7`Sb8$1y-A;~RCygqa}^jyN8_7AJ`5*_>-ZcT^1QTRcUG9tnC zWx?{q`9pzSzt`m6OrME(bblbkIc?)$$Mx*Cwq2Es=|;RO6qOZZg^2*fzMuWF5|uCG zK#Ksgh~)$WQnDLz@~=1sM4t(CLEr}9)Xa#R83k7IN*7@581Pp!n7NS^8SR<6KvLXE z$_qyNf^o%aUhN~K=#(TS{mmCK7lsl%Iq)SS2qBGNs7U`%uSfCz)?f~NNLY%bykKa! z##p1OObg$KKu-iV7=DA%V-2s-1IyZioF@YMxt1SFh*G`x~DpXIf7 zsFwo+33G_!1w+GeS>qM{uit!#z?QaQry8<}Y zB#I4j3$R$-6cy5dg%hek5g~31AJk3nP23hPsGA}|Y6~D(Ywx@JwBqOWGqW-lizQ;q zU$t-$R)9kGA0t@ls`RDnMp)&e3ZlJ|@fy^-cZ}Cv6F{$tF!sSgbgT$F8Ju?0t0Sy+ z(Hdx1WD!2PAUaa2OL`TEq+OX!Cy!g6i_F%o_jz literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/midi_coremidi.cpp.1405C3B7583C7F77.idx b/.cache/clangd/index/midi_coremidi.cpp.1405C3B7583C7F77.idx new file mode 100644 index 0000000000000000000000000000000000000000..4eba18957d2bf29a1e4a798bb94d7879d4618e95 GIT binary patch literal 6260 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 L^r!vO%rO54{}8}q literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/platform.h.0F95EF7BB057FB07.idx b/.cache/clangd/index/platform.h.0F95EF7BB057FB07.idx new file mode 100644 index 0000000000000000000000000000000000000000..7566fe2eec3ed6197a76d05eb440ba8099f924aa GIT binary patch literal 4532 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 H`u+a^siDE@ literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/renderer.h.72DA7B7C6BAF6E31.idx b/.cache/clangd/index/renderer.h.72DA7B7C6BAF6E31.idx new file mode 100644 index 0000000000000000000000000000000000000000..766d8686de87fef1c225f7a6762a69441f0e300c GIT binary patch literal 1800 zcmYLJ3s4hR6n$B;o9yO6APE8qZ;?>Iinby;3d$gg6p(@I zinO*f@+tmMk&h0FO#NUHv7n-ZD8E4l1zKB_h$1R2eJpvnGkKGocjugQ?>*TdUvF<9 z0btPv-^94c9pPdCfEoVicEm?nQ2;Ou0ExMmmM=NfG{bZ#P+#a1yP`R|t!8S`_a#@# zrBMyqE!offm1*l&UT!*de)yGt_x!ehj+Uk$e*H_|oM-QEKkt7YW%e}fes$`p%}9t_AoJ{+Q7q_|pndRo=}&d>(=taN*AOUGjC@QT5TBW-IP^8;L7COr$iy*bQ# zb8_sP6=fC{&Xx7s0^>536;~ItVITaU zU#i{JjDi$#$$~!joqxu~R0QUZ(EWc5d2iei=9-)0vRItbsn5J^wcGyKri*L4wt0Un zgb~{vZuAr@UkCV%s-JGtIdrFIpJ3E8v*H>4!Q9j9?#P;6aqrR!9zMKVcIN?fE!}?n zmUB{zZIE}>>32_pBf{&ib!R<}bds(5ozcxb^2qf)r~OcV&?ow!R-4}uyTvVMU(uzu z<^}Gx34xb`JnvfmmUFgesQPiaBy#7v&?T?)9u3fX>-O^=ZbSPs8;@mv;EmEO3R!mum0D?{cM%L`hdh@OI~H~49fwJ zADs5i>&Q6SnRzq1u=BcSLYJ~|&l8IkR>?1*Y6U`?gK+yV-fQ905k^Ct8Nk{c2jN+5$xz_Z0NFn0HoQxQPNG6ua$ig9lVkje#yKt@6tusw;S(Ce7|v7s&5aOmO-&dEm4~o ziUC9#ppg&-5+F<|#KurXri5W3Dh8MY1;au-46wp)@QUP1T@}kBT0+Y|DanCcN(KUv zsm9c9;;)CyY|I=9A4CeIuqR&d+SE!du>g@utC~t$hsfN{+-V|ih@Z?)Aq|sA;9-sh zlnhgqwv4O#`fL-P10!MhPwo`X3Lm0L7;$GpYz=-QPo13EG{(lr`L4<)=eD*ylh literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/renderer_metal.mm.4A5862252690E2BA.idx b/.cache/clangd/index/renderer_metal.mm.4A5862252690E2BA.idx new file mode 100644 index 0000000000000000000000000000000000000000..670e301ee18983c7289c4a8d1f15e16d7044969b GIT binary patch literal 23630 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 Xd!%e>2c>mAKQv(hfgBQWd@KJ0rKYj) literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/ui_icons.h.C42E196812AFE48E.idx b/.cache/clangd/index/ui_icons.h.C42E196812AFE48E.idx new file mode 100644 index 0000000000000000000000000000000000000000..230f1f48f9fcbbb3d10930e668e5e4c9ee29226b GIT binary patch literal 1344 zcmWIYbaS&{Wngel@vO*AElFfyU|9ffZef!vZ)8d9p2`Wx2_ZhHuzS1~*HR$eg<|j_OUgk*7&v|a3a@#aoe-UrA%{npv z*l$v|%O&^bMt!vO3e)Z0He397&fc$^9Dgo+@krstrtde|1slcJ9lEB`v`}0v@p`_h zBcImMhM-+nBQj@6`kh|&!zIz==~hjX^QS~su4B1d*!wO?OMKs-=FMT>47TgXPkCxT zcaltgheuGqr>)|5f$V?tb{?I0dBz;ICq8wI>DzAS6<6ja9Rh~0bBDp#UshVGtPGqC z3_`4|tbAYs!~+6GA;#zF|F8GPT~+00UH0VWU%%5TOmVQ7o2;u9804-e9G=_OQP0O936d7!6$2A6 z>Hfg}!`o$}C$KSa0gdNj=Me%E2nTNEJbQW7)|=aS7{q}|N18=iMo5fBOooAziIJIw zALzilkt}fsq#SngF-U+c;Nj&16EF*2pUbOG;L$zL4-ydKWZ@J96A1UKUdejYxc{Wq%B)jW22eCJ~j z0qN)F;{g*e{jaOL)0ag?{^Vx>W&mtC*=viGk&yNHoTNQ^;@2^L-AvpzgnUsSP~iARW+g_j-XNnqHp z@Ug>!9Hy9&6B4IT3t%w{at<&$Va|ZXB+xmaXoUF*=p1Q54VZIaMg#SNq7tSTCJnR+ z9D7ik4w^qWpJtu+jv459Suq8edw@JmF&!kHsh9pfD(oVUC0O s8{`#W%)(SN6s6`Qg4|GClw1r<*9;5-AOZq}jO2`!&R!_F!YIH104Fw_CjbBd literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/ui_widgets.cpp.95BDB5FDC10F055B.idx b/.cache/clangd/index/ui_widgets.cpp.95BDB5FDC10F055B.idx new file mode 100644 index 0000000000000000000000000000000000000000..d132a0aff1917af33d4eb35d11c91b2523d54ffe GIT binary patch literal 39670 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ⅈ3nPyoF5sGg(#-T}NVxVUb0iKQ;;3yKm-0 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 literal 0 HcmV?d00001 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c65124d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "lldb-dap.executable-path": "/opt/homebrew/opt/llvm/bin/lldb-dap" +} diff --git a/build.c b/build.c index 7bc8686..8a6fc02 100644 --- a/build.c +++ b/build.c @@ -6,6 +6,36 @@ #define NOB_IMPLEMENTATION #include "vendor/nob/nob.h" +//////////////////////////////// +// lunasvg/plutovg static library build helpers + +static const char *plutovg_sources[] = { + "vendor/lunasvg/plutovg/source/plutovg-blend.c", + "vendor/lunasvg/plutovg/source/plutovg-canvas.c", + "vendor/lunasvg/plutovg/source/plutovg-font.c", + "vendor/lunasvg/plutovg/source/plutovg-ft-math.c", + "vendor/lunasvg/plutovg/source/plutovg-ft-raster.c", + "vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c", + "vendor/lunasvg/plutovg/source/plutovg-matrix.c", + "vendor/lunasvg/plutovg/source/plutovg-paint.c", + "vendor/lunasvg/plutovg/source/plutovg-path.c", + "vendor/lunasvg/plutovg/source/plutovg-rasterize.c", + "vendor/lunasvg/plutovg/source/plutovg-surface.c", +}; + +static const char *lunasvg_sources[] = { + "vendor/lunasvg/source/graphics.cpp", + "vendor/lunasvg/source/lunasvg.cpp", + "vendor/lunasvg/source/svgelement.cpp", + "vendor/lunasvg/source/svggeometryelement.cpp", + "vendor/lunasvg/source/svglayoutstate.cpp", + "vendor/lunasvg/source/svgpaintelement.cpp", + "vendor/lunasvg/source/svgparser.cpp", + "vendor/lunasvg/source/svgproperty.cpp", + "vendor/lunasvg/source/svgrenderstate.cpp", + "vendor/lunasvg/source/svgtextelement.cpp", +}; + #ifdef __APPLE__ //////////////////////////////// // macOS build (clang++ with Objective-C++) @@ -22,6 +52,128 @@ static const char *frameworks[] = { "-framework", "CoreGraphics", }; +static bool build_lunasvg_lib(const char *build_dir, bool debug) { + const char *obj_dir = nob_temp_sprintf("%s/lunasvg_obj", build_dir); + const char *lib_path = nob_temp_sprintf("%s/liblunasvg.a", build_dir); + + // Collect all source paths to check if rebuild is needed + { + const char *all_sources[NOB_ARRAY_LEN(plutovg_sources) + NOB_ARRAY_LEN(lunasvg_sources)]; + size_t n = 0; + for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) + all_sources[n++] = plutovg_sources[i]; + for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) + all_sources[n++] = lunasvg_sources[i]; + + if (!nob_needs_rebuild(lib_path, all_sources, n)) { + nob_log(NOB_INFO, "lunasvg is up to date"); + return true; + } + } + + if (!nob_mkdir_if_not_exists(obj_dir)) return false; + + // Compile plutovg C sources + for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) { + const char *src = plutovg_sources[i]; + // Extract filename for .o + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char obj_name[256]; + snprintf(obj_name, sizeof(obj_name), "%s", base); + char *dot = strrchr(obj_name, '.'); + if (dot) { dot[1] = 'o'; dot[2] = '\0'; } + const char *obj_path = nob_temp_sprintf("%s/%s", obj_dir, obj_name); + + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "clang"); + nob_cmd_append(&cmd, "-std=c11", "-c"); + nob_cmd_append(&cmd, "-DPLUTVOG_BUILD", "-DPLUTVOG_BUILD_STATIC"); + nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include"); + nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/source"); + nob_cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter"); + if (debug) { + nob_cmd_append(&cmd, "-g", "-O0"); + } else { + nob_cmd_append(&cmd, "-O2"); + } + nob_cmd_append(&cmd, "-o", obj_path); + nob_cmd_append(&cmd, src); + Nob_Cmd_Opt copt = {0}; + if (!nob_cmd_run_opt(&cmd, copt)) return false; + } + + // Compile lunasvg C++ sources + for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) { + const char *src = lunasvg_sources[i]; + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char obj_name[256]; + snprintf(obj_name, sizeof(obj_name), "%s", base); + char *dot = strrchr(obj_name, '.'); + if (dot) { dot[1] = 'o'; dot[2] = '\0'; } + const char *obj_path = nob_temp_sprintf("%s/%s", obj_dir, obj_name); + + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "clang++"); + nob_cmd_append(&cmd, "-std=c++17", "-c"); + nob_cmd_append(&cmd, "-fno-exceptions", "-fno-rtti"); + nob_cmd_append(&cmd, "-DLUNASVG_BUILD", "-DLUNASVG_BUILD_STATIC"); + nob_cmd_append(&cmd, "-Ivendor/lunasvg/include"); + nob_cmd_append(&cmd, "-Ivendor/lunasvg/source"); + nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include"); + nob_cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter"); + if (debug) { + nob_cmd_append(&cmd, "-g", "-O0"); + } else { + nob_cmd_append(&cmd, "-O2"); + } + nob_cmd_append(&cmd, "-o", obj_path); + nob_cmd_append(&cmd, src); + Nob_Cmd_Opt copt = {0}; + if (!nob_cmd_run_opt(&cmd, copt)) return false; + } + + // Archive into static library + { + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "ar", "rcs", lib_path); + for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) { + const char *src = plutovg_sources[i]; + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char obj_name[256]; + snprintf(obj_name, sizeof(obj_name), "%s", base); + char *dot = strrchr(obj_name, '.'); + if (dot) { dot[1] = 'o'; dot[2] = '\0'; } + nob_cmd_append(&cmd, nob_temp_sprintf("%s/%s", obj_dir, obj_name)); + } + for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) { + const char *src = lunasvg_sources[i]; + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char obj_name[256]; + snprintf(obj_name, sizeof(obj_name), "%s", base); + char *dot = strrchr(obj_name, '.'); + if (dot) { dot[1] = 'o'; dot[2] = '\0'; } + nob_cmd_append(&cmd, nob_temp_sprintf("%s/%s", obj_dir, obj_name)); + } + Nob_Cmd_Opt copt = {0}; + if (!nob_cmd_run_opt(&cmd, copt)) return false; + } + + // Clean up obj dir — only the .a is needed + { + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "rm", "-rf", obj_dir); + Nob_Cmd_Opt copt = {0}; + nob_cmd_run_opt(&cmd, copt); + } + + nob_log(NOB_INFO, "Built %s", lib_path); + return true; +} + int main(int argc, char **argv) { NOB_GO_REBUILD_URSELF(argc, argv); @@ -55,6 +207,9 @@ int main(int argc, char **argv) { if (!nob_mkdir_if_not_exists(macos_dir)) return 1; if (!nob_mkdir_if_not_exists(res_dir)) return 1; + // Build lunasvg static library + if (!build_lunasvg_lib(build_dir, debug)) return 1; + // Unity build: single clang++ invocation compiles main.cpp (which #includes everything) { Nob_Cmd cmd = {0}; @@ -64,6 +219,8 @@ int main(int argc, char **argv) { nob_cmd_append(&cmd, "-Wall", "-Wextra", "-Wno-missing-field-initializers"); nob_cmd_append(&cmd, "-Wno-deprecated-declarations"); nob_cmd_append(&cmd, "-Isrc", "-Ivendor/clay"); + nob_cmd_append(&cmd, "-Ivendor/lunasvg/include"); + nob_cmd_append(&cmd, "-DLUNASVG_BUILD_STATIC"); if (debug) { nob_cmd_append(&cmd, "-g", "-O0", "-D_DEBUG"); @@ -74,11 +231,16 @@ int main(int argc, char **argv) { nob_cmd_append(&cmd, "-o", binary_path); nob_cmd_append(&cmd, "src/main.cpp"); + // Reset language mode so .a is treated as a library, not source + nob_cmd_append(&cmd, "-x", "none"); + nob_cmd_append(&cmd, nob_temp_sprintf("%s/liblunasvg.a", build_dir)); + { size_t i; for (i = 0; i < NOB_ARRAY_LEN(frameworks); i++) nob_cmd_append(&cmd, frameworks[i]); } + nob_cmd_append(&cmd, "-lstdc++"); { Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; } } @@ -157,6 +319,92 @@ static const char *link_libs[] = { "winmm.lib", }; +static bool build_lunasvg_lib(const char *build_dir, bool debug) { + const char *obj_dir = nob_temp_sprintf("%s\\lunasvg_obj", build_dir); + const char *lib_path = nob_temp_sprintf("%s\\lunasvg.lib", build_dir); + + // Check if rebuild is needed + { + const char *all_sources[NOB_ARRAY_LEN(plutovg_sources) + NOB_ARRAY_LEN(lunasvg_sources)]; + size_t n = 0; + for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) + all_sources[n++] = plutovg_sources[i]; + for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) + all_sources[n++] = lunasvg_sources[i]; + + if (!nob_needs_rebuild(lib_path, all_sources, n)) { + nob_log(NOB_INFO, "lunasvg is up to date"); + return true; + } + } + + if (!nob_mkdir_if_not_exists(obj_dir)) return false; + + // Compile plutovg C sources + for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) { + const char *src = plutovg_sources[i]; + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c"); + nob_cmd_append(&cmd, "/std:c11"); + nob_cmd_append(&cmd, "/DPLUTVOG_BUILD", "/DPLUTVOG_BUILD_STATIC"); + nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include"); + nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/source"); + nob_cmd_append(&cmd, "/W3"); + if (debug) { + nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od"); + } else { + nob_cmd_append(&cmd, "/MT", "/O2"); + } + nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", obj_dir)); + nob_cmd_append(&cmd, src); + Nob_Cmd_Opt copt = {0}; + if (!nob_cmd_run_opt(&cmd, copt)) return false; + } + + // Compile lunasvg C++ sources + for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) { + const char *src = lunasvg_sources[i]; + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c"); + nob_cmd_append(&cmd, "/std:c++17", "/EHsc"); + nob_cmd_append(&cmd, "/DLUNASVG_BUILD", "/DLUNASVG_BUILD_STATIC"); + nob_cmd_append(&cmd, "/Ivendor/lunasvg/include"); + nob_cmd_append(&cmd, "/Ivendor/lunasvg/source"); + nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include"); + nob_cmd_append(&cmd, "/W3"); + if (debug) { + nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od"); + } else { + nob_cmd_append(&cmd, "/MT", "/O2"); + } + nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", obj_dir)); + nob_cmd_append(&cmd, src); + Nob_Cmd_Opt copt = {0}; + if (!nob_cmd_run_opt(&cmd, copt)) return false; + } + + // Archive into static library + { + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "lib.exe", "/nologo", nob_temp_sprintf("/OUT:%s", lib_path)); + nob_cmd_append(&cmd, nob_temp_sprintf("%s/*.obj", obj_dir)); + Nob_Cmd_Opt copt = {0}; + if (!nob_cmd_run_opt(&cmd, copt)) return false; + } + + // Clean up obj dir — only the .lib is needed + { + Nob_Cmd cmd = {0}; + nob_cmd_append(&cmd, "cmd.exe", "/c", + nob_temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir)); + Nob_Cmd_Opt copt = {0}; + nob_cmd_run_opt(&cmd, copt); + } + + nob_log(NOB_INFO, "Built %s", lib_path); + return true; +} + int main(int argc, char **argv) { NOB_GO_REBUILD_URSELF(argc, argv); @@ -180,12 +428,17 @@ int main(int argc, char **argv) { if (!nob_mkdir_if_not_exists(build_dir)) return 1; + // Build lunasvg static library + if (!build_lunasvg_lib(build_dir, debug)) return 1; + // Unity build: single cl.exe invocation compiles main.cpp (which #includes everything) { Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cl.exe"); nob_cmd_append(&cmd, "/nologo", "/std:c++20", "/EHsc", "/W3"); nob_cmd_append(&cmd, "/Isrc", "/Ivendor/clay"); + nob_cmd_append(&cmd, "/Ivendor/lunasvg/include"); + nob_cmd_append(&cmd, "/DLUNASVG_BUILD_STATIC"); if (debug) { nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od", "/D_DEBUG"); @@ -204,6 +457,7 @@ int main(int argc, char **argv) { nob_cmd_append(&cmd, "/SUBSYSTEM:CONSOLE"); nob_cmd_append(&cmd, nob_temp_sprintf("/PDB:%s/autosample.pdb", build_dir)); nob_cmd_append(&cmd, "/DEBUG"); + nob_cmd_append(&cmd, nob_temp_sprintf("%s/lunasvg.lib", build_dir)); { size_t i; for (i = 0; i < NOB_ARRAY_LEN(link_libs); i++) diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..d5daffc --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,24 @@ +[ + { + "directory": "/Users/mta/projects/autosample", + "file": "src/main.cpp", + "arguments": [ + "clang++", + "-std=c++20", + "-x", "objective-c++", + "-fno-exceptions", + "-fno-rtti", + "-Isrc", + "-Ivendor/clay", + "-Ivendor/lunasvg/include", + "-DLUNASVG_BUILD_STATIC", + "-D_DEBUG", + "-Wall", + "-Wextra", + "-Wno-missing-field-initializers", + "-Wno-deprecated-declarations", + "-c", + "src/main.cpp" + ] + } +] diff --git a/src/main.cpp b/src/main.cpp index cbed27e..ad3b23d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,11 +10,13 @@ #include "midi/midi.h" #include "audio/audio.h" #include "ui/ui_core.h" +#include "ui/ui_icons.h" #include "ui/ui_widgets.h" // [cpp] #include "base/base_inc.cpp" #include "ui/ui_core.cpp" +#include "ui/ui_icons.cpp" #include "ui/ui_widgets.cpp" #ifdef __APPLE__ #include "platform/platform_macos.mm" @@ -808,6 +810,16 @@ int main(int argc, char **argv) { setup_menus(window); ui_widgets_init(); + // Rasterize icon atlas and upload to GPU + { + S32 iw, ih; + U8 *icon_atlas = ui_icons_rasterize_atlas(&iw, &ih, 48); + if (icon_atlas) { + renderer_create_icon_atlas(renderer, icon_atlas, iw, ih); + free(icon_atlas); + } + } + AppState app = {}; app.window = window; app.renderer = renderer; diff --git a/src/renderer/renderer.h b/src/renderer/renderer.h index 06b1974..2650b9b 100644 --- a/src/renderer/renderer.h +++ b/src/renderer/renderer.h @@ -26,3 +26,6 @@ void renderer_set_clear_color(Renderer *renderer, float r, float g, float b // user_data should be the Renderer pointer. struct Vec2F32; Vec2F32 renderer_measure_text(const char *text, int32_t length, float font_size, void *user_data); + +// Upload an R8 icon atlas texture for icon rendering +void renderer_create_icon_atlas(Renderer *renderer, const uint8_t *data, int32_t w, int32_t h); diff --git a/src/renderer/renderer_dx12.cpp b/src/renderer/renderer_dx12.cpp index 1ecf2e8..9de7fd0 100644 --- a/src/renderer/renderer_dx12.cpp +++ b/src/renderer/renderer_dx12.cpp @@ -1,5 +1,6 @@ #include "renderer/renderer.h" #include "ui/ui_core.h" +#include "ui/ui_icons.h" #include #include @@ -201,6 +202,10 @@ struct Renderer { F32 font_atlas_size; // font size the atlas was built at F32 font_line_height; + // Icon atlas + ID3D12Resource *icon_texture; + ID3D12DescriptorHeap *icon_srv_heap; + // GDI text measurement HDC measure_dc; HFONT measure_font; @@ -976,7 +981,7 @@ static void emit_text_glyphs(DrawBatch *batch, Renderer *r, //////////////////////////////// // Flush helper: issues a draw call for accumulated vertices, then resets batch -static void flush_batch(Renderer *r, DrawBatch *batch, UINT buf_idx) { +static void flush_batch(Renderer *r, DrawBatch *batch, UINT buf_idx, ID3D12DescriptorHeap *tex_heap = nullptr) { if (batch->index_count == 0) return; r->command_list->SetPipelineState(r->pipeline_state); @@ -985,10 +990,11 @@ static void flush_batch(Renderer *r, DrawBatch *batch, UINT buf_idx) { float constants[4] = { (float)r->width, (float)r->height, 0, 0 }; r->command_list->SetGraphicsRoot32BitConstants(0, 4, constants, 0); - // Bind font texture - r->command_list->SetDescriptorHeaps(1, &r->srv_heap); + // Bind texture (font or icon) + ID3D12DescriptorHeap *heap = tex_heap ? tex_heap : r->srv_heap; + r->command_list->SetDescriptorHeaps(1, &heap); r->command_list->SetGraphicsRootDescriptorTable(1, - r->srv_heap->GetGPUDescriptorHandleForHeapStart()); + heap->GetGPUDescriptorHandleForHeapStart()); r->command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); @@ -1053,6 +1059,8 @@ void renderer_destroy(Renderer *r) { if (r->index_buffers[i]) r->index_buffers[i]->Release(); } if (r->font_texture) r->font_texture->Release(); + if (r->icon_texture) r->icon_texture->Release(); + if (r->icon_srv_heap) r->icon_srv_heap->Release(); if (r->pipeline_state) r->pipeline_state->Release(); if (r->root_signature) r->root_signature->Release(); @@ -1133,6 +1141,23 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { batch.vertex_count = 0; batch.index_count = 0; + // Track which texture is currently bound (0 = font, 1 = icon) + int bound_texture = 0; + + auto bind_font = [&]() { + if (bound_texture != 0) { + flush_batch(r, &batch, buf_idx, r->srv_heap); + bound_texture = 0; + } + }; + + auto bind_icon = [&]() { + if (bound_texture != 1 && r->icon_srv_heap) { + flush_batch(r, &batch, buf_idx); + bound_texture = 1; + } + }; + for (int32_t i = 0; i < render_commands.length; i++) { Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&render_commands, i); Clay_BoundingBox bb = cmd->boundingBox; @@ -1177,6 +1202,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { } break; case CLAY_RENDER_COMMAND_TYPE_TEXT: { + bind_font(); Clay_TextRenderData *text = &cmd->renderData.text; emit_text_glyphs(&batch, r, bb, text->textColor, text->stringContents.chars, text->stringContents.length, @@ -1185,7 +1211,8 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { // Flush before changing scissor - flush_batch(r, &batch, buf_idx); + ID3D12DescriptorHeap *heap = bound_texture == 1 ? r->icon_srv_heap : r->srv_heap; + flush_batch(r, &batch, buf_idx, heap); D3D12_RECT clip = {}; clip.left = (LONG)Max(bb.x, 0.f); clip.top = (LONG)Max(bb.y, 0.f); @@ -1197,7 +1224,8 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { } break; case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { - flush_batch(r, &batch, buf_idx); + ID3D12DescriptorHeap *heap = bound_texture == 1 ? r->icon_srv_heap : r->srv_heap; + flush_batch(r, &batch, buf_idx, heap); D3D12_RECT full_scissor = { 0, 0, (LONG)r->width, (LONG)r->height }; r->command_list->RSSetScissorRects(1, &full_scissor); } break; @@ -1207,6 +1235,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { if (custom->customData) { CustomRenderType type = *(CustomRenderType *)custom->customData; if (type == CUSTOM_RENDER_VGRADIENT) { + bind_font(); CustomGradientData *grad = (CustomGradientData *)custom->customData; Clay_Color tc = grad->top_color; Clay_Color bc = grad->bottom_color; @@ -1217,6 +1246,20 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { custom->cornerRadius.topLeft, custom->cornerRadius.topRight, custom->cornerRadius.bottomRight, custom->cornerRadius.bottomLeft, 1.0f); + } else if (type == CUSTOM_RENDER_ICON) { + bind_icon(); + CustomIconData *icon = (CustomIconData *)custom->customData; + Clay_Color c = icon->color; + float cr = c.r / 255.f, cg = c.g / 255.f; + float cb = c.b / 255.f, ca = c.a / 255.f; + UI_IconInfo *info = &g_icons[icon->icon_id]; + emit_quad(&batch, + bb.x, bb.y, bb.x + bb.width, bb.y + bb.height, + info->u0, info->v0, info->u1, info->v1, + cr, cg, cb, ca, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 1.0f); } } } break; @@ -1228,7 +1271,8 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { } // Flush remaining - flush_batch(r, &batch, buf_idx); + ID3D12DescriptorHeap *heap = bound_texture == 1 ? r->icon_srv_heap : r->srv_heap; + flush_batch(r, &batch, buf_idx, heap); } barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; @@ -1245,6 +1289,101 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { r->frame_index++; } +void renderer_create_icon_atlas(Renderer *r, const uint8_t *data, int32_t w, int32_t h) { + // Create texture resource + D3D12_HEAP_PROPERTIES heap_props = {}; + heap_props.Type = D3D12_HEAP_TYPE_DEFAULT; + + D3D12_RESOURCE_DESC tex_desc = {}; + tex_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + tex_desc.Width = w; + tex_desc.Height = h; + tex_desc.DepthOrArraySize = 1; + tex_desc.MipLevels = 1; + tex_desc.Format = DXGI_FORMAT_R8_UNORM; + tex_desc.SampleDesc.Count = 1; + tex_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + + r->device->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_NONE, + &tex_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, + IID_PPV_ARGS(&r->icon_texture)); + + // Upload via staging buffer + D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {}; + UINT64 total_bytes = 0; + r->device->GetCopyableFootprints(&tex_desc, 0, 1, 0, &footprint, nullptr, nullptr, &total_bytes); + + D3D12_HEAP_PROPERTIES upload_heap = {}; + upload_heap.Type = D3D12_HEAP_TYPE_UPLOAD; + + D3D12_RESOURCE_DESC upload_desc = {}; + upload_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + upload_desc.Width = total_bytes; + upload_desc.Height = 1; + upload_desc.DepthOrArraySize = 1; + upload_desc.MipLevels = 1; + upload_desc.SampleDesc.Count = 1; + upload_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + + ID3D12Resource *upload_buf = nullptr; + r->device->CreateCommittedResource(&upload_heap, D3D12_HEAP_FLAG_NONE, + &upload_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, + IID_PPV_ARGS(&upload_buf)); + + void *mapped = nullptr; + D3D12_RANGE read_range = {0, 0}; + upload_buf->Map(0, &read_range, &mapped); + U8 *dst = (U8 *)mapped; + for (int y = 0; y < h; y++) { + memcpy(dst + y * footprint.Footprint.RowPitch, data + y * w, w); + } + upload_buf->Unmap(0, nullptr); + + r->frames[0].command_allocator->Reset(); + r->command_list->Reset(r->frames[0].command_allocator, nullptr); + + D3D12_TEXTURE_COPY_LOCATION src_loc = {}; + src_loc.pResource = upload_buf; + src_loc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + src_loc.PlacedFootprint = footprint; + + D3D12_TEXTURE_COPY_LOCATION dst_loc = {}; + dst_loc.pResource = r->icon_texture; + dst_loc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dst_loc.SubresourceIndex = 0; + + r->command_list->CopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc, nullptr); + + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.pResource = r->icon_texture; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + r->command_list->ResourceBarrier(1, &barrier); + + r->command_list->Close(); + r->command_queue->ExecuteCommandLists(1, (ID3D12CommandList *const *)&r->command_list); + wait_for_pending(r); + upload_buf->Release(); + + // Create separate SRV heap for icon texture + D3D12_DESCRIPTOR_HEAP_DESC srv_desc = {}; + srv_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + srv_desc.NumDescriptors = 1; + srv_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + r->device->CreateDescriptorHeap(&srv_desc, IID_PPV_ARGS(&r->icon_srv_heap)); + + D3D12_SHADER_RESOURCE_VIEW_DESC srv_view = {}; + srv_view.Format = DXGI_FORMAT_R8_UNORM; + srv_view.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srv_view.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srv_view.Texture2D.MipLevels = 1; + + r->device->CreateShaderResourceView(r->icon_texture, + &srv_view, r->icon_srv_heap->GetCPUDescriptorHandleForHeapStart()); +} + void renderer_resize(Renderer *r, int32_t width, int32_t height) { if (width <= 0 || height <= 0) return; diff --git a/src/renderer/renderer_metal.mm b/src/renderer/renderer_metal.mm index 425160f..37dd008 100644 --- a/src/renderer/renderer_metal.mm +++ b/src/renderer/renderer_metal.mm @@ -1,5 +1,6 @@ #include "renderer/renderer.h" #include "ui/ui_core.h" +#include "ui/ui_icons.h" #import #import @@ -162,6 +163,9 @@ struct Renderer { F32 font_atlas_size; F32 font_line_height; + // Icon atlas + id icon_texture; + // Text measurement (Core Text) CTFontRef measure_font; F32 measure_font_size; @@ -694,6 +698,9 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { batch.vertex_count = 0; batch.index_count = 0; + // Track which texture is currently bound (0 = font, 1 = icon) + int bound_texture = 0; + auto flush_batch = [&]() { if (batch.index_count == 0) return; @@ -708,6 +715,22 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { batch.index_count = 0; }; + auto bind_font_texture = [&]() { + if (bound_texture != 0) { + flush_batch(); + [encoder setFragmentTexture:r->font_texture atIndex:0]; + bound_texture = 0; + } + }; + + auto bind_icon_texture = [&]() { + if (bound_texture != 1 && r->icon_texture) { + flush_batch(); + [encoder setFragmentTexture:r->icon_texture atIndex:0]; + bound_texture = 1; + } + }; + for (int32_t i = 0; i < render_commands.length; i++) { Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&render_commands, i); Clay_BoundingBox bb = cmd->boundingBox; @@ -751,6 +774,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { } break; case CLAY_RENDER_COMMAND_TYPE_TEXT: { + bind_font_texture(); Clay_TextRenderData *text = &cmd->renderData.text; emit_text_glyphs(&batch, r, bb, text->textColor, text->stringContents.chars, text->stringContents.length, @@ -779,6 +803,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { if (custom->customData) { CustomRenderType type = *(CustomRenderType *)custom->customData; if (type == CUSTOM_RENDER_VGRADIENT) { + bind_font_texture(); CustomGradientData *grad = (CustomGradientData *)custom->customData; Clay_Color tc = grad->top_color; Clay_Color bc = grad->bottom_color; @@ -789,6 +814,20 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { custom->cornerRadius.topLeft, custom->cornerRadius.topRight, custom->cornerRadius.bottomRight, custom->cornerRadius.bottomLeft, 1.0f); + } else if (type == CUSTOM_RENDER_ICON) { + bind_icon_texture(); + CustomIconData *icon = (CustomIconData *)custom->customData; + Clay_Color c = icon->color; + float cr = c.r / 255.f, cg = c.g / 255.f; + float cb = c.b / 255.f, ca = c.a / 255.f; + UI_IconInfo *info = &g_icons[icon->icon_id]; + emit_quad(&batch, + bb.x, bb.y, bb.x + bb.width, bb.y + bb.height, + info->u0, info->v0, info->u1, info->v1, + cr, cg, cb, ca, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 1.0f); } } } break; @@ -816,6 +855,20 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { r->frame_index++; } +void renderer_create_icon_atlas(Renderer *r, const uint8_t *data, int32_t w, int32_t h) { + MTLTextureDescriptor *tex_desc = [[MTLTextureDescriptor alloc] init]; + tex_desc.pixelFormat = MTLPixelFormatR8Unorm; + tex_desc.width = w; + tex_desc.height = h; + tex_desc.usage = MTLTextureUsageShaderRead; + + r->icon_texture = [r->device newTextureWithDescriptor:tex_desc]; + [r->icon_texture replaceRegion:MTLRegionMake2D(0, 0, w, h) + mipmapLevel:0 + withBytes:data + bytesPerRow:w]; +} + void renderer_set_clear_color(Renderer *r, float cr, float cg, float cb) { r->clear_r = cr; r->clear_g = cg; diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index 77cba87..d655d36 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -113,6 +113,7 @@ static inline uint16_t uifs(float x) { return (uint16_t)(x * g_ui_scale + 0.5f); enum CustomRenderType { CUSTOM_RENDER_VGRADIENT = 1, + CUSTOM_RENDER_ICON = 2, }; struct CustomGradientData { @@ -121,6 +122,12 @@ struct CustomGradientData { Clay_Color bottom_color; }; +struct CustomIconData { + CustomRenderType type; // CUSTOM_RENDER_ICON + S32 icon_id; + Clay_Color color; +}; + //////////////////////////////// // Font sizes diff --git a/src/ui/ui_icons.cpp b/src/ui/ui_icons.cpp new file mode 100644 index 0000000..5594e63 --- /dev/null +++ b/src/ui/ui_icons.cpp @@ -0,0 +1,76 @@ +// ui_icons.cpp - SVG icon rasterization via lunasvg + +#include "ui/ui_icons.h" +#include +#include +#include + +UI_IconInfo g_icons[UI_ICON_COUNT] = {}; + +// Simple SVG icon sources (24x24 viewBox) +static const char *g_icon_svgs[UI_ICON_COUNT] = { + // UI_ICON_CLOSE - X mark + R"( + + )", + + // UI_ICON_CHECK - checkmark + R"( + + )", + + // UI_ICON_CHEVRON_DOWN - downward arrow + R"( + + )", +}; + +U8 *ui_icons_rasterize_atlas(S32 *out_w, S32 *out_h, S32 icon_size) { + // Pack icons in a row + S32 atlas_w = icon_size * UI_ICON_COUNT; + S32 atlas_h = icon_size; + + // Pad to power of 2 isn't necessary for correctness, but ensure minimum size + if (atlas_w < 64) atlas_w = 64; + if (atlas_h < 64) atlas_h = 64; + + U8 *atlas = (U8 *)calloc(atlas_w * atlas_h, 1); + if (!atlas) return nullptr; + + S32 pen_x = 0; + for (S32 i = 0; i < UI_ICON_COUNT; i++) { + auto doc = lunasvg::Document::loadFromData(g_icon_svgs[i]); + if (!doc) continue; + + lunasvg::Bitmap bmp = doc->renderToBitmap(icon_size, icon_size); + if (bmp.isNull()) continue; + + // Extract alpha channel from ARGB32 premultiplied into R8 + U8 *src = bmp.data(); + S32 bmp_w = bmp.width(); + S32 bmp_h = bmp.height(); + S32 stride = bmp.stride(); + + for (S32 y = 0; y < bmp_h && y < atlas_h; y++) { + for (S32 x = 0; x < bmp_w && (pen_x + x) < atlas_w; x++) { + // ARGB32 premultiplied: bytes are B, G, R, A (little-endian) + U8 a = src[y * stride + x * 4 + 3]; + atlas[y * atlas_w + pen_x + x] = a; + } + } + + // Store UV and pixel info + g_icons[i].u0 = (F32)pen_x / (F32)atlas_w; + g_icons[i].v0 = 0.0f; + g_icons[i].u1 = (F32)(pen_x + bmp_w) / (F32)atlas_w; + g_icons[i].v1 = (F32)bmp_h / (F32)atlas_h; + g_icons[i].w = (F32)bmp_w; + g_icons[i].h = (F32)bmp_h; + + pen_x += icon_size; + } + + *out_w = atlas_w; + *out_h = atlas_h; + return atlas; +} diff --git a/src/ui/ui_icons.h b/src/ui/ui_icons.h new file mode 100644 index 0000000..c5c3668 --- /dev/null +++ b/src/ui/ui_icons.h @@ -0,0 +1,23 @@ +#pragma once +// ui_icons.h - SVG icon definitions and atlas rasterization via lunasvg + +#include "base/base_inc.h" + +enum UI_IconID { + UI_ICON_CLOSE, + UI_ICON_CHECK, + UI_ICON_CHEVRON_DOWN, + UI_ICON_COUNT +}; + +struct UI_IconInfo { + F32 u0, v0, u1, v1; // UV coordinates in icon atlas + F32 w, h; // pixel dimensions at rasterized size +}; + +extern UI_IconInfo g_icons[UI_ICON_COUNT]; + +// Rasterizes all icons into an R8 atlas bitmap. +// Returns malloc'd data (caller frees). Sets *out_w, *out_h to atlas dimensions. +// icon_size is the pixel height to rasterize each icon at. +U8 *ui_icons_rasterize_atlas(S32 *out_w, S32 *out_h, S32 icon_size); diff --git a/src/ui/ui_widgets.cpp b/src/ui/ui_widgets.cpp index e460aa7..532ec3b 100644 --- a/src/ui/ui_widgets.cpp +++ b/src/ui/ui_widgets.cpp @@ -10,6 +10,11 @@ UI_WidgetState g_wstate = {}; +// Icon per-frame pool (forward declaration for begin_frame) +#define UI_MAX_ICONS_PER_FRAME 32 +static CustomIconData g_icon_pool[UI_MAX_ICONS_PER_FRAME]; +static S32 g_icon_pool_count = 0; + void ui_widgets_init() { g_wstate = {}; } @@ -17,6 +22,7 @@ void ui_widgets_init() { void ui_widgets_begin_frame(PlatformInput input) { g_wstate.input = input; g_wstate.mouse_clicked = (input.mouse_down && !input.was_mouse_down); + g_icon_pool_count = 0; g_wstate.cursor_blink += 1.0f / 60.0f; g_wstate.text_input_count = 0; g_wstate.tab_pressed = 0; @@ -96,6 +102,26 @@ static Clay_String clay_str(const char *s) { #define WID(s) CLAY_SID(clay_str(s)) #define WIDI(s, i) CLAY_SIDI(clay_str(s), i) +//////////////////////////////// +// Icon + +void ui_icon(UI_IconID icon, F32 size, Clay_Color color) { + if (g_icon_pool_count >= UI_MAX_ICONS_PER_FRAME) return; + + S32 idx = g_icon_pool_count; + CustomIconData *data = &g_icon_pool[g_icon_pool_count++]; + data->type = CUSTOM_RENDER_ICON; + data->icon_id = (S32)icon; + data->color = color; + + CLAY(CLAY_IDI("UIIcon", idx), + .layout = { + .sizing = { .width = CLAY_SIZING_FIXED(size), .height = CLAY_SIZING_FIXED(size) }, + }, + .custom = { .customData = data } + ) {} +} + //////////////////////////////// // Label @@ -168,7 +194,7 @@ B32 ui_checkbox(const char *id, const char *label, B32 *value) { .border = { .color = g_theme.border, .width = { 1, 1, 1, 1 } } ) { if (*value) { - CLAY_TEXT(CLAY_STRING("x"), &g_widget_text_config); + ui_icon(UI_ICON_CHECK, WIDGET_CHECKBOX_SIZE * 0.75f, g_theme.text); } } @@ -707,11 +733,11 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected) CLAY(WIDI(id, 501), .layout = { - .sizing = { .width = CLAY_SIZING_FIXED(uis(20)), .height = CLAY_SIZING_FIT() }, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER }, + .sizing = { .width = CLAY_SIZING_FIXED(uis(20)), .height = CLAY_SIZING_FIXED(uis(20)) }, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, } ) { - CLAY_TEXT(CLAY_STRING("v"), &g_widget_text_config_dim); + ui_icon(UI_ICON_CHEVRON_DOWN, uis(12), g_theme.text_dim); } } @@ -1078,7 +1104,7 @@ B32 ui_window(const char *id, const char *title, B32 *open, .backgroundColor = close_bg, .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM) ) { - CLAY_TEXT(CLAY_STRING("x"), &g_widget_text_config_btn); + ui_icon(UI_ICON_CLOSE, uis(12), g_theme.button_text); } } diff --git a/src/ui/ui_widgets.h b/src/ui/ui_widgets.h index e67cdb1..b539cc9 100644 --- a/src/ui/ui_widgets.h +++ b/src/ui/ui_widgets.h @@ -7,6 +7,7 @@ // like which text field is focused or which dropdown is open. #include "ui/ui_core.h" +#include "ui/ui_icons.h" #include "platform/platform.h" //////////////////////////////// @@ -89,6 +90,9 @@ void ui_text_input_reset_display_bufs(); // Widgets // All IDs must be unique string literals (passed to CLAY_ID internally). +// Icon element (rendered via icon atlas) +void ui_icon(UI_IconID icon, F32 size, Clay_Color color); + // Simple label void ui_label(const char *id, const char *text); diff --git a/vendor/lunasvg/LICENSE b/vendor/lunasvg/LICENSE new file mode 100644 index 0000000..f535cea --- /dev/null +++ b/vendor/lunasvg/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2026 Samuel Ugochukwu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/lunasvg/include/lunasvg.h b/vendor/lunasvg/include/lunasvg.h new file mode 100644 index 0000000..cf871ad --- /dev/null +++ b/vendor/lunasvg/include/lunasvg.h @@ -0,0 +1,791 @@ +/* + * Copyright (c) 2020-2026 Samuel Ugochukwu + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. +*/ + +#ifndef LUNASVG_H +#define LUNASVG_H + +#include +#include +#include +#include + +#if defined(LUNASVG_BUILD_STATIC) +#define LUNASVG_EXPORT +#define LUNASVG_IMPORT +#elif (defined(_WIN32) || defined(__CYGWIN__)) +#define LUNASVG_EXPORT __declspec(dllexport) +#define LUNASVG_IMPORT __declspec(dllimport) +#elif defined(__GNUC__) && (__GNUC__ >= 4) +#define LUNASVG_EXPORT __attribute__((__visibility__("default"))) +#define LUNASVG_IMPORT +#else +#define LUNASVG_EXPORT +#define LUNASVG_IMPORT +#endif + +#ifdef LUNASVG_BUILD +#define LUNASVG_API LUNASVG_EXPORT +#else +#define LUNASVG_API LUNASVG_IMPORT +#endif + +#define LUNASVG_VERSION_MAJOR 3 +#define LUNASVG_VERSION_MINOR 5 +#define LUNASVG_VERSION_MICRO 0 + +#define LUNASVG_VERSION_ENCODE(major, minor, micro) (((major) * 10000) + ((minor) * 100) + ((micro) * 1)) +#define LUNASVG_VERSION LUNASVG_VERSION_ENCODE(LUNASVG_VERSION_MAJOR, LUNASVG_VERSION_MINOR, LUNASVG_VERSION_MICRO) + +#define LUNASVG_VERSION_XSTRINGIZE(major, minor, micro) #major"."#minor"."#micro +#define LUNASVG_VERSION_STRINGIZE(major, minor, micro) LUNASVG_VERSION_XSTRINGIZE(major, minor, micro) +#define LUNASVG_VERSION_STRING LUNASVG_VERSION_STRINGIZE(LUNASVG_VERSION_MAJOR, LUNASVG_VERSION_MINOR, LUNASVG_VERSION_MICRO) + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct plutovg_surface plutovg_surface_t; +typedef struct plutovg_matrix plutovg_matrix_t; + +/** + * @brief Callback for cleaning up resources. + * + * This function is called to release resources associated with a specific operation. + * + * @param closure A user-defined pointer to the resource or context to be freed. + */ +typedef void (*lunasvg_destroy_func_t)(void* closure); + +/** + * @brief A function pointer type for a write callback. + * @param closure A pointer to user-defined data or context. + * @param data A pointer to the data to be written. + * @param size The size of the data in bytes. + */ +typedef void (*lunasvg_write_func_t)(void* closure, void* data, int size); + +/** + * @brief Returns the version of the lunasvg library encoded in a single integer. + * + * Encodes the version of the lunasvg library into a single integer for easier comparison. + * The version is typically represented by combining major, minor, and patch numbers into one integer. + * + * @return The lunasvg library version as a single integer. + */ +LUNASVG_API int lunasvg_version(void); + +/** + * @brief Returns the lunasvg library version as a human-readable string in "X.Y.Z" format. + * + * Provides the version of the lunasvg library as a human-readable string in the format "X.Y.Z", + * where X represents the major version, Y the minor version, and Z the patch version. + * + * @return A pointer to a string containing the version in "X.Y.Z" format. + */ +LUNASVG_API const char* lunasvg_version_string(void); + +/** +* @brief Add a font face from a file to the cache. +* @param family The name of the font family. If an empty string is provided, the font will act as a fallback. +* @param bold Use `true` for bold, `false` otherwise. +* @param italic Use `true` for italic, `false` otherwise. +* @param filename The path to the font file. +* @return `true` if the font face was successfully added to the cache, `false` otherwise. +*/ +LUNASVG_API bool lunasvg_add_font_face_from_file(const char* family, bool bold, bool italic, const char* filename); + +/** +* @brief Add a font face from memory to the cache. +* @param family The name of the font family. If an empty string is provided, the font will act as a fallback. +* @param bold Use `true` for bold, `false` otherwise. +* @param italic Use `true` for italic, `false` otherwise. +* @param data A pointer to the memory buffer containing the font data. +* @param length The size of the memory buffer in bytes. +* @param destroy_func Callback function to free the memory buffer when it is no longer needed. +* @param closure User-defined pointer passed to the `destroy_func` callback. +* @return `true` if the font face was successfully added to the cache, `false` otherwise. +*/ +LUNASVG_API bool lunasvg_add_font_face_from_data(const char* family, bool bold, bool italic, const void* data, size_t length, lunasvg_destroy_func_t destroy_func, void* closure); + +#ifdef __cplusplus +} +#endif + +namespace lunasvg { + +/** +* @note Bitmap pixel format is ARGB32_Premultiplied. +*/ +class LUNASVG_API Bitmap { +public: + /** + * @brief Constructs a null bitmap. + */ + Bitmap() = default; + + /** + * @brief Constructs a bitmap with the specified width and height. + * @note A null bitmap will be returned if memory cannot be allocated. + * @param width The width of the bitmap in pixels. + * @param height The height of the bitmap in pixels. + */ + Bitmap(int width, int height); + + /** + * @brief Constructs a bitmap with the provided pixel data, width, height, and stride. + * + * @param data A pointer to the raw pixel data in ARGB32 Premultiplied format. + * @param width The width of the bitmap in pixels. + * @param height The height of the bitmap in pixels. + * @param stride The number of bytes per row of pixel data (stride). + */ + Bitmap(uint8_t* data, int width, int height, int stride); + + /** + * @brief Copy constructor. + * @param bitmap The bitmap to copy. + */ + Bitmap(const Bitmap& bitmap); + + /** + * @brief Move constructor. + * @param bitmap The bitmap to move. + */ + Bitmap(Bitmap&& bitmap); + + /** + * @internal + */ + Bitmap(plutovg_surface_t* surface) : m_surface(surface) {} + + /** + * @brief Cleans up any resources associated with the bitmap. + */ + ~Bitmap(); + + /** + * @brief Copy assignment operator. + * @param bitmap The bitmap to copy. + * @return A reference to this bitmap. + */ + Bitmap& operator=(const Bitmap& bitmap); + + /** + * @brief Move assignment operator. + * @param bitmap The bitmap to move. + * @return A reference to this bitmap. + */ + Bitmap& operator=(Bitmap&& bitmap); + + /** + * @brief Swaps the content of this bitmap with another. + * @param bitmap The bitmap to swap with. + */ + void swap(Bitmap& bitmap); + + /** + * @brief Gets the pointer to the raw pixel data. + * @return A pointer to the raw pixel data. + */ + uint8_t* data() const; + + /** + * @brief Gets the width of the bitmap. + * @return The width of the bitmap in pixels. + */ + int width() const; + + /** + * @brief Gets the height of the bitmap. + * @return The height of the bitmap in pixels. + */ + int height() const; + + /** + * @brief Gets the stride of the bitmap. + * @return The number of bytes per row of pixel data (stride). + */ + int stride() const; + + /** + * @brief Clears the bitmap with the specified color. + * @param The color value in 0xRRGGBBAA format. + */ + void clear(uint32_t value); + + /** + * @brief Converts the bitmap pixel data from ARGB32 Premultiplied to RGBA Plain format in place. + */ + void convertToRGBA(); + + /** + * @brief Checks if the bitmap is null. + * @return True if the bitmap is null, false otherwise. + */ + bool isNull() const { return m_surface == nullptr; } + + /** + * @brief Checks if the bitmap is valid. + * @deprecated This function has been deprecated. Use `isNull()` instead to check whether the bitmap is null. + * @return True if the bitmap is valid, false otherwise. + */ + bool valid() const { return !isNull(); } + + /** + * @brief Writes the bitmap to a PNG file. + * @param filename The name of the file to write. + * @return True if the file was written successfully, false otherwise. + */ + bool writeToPng(const std::string& filename) const; + + /** + * @brief Writes the bitmap to a PNG stream. + * @param callback Callback function for writing data. + * @param closure User-defined data passed to the callback. + * @return True if successful, false otherwise. + */ + bool writeToPng(lunasvg_write_func_t callback, void* closure) const; + + /** + * @internal + */ + plutovg_surface_t* surface() const { return m_surface; } + +private: + plutovg_surface_t* release(); + plutovg_surface_t* m_surface{nullptr}; +}; + +class Rect; +class Matrix; + +/** + * @brief Represents a 2D axis-aligned bounding box. + */ +class LUNASVG_API Box { +public: + /** + * @brief Constructs a box with zero dimensions. + */ + Box() = default; + + /** + * @brief Constructs a box with the specified position and size. + * @param x The x-coordinate of the box's origin. + * @param y The y-coordinate of the box's origin. + * @param w The width of the box. + * @param h The height of the box. + */ + Box(float x, float y, float w, float h); + + /** + * @internal + */ + Box(const Rect& rect); + + /** + * @brief Transforms the box using the specified matrix. + * @param matrix The transformation matrix. + * @return A reference to this box, modified by the transformation. + */ + Box& transform(const Matrix& matrix); + + /** + * @brief Returns a new box transformed by the specified matrix. + * @param matrix The transformation matrix. + * @return A new box, transformed by the matrix. + */ + Box transformed(const Matrix& matrix) const; + + float x{0}; ///< The x-coordinate of the box's origin. + float y{0}; ///< The y-coordinate of the box's origin. + float w{0}; ///< The width of the box. + float h{0}; ///< The height of the box. +}; + +class Transform; + +/** + * @brief Represents a 2D transformation matrix. + */ +class LUNASVG_API Matrix { +public: + /** + * @brief Initializes the matrix to the identity matrix. + */ + Matrix() = default; + + /** + * @brief Constructs a matrix with the specified values. + * @param a The horizontal scaling factor. + * @param b The vertical shearing factor. + * @param c The horizontal shearing factor. + * @param d The vertical scaling factor. + * @param e The horizontal translation offset. + * @param f The vertical translation offset. + */ + Matrix(float a, float b, float c, float d, float e, float f); + + /** + * @internal + */ + Matrix(const plutovg_matrix_t& matrix); + + /** + * @internal + */ + Matrix(const Transform& transform); + + /** + * @brief Multiplies this matrix with another matrix. + * @param matrix The matrix to multiply with. + * @return A new matrix that is the result of the multiplication. + */ + Matrix operator*(const Matrix& matrix) const; + + /** + * @brief Multiplies this matrix with another matrix in place. + * @param matrix The matrix to multiply with. + * @return A reference to this matrix after multiplication. + */ + Matrix& operator*=(const Matrix& matrix); + + /** + * @brief Multiplies this matrix with another matrix. + * @param matrix The matrix to multiply with. + * @return A reference to this matrix after multiplication. + */ + Matrix& multiply(const Matrix& matrix); + + /** + * @brief Translates this matrix by the specified offsets. + * @param tx The horizontal translation offset. + * @param ty The vertical translation offset. + * @return A reference to this matrix after translation. + */ + Matrix& translate(float tx, float ty); + + /** + * @brief Scales this matrix by the specified factors. + * @param sx The horizontal scaling factor. + * @param sy The vertical scaling factor. + * @return A reference to this matrix after scaling. + */ + Matrix& scale(float sx, float sy); + + /** + * @brief Rotates this matrix by the specified angle around a point. + * @param angle The rotation angle in degrees. + * @param cx The x-coordinate of the center of rotation. + * @param cy The y-coordinate of the center of rotation. + * @return A reference to this matrix after rotation. + */ + Matrix& rotate(float angle, float cx = 0.f, float cy = 0.f); + + /** + * @brief Shears this matrix by the specified factors. + * @param shx The horizontal shearing factor. + * @param shy The vertical shearing factor. + * @return A reference to this matrix after shearing. + */ + Matrix& shear(float shx, float shy); + + /** + * @brief Inverts this matrix. + * @return A reference to this matrix after inversion. + */ + Matrix& invert(); + + /** + * @brief Returns the inverse of this matrix. + * @return A new matrix that is the inverse of this matrix. + */ + Matrix inverse() const; + + /** + * @brief Resets this matrix to the identity matrix. + */ + void reset(); + + /** + * @brief Creates a translation matrix with the specified offsets. + * @param tx The horizontal translation offset. + * @param ty The vertical translation offset. + * @return A new translation matrix. + */ + static Matrix translated(float tx, float ty); + + /** + * @brief Creates a scaling matrix with the specified factors. + * @param sx The horizontal scaling factor. + * @param sy The vertical scaling factor. + * @return A new scaling matrix. + */ + static Matrix scaled(float sx, float sy); + + /** + * @brief Creates a rotation matrix with the specified angle around a point. + * @param angle The rotation angle in degrees. + * @param cx The x-coordinate of the center of rotation. + * @param cy The y-coordinate of the center of rotation. + * @return A new rotation matrix. + */ + static Matrix rotated(float angle, float cx = 0.f, float cy = 0.f); + + /** + * @brief Creates a shearing matrix with the specified factors. + * @param shx The horizontal shearing factor. + * @param shy The vertical shearing factor. + * @return A new shearing matrix. + */ + static Matrix sheared(float shx, float shy); + + float a{1}; ///< The horizontal scaling factor. + float b{0}; ///< The vertical shearing factor. + float c{0}; ///< The horizontal shearing factor. + float d{1}; ///< The vertical scaling factor. + float e{0}; ///< The horizontal translation offset. + float f{0}; ///< The vertical translation offset. +}; + +class SVGNode; +class SVGTextNode; +class SVGElement; + +class Element; +class TextNode; + +class LUNASVG_API Node { +public: + /** + * @brief Constructs a null node. + */ + Node() = default; + + /** + * @brief Checks if the node is a text node. + * @return True if the node is a text node, false otherwise. + */ + bool isTextNode() const; + + /** + * @brief Checks if the node is an element node. + * @return True if the node is an element node, false otherwise. + */ + bool isElement() const; + + /** + * @brief Converts the node to a TextNode. + * @return A TextNode or a null node if conversion is not possible. + */ + TextNode toTextNode() const; + + /** + * @brief Converts the node to an Element. + * @return An Element or a null node if conversion is not possible. + */ + Element toElement() const; + + /** + * @brief Returns the parent element. + * @return The parent element of this node. If this node has no parent, a null `Element` is returned. + */ + Element parentElement() const; + + /** + * @brief Checks if the node is null. + * @return True if the node is null, false otherwise. + */ + bool isNull() const { return m_node == nullptr; } + + /** + * @brief Checks if the node is not null. + * @return True if the node is not null, false otherwise. + */ + operator bool() const { return !isNull(); } + + /** + * @brief Checks if two nodes are equal. + * @param element The node to compare. + * @return True if equal, otherwise false. + */ + bool operator==(const Node& node) const { return m_node == node.m_node; } + + /** + * @brief Checks if two nodes are not equal. + * @param element The node to compare. + * @return True if not equal, otherwise false. + */ + bool operator!=(const Node& node) const { return m_node != node.m_node; } + +protected: + Node(SVGNode* node); + SVGNode* node() const { return m_node; } + SVGNode* m_node{nullptr}; + friend class Element; +}; + +using NodeList = std::vector; + +class LUNASVG_API TextNode : public Node { +public: + /** + * @brief Constructs a null text node. + */ + TextNode() = default; + + /** + * @brief Returns the text content of the node. + * @return A string representing the text content. + */ + const std::string& data() const; + + /** + * @brief Sets the text content of the node. + * @param data The new text content to set. + */ + void setData(const std::string& data); + +private: + TextNode(SVGTextNode* text); + SVGTextNode* text() const; + friend class Node; +}; + +class LUNASVG_API Element : public Node { +public: + /** + * @brief Constructs a null element. + */ + Element() = default; + + /** + * @brief Checks if the element has a specific attribute. + * @param name The name of the attribute to check. + * @return True if the element has the specified attribute, false otherwise. + */ + bool hasAttribute(const std::string& name) const; + + /** + * @brief Retrieves the value of an attribute. + * @param name The name of the attribute to retrieve. + * @return The value of the attribute as a string. + */ + const std::string& getAttribute(const std::string& name) const; + + /** + * @brief Sets the value of an attribute. + * @param name The name of the attribute to set. + * @param value The value to assign to the attribute. + */ + void setAttribute(const std::string& name, const std::string& value); + + /** + * @brief Renders the element onto a bitmap using a transformation matrix. + * @param bitmap The bitmap to render onto. + * @param The root transformation matrix. + */ + void render(Bitmap& bitmap, const Matrix& matrix = Matrix()) const; + + /** + * @brief Renders the element to a bitmap with specified dimensions. + * @param width The desired width in pixels, or -1 to auto-scale based on the intrinsic size. + * @param height The desired height in pixels, or -1 to auto-scale based on the intrinsic size. + * @param backgroundColor The background color in 0xRRGGBBAA format. + * @return A Bitmap containing the raster representation of the element. + */ + Bitmap renderToBitmap(int width = -1, int height = -1, uint32_t backgroundColor = 0x00000000) const; + + /** + * @brief Retrieves the local transformation matrix of the element. + * @return The matrix that applies only to the element, relative to its parent. + */ + Matrix getLocalMatrix() const; + + /** + * @brief Retrieves the global transformation matrix of the element. + * @return The matrix combining the element's local and all parent transformations. + */ + Matrix getGlobalMatrix() const; + + /** + * @brief Retrieves the local bounding box of the element. + * @return A Box representing the bounding box after applying local transformations. + */ + Box getLocalBoundingBox() const; + + /** + * @brief Retrieves the global bounding box of the element. + * @return A Box representing the bounding box after applying global transformations. + */ + Box getGlobalBoundingBox() const; + + /** + * @brief Retrieves the bounding box of the element without any transformations. + * @return A Box representing the bounding box of the element without any transformations applied. + */ + Box getBoundingBox() const; + + /** + * @brief Returns the child nodes of this node. + * @return A NodeList containing the child nodes. + */ + NodeList children() const; + +private: + Element(SVGElement* element); + SVGElement* element(bool layoutIfNeeded = false) const; + friend class Node; + friend class Document; +}; + +using ElementList = std::vector; + +class SVGRootElement; + +class LUNASVG_API Document { +public: + /** + * @brief Load an SVG document from a file. + * @param filename The path to the SVG file. + * @return A pointer to the loaded `Document`, or `nullptr` on failure. + */ + static std::unique_ptr loadFromFile(const std::string& filename); + + /** + * @brief Load an SVG document from a string. + * @param string The SVG data as a string. + * @return A pointer to the loaded `Document`, or `nullptr` on failure. + */ + static std::unique_ptr loadFromData(const std::string& string); + + /** + * @brief Load an SVG document from a null-terminated string. + * @param data The string containing the SVG data. + * @return A pointer to the loaded `Document`, or `nullptr` on failure. + */ + static std::unique_ptr loadFromData(const char* data); + + /** + * @brief Load an SVG document from a string with a specified length. + * @param data The string containing the SVG data. + * @param length The length of the string in bytes. + * @return A pointer to the loaded `Document`, or `nullptr` on failure. + */ + static std::unique_ptr loadFromData(const char* data, size_t length); + + /** + * @brief Applies a CSS stylesheet to the document. + * @param content A string containing the CSS rules to apply, with comments removed. + */ + void applyStyleSheet(const std::string& content); + + /** + * @brief Selects all elements that match the given CSS selector(s). + * @param content A string containing the CSS selector(s) to match elements. + * @return A list of elements matching the selector(s). + */ + ElementList querySelectorAll(const std::string& content) const; + + /** + * @brief Returns the intrinsic width of the document in pixels. + * @return The width of the document. + */ + float width() const; + + /** + * @brief Returns the intrinsic height of the document in pixels. + * @return The height of the document. + */ + float height() const; + + /** + * @brief Returns the smallest rectangle that encloses the document content. + * @return A Box representing the bounding box of the document. + */ + Box boundingBox() const; + + /** + * @brief Updates the layout of the document if needed. + */ + void updateLayout(); + + /** + * @brief Forces an immediate layout update. + */ + void forceLayout(); + + /** + * @brief Renders the document onto a bitmap using a transformation matrix. + * @param bitmap The bitmap to render onto. + * @param The root transformation matrix. + */ + void render(Bitmap& bitmap, const Matrix& matrix = Matrix()) const; + + /** + * @brief Renders the document to a bitmap with specified dimensions. + * @param width The desired width in pixels, or -1 to auto-scale based on the intrinsic size. + * @param height The desired height in pixels, or -1 to auto-scale based on the intrinsic size. + * @param backgroundColor The background color in 0xRRGGBBAA format. + * @return A Bitmap containing the raster representation of the document. + */ + Bitmap renderToBitmap(int width = -1, int height = -1, uint32_t backgroundColor = 0x00000000) const; + + /** + * @brief Returns the topmost element under the specified point. + * @param x The x-coordinate in viewport space. + * @param y The y-coordinate in viewport space. + * @return The topmost Element at the given point, or a null `Element` if no match is found. + */ + Element elementFromPoint(float x, float y) const; + + /** + * @brief Retrieves an element by its ID. + * @param id The ID of the element to retrieve. + * @return The Element with the specified ID, or a null `Element` if not found. + */ + Element getElementById(const std::string& id) const; + + /** + * @brief Retrieves the document element. + * @return The root Element of the document. + */ + Element documentElement() const; + + Document(Document&&); + Document& operator=(Document&&); + ~Document(); + +private: + Document(); + Document(const Document&) = delete; + Document& operator=(const Document&) = delete; + SVGRootElement* rootElement(bool layoutIfNeeded = false) const; + bool parse(const char* data, size_t length); + std::unique_ptr m_rootElement; + friend class SVGURIReference; + friend class SVGNode; +}; + +} // namespace lunasvg + +#endif // LUNASVG_H diff --git a/vendor/lunasvg/plutovg/include/plutovg.h b/vendor/lunasvg/plutovg/include/plutovg.h new file mode 100644 index 0000000..c34d0c0 --- /dev/null +++ b/vendor/lunasvg/plutovg/include/plutovg.h @@ -0,0 +1,2548 @@ +/* + * Copyright (c) 2020-2025 Samuel Ugochukwu + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. +*/ + +#ifndef PLUTOVG_H +#define PLUTOVG_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(PLUTOVG_BUILD_STATIC) +#define PLUTOVG_EXPORT +#define PLUTOVG_IMPORT +#elif (defined(_WIN32) || defined(__CYGWIN__)) +#define PLUTOVG_EXPORT __declspec(dllexport) +#define PLUTOVG_IMPORT __declspec(dllimport) +#elif defined(__GNUC__) && (__GNUC__ >= 4) +#define PLUTOVG_EXPORT __attribute__((__visibility__("default"))) +#define PLUTOVG_IMPORT +#else +#define PLUTOVG_EXPORT +#define PLUTOVG_IMPORT +#endif + +#ifdef PLUTOVG_BUILD +#define PLUTOVG_API PLUTOVG_EXPORT +#else +#define PLUTOVG_API PLUTOVG_IMPORT +#endif + +#define PLUTOVG_VERSION_MAJOR 1 +#define PLUTOVG_VERSION_MINOR 3 +#define PLUTOVG_VERSION_MICRO 2 + +#define PLUTOVG_VERSION_ENCODE(major, minor, micro) (((major) * 10000) + ((minor) * 100) + ((micro) * 1)) +#define PLUTOVG_VERSION PLUTOVG_VERSION_ENCODE(PLUTOVG_VERSION_MAJOR, PLUTOVG_VERSION_MINOR, PLUTOVG_VERSION_MICRO) + +#define PLUTOVG_VERSION_XSTRINGIZE(major, minor, micro) #major"."#minor"."#micro +#define PLUTOVG_VERSION_STRINGIZE(major, minor, micro) PLUTOVG_VERSION_XSTRINGIZE(major, minor, micro) +#define PLUTOVG_VERSION_STRING PLUTOVG_VERSION_STRINGIZE(PLUTOVG_VERSION_MAJOR, PLUTOVG_VERSION_MINOR, PLUTOVG_VERSION_MICRO) + +/** + * @brief Gets the version of the plutovg library. + * @return An integer representing the version of the plutovg library. + */ +PLUTOVG_API int plutovg_version(void); + +/** + * @brief Gets the version of the plutovg library as a string. + * @return A string representing the version of the plutovg library. + */ +PLUTOVG_API const char* plutovg_version_string(void); + +/** + * @brief A function pointer type for a cleanup callback. + * @param closure A pointer to the resource to be cleaned up. + */ +typedef void (*plutovg_destroy_func_t)(void* closure); + +/** + * @brief A function pointer type for a write callback. + * @param closure A pointer to user-defined data or context. + * @param data A pointer to the data to be written. + * @param size The size of the data in bytes. + */ +typedef void (*plutovg_write_func_t)(void* closure, void* data, int size); + +#define PLUTOVG_PI 3.14159265358979323846f +#define PLUTOVG_TWO_PI 6.28318530717958647693f +#define PLUTOVG_HALF_PI 1.57079632679489661923f +#define PLUTOVG_SQRT2 1.41421356237309504880f +#define PLUTOVG_KAPPA 0.55228474983079339840f + +#define PLUTOVG_DEG2RAD(x) ((x) * (PLUTOVG_PI / 180.0f)) +#define PLUTOVG_RAD2DEG(x) ((x) * (180.0f / PLUTOVG_PI)) + +/** + * @brief A structure representing a point in 2D space. + */ +typedef struct plutovg_point { + float x; ///< The x-coordinate of the point. + float y; ///< The y-coordinate of the point. +} plutovg_point_t; + +#define PLUTOVG_MAKE_POINT(x, y) ((plutovg_point_t){x, y}) +#define PLUTOVG_EMPTY_POINT PLUTOVG_MAKE_POINT(0, 0) + +/** + * @brief A structure representing a rectangle in 2D space. + */ +typedef struct plutovg_rect { + float x; ///< The x-coordinate of the top-left corner of the rectangle. + float y; ///< The y-coordinate of the top-left corner of the rectangle. + float w; ///< The width of the rectangle. + float h; ///< The height of the rectangle. +} plutovg_rect_t; + +#define PLUTOVG_MAKE_RECT(x, y, w, h) ((plutovg_rect_t){x, y, w, h}) +#define PLUTOVG_EMPTY_RECT PLUTOVG_MAKE_RECT(0, 0, 0, 0) + +/** + * @brief A structure representing a 2D transformation matrix. + */ +typedef struct plutovg_matrix { + float a; ///< The horizontal scaling factor. + float b; ///< The vertical shearing factor. + float c; ///< The horizontal shearing factor. + float d; ///< The vertical scaling factor. + float e; ///< The horizontal translation offset. + float f; ///< The vertical translation offset. +} plutovg_matrix_t; + +#define PLUTOVG_MAKE_MATRIX(a, b, c, d, e, f) ((plutovg_matrix_t){a, b, c, d, e, f}) +#define PLUTOVG_MAKE_SCALE(x, y) PLUTOVG_MAKE_MATRIX(x, 0, 0, y, 0, 0) +#define PLUTOVG_MAKE_TRANSLATE(x, y) PLUTOVG_MAKE_MATRIX(1, 0, 0, 1, x, y) +#define PLUTOVG_IDENTITY_MATRIX PLUTOVG_MAKE_MATRIX(1, 0, 0, 1, 0, 0) + +/** + * @brief Initializes a 2D transformation matrix. + * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. + * @param a The horizontal scaling factor. + * @param b The vertical shearing factor. + * @param c The horizontal shearing factor. + * @param d The vertical scaling factor. + * @param e The horizontal translation offset. + * @param f The vertical translation offset. + */ +PLUTOVG_API void plutovg_matrix_init(plutovg_matrix_t* matrix, float a, float b, float c, float d, float e, float f); + +/** + * @brief Initializes a 2D transformation matrix to the identity matrix. + * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. + */ +PLUTOVG_API void plutovg_matrix_init_identity(plutovg_matrix_t* matrix); + +/** + * @brief Initializes a 2D transformation matrix for translation. + * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. + * @param tx The translation offset in the x-direction. + * @param ty The translation offset in the y-direction. + */ +PLUTOVG_API void plutovg_matrix_init_translate(plutovg_matrix_t* matrix, float tx, float ty); + +/** + * @brief Initializes a 2D transformation matrix for scaling. + * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. + * @param sx The scaling factor in the x-direction. + * @param sy The scaling factor in the y-direction. + */ +PLUTOVG_API void plutovg_matrix_init_scale(plutovg_matrix_t* matrix, float sx, float sy); + +/** + * @brief Initializes a 2D transformation matrix for rotation. + * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. + * @param angle The rotation angle in radians. + */ +PLUTOVG_API void plutovg_matrix_init_rotate(plutovg_matrix_t* matrix, float angle); + +/** + * @brief Initializes a 2D transformation matrix for shearing. + * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. + * @param shx The shearing factor in the x-direction. + * @param shy The shearing factor in the y-direction. + */ +PLUTOVG_API void plutovg_matrix_init_shear(plutovg_matrix_t* matrix, float shx, float shy); + +/** + * @brief Adds a translation with offsets `tx` and `ty` to the matrix. + * @param matrix A pointer to the `plutovg_matrix_t` object to be modified. + * @param tx The translation offset in the x-direction. + * @param ty The translation offset in the y-direction. + */ +PLUTOVG_API void plutovg_matrix_translate(plutovg_matrix_t* matrix, float tx, float ty); + +/** + * @brief Scales the matrix by factors `sx` and `sy` + * @param matrix A pointer to the `plutovg_matrix_t` object to be modified. + * @param sx The scaling factor in the x-direction. + * @param sy The scaling factor in the y-direction. + */ +PLUTOVG_API void plutovg_matrix_scale(plutovg_matrix_t* matrix, float sx, float sy); + +/** + * @brief Rotates the matrix by the specified angle (in radians). + * @param matrix A pointer to the `plutovg_matrix_t` object to be modified. + * @param angle The rotation angle in radians. + */ +PLUTOVG_API void plutovg_matrix_rotate(plutovg_matrix_t* matrix, float angle); + +/** + * @brief Shears the matrix by factors `shx` and `shy`. + * @param matrix A pointer to the `plutovg_matrix_t` object to be modified. + * @param shx The shearing factor in the x-direction. + * @param shy The shearing factor in the y-direction. + */ +PLUTOVG_API void plutovg_matrix_shear(plutovg_matrix_t* matrix, float shx, float shy); + +/** + * @brief Multiplies `left` and `right` matrices and stores the result in `matrix`. + * @note `matrix` can be identical to either `left` or `right`. + * @param matrix A pointer to the `plutovg_matrix_t` object to store the result. + * @param left A pointer to the first `plutovg_matrix_t` matrix. + * @param right A pointer to the second `plutovg_matrix_t` matrix. + */ +PLUTOVG_API void plutovg_matrix_multiply(plutovg_matrix_t* matrix, const plutovg_matrix_t* left, const plutovg_matrix_t* right); + +/** + * @brief Calculates the inverse of `matrix` and stores it in `inverse`. + * + * If `inverse` is `NULL`, the function only checks if the matrix is invertible. + * + * @note `matrix` and `inverse` can be identical. + * @param matrix A pointer to the `plutovg_matrix_t` object to invert. + * @param inverse A pointer to the `plutovg_matrix_t` object to store the result, or `NULL`. + * @return `true` if the matrix is invertible; `false` otherwise. + */ +PLUTOVG_API bool plutovg_matrix_invert(const plutovg_matrix_t* matrix, plutovg_matrix_t* inverse); + +/** + * @brief Transforms the point `(x, y)` using `matrix` and stores the result in `(xx, yy)`. + * @param matrix A pointer to a `plutovg_matrix_t` object. + * @param x The x-coordinate of the point to transform. + * @param y The y-coordinate of the point to transform. + * @param xx A pointer to store the transformed x-coordinate. + * @param yy A pointer to store the transformed y-coordinate. + */ +PLUTOVG_API void plutovg_matrix_map(const plutovg_matrix_t* matrix, float x, float y, float* xx, float* yy); + +/** + * @brief Transforms the `src` point using `matrix` and stores the result in `dst`. + * @note `src` and `dst` can be identical. + * @param matrix A pointer to a `plutovg_matrix_t` object. + * @param src A pointer to the `plutovg_point_t` object to transform. + * @param dst A pointer to the `plutovg_point_t` to store the transformed point. + */ +PLUTOVG_API void plutovg_matrix_map_point(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst); + +/** + * @brief Transforms an array of `src` points using `matrix` and stores the results in `dst`. + * @note `src` and `dst` can be identical. + * @param matrix A pointer to a `plutovg_matrix_t` object. + * @param src A pointer to the array of `plutovg_point_t` objects to transform. + * @param dst A pointer to the array of `plutovg_point_t` to store the transformed points. + * @param count The number of points to transform. + */ +PLUTOVG_API void plutovg_matrix_map_points(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst, int count); + +/** + * @brief Transforms the `src` rectangle using `matrix` and stores the result in `dst`. + * @note `src` and `dst` can be identical. + * @param matrix A pointer to a `plutovg_matrix_t` object. + * @param src A pointer to the `plutovg_rect_t` object to transform. + * @param dst A pointer to the `plutovg_rect_t` to store the transformed rectangle. + */ +PLUTOVG_API void plutovg_matrix_map_rect(const plutovg_matrix_t* matrix, const plutovg_rect_t* src, plutovg_rect_t* dst); + +/** + * @brief Parses an SVG transform string into a matrix. + * + * @param matrix A pointer to a `plutovg_matrix_t` object to store the result. + * @param data Input SVG transform string. + * @param length Length of the string, or `-1` if null-terminated. + * + * @return `true` on success, `false` on failure. + */ +PLUTOVG_API bool plutovg_matrix_parse(plutovg_matrix_t* matrix, const char* data, int length); + +/** + * @brief Represents a 2D path for drawing operations. + */ +typedef struct plutovg_path plutovg_path_t; + +/** + * @brief Enumeration defining path commands. + */ +typedef enum plutovg_path_command { + PLUTOVG_PATH_COMMAND_MOVE_TO, ///< Moves the current point to a new position. + PLUTOVG_PATH_COMMAND_LINE_TO, ///< Draws a straight line to a new point. + PLUTOVG_PATH_COMMAND_CUBIC_TO, ///< Draws a cubic Bézier curve to a new point. + PLUTOVG_PATH_COMMAND_CLOSE ///< Closes the current path by drawing a line to the starting point. +} plutovg_path_command_t; + +/** + * @brief Union representing a path element. + * + * A path element can be a command with a length or a coordinate point. + * Each command type in the path element array is followed by a specific number of points: + * - `PLUTOVG_PATH_COMMAND_MOVE_TO`: 1 point + * - `PLUTOVG_PATH_COMMAND_LINE_TO`: 1 point + * - `PLUTOVG_PATH_COMMAND_CUBIC_TO`: 3 points + * - `PLUTOVG_PATH_COMMAND_CLOSE`: 1 point + * + * @example + * const plutovg_path_element_t* elements; + * int count = plutovg_path_get_elements(path, &elements); + * for(int i = 0; i < count; i += elements[i].header.length) { + * plutovg_path_command_t command = elements[i].header.command; + * switch(command) { + * case PLUTOVG_PATH_COMMAND_MOVE_TO: + * printf("MoveTo: %g %g\n", elements[i + 1].point.x, elements[i + 1].point.y); + * break; + * case PLUTOVG_PATH_COMMAND_LINE_TO: + * printf("LineTo: %g %g\n", elements[i + 1].point.x, elements[i + 1].point.y); + * break; + * case PLUTOVG_PATH_COMMAND_CUBIC_TO: + * printf("CubicTo: %g %g %g %g %g %g\n", + * elements[i + 1].point.x, elements[i + 1].point.y, + * elements[i + 2].point.x, elements[i + 2].point.y, + * elements[i + 3].point.x, elements[i + 3].point.y); + * break; + * case PLUTOVG_PATH_COMMAND_CLOSE: + * printf("Close: %g %g\n", elements[i + 1].point.x, elements[i + 1].point.y); + * break; + * } + * } + */ +typedef union plutovg_path_element { + struct { + plutovg_path_command_t command; ///< The path command. + int length; ///< Number of elements including the header. + } header; ///< Header for path commands. + plutovg_point_t point; ///< A coordinate point in the path. +} plutovg_path_element_t; + +/** + * @brief Iterator for traversing path elements in a path. + */ +typedef struct plutovg_path_iterator { + const plutovg_path_element_t* elements; ///< Pointer to the array of path elements. + int size; ///< Total number of elements in the array. + int index; ///< Current position in the array. +} plutovg_path_iterator_t; + +/** + * @brief Initializes a path iterator for a given path. + * + * @param it The path iterator to initialize. + * @param path The path to iterate over. + */ +PLUTOVG_API void plutovg_path_iterator_init(plutovg_path_iterator_t* it, const plutovg_path_t* path); + +/** + * @brief Checks if there are more elements to iterate over. + * + * @param it The path iterator. + * @return `true` if there are more elements; otherwise, `false`. + */ +PLUTOVG_API bool plutovg_path_iterator_has_next(const plutovg_path_iterator_t* it); + +/** + * @brief Retrieves the current command and its associated points, then advances the iterator. + * + * @param it The path iterator. + * @param points An array to store the points for the current command. + * @return The path command for the current element. + */ +PLUTOVG_API plutovg_path_command_t plutovg_path_iterator_next(plutovg_path_iterator_t* it, plutovg_point_t points[3]); + +/** + * @brief Creates a new path object. + * + * @return A pointer to the newly created path object. + */ +PLUTOVG_API plutovg_path_t* plutovg_path_create(void); + +/** + * @brief Increases the reference count of a path object. + * + * @param path A pointer to a `plutovg_path_t` object. + * @return A pointer to the same `plutovg_path_t` object. + */ +PLUTOVG_API plutovg_path_t* plutovg_path_reference(plutovg_path_t* path); + +/** + * @brief Decreases the reference count of a path object. + * + * This function decrements the reference count of the given path object. If + * the reference count reaches zero, the path object is destroyed and its + * resources are freed. + * + * @param path A pointer to the `plutovg_path_t` object. + */ +PLUTOVG_API void plutovg_path_destroy(plutovg_path_t* path); + +/** + * @brief Retrieves the reference count of a path object. + * + * @param path A pointer to a `plutovg_path_t` object. + * @return The current reference count of the path object. + */ +PLUTOVG_API int plutovg_path_get_reference_count(const plutovg_path_t* path); + +/** + * @brief Retrieves the elements of a path. + * + * Provides access to the array of path elements. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param elements A pointer to a pointer that will be set to the array of path elements. + * @return The number of elements in the path. + */ +PLUTOVG_API int plutovg_path_get_elements(const plutovg_path_t* path, const plutovg_path_element_t** elements); + +/** + * @brief Moves the current point to a new position. + * + * This function moves the current point to the specified coordinates without + * drawing a line. This is equivalent to the `M` command in SVG path syntax. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param x The x-coordinate of the new position. + * @param y The y-coordinate of the new position. + */ +PLUTOVG_API void plutovg_path_move_to(plutovg_path_t* path, float x, float y); + +/** + * @brief Adds a straight line segment to the path. + * + * This function adds a straight line segment from the current point to the + * specified coordinates. This is equivalent to the `L` command in SVG path syntax. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param x The x-coordinate of the end point of the line segment. + * @param y The y-coordinate of the end point of the line segment. + */ +PLUTOVG_API void plutovg_path_line_to(plutovg_path_t* path, float x, float y); + +/** + * @brief Adds a quadratic Bézier curve to the path. + * + * This function adds a quadratic Bézier curve segment from the current point + * to the specified end point, using the given control point. This is equivalent + * to the `Q` command in SVG path syntax. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param x1 The x-coordinate of the control point. + * @param y1 The y-coordinate of the control point. + * @param x2 The x-coordinate of the end point of the curve. + * @param y2 The y-coordinate of the end point of the curve. + */ +PLUTOVG_API void plutovg_path_quad_to(plutovg_path_t* path, float x1, float y1, float x2, float y2); + +/** + * @brief Adds a cubic Bézier curve to the path. + * + * This function adds a cubic Bézier curve segment from the current point + * to the specified end point, using the given two control points. This is + * equivalent to the `C` command in SVG path syntax. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param x1 The x-coordinate of the first control point. + * @param y1 The y-coordinate of the first control point. + * @param x2 The x-coordinate of the second control point. + * @param y2 The y-coordinate of the second control point. + * @param x3 The x-coordinate of the end point of the curve. + * @param y3 The y-coordinate of the end point of the curve. + */ +PLUTOVG_API void plutovg_path_cubic_to(plutovg_path_t* path, float x1, float y1, float x2, float y2, float x3, float y3); + +/** + * @brief Adds an elliptical arc to the path. + * + * This function adds an elliptical arc segment from the current point to the + * specified end point. The arc is defined by the radii, rotation angle, and + * flags for large arc and sweep. This is equivalent to the `A` command in SVG + * path syntax. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param rx The x-radius of the ellipse. + * @param ry The y-radius of the ellipse. + * @param angle The rotation angle of the ellipse in radians. + * @param large_arc_flag If true, draw the large arc; otherwise, draw the small arc. + * @param sweep_flag If true, draw the arc in the positive-angle direction; otherwise, in the negative-angle direction. + * @param x The x-coordinate of the end point of the arc. + * @param y The y-coordinate of the end point of the arc. + */ +PLUTOVG_API void plutovg_path_arc_to(plutovg_path_t* path, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y); + +/** + * @brief Closes the current sub-path. + * + * This function closes the current sub-path by drawing a straight line back to + * the start point of the sub-path. This is equivalent to the `Z` command in SVG + * path syntax. + * + * @param path A pointer to a `plutovg_path_t` object. + */ +PLUTOVG_API void plutovg_path_close(plutovg_path_t* path); + +/** + * @brief Retrieves the current point of the path. + * + * Gets the current point's coordinates in the path. This point is the last + * position used or the point where the path was last moved to. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param x The x-coordinate of the current point. + * @param y The y-coordinate of the current point. + */ +PLUTOVG_API void plutovg_path_get_current_point(const plutovg_path_t* path, float* x, float* y); + +/** + * @brief Reserves space for path elements. + * + * Reserves space for a specified number of elements in the path. This helps optimize + * memory allocation for future path operations. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param count The number of path elements to reserve space for. + */ +PLUTOVG_API void plutovg_path_reserve(plutovg_path_t* path, int count); + +/** + * @brief Resets the path. + * + * Clears all path data, effectively resetting the `plutovg_path_t` object to its initial state. + * + * @param path A pointer to a `plutovg_path_t` object. + */ +PLUTOVG_API void plutovg_path_reset(plutovg_path_t* path); + +/** + * @brief Adds a rectangle to the path. + * + * Adds a rectangle defined by the top-left corner (x, y) and dimensions (w, h) to the path. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param x The x-coordinate of the rectangle's top-left corner. + * @param y The y-coordinate of the rectangle's top-left corner. + * @param w The width of the rectangle. + * @param h The height of the rectangle. + */ +PLUTOVG_API void plutovg_path_add_rect(plutovg_path_t* path, float x, float y, float w, float h); + +/** + * @brief Adds a rounded rectangle to the path. + * + * Adds a rounded rectangle defined by the top-left corner (x, y), dimensions (w, h), + * and corner radii (rx, ry) to the path. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param x The x-coordinate of the rectangle's top-left corner. + * @param y The y-coordinate of the rectangle's top-left corner. + * @param w The width of the rectangle. + * @param h The height of the rectangle. + * @param rx The x-radius of the rectangle's corners. + * @param ry The y-radius of the rectangle's corners. + */ +PLUTOVG_API void plutovg_path_add_round_rect(plutovg_path_t* path, float x, float y, float w, float h, float rx, float ry); + +/** + * @brief Adds an ellipse to the path. + * + * Adds an ellipse defined by the center (cx, cy) and radii (rx, ry) to the path. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param cx The x-coordinate of the ellipse's center. + * @param cy The y-coordinate of the ellipse's center. + * @param rx The x-radius of the ellipse. + * @param ry The y-radius of the ellipse. + */ +PLUTOVG_API void plutovg_path_add_ellipse(plutovg_path_t* path, float cx, float cy, float rx, float ry); + +/** + * @brief Adds a circle to the path. + * + * Adds a circle defined by its center (cx, cy) and radius (r) to the path. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param cx The x-coordinate of the circle's center. + * @param cy The y-coordinate of the circle's center. + * @param r The radius of the circle. + */ +PLUTOVG_API void plutovg_path_add_circle(plutovg_path_t* path, float cx, float cy, float r); + +/** + * @brief Adds an arc to the path. + * + * Adds an arc defined by the center (cx, cy), radius (r), start angle (a0), end angle (a1), + * and direction (ccw) to the path. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param cx The x-coordinate of the arc's center. + * @param cy The y-coordinate of the arc's center. + * @param r The radius of the arc. + * @param a0 The start angle of the arc in radians. + * @param a1 The end angle of the arc in radians. + * @param ccw If true, the arc is drawn counter-clockwise; if false, clockwise. + */ +PLUTOVG_API void plutovg_path_add_arc(plutovg_path_t* path, float cx, float cy, float r, float a0, float a1, bool ccw); + +/** + * @brief Adds a sub-path to the path. + * + * Adds all elements from another path (`source`) to the current path, optionally + * applying a transformation matrix. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param source A pointer to the `plutovg_path_t` object to copy elements from. + * @param matrix A pointer to a `plutovg_matrix_t` object, or `NULL` to apply no transformation. + */ +PLUTOVG_API void plutovg_path_add_path(plutovg_path_t* path, const plutovg_path_t* source, const plutovg_matrix_t* matrix); + +/** + * @brief Applies a transformation matrix to the path. + * + * Transforms the entire path using the provided transformation matrix. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param matrix A pointer to a `plutovg_matrix_t` object. + */ +PLUTOVG_API void plutovg_path_transform(plutovg_path_t* path, const plutovg_matrix_t* matrix); + +/** + * @brief Callback function type for traversing a path. + * + * This function type defines a callback used to traverse path elements. + * + * @param closure A pointer to user-defined data passed to the callback. + * @param command The current path command. + * @param points An array of points associated with the command. + * @param npoints The number of points in the array. + */ +typedef void (*plutovg_path_traverse_func_t)(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints); + +/** + * @brief Traverses the path and calls the callback for each element. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param traverse_func The callback function to be called for each element of the path. + * @param closure User-defined data passed to the callback. + */ +PLUTOVG_API void plutovg_path_traverse(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure); + +/** + * @brief Traverses the path with Bézier curves flattened to line segments. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param traverse_func The callback function to be called for each element of the path. + * @param closure User-defined data passed to the callback. + */ +PLUTOVG_API void plutovg_path_traverse_flatten(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure); + +/** + * @brief Traverses the path with a dashed pattern and calls the callback for each segment. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param offset The starting offset into the dash pattern. + * @param dashes An array of dash lengths. + * @param ndashes The number of elements in the `dashes` array. + * @param traverse_func The callback function to be called for each element of the path. + * @param closure User-defined data passed to the callback. + */ +PLUTOVG_API void plutovg_path_traverse_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes, plutovg_path_traverse_func_t traverse_func, void* closure); + +/** + * @brief Creates a copy of the path. + * + * @param path A pointer to the `plutovg_path_t` object to clone. + * @return A pointer to the newly created path clone. + */ +PLUTOVG_API plutovg_path_t* plutovg_path_clone(const plutovg_path_t* path); + +/** + * @brief Creates a copy of the path with Bézier curves flattened to line segments. + * + * @param path A pointer to the `plutovg_path_t` object to clone. + * @return A pointer to the newly created path clone with flattened curves. + */ +PLUTOVG_API plutovg_path_t* plutovg_path_clone_flatten(const plutovg_path_t* path); + +/** + * @brief Creates a copy of the path with a dashed pattern applied. + * + * @param path A pointer to the `plutovg_path_t` object to clone. + * @param offset The starting offset into the dash pattern. + * @param dashes An array of dash lengths. + * @param ndashes The number of elements in the `dashes` array. + * @return A pointer to the newly created path clone with dashed pattern. + */ +PLUTOVG_API plutovg_path_t* plutovg_path_clone_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes); + +/** + * @brief Computes the bounding box and total length of the path. + * + * @param path A pointer to a `plutovg_path_t` object. + * @param extents A pointer to a `plutovg_rect_t` object to store the bounding box. + * @param tight If `true`, computes a precise bounding box; otherwise, aligns to control points. + * @return The total length of the path. + */ +PLUTOVG_API float plutovg_path_extents(const plutovg_path_t* path, plutovg_rect_t* extents, bool tight); + +/** + * @brief Calculates the total length of the path. + * + * @param path A pointer to a `plutovg_path_t` object. + * @return The total length of the path. + */ +PLUTOVG_API float plutovg_path_length(const plutovg_path_t* path); + +/** + * @brief Parses SVG path data into a `plutovg_path_t` object. + * + * @param path A pointer to the `plutovg_path_t` object to populate. + * @param data The SVG path data string. + * @param length The length of `data`, or `-1` for null-terminated data. + * @return `true` if successful; `false` otherwise. + */ +PLUTOVG_API bool plutovg_path_parse(plutovg_path_t* path, const char* data, int length); + +/** + * @brief Text encodings used for converting text data to code points. + */ +typedef enum plutovg_text_encoding { + PLUTOVG_TEXT_ENCODING_LATIN1, ///< Latin-1 encoding + PLUTOVG_TEXT_ENCODING_UTF8, ///< UTF-8 encoding + PLUTOVG_TEXT_ENCODING_UTF16, ///< UTF-16 encoding + PLUTOVG_TEXT_ENCODING_UTF32 ///< UTF-32 encoding +} plutovg_text_encoding_t; + +/** + * @brief Iterator for traversing code points in text data. + */ +typedef struct plutovg_text_iterator { + const void* text; ///< Pointer to the text data. + int length; ///< Length of the text data. + plutovg_text_encoding_t encoding; ///< Encoding format of the text data. + int index; ///< Current position in the text data. +} plutovg_text_iterator_t; + +/** + * @brief Represents a Unicode code point. + */ +typedef unsigned int plutovg_codepoint_t; + +/** + * @brief Initializes a text iterator. + * + * @param it Pointer to the text iterator. + * @param text Pointer to the text data. + * @param length Length of the text data, or -1 if the data is null-terminated. + * @param encoding Encoding of the text data. + */ +PLUTOVG_API void plutovg_text_iterator_init(plutovg_text_iterator_t* it, const void* text, int length, plutovg_text_encoding_t encoding); + +/** + * @brief Checks if there are more code points to iterate. + * + * @param it Pointer to the text iterator. + * @return `true` if more code points are available; otherwise, `false`. + */ +PLUTOVG_API bool plutovg_text_iterator_has_next(const plutovg_text_iterator_t* it); + +/** + * @brief Retrieves the next code point and advances the iterator. + * + * @param it Pointer to the text iterator. + * @return The next code point. + */ +PLUTOVG_API plutovg_codepoint_t plutovg_text_iterator_next(plutovg_text_iterator_t* it); + +/** + * @brief Represents a font face. + */ +typedef struct plutovg_font_face plutovg_font_face_t; + +/** + * @brief Loads a font face from a file. + * + * @param filename Path to the font file. + * @param ttcindex Index of the font face within a TrueType Collection (TTC). + * @return A pointer to the loaded `plutovg_font_face_t` object, or `NULL` on failure. + */ +PLUTOVG_API plutovg_font_face_t* plutovg_font_face_load_from_file(const char* filename, int ttcindex); + +/** + * @brief Loads a font face from memory. + * + * @param data Pointer to the font data. + * @param length Length of the font data. + * @param ttcindex Index of the font face within a TrueType Collection (TTC). + * @param destroy_func Function to free the font data when no longer needed. + * @param closure User-defined data passed to `destroy_func`. + * @return A pointer to the loaded `plutovg_font_face_t` object, or `NULL` on failure. + */ +PLUTOVG_API plutovg_font_face_t* plutovg_font_face_load_from_data(const void* data, unsigned int length, int ttcindex, plutovg_destroy_func_t destroy_func, void* closure); + +/** + * @brief Increments the reference count of a font face. + * + * @param face A pointer to a `plutovg_font_face_t` object. + * @return A pointer to the same `plutovg_font_face_t` object with an incremented reference count. + */ +PLUTOVG_API plutovg_font_face_t* plutovg_font_face_reference(plutovg_font_face_t* face); + +/** + * @brief Decrements the reference count and potentially destroys the font face. + * + * @param face A pointer to a `plutovg_font_face_t` object. + */ +PLUTOVG_API void plutovg_font_face_destroy(plutovg_font_face_t* face); + +/** + * @brief Retrieves the current reference count of a font face. + * + * @param face A pointer to a `plutovg_font_face_t` object. + * @return The reference count of the font face. + */ +PLUTOVG_API int plutovg_font_face_get_reference_count(const plutovg_font_face_t* face); + +/** + * @brief Retrieves metrics for a font face at a specified size. + * + * @param face A pointer to a `plutovg_font_face_t` object. + * @param size The font size in pixels. + * @param ascent Pointer to store the ascent metric. + * @param descent Pointer to store the descent metric. + * @param line_gap Pointer to store the line gap metric. + * @param extents Pointer to a `plutovg_rect_t` object to store the font bounding box. + */ +PLUTOVG_API void plutovg_font_face_get_metrics(const plutovg_font_face_t* face, float size, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents); + +/** + * @brief Retrieves metrics for a specified glyph at a given size. + * + * @param face A pointer to a `plutovg_font_face_t` object. + * @param size The font size in pixels. + * @param codepoint The Unicode code point of the glyph. + * @param advance_width Pointer to store the advance width of the glyph. + * @param left_side_bearing Pointer to store the left side bearing of the glyph. + * @param extents Pointer to a `plutovg_rect_t` object to store the glyph bounding box. + */ +PLUTOVG_API void plutovg_font_face_get_glyph_metrics(plutovg_font_face_t* face, float size, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents); + +/** + * @brief Retrieves the path of a glyph and its advance width. + * + * @param face A pointer to a `plutovg_font_face_t` object. + * @param size The font size in pixels. + * @param x The x-coordinate for positioning the glyph. + * @param y The y-coordinate for positioning the glyph. + * @param codepoint The Unicode code point of the glyph. + * @param path Pointer to a `plutovg_path_t` object to store the glyph path. + * @return The advance width of the glyph. + */ +PLUTOVG_API float plutovg_font_face_get_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_t* path); + +/** + * @brief Traverses the path of a glyph and calls a callback for each path element. + * + * @param face A pointer to a `plutovg_font_face_t` object. + * @param size The font size in pixels. + * @param x The x-coordinate for positioning the glyph. + * @param y The y-coordinate for positioning the glyph. + * @param codepoint The Unicode code point of the glyph. + * @param traverse_func The callback function to be called for each path element. + * @param closure User-defined data passed to the callback function. + * @return The advance width of the glyph. + */ +PLUTOVG_API float plutovg_font_face_traverse_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_traverse_func_t traverse_func, void* closure); + +/** + * @brief Computes the bounding box of a text string and its advance width. + * + * @param face A pointer to a `plutovg_font_face_t` object. + * @param size The font size in pixels. + * @param text Pointer to the text data. + * @param length Length of the text data, or -1 if null-terminated. + * @param encoding Encoding of the text data. + * @param extents Pointer to a `plutovg_rect_t` object to store the bounding box of the text. + * @return The total advance width of the text. + */ +PLUTOVG_API float plutovg_font_face_text_extents(plutovg_font_face_t* face, float size, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents); + +/** + * @brief Represents a cache of loaded font faces. + */ +typedef struct plutovg_font_face_cache plutovg_font_face_cache_t; + +/** + * @brief Create a new, empty font‐face cache. + * + * @return Pointer to a newly allocated `plutovg_font_face_cache_t` object. + */ +PLUTOVG_API plutovg_font_face_cache_t* plutovg_font_face_cache_create(void); + +/** + * @brief Increments the reference count of a font‐face cache. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @return A pointer to the same `plutovg_font_face_cache_t` object with an incremented reference count. + */ +PLUTOVG_API plutovg_font_face_cache_t* plutovg_font_face_cache_reference(plutovg_font_face_cache_t* cache); + +/** + * @brief Decrement the reference count of a font‐face cache and destroy it when it reaches zero. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object to release. + */ +PLUTOVG_API void plutovg_font_face_cache_destroy(plutovg_font_face_cache_t* cache); + +/** + * @brief Retrieve the current reference count of a font‐face cache. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @return The current reference count, or 0 if cache is NULL. + */ +PLUTOVG_API int plutovg_font_face_cache_reference_count(const plutovg_font_face_cache_t* cache); + +/** + * @brief Remove all entries from a font‐face cache. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object to reset. + */ +PLUTOVG_API void plutovg_font_face_cache_reset(plutovg_font_face_cache_t* cache); + +/** + * @brief Add a font face to the cache with the specified family and style. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @param family The font family name. + * @param bold Whether the font is bold. + * @param italic Whether the font is italic. + * @param face A pointer to the `plutovg_font_face_t` to add. The cache increments its reference count. + */ +PLUTOVG_API void plutovg_font_face_cache_add(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, plutovg_font_face_t* face); + +/** + * @brief Load a font face from a file and add it to the cache with the specified family and style. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @param family The font family name to associate with the face. + * @param bold Whether the font is bold. + * @param italic Whether the font is italic. + * @param filename Path to the font file. + * @param ttcindex Index of the face in a TrueType collection (use 0 for non-TTC fonts). + * @return `true` on success, `false` if the file could not be loaded. + */ +PLUTOVG_API bool plutovg_font_face_cache_add_file(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, const char* filename, int ttcindex); + +/** + * @brief Retrieve a font face from the cache by family and style. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @param family The font family name. + * @param bold Whether the font is bold. + * @param italic Whether the font is italic. + * @return A pointer to the matching `plutovg_font_face_t` object, or NULL if not found. The returned face is owned by the cache and must not be destroyed by the caller. + */ +PLUTOVG_API plutovg_font_face_t* plutovg_font_face_cache_get(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic); + +/** + * @brief Load all font faces from a file and add them to the cache. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @param filename Path to the font file (TrueType, OpenType, or font collection). + * @return The number of faces successfully loaded, or `-1` if font face cache loading is disabled. + */ +PLUTOVG_API int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const char* filename); + +/** + * @brief Load all font faces from files in a directory recursively and add them to the cache. + * + * This scans the specified directory recursively and loads all supported font files. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @param dirname Path to the directory containing font files. + * @return The number of faces successfully loaded, or `-1` if font face cache loading is disabled. + */ +PLUTOVG_API int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname); + +/** + * @brief Load all available system font faces and add them to the cache. + * + * This scans standard system font directories recursively and loads all supported font files. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @return The number of faces successfully loaded, or `-1` if font face cache loading is disabled. + */ +PLUTOVG_API int plutovg_font_face_cache_load_sys(plutovg_font_face_cache_t* cache); + +/** + * @brief Represents a color with red, green, blue, and alpha components. + */ +typedef struct plutovg_color { + float r; ///< Red component (0 to 1). + float g; ///< Green component (0 to 1). + float b; ///< Blue component (0 to 1). + float a; ///< Alpha (opacity) component (0 to 1). +} plutovg_color_t; + +#define PLUTOVG_MAKE_COLOR(r, g, b, a) ((plutovg_color_t){r, g, b, a}) + +#define PLUTOVG_BLACK_COLOR PLUTOVG_MAKE_COLOR(0, 0, 0, 1) +#define PLUTOVG_WHITE_COLOR PLUTOVG_MAKE_COLOR(1, 1, 1, 1) +#define PLUTOVG_RED_COLOR PLUTOVG_MAKE_COLOR(1, 0, 0, 1) +#define PLUTOVG_GREEN_COLOR PLUTOVG_MAKE_COLOR(0, 1, 0, 1) +#define PLUTOVG_BLUE_COLOR PLUTOVG_MAKE_COLOR(0, 0, 1, 1) +#define PLUTOVG_YELLOW_COLOR PLUTOVG_MAKE_COLOR(1, 1, 0, 1) +#define PLUTOVG_CYAN_COLOR PLUTOVG_MAKE_COLOR(0, 1, 1, 1) +#define PLUTOVG_MAGENTA_COLOR PLUTOVG_MAKE_COLOR(1, 0, 1, 1) + +/** + * @brief Initializes a color using RGB components in the 0-1 range. + * + * @param color A pointer to a `plutovg_color_t` object. + * @param r Red component (0 to 1). + * @param g Green component (0 to 1). + * @param b Blue component (0 to 1). + */ +PLUTOVG_API void plutovg_color_init_rgb(plutovg_color_t* color, float r, float g, float b); + +/** + * @brief Initializes a color using RGBA components in the 0-1 range. + * + * @param color A pointer to a `plutovg_color_t` object. + * @param r Red component (0 to 1). + * @param g Green component (0 to 1). + * @param b Blue component (0 to 1). + * @param a Alpha component (0 to 1). + */ +PLUTOVG_API void plutovg_color_init_rgba(plutovg_color_t* color, float r, float g, float b, float a); + +/** + * @brief Initializes a color using RGB components in the 0-255 range. + * + * @param color A pointer to a `plutovg_color_t` object. + * @param r Red component (0 to 255). + * @param g Green component (0 to 255). + * @param b Blue component (0 to 255). + */ +PLUTOVG_API void plutovg_color_init_rgb8(plutovg_color_t* color, int r, int g, int b); + +/** + * @brief Initializes a color using RGBA components in the 0-255 range. + * + * @param color A pointer to a `plutovg_color_t` object. + * @param r Red component (0 to 255). + * @param g Green component (0 to 255). + * @param b Blue component (0 to 255). + * @param a Alpha component (0 to 255). + */ +PLUTOVG_API void plutovg_color_init_rgba8(plutovg_color_t* color, int r, int g, int b, int a); + +/** + * @brief Initializes a color from a 32-bit unsigned RGBA value. + * + * @param color A pointer to a `plutovg_color_t` object. + * @param value 32-bit unsigned RGBA value. + */ +PLUTOVG_API void plutovg_color_init_rgba32(plutovg_color_t* color, unsigned int value); + +/** + * @brief Initializes a color from a 32-bit unsigned ARGB value. + * + * @param color A pointer to a `plutovg_color_t` object. + * @param value 32-bit unsigned ARGB value. + */ +PLUTOVG_API void plutovg_color_init_argb32(plutovg_color_t* color, unsigned int value); + +/** + * @brief Initializes a color with the specified HSL color values. + * + * @param color A pointer to a `plutovg_color_t` object. + * @param h Hue component in degrees (0 to 360). + * @param s Saturation component (0 to 1). + * @param l Lightness component (0 to 1). + */ +PLUTOVG_API void plutovg_color_init_hsl(plutovg_color_t* color, float h, float s, float l); + +/** + * @brief Initializes a color with the specified HSLA color values. + * + * @param color A pointer to a `plutovg_color_t` object. + * @param h Hue component in degrees (0 to 360). + * @param s Saturation component (0 to 1). + * @param l Lightness component (0 to 1). + * @param a Alpha component (0 to 1). + */ +PLUTOVG_API void plutovg_color_init_hsla(plutovg_color_t* color, float h, float s, float l, float a); + +/** + * @brief Converts a color to a 32-bit unsigned RGBA value. + * + * @param color A pointer to a `plutovg_color_t` object. + * + * @return 32-bit unsigned RGBA value. + */ +PLUTOVG_API unsigned int plutovg_color_to_rgba32(const plutovg_color_t* color); + +/** + * @brief Converts a color to a 32-bit unsigned ARGB value. + * + * @param color A pointer to a `plutovg_color_t` object. + * + * @return 32-bit unsigned ARGB value. + */ +PLUTOVG_API unsigned int plutovg_color_to_argb32(const plutovg_color_t* color); + +/** + * @brief Parses a color from a string using CSS color syntax. + * + * @param color A pointer to a `plutovg_color_t` object to store the parsed color. + * @param data A pointer to the input string containing the color data. + * @param length The length of the input string in bytes, or `-1` if the string is null-terminated. + * + * @return The number of characters consumed on success (including leading/trailing spaces), or 0 on failure. + */ +PLUTOVG_API int plutovg_color_parse(plutovg_color_t* color, const char* data, int length); + +/** + * @brief Represents an image surface for drawing operations. + * + * Stores pixel data in a 32-bit premultiplied ARGB format (0xAARRGGBB), + * where red, green, and blue channels are multiplied by the alpha channel + * and divided by 255. + */ +typedef struct plutovg_surface plutovg_surface_t; + +/** + * @brief Creates a new image surface with the specified dimensions. + * + * @param width The width of the surface in pixels. + * @param height The height of the surface in pixels. + * @return A pointer to the newly created `plutovg_surface_t` object. + */ +PLUTOVG_API plutovg_surface_t* plutovg_surface_create(int width, int height); + +/** + * @brief Creates an image surface using existing pixel data. + * + * @param data Pointer to the pixel data. + * @param width The width of the surface in pixels. + * @param height The height of the surface in pixels. + * @param stride The number of bytes per row in the pixel data. + * @return A pointer to the newly created `plutovg_surface_t` object. + */ +PLUTOVG_API plutovg_surface_t* plutovg_surface_create_for_data(unsigned char* data, int width, int height, int stride); + +/** + * @brief Loads an image surface from a file. + * + * @param filename Path to the image file. + * @return Pointer to the surface, or `NULL` on failure. + */ +PLUTOVG_API plutovg_surface_t* plutovg_surface_load_from_image_file(const char* filename); + +/** + * @brief Loads an image surface from raw image data. + * + * @param data Pointer to the image data. + * @param length Length of the data in bytes. + * @return Pointer to the surface, or `NULL` on failure. + */ +PLUTOVG_API plutovg_surface_t* plutovg_surface_load_from_image_data(const void* data, int length); + +/** + * @brief Loads an image surface from base64-encoded data. + * + * @param data Pointer to the base64-encoded image data. + * @param length Length of the data in bytes, or `-1` if null-terminated. + * @return Pointer to the surface, or `NULL` on failure. + */ +PLUTOVG_API plutovg_surface_t* plutovg_surface_load_from_image_base64(const char* data, int length); + +/** + * @brief Increments the reference count for a surface. + * + * @param surface Pointer to the `plutovg_surface_t` object. + * @return Pointer to the `plutovg_surface_t` object. + */ +PLUTOVG_API plutovg_surface_t* plutovg_surface_reference(plutovg_surface_t* surface); + +/** + * @brief Decrements the reference count and destroys the surface if the count reaches zero. + * + * @param surface Pointer to the `plutovg_surface_t` object . + */ +PLUTOVG_API void plutovg_surface_destroy(plutovg_surface_t* surface); + +/** + * @brief Gets the current reference count of a surface. + * + * @param surface Pointer to the `plutovg_surface_t` object. + * @return The reference count of the surface. + */ +PLUTOVG_API int plutovg_surface_get_reference_count(const plutovg_surface_t* surface); + +/** + * @brief Gets the pixel data of the surface. + * + * @param surface Pointer to the `plutovg_surface_t` object. + * @return Pointer to the pixel data. + */ +PLUTOVG_API unsigned char* plutovg_surface_get_data(const plutovg_surface_t* surface); + +/** + * @brief Gets the width of the surface. + * + * @param surface Pointer to the `plutovg_surface_t` object. + * @return Width of the surface in pixels. + */ +PLUTOVG_API int plutovg_surface_get_width(const plutovg_surface_t* surface); + +/** + * @brief Gets the height of the surface. + * + * @param surface Pointer to the `plutovg_surface_t` object. + * @return Height of the surface in pixels. + */ +PLUTOVG_API int plutovg_surface_get_height(const plutovg_surface_t* surface); + +/** + * @brief Gets the stride of the surface. + * + * @param surface Pointer to the `plutovg_surface_t` object. + * @return Number of bytes per row. + */ +PLUTOVG_API int plutovg_surface_get_stride(const plutovg_surface_t* surface); + +/** + * @brief Clears the entire surface with the specified color. + * + * @param surface Pointer to the target surface. + * @param color Pointer to the color used for clearing. + */ +PLUTOVG_API void plutovg_surface_clear(plutovg_surface_t* surface, const plutovg_color_t* color); + +/** + * @brief Writes the surface to a PNG file. + * + * @param surface Pointer to the `plutovg_surface_t` object. + * @param filename Path to the output PNG file. + * @return `true` if successful, `false` otherwise. + */ +PLUTOVG_API bool plutovg_surface_write_to_png(const plutovg_surface_t* surface, const char* filename); + +/** + * @brief Writes the surface to a JPEG file. + * + * @param surface Pointer to the `plutovg_surface_t` object. + * @param filename Path to the output JPEG file. + * @param quality JPEG quality (0 to 100). + * @return `true` if successful, `false` otherwise. + */ +PLUTOVG_API bool plutovg_surface_write_to_jpg(const plutovg_surface_t* surface, const char* filename, int quality); + +/** + * @brief Writes the surface to a PNG stream. + * + * @param surface Pointer to the `plutovg_surface_t` object. + * @param write_func Callback function for writing data. + * @param closure User-defined data passed to the callback. + * @return `true` if successful, `false` otherwise. + */ +PLUTOVG_API bool plutovg_surface_write_to_png_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure); + +/** + * @brief Writes the surface to a JPEG stream. + * + * @param surface Pointer to the `plutovg_surface_t` object. + * @param write_func Callback function for writing data. + * @param closure User-defined data passed to the callback. + * @param quality JPEG quality (0 to 100). + * @return `true` if successful, `false` otherwise. + */ +PLUTOVG_API bool plutovg_surface_write_to_jpg_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure, int quality); + +/** + * @brief Converts pixel data from premultiplied ARGB to RGBA format. + * + * Transforms pixel data from native-endian 32-bit ARGB premultiplied format + * to a non-premultiplied RGBA byte sequence. + * + * @param dst Pointer to the destination buffer (can overlap with `src`). + * @param src Pointer to the source buffer in ARGB premultiplied format. + * @param width Image width in pixels. + * @param height Image height in pixels. + * @param stride Number of bytes per image row in the buffers. + */ +PLUTOVG_API void plutovg_convert_argb_to_rgba(unsigned char* dst, const unsigned char* src, int width, int height, int stride); + +/** + * @brief Converts pixel data from RGBA to premultiplied ARGB format. + * + * Transforms pixel data from a non-premultiplied RGBA byte sequence + * to a native-endian 32-bit ARGB premultiplied format. + * + * @param dst Pointer to the destination buffer (can overlap with `src`). + * @param src Pointer to the source buffer in RGBA format. + * @param width Image width in pixels. + * @param height Image height in pixels. + * @param stride Number of bytes per image row in the buffers. + */ +PLUTOVG_API void plutovg_convert_rgba_to_argb(unsigned char* dst, const unsigned char* src, int width, int height, int stride); + +/** + * @brief Defines the type of texture, either plain or tiled. + */ +typedef enum { + PLUTOVG_TEXTURE_TYPE_PLAIN, ///< Plain texture. + PLUTOVG_TEXTURE_TYPE_TILED ///< Tiled texture. +} plutovg_texture_type_t; + +/** + * @brief Defines the spread method for gradients. + */ +typedef enum { + PLUTOVG_SPREAD_METHOD_PAD, ///< Pad the gradient's edges. + PLUTOVG_SPREAD_METHOD_REFLECT, ///< Reflect the gradient beyond its bounds. + PLUTOVG_SPREAD_METHOD_REPEAT ///< Repeat the gradient pattern. +} plutovg_spread_method_t; + +/** + * @brief Represents a gradient stop. + */ +typedef struct { + float offset; ///< The offset of the gradient stop, as a value between 0 and 1. + plutovg_color_t color; ///< The color of the gradient stop. +} plutovg_gradient_stop_t; + +/** + * @brief Represents a paint object used for drawing operations. + */ +typedef struct plutovg_paint plutovg_paint_t; + +/** + * @brief Creates a solid RGB paint. + * + * @param r The red component (0 to 1). + * @param g The green component (0 to 1). + * @param b The blue component (0 to 1). + * @return A pointer to the created `plutovg_paint_t` object. + */ +PLUTOVG_API plutovg_paint_t* plutovg_paint_create_rgb(float r, float g, float b); + +/** + * @brief Creates a solid RGBA paint. + * + * @param r The red component (0 to 1). + * @param g The green component (0 to 1). + * @param b The blue component (0 to 1). + * @param a The alpha component (0 to 1). + * @return A pointer to the created `plutovg_paint_t` object. + */ +PLUTOVG_API plutovg_paint_t* plutovg_paint_create_rgba(float r, float g, float b, float a); + +/** + * @brief Creates a solid color paint. + * + * @param color A pointer to the `plutovg_color_t` object. + * @return A pointer to the created `plutovg_paint_t` object. + */ +PLUTOVG_API plutovg_paint_t* plutovg_paint_create_color(const plutovg_color_t* color); + +/** + * @brief Creates a linear gradient paint. + * + * @param x1 The x coordinate of the gradient start. + * @param y1 The y coordinate of the gradient start. + * @param x2 The x coordinate of the gradient end. + * @param y2 The y coordinate of the gradient end. + * @param spread The gradient spread method. + * @param stops Array of gradient stops. + * @param nstops Number of gradient stops. + * @param matrix Optional transformation matrix. + * @return A pointer to the created `plutovg_paint_t` object. + */ +PLUTOVG_API plutovg_paint_t* plutovg_paint_create_linear_gradient(float x1, float y1, float x2, float y2, + plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix); + +/** + * @brief Creates a radial gradient paint. + * + * @param cx The x coordinate of the gradient center. + * @param cy The y coordinate of the gradient center. + * @param cr The radius of the gradient. + * @param fx The x coordinate of the focal point. + * @param fy The y coordinate of the focal point. + * @param fr The radius of the focal point. + * @param spread The gradient spread method. + * @param stops Array of gradient stops. + * @param nstops Number of gradient stops. + * @param matrix Optional transformation matrix. + * @return A pointer to the created `plutovg_paint_t` object. + */ +PLUTOVG_API plutovg_paint_t* plutovg_paint_create_radial_gradient(float cx, float cy, float cr, float fx, float fy, float fr, + plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix); + +/** + * @brief Creates a texture paint from a surface. + * + * @param surface The texture surface. + * @param type The texture type (plain or tiled). + * @param opacity The opacity of the texture (0 to 1). + * @param matrix Optional transformation matrix. + * @return A pointer to the created `plutovg_paint_t` object. + */ +PLUTOVG_API plutovg_paint_t* plutovg_paint_create_texture(plutovg_surface_t* surface, plutovg_texture_type_t type, float opacity, const plutovg_matrix_t* matrix); + +/** + * @brief Increments the reference count of a paint object. + * + * @param paint A pointer to the `plutovg_paint_t` object. + * @return A pointer to the referenced `plutovg_paint_t` object. + */ +PLUTOVG_API plutovg_paint_t* plutovg_paint_reference(plutovg_paint_t* paint); + +/** + * @brief Decrements the reference count and destroys the paint if the count reaches zero. + * + * @param paint A pointer to the `plutovg_paint_t` object. + */ +PLUTOVG_API void plutovg_paint_destroy(plutovg_paint_t* paint); + +/** + * @brief Retrieves the reference count of a paint object. + * + * @param paint A pointer to the `plutovg_paint_t` object. + * @return The reference count of the `plutovg_paint_t` object. + */ +PLUTOVG_API int plutovg_paint_get_reference_count(const plutovg_paint_t* paint); + +/** + * @brief Defines fill rule types for filling paths. + */ +typedef enum { + PLUTOVG_FILL_RULE_NON_ZERO, ///< Non-zero winding fill rule. + PLUTOVG_FILL_RULE_EVEN_ODD ///< Even-odd fill rule. +} plutovg_fill_rule_t; + +/** + * @brief Defines compositing operations. + */ +typedef enum { + PLUTOVG_OPERATOR_CLEAR, ///< Clears the destination (resulting in a fully transparent image). + PLUTOVG_OPERATOR_SRC, ///< Source replaces destination. + PLUTOVG_OPERATOR_DST, ///< Destination is kept, source is ignored. + PLUTOVG_OPERATOR_SRC_OVER, ///< Source is composited over destination. + PLUTOVG_OPERATOR_DST_OVER, ///< Destination is composited over source. + PLUTOVG_OPERATOR_SRC_IN, ///< Source within destination (only the overlapping part of source is shown). + PLUTOVG_OPERATOR_DST_IN, ///< Destination within source. + PLUTOVG_OPERATOR_SRC_OUT, ///< Source outside destination (non-overlapping part of source is shown). + PLUTOVG_OPERATOR_DST_OUT, ///< Destination outside source. + PLUTOVG_OPERATOR_SRC_ATOP, ///< Source atop destination (source shown over destination but only in the destination's bounds). + PLUTOVG_OPERATOR_DST_ATOP, ///< Destination atop source (destination shown over source but only in the source's bounds). + PLUTOVG_OPERATOR_XOR ///< Source and destination are combined, but their overlapping regions are cleared. +} plutovg_operator_t; + +/** + * @brief Defines the shape used at the ends of open subpaths. + */ +typedef enum { + PLUTOVG_LINE_CAP_BUTT, ///< Flat edge at the end of the stroke. + PLUTOVG_LINE_CAP_ROUND, ///< Rounded ends at the end of the stroke. + PLUTOVG_LINE_CAP_SQUARE ///< Square ends at the end of the stroke. +} plutovg_line_cap_t; + +/** + * @brief Defines the shape used at the corners of paths. + */ +typedef enum { + PLUTOVG_LINE_JOIN_MITER, ///< Miter join with sharp corners. + PLUTOVG_LINE_JOIN_ROUND, ///< Rounded join. + PLUTOVG_LINE_JOIN_BEVEL ///< Beveled join with a flattened corner. +} plutovg_line_join_t; + +/** + * @brief Represents a drawing context. + */ +typedef struct plutovg_canvas plutovg_canvas_t; + +/** + * @brief Creates a drawing context on a surface. + * + * @param surface A pointer to a `plutovg_surface_t` object. + * @return A pointer to the newly created `plutovg_canvas_t` object. + */ +PLUTOVG_API plutovg_canvas_t* plutovg_canvas_create(plutovg_surface_t* surface); + +/** + * @brief Increases the reference count of the canvas. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The same pointer to the `plutovg_canvas_t` object. + */ +PLUTOVG_API plutovg_canvas_t* plutovg_canvas_reference(plutovg_canvas_t* canvas); + +/** + * @brief Decreases the reference count and destroys the canvas when it reaches zero. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_destroy(plutovg_canvas_t* canvas); + +/** + * @brief Retrieves the reference count of the canvas. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current reference count. + */ +PLUTOVG_API int plutovg_canvas_get_reference_count(const plutovg_canvas_t* canvas); + +/** + * @brief Gets the surface associated with the canvas. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return A pointer to the `plutovg_surface_t` object. + */ +PLUTOVG_API plutovg_surface_t* plutovg_canvas_get_surface(const plutovg_canvas_t* canvas); + +/** + * @brief Saves the current state of the canvas. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_save(plutovg_canvas_t* canvas); + +/** + * @brief Restores the canvas to the most recently saved state. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_restore(plutovg_canvas_t* canvas); + +/** + * @brief Sets the current paint to a solid color. + * + * If not set, the default paint is opaque black color. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param r The red component (0 to 1). + * @param g The green component (0 to 1). + * @param b The blue component (0 to 1). + */ +PLUTOVG_API void plutovg_canvas_set_rgb(plutovg_canvas_t* canvas, float r, float g, float b); + +/** + * @brief Sets the current paint to a solid color. + * + * If not set, the default paint is opaque black color. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param r The red component (0 to 1). + * @param g The green component (0 to 1). + * @param b The blue component (0 to 1). + * @param a The alpha component (0 to 1). + */ +PLUTOVG_API void plutovg_canvas_set_rgba(plutovg_canvas_t* canvas, float r, float g, float b, float a); + +/** + * @brief Sets the current paint to a solid color. + * + * If not set, the default paint is opaque black color. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param color A pointer to a `plutovg_color_t` object. + */ +PLUTOVG_API void plutovg_canvas_set_color(plutovg_canvas_t* canvas, const plutovg_color_t* color); + +/** + * @brief Sets the current paint to a linear gradient. + * + * If not set, the default paint is opaque black color. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x1 The x coordinate of the start point. + * @param y1 The y coordinate of the start point. + * @param x2 The x coordinate of the end point. + * @param y2 The y coordinate of the end point. + * @param spread The gradient spread method. + * @param stops Array of gradient stops. + * @param nstops Number of gradient stops. + * @param matrix Optional transformation matrix. + */ +PLUTOVG_API void plutovg_canvas_set_linear_gradient(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2, + plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix); + +/** + * @brief Sets the current paint to a radial gradient. + * + * If not set, the default paint is opaque black color. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param cx The x coordinate of the center. + * @param cy The y coordinate of the center. + * @param cr The radius of the gradient. + * @param fx The x coordinate of the focal point. + * @param fy The y coordinate of the focal point. + * @param fr The radius of the focal point. + * @param spread The gradient spread method. + * @param stops Array of gradient stops. + * @param nstops Number of gradient stops. + * @param matrix Optional transformation matrix. + */ +PLUTOVG_API void plutovg_canvas_set_radial_gradient(plutovg_canvas_t* canvas, float cx, float cy, float cr, float fx, float fy, float fr, + plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix); + +/** + * @brief Sets the current paint to a texture. + * + * If not set, the default paint is opaque black color. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param surface The texture surface. + * @param type The texture type (plain or tiled). + * @param opacity The opacity of the texture (0 to 1). + * @param matrix Optional transformation matrix. + */ +PLUTOVG_API void plutovg_canvas_set_texture(plutovg_canvas_t* canvas, plutovg_surface_t* surface, plutovg_texture_type_t type, float opacity, const plutovg_matrix_t* matrix); + +/** + * @brief Sets the current paint. + * + * If not set, the default paint is opaque black color. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param paint The paint to be used for subsequent drawing operations. + */ +PLUTOVG_API void plutovg_canvas_set_paint(plutovg_canvas_t* canvas, plutovg_paint_t* paint); + +/** + * @brief Retrieves the current paint. + * + * If not set, the default paint is opaque black color. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param color A pointer to a `plutovg_color_t` object where the current color will be stored. + * @return The current `plutovg_paint_t` used for drawing operations. If no paint is set, `NULL` is returned. + */ +PLUTOVG_API plutovg_paint_t* plutovg_canvas_get_paint(const plutovg_canvas_t* canvas, plutovg_color_t* color); + +/** + * @brief Assigns a font-face cache to the canvas for font management. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param cache A pointer to a `plutovg_font_face_cache_t` object, or NULL to unset the current cache. + */ +PLUTOVG_API void plutovg_canvas_set_font_face_cache(plutovg_canvas_t* canvas, plutovg_font_face_cache_t* cache); + +/** + * @brief Returns the font-face cache associated with the canvas. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return A pointer to the associated `plutovg_font_face_cache_t` object, or NULL if none is set. + */ +PLUTOVG_API plutovg_font_face_cache_t* plutovg_canvas_get_font_face_cache(const plutovg_canvas_t* canvas); + +/** + * @brief Add a font face to the canvas using the specified family and style. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param family The font family name to associate with the face. + * @param bold Whether the font is bold. + * @param italic Whether the font is italic. + * @param face A pointer to the `plutovg_font_face_t` object to add. + */ +PLUTOVG_API void plutovg_canvas_add_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, plutovg_font_face_t* face); + +/** + * @brief Load a font face from a file and add it to the canvas using the specified family and style. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param family The font family name to associate with the face. + * @param bold Whether the font is bold. + * @param italic Whether the font is italic. + * @param filename Path to the font file. + * @param ttcindex Index within a TrueType Collection (use 0 for regular font files). + * @return `true` on success, or `false` if the font could not be loaded. + */ +PLUTOVG_API bool plutovg_canvas_add_font_file(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, const char* filename, int ttcindex); + +/** + * @brief Selects and sets the current font face on the canvas. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param family The font family name to select. + * @param bold Whether to match a bold variant. + * @param italic Whether to match an italic variant. + * @return `true` if a matching font was found and set, `false` otherwise. + */ +PLUTOVG_API bool plutovg_canvas_select_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic); + +/** + * @brief Sets the font face and size for text rendering on the canvas. + * + * If not set, the default font face is `NULL`, and the default face size is 12. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param face A pointer to a `plutovg_font_face_t` object representing the font face to use. + * @param size The size of the font, in pixels. This determines the height of the rendered text. + */ +PLUTOVG_API void plutovg_canvas_set_font(plutovg_canvas_t* canvas, plutovg_font_face_t* face, float size); + +/** + * @brief Sets the font face for text rendering on the canvas. + * + * If not set, the default font face is `NULL`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param face A pointer to a `plutovg_font_face_t` object representing the font face to use. + */ +PLUTOVG_API void plutovg_canvas_set_font_face(plutovg_canvas_t* canvas, plutovg_font_face_t* face); + +/** + * @brief Retrieves the current font face used for text rendering on the canvas. + * + * If not set, the default font face is `NULL`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return A pointer to a `plutovg_font_face_t` object representing the current font face. + */ +PLUTOVG_API plutovg_font_face_t* plutovg_canvas_get_font_face(const plutovg_canvas_t* canvas); + +/** + * @brief Sets the font size for text rendering on the canvas. + * + * If not set, the default font size is 12. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param size The size of the font, in pixels. This value defines the height of the rendered text. + */ +PLUTOVG_API void plutovg_canvas_set_font_size(plutovg_canvas_t* canvas, float size); + +/** + * @brief Retrieves the current font size used for text rendering on the canvas. + * + * If not set, the default font size is 12. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current font size, in pixels. This value represents the height of the rendered text. + */ +PLUTOVG_API float plutovg_canvas_get_font_size(const plutovg_canvas_t* canvas); + +/** + * @brief Sets the fill rule. + * + * If not set, the default fill rule is `PLUTOVG_FILL_RULE_NON_ZERO`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param winding The fill rule. + */ +PLUTOVG_API void plutovg_canvas_set_fill_rule(plutovg_canvas_t* canvas, plutovg_fill_rule_t winding); + +/** + * @brief Retrieves the current fill rule. + * + * If not set, the default fill rule is `PLUTOVG_FILL_RULE_NON_ZERO`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current fill rule. + */ +PLUTOVG_API plutovg_fill_rule_t plutovg_canvas_get_fill_rule(const plutovg_canvas_t* canvas); + +/** + * @brief Sets the compositing operator. + * + * If not set, the default compositing operator is `PLUTOVG_OPERATOR_SRC_OVER`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param op The compositing operator. + */ +PLUTOVG_API void plutovg_canvas_set_operator(plutovg_canvas_t* canvas, plutovg_operator_t op); + +/** + * @brief Retrieves the current compositing operator. + * + * If not set, the default compositing operator is `PLUTOVG_OPERATOR_SRC_OVER`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current compositing operator. + */ +PLUTOVG_API plutovg_operator_t plutovg_canvas_get_operator(const plutovg_canvas_t* canvas); + +/** + * @brief Sets the global opacity. + * + * If not set, the default global opacity is 1. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param opacity The opacity value (0 to 1). + */ +PLUTOVG_API void plutovg_canvas_set_opacity(plutovg_canvas_t* canvas, float opacity); + +/** + * @brief Retrieves the current global opacity. + * + * If not set, the default global opacity is 1. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current opacity value. + */ +PLUTOVG_API float plutovg_canvas_get_opacity(const plutovg_canvas_t* canvas); + +/** + * @brief Sets the line width. + * + * If not set, the default line width is 1. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param line_width The width of the stroke. + */ +PLUTOVG_API void plutovg_canvas_set_line_width(plutovg_canvas_t* canvas, float line_width); + +/** + * @brief Retrieves the current line width. + * + * If not set, the default line width is 1. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current line width. + */ +PLUTOVG_API float plutovg_canvas_get_line_width(const plutovg_canvas_t* canvas); + +/** + * @brief Sets the line cap style. + * + * If not set, the default line cap is `PLUTOVG_LINE_CAP_BUTT`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param line_cap The line cap style. + */ +PLUTOVG_API void plutovg_canvas_set_line_cap(plutovg_canvas_t* canvas, plutovg_line_cap_t line_cap); + +/** + * @brief Retrieves the current line cap style. + * + * If not set, the default line cap is `PLUTOVG_LINE_CAP_BUTT`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current line cap style. + */ +PLUTOVG_API plutovg_line_cap_t plutovg_canvas_get_line_cap(const plutovg_canvas_t* canvas); + +/** + * @brief Sets the line join style. + * + * If not set, the default line join is `PLUTOVG_LINE_JOIN_MITER`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param line_join The line join style. + */ +PLUTOVG_API void plutovg_canvas_set_line_join(plutovg_canvas_t* canvas, plutovg_line_join_t line_join); + +/** + * @brief Retrieves the current line join style. + * + * If not set, the default line join is `PLUTOVG_LINE_JOIN_MITER`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current line join style. + */ +PLUTOVG_API plutovg_line_join_t plutovg_canvas_get_line_join(const plutovg_canvas_t* canvas); + +/** + * @brief Sets the miter limit. + * + * If not set, the default miter limit is 10. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param miter_limit The miter limit value. + */ +PLUTOVG_API void plutovg_canvas_set_miter_limit(plutovg_canvas_t* canvas, float miter_limit); + +/** + * @brief Retrieves the current miter limit. + * + * If not set, the default miter limit is 10. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current miter limit value. + */ +PLUTOVG_API float plutovg_canvas_get_miter_limit(const plutovg_canvas_t* canvas); + +/** + * @brief Sets the dash pattern. + * + * If not set, the default dash offset is 0, and the default dash array is `NULL`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param offset The dash offset. + * @param dashes Array of dash lengths. + * @param ndashes Number of dash lengths. + */ +PLUTOVG_API void plutovg_canvas_set_dash(plutovg_canvas_t* canvas, float offset, const float* dashes, int ndashes); + +/** + * @brief Sets the dash offset. + * + * If not set, the default dash offset is 0. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param offset The dash offset. + */ +PLUTOVG_API void plutovg_canvas_set_dash_offset(plutovg_canvas_t* canvas, float offset); + +/** + * @brief Retrieves the current dash offset. + * + * If not set, the default dash offset is 0. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current dash offset. + */ +PLUTOVG_API float plutovg_canvas_get_dash_offset(const plutovg_canvas_t* canvas); + +/** + * @brief Sets the dash pattern. + * + * If not set, the default dash array is `NULL`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param dashes Array of dash lengths. + * @param ndashes Number of dash lengths. + */ +PLUTOVG_API void plutovg_canvas_set_dash_array(plutovg_canvas_t* canvas, const float* dashes, int ndashes); + +/** + * @brief Retrieves the current dash pattern. + * + * If not set, the default dash array is `NULL`. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param dashes Pointer to store the dash array. + * @return The number of dash lengths. + */ +PLUTOVG_API int plutovg_canvas_get_dash_array(const plutovg_canvas_t* canvas, const float** dashes); + +/** + * @brief Translates the current transformation matrix by offsets `tx` and `ty`. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param tx The translation offset in the x-direction. + * @param ty The translation offset in the y-direction. + */ +PLUTOVG_API void plutovg_canvas_translate(plutovg_canvas_t* canvas, float tx, float ty); + +/** + * @brief Scales the current transformation matrix by factors `sx` and `sy`. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param sx The scaling factor in the x-direction. + * @param sy The scaling factor in the y-direction. + */ +PLUTOVG_API void plutovg_canvas_scale(plutovg_canvas_t* canvas, float sx, float sy); + +/** + * @brief Shears the current transformation matrix by factors `shx` and `shy`. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param shx The shearing factor in the x-direction. + * @param shy The shearing factor in the y-direction. + */ +PLUTOVG_API void plutovg_canvas_shear(plutovg_canvas_t* canvas, float shx, float shy); + +/** + * @brief Rotates the current transformation matrix by the specified angle (in radians). + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param angle The rotation angle in radians. + */ +PLUTOVG_API void plutovg_canvas_rotate(plutovg_canvas_t* canvas, float angle); + +/** + * @brief Multiplies the current transformation matrix with the specified `matrix`. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param matrix A pointer to the `plutovg_matrix_t` object. + */ +PLUTOVG_API void plutovg_canvas_transform(plutovg_canvas_t* canvas, const plutovg_matrix_t* matrix); + +/** + * @brief Resets the current transformation matrix to the identity matrix. + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_reset_matrix(plutovg_canvas_t* canvas); + +/** + * @brief Resets the current transformation matrix to the specified `matrix`. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param matrix A pointer to the `plutovg_matrix_t` object. + */ +PLUTOVG_API void plutovg_canvas_set_matrix(plutovg_canvas_t* canvas, const plutovg_matrix_t* matrix); + +/** + * @brief Stores the current transformation matrix in `matrix`. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param A pointer to the `plutovg_matrix_t` to store the matrix. + */ +PLUTOVG_API void plutovg_canvas_get_matrix(const plutovg_canvas_t* canvas, plutovg_matrix_t* matrix); + +/** + * @brief Transforms the point `(x, y)` using the current transformation matrix and stores the result in `(xx, yy)`. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The x-coordinate of the point to transform. + * @param y The y-coordinate of the point to transform. + * @param xx A pointer to store the transformed x-coordinate. + * @param yy A pointer to store the transformed y-coordinate. + */ +PLUTOVG_API void plutovg_canvas_map(const plutovg_canvas_t* canvas, float x, float y, float* xx, float* yy); + +/** + * @brief Transforms the `src` point using the current transformation matrix and stores the result in `dst`. + * @note `src` and `dst` can be identical. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param src A pointer to the `plutovg_point_t` point to transform. + * @param dst A pointer to the `plutovg_point_t` to store the transformed point. + */ +PLUTOVG_API void plutovg_canvas_map_point(const plutovg_canvas_t* canvas, const plutovg_point_t* src, plutovg_point_t* dst); + +/** + * @brief Transforms the `src` rectangle using the current transformation matrix and stores the result in `dst`. + * @note `src` and `dst` can be identical. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param src A pointer to the `plutovg_rect_t` rectangle to transform. + * @param dst A pointer to the `plutovg_rect_t` to store the transformed rectangle. + */ +PLUTOVG_API void plutovg_canvas_map_rect(const plutovg_canvas_t* canvas, const plutovg_rect_t* src, plutovg_rect_t* dst); + +/** + * @brief Moves the current point to a new position. + * + * Moves the current point to the specified coordinates without adding a line. + * This operation is added to the current path. Equivalent to the SVG `M` command. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The x-coordinate of the new position. + * @param y The y-coordinate of the new position. + */ +PLUTOVG_API void plutovg_canvas_move_to(plutovg_canvas_t* canvas, float x, float y); + +/** + * @brief Adds a straight line segment to the current path. + * + * Adds a straight line from the current point to the specified coordinates. + * This segment is added to the current path. Equivalent to the SVG `L` command. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The x-coordinate of the end point of the line. + * @param y The y-coordinate of the end point of the line. + */ +PLUTOVG_API void plutovg_canvas_line_to(plutovg_canvas_t* canvas, float x, float y); + +/** + * @brief Adds a quadratic Bézier curve to the current path. + * + * Adds a quadratic Bézier curve from the current point to the specified end point, + * using the given control point. This curve is added to the current path. Equivalent to the SVG `Q` command. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x1 The x-coordinate of the control point. + * @param y1 The y-coordinate of the control point. + * @param x2 The x-coordinate of the end point of the curve. + * @param y2 The y-coordinate of the end point of the curve. + */ +PLUTOVG_API void plutovg_canvas_quad_to(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2); + +/** + * @brief Adds a cubic Bézier curve to the current path. + * + * Adds a cubic Bézier curve from the current point to the specified end point, + * using the given control points. This curve is added to the current path. Equivalent to the SVG `C` command. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x1 The x-coordinate of the first control point. + * @param y1 The y-coordinate of the first control point. + * @param x2 The x-coordinate of the second control point. + * @param y2 The y-coordinate of the second control point. + * @param x3 The x-coordinate of the end point of the curve. + * @param y3 The y-coordinate of the end point of the curve. + */ +PLUTOVG_API void plutovg_canvas_cubic_to(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2, float x3, float y3); + +/** + * @brief Adds an elliptical arc to the current path. + * + * Adds an elliptical arc from the current point to the specified end point, + * defined by radii, rotation angle, and flags for arc type and direction. + * This arc segment is added to the current path. Equivalent to the SVG `A` command. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param rx The x-radius of the ellipse. + * @param ry The y-radius of the ellipse. + * @param angle The rotation angle of the ellipse in degrees. + * @param large_arc_flag If true, add the large arc; otherwise, add the small arc. + * @param sweep_flag If true, add the arc in the positive-angle direction; otherwise, in the negative-angle direction. + * @param x The x-coordinate of the end point. + * @param y The y-coordinate of the end point. + */ +PLUTOVG_API void plutovg_canvas_arc_to(plutovg_canvas_t* canvas, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y); + +/** + * @brief Adds a rectangle to the current path. + * + * Adds a rectangle with the specified position and dimensions to the current path. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The x-coordinate of the rectangle's origin. + * @param y The y-coordinate of the rectangle's origin. + * @param w The width of the rectangle. + * @param h The height of the rectangle. + */ +PLUTOVG_API void plutovg_canvas_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h); + +/** + * @brief Adds a rounded rectangle to the current path. + * + * Adds a rectangle with rounded corners defined by the specified position, + * dimensions, and corner radii to the current path. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The x-coordinate of the rectangle's origin. + * @param y The y-coordinate of the rectangle's origin. + * @param w The width of the rectangle. + * @param h The height of the rectangle. + * @param rx The x-radius of the corners. + * @param ry The y-radius of the corners. + */ +PLUTOVG_API void plutovg_canvas_round_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h, float rx, float ry); + +/** + * @brief Adds an ellipse to the current path. + * + * Adds an ellipse centered at the specified coordinates with the given radii to the current path. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param cx The x-coordinate of the ellipse's center. + * @param cy The y-coordinate of the ellipse's center. + * @param rx The x-radius of the ellipse. + * @param ry The y-radius of the ellipse. + */ +PLUTOVG_API void plutovg_canvas_ellipse(plutovg_canvas_t* canvas, float cx, float cy, float rx, float ry); + +/** + * @brief Adds a circle to the current path. + * + * Adds a circle centered at the specified coordinates with the given radius to the current path. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param cx The x-coordinate of the circle's center. + * @param cy The y-coordinate of the circle's center. + * @param r The radius of the circle. + */ +PLUTOVG_API void plutovg_canvas_circle(plutovg_canvas_t* canvas, float cx, float cy, float r); + +/** + * @brief Adds an arc to the current path. + * + * Adds an arc centered at the specified coordinates, with a given radius, + * starting and ending at the specified angles. The direction of the arc is + * determined by `ccw`. This arc segment is added to the current path. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param cx The x-coordinate of the arc's center. + * @param cy The y-coordinate of the arc's center. + * @param r The radius of the arc. + * @param a0 The starting angle of the arc in radians. + * @param a1 The ending angle of the arc in radians. + * @param ccw If true, add the arc counter-clockwise; otherwise, clockwise. + */ +PLUTOVG_API void plutovg_canvas_arc(plutovg_canvas_t* canvas, float cx, float cy, float r, float a0, float a1, bool ccw); + +/** + * @brief Adds a path to the current path. + * + * Appends the elements of the specified path to the current path. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param path A pointer to the `plutovg_path_t` object to be added. + */ +PLUTOVG_API void plutovg_canvas_add_path(plutovg_canvas_t* canvas, const plutovg_path_t* path); + +/** + * @brief Starts a new path on the canvas. + * + * Begins a new path, clearing any existing path data. The new path starts with no commands. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_new_path(plutovg_canvas_t* canvas); + +/** + * @brief Closes the current path. + * + * Closes the current path by adding a straight line back to the starting point. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_close_path(plutovg_canvas_t* canvas); + +/** + * @brief Retrieves the current point of the canvas. + * + * Gets the coordinates of the current point in the canvas, which is the last point + * added or moved to in the current path. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The x-coordinate of the current point. + * @param y The y-coordinate of the current point. + */ +PLUTOVG_API void plutovg_canvas_get_current_point(const plutovg_canvas_t* canvas, float* x, float* y); + +/** + * @brief Gets the current path from the canvas. + * + * Retrieves the path object representing the sequence of path commands added to the canvas. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return The current path. + */ +PLUTOVG_API plutovg_path_t* plutovg_canvas_get_path(const plutovg_canvas_t* canvas); + +/** + * @brief Tests whether a point lies within the current fill region. + * + * Determines whether the point at coordinates `(x, y)` falls within the area + * that would be filled by a `plutovg_canvas_fill()` operation, given the current path, + * fill rule, and transformation state. + * + * @note Clipping and surface dimensions are not considered in this test. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The X coordinate of the point, in user space. + * @param y The Y coordinate of the point, in user space. + * @return `true` if the point is within the fill region, `false` otherwise. + */ +PLUTOVG_API bool plutovg_canvas_fill_contains(plutovg_canvas_t* canvas, float x, float y); + +/** + * @brief Tests whether a point lies within the current stroke region. + * + * Determines whether the point at coordinates `(x, y)` falls within the area + * that would be stroked by a `plutovg_canvas_stroke()` operation, given the current path, + * stroke width, joins, caps, miter limit, dash pattern, and transformation state. + * + * @note Clipping and surface dimensions are not considered in this test. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The X coordinate of the point, in user space. + * @param y The Y coordinate of the point, in user space. + * @return `true` if the point is within the stroke region, `false` otherwise. + */ +PLUTOVG_API bool plutovg_canvas_stroke_contains(plutovg_canvas_t* canvas, float x, float y); + +/** + * @brief Tests whether a point lies within the current clipping region. + * + * Determines whether the point at coordinates `(x, y)` falls within the active clipping + * region on the canvas. + * + * If no clipping is applied, the default clipping region covers the entire canvas + * area starting at `(0, 0)` with width and height equal to the canvas dimensions. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The X coordinate of the point, in user space. + * @param y The Y coordinate of the point, in user space. + * @return `true` if the point is within the clipping region, `false` otherwise. + */ +PLUTOVG_API bool plutovg_canvas_clip_contains(plutovg_canvas_t* canvas, float x, float y); + +/** + * @brief Computes the bounding box of the area that would be affected by a fill operation. + * + * Computes an axis-aligned bounding box in user space that encloses the area + * which would be affected by a fill operation (`plutovg_canvas_fill()`) given the current path, + * fill rule, and transformation state. + * + * @note Clipping and surface dimensions are not considered in this calculation. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param extents A pointer to a `plutovg_rect_t` structure that receives the bounding box. + */ +PLUTOVG_API void plutovg_canvas_fill_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents); + +/** + * @brief Computes the bounding box of the area that would be affected by a stroke operation. + * + * Computes an axis-aligned bounding box in user space that encloses the area + * which would be affected by a stroke operation (`plutovg_canvas_stroke()`) given the current path, + * stroke width, joins, caps, miter limit, dash pattern, and transformation state. + * + * @note Clipping and surface dimensions are not considered in this calculation. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param extents A pointer to a `plutovg_rect_t` structure that receives the bounding box. + */ +PLUTOVG_API void plutovg_canvas_stroke_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents); + +/** + * @brief Gets the bounding box of the current clipping region. + * + * Computes an axis-aligned bounding box in user space that encloses the currently active + * clipping region on the canvas. + * + * If no clip is applied, the returned rectangle covers the entire canvas area, + * starting at `(0, 0)` with width and height equal to the canvas dimensions. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param extents A pointer to a `plutovg_rect_t` structure that receives the bounding box. + */ +PLUTOVG_API void plutovg_canvas_clip_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents); + +/** + * @brief A drawing operator that fills the current path according to the current fill rule. + * + * The current path will be cleared after this operation. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_fill(plutovg_canvas_t* canvas); + +/** + * @brief A drawing operator that strokes the current path according to the current stroke settings. + * + * The current path will be cleared after this operation. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_stroke(plutovg_canvas_t* canvas); + +/** + * @brief A drawing operator that intersects the current clipping region with the current path according to the current fill rule. + * + * The current path will be cleared after this operation. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_clip(plutovg_canvas_t* canvas); + +/** + * @brief A drawing operator that paints the current clipping region using the current paint. + * + * @note The current path will not be affected by this operation. + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_paint(plutovg_canvas_t* canvas); + +/** + * @brief A drawing operator that fills the current path according to the current fill rule. + * + * The current path will be preserved after this operation. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_fill_preserve(plutovg_canvas_t* canvas); + +/** + * @brief A drawing operator that strokes the current path according to the current stroke settings. + * + * The current path will be preserved after this operation. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_stroke_preserve(plutovg_canvas_t* canvas); + +/** + * @brief A drawing operator that intersects the current clipping region with the current path according to the current fill rule. + * + * The current path will be preserved after this operation. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + */ +PLUTOVG_API void plutovg_canvas_clip_preserve(plutovg_canvas_t* canvas); + +/** + * @brief Fills a rectangle according to the current fill rule. + * + * @note The current path will be cleared by this operation. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The x-coordinate of the rectangle's origin. + * @param y The y-coordinate of the rectangle's origin. + * @param w The width of the rectangle. + * @param h The height of the rectangle. + */ +PLUTOVG_API void plutovg_canvas_fill_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h); + +/** + * @brief Fills a path according to the current fill rule. + * + * @note The current path will be cleared by this operation. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param path The `plutovg_path_t` object. + */ +PLUTOVG_API void plutovg_canvas_fill_path(plutovg_canvas_t* canvas, const plutovg_path_t* path); + +/** + * @brief Strokes a rectangle with the current stroke settings. + * + * @note The current path will be cleared by this operation. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The x-coordinate of the rectangle's origin. + * @param y The y-coordinate of the rectangle's origin. + * @param w The width of the rectangle. + * @param h The height of the rectangle. + */ +PLUTOVG_API void plutovg_canvas_stroke_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h); + +/** + * @brief Strokes a path with the current stroke settings. + * + * @note The current path will be cleared by this operation. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param path The `plutovg_path_t` object. + */ +PLUTOVG_API void plutovg_canvas_stroke_path(plutovg_canvas_t* canvas, const plutovg_path_t* path); + +/** + * @brief Intersects the current clipping region with a rectangle according to the current fill rule. + * + * @note The current path will be cleared by this operation. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The x-coordinate of the rectangle's origin. + * @param y The y-coordinate of the rectangle's origin. + * @param w The width of the rectangle. + * @param h The height of the rectangle. + */ +PLUTOVG_API void plutovg_canvas_clip_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h); + +/** + * @brief Intersects the current clipping region with a path according to the current fill rule. + * + * @note The current path will be cleared by this operation. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param path The `plutovg_path_t` object. + */ +PLUTOVG_API void plutovg_canvas_clip_path(plutovg_canvas_t* canvas, const plutovg_path_t* path); + +/** + * @brief Adds a glyph to the current path at the specified origin. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param codepoint The glyph codepoint. + * @param x The x-coordinate of the origin. + * @param y The y-coordinate of the origin. + * @return The advance width of the glyph. + */ +PLUTOVG_API float plutovg_canvas_add_glyph(plutovg_canvas_t* canvas, plutovg_codepoint_t codepoint, float x, float y); + +/** + * @brief Adds text to the current path at the specified origin. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param text The text data. + * @param length The length of the text data, or -1 if null-terminated. + * @param encoding The encoding of the text data. + * @param x The x-coordinate of the origin. + * @param y The y-coordinate of the origin. + * @return The total advance width of the text. + */ +PLUTOVG_API float plutovg_canvas_add_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y); + +/** + * @brief Fills a text at the specified origin. + * + * @note The current path will be cleared by this operation. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param text The text data. + * @param length The length of the text data, or -1 if null-terminated. + * @param encoding The encoding of the text data. + * @param x The x-coordinate of the origin. + * @param y The y-coordinate of the origin. + * @return The total advance width of the text. + */ +PLUTOVG_API float plutovg_canvas_fill_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y); + +/** + * @brief Strokes a text at the specified origin. + * + * @note The current path will be cleared by this operation. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param text The text data. + * @param length The length of the text data, or -1 if null-terminated. + * @param encoding The encoding of the text data. + * @param x The x-coordinate of the origin. + * @param y The y-coordinate of the origin. + * @return The total advance width of the text. + */ +PLUTOVG_API float plutovg_canvas_stroke_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y); + +/** + * @brief Intersects the current clipping region with text at the specified origin. + * + * @note The current path will be cleared by this operation. + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param text The text data. + * @param length The length of the text data, or -1 if null-terminated. + * @param encoding The encoding of the text data. + * @param x The x-coordinate of the origin. + * @param y The y-coordinate of the origin. + * @return The total advance width of the text. + */ +PLUTOVG_API float plutovg_canvas_clip_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y); + +/** + * @brief Retrieves font metrics for the current font. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param ascent The ascent of the font. + * @param descent The descent of the font. + * @param line_gap The line gap of the font. + * @param extents The bounding box of the font. + */ +PLUTOVG_API void plutovg_canvas_font_metrics(const plutovg_canvas_t* canvas, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents); + +/** + * @brief Retrieves metrics for a specific glyph. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param codepoint The glyph codepoint. + * @param advance_width The advance width of the glyph. + * @param left_side_bearing The left side bearing of the glyph. + * @param extents The bounding box of the glyph. + */ +PLUTOVG_API void plutovg_canvas_glyph_metrics(plutovg_canvas_t* canvas, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents); + +/** + * @brief Retrieves the extents of a text. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param text The text data. + * @param length The length of the text data, or -1 if null-terminated. + * @param encoding The encoding of the text data. + * @param extents The bounding box of the text. + * @return The total advance width of the text. + */ +PLUTOVG_API float plutovg_canvas_text_extents(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents); + +#ifdef __cplusplus +} +#endif + +#endif // PLUTOVG_H diff --git a/vendor/lunasvg/plutovg/source/plutovg-blend.c b/vendor/lunasvg/plutovg/source/plutovg-blend.c new file mode 100644 index 0000000..77d8628 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-blend.c @@ -0,0 +1,1144 @@ +#include "plutovg-private.h" +#include "plutovg-utils.h" + +#include +#include + +#define COLOR_TABLE_SIZE 1024 +typedef struct { + plutovg_matrix_t matrix; + plutovg_spread_method_t spread; + uint32_t colortable[COLOR_TABLE_SIZE]; + union { + struct { + float x1, y1; + float x2, y2; + } linear; + struct { + float cx, cy, cr; + float fx, fy, fr; + } radial; + } values; +} gradient_data_t; + +typedef struct { + plutovg_matrix_t matrix; + uint8_t* data; + int width; + int height; + int stride; + int const_alpha; +} texture_data_t; + +typedef struct { + float dx; + float dy; + float l; + float off; +} linear_gradient_values_t; + +typedef struct { + float dx; + float dy; + float dr; + float sqrfr; + float a; + bool extended; +} radial_gradient_values_t; + +static inline uint32_t premultiply_color_with_opacity(const plutovg_color_t* color, float opacity) +{ + uint32_t alpha = lroundf(color->a * opacity * 255); + uint32_t pr = lroundf(color->r * alpha); + uint32_t pg = lroundf(color->g * alpha); + uint32_t pb = lroundf(color->b * alpha); + return (alpha << 24) | (pr << 16) | (pg << 8) | (pb); +} + +static inline uint32_t INTERPOLATE_PIXEL_255(uint32_t x, uint32_t a, uint32_t y, uint32_t b) +{ + uint32_t t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; + x = (x + ((x >> 8) & 0xff00ff) + 0x800080); + x &= 0xff00ff00; + x |= t; + return x; +} + +static inline uint32_t INTERPOLATE_PIXEL_256(uint32_t x, uint32_t a, uint32_t y, uint32_t b) +{ + uint32_t t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; + t >>= 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; + x &= 0xff00ff00; + x |= t; + return x; +} + +static inline uint32_t BYTE_MUL(uint32_t x, uint32_t a) +{ + uint32_t t = (x & 0xff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff00ff) * a; + x = (x + ((x >> 8) & 0xff00ff) + 0x800080); + x &= 0xff00ff00; + x |= t; + return x; +} + +#ifdef __SSE2__ + +#include + +void plutovg_memfill32(unsigned int* dest, int length, unsigned int value) +{ + __m128i vector_data = _mm_set_epi32(value, value, value, value); + while(length && ((uintptr_t)dest & 0xf)) { + *dest++ = value; + length--; + } + + while(length >= 32) { + _mm_store_si128((__m128i*)(dest), vector_data); + _mm_store_si128((__m128i*)(dest + 4), vector_data); + _mm_store_si128((__m128i*)(dest + 8), vector_data); + _mm_store_si128((__m128i*)(dest + 12), vector_data); + _mm_store_si128((__m128i*)(dest + 16), vector_data); + _mm_store_si128((__m128i*)(dest + 20), vector_data); + _mm_store_si128((__m128i*)(dest + 24), vector_data); + _mm_store_si128((__m128i*)(dest + 28), vector_data); + + dest += 32; + length -= 32; + } + + if(length >= 16) { + _mm_store_si128((__m128i*)(dest), vector_data); + _mm_store_si128((__m128i*)(dest + 4), vector_data); + _mm_store_si128((__m128i*)(dest + 8), vector_data); + _mm_store_si128((__m128i*)(dest + 12), vector_data); + + dest += 16; + length -= 16; + } + + if(length >= 8) { + _mm_store_si128((__m128i*)(dest), vector_data); + _mm_store_si128((__m128i*)(dest + 4), vector_data); + + dest += 8; + length -= 8; + } + + if(length >= 4) { + _mm_store_si128((__m128i*)(dest), vector_data); + + dest += 4; + length -= 4; + } + + while(length) { + *dest++ = value; + length--; + } +} + +#else + +void plutovg_memfill32(unsigned int* dest, int length, unsigned int value) +{ + while(length--) { + *dest++ = value; + } +} + +#endif // __SSE2__ + +static inline int gradient_clamp(const gradient_data_t* gradient, int ipos) +{ + if(gradient->spread == PLUTOVG_SPREAD_METHOD_REPEAT) { + ipos = ipos % COLOR_TABLE_SIZE; + ipos = ipos < 0 ? COLOR_TABLE_SIZE + ipos : ipos; + } else if(gradient->spread == PLUTOVG_SPREAD_METHOD_REFLECT) { + const int limit = COLOR_TABLE_SIZE * 2; + ipos = ipos % limit; + ipos = ipos < 0 ? limit + ipos : ipos; + ipos = ipos >= COLOR_TABLE_SIZE ? limit - 1 - ipos : ipos; + } else { + if(ipos < 0) { + ipos = 0; + } else if(ipos >= COLOR_TABLE_SIZE) { + ipos = COLOR_TABLE_SIZE - 1; + } + } + + return ipos; +} + +#define FIXPT_BITS 8 +#define FIXPT_SIZE (1 << FIXPT_BITS) +static inline uint32_t gradient_pixel_fixed(const gradient_data_t* gradient, int fixed_pos) +{ + int ipos = (fixed_pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS; + return gradient->colortable[gradient_clamp(gradient, ipos)]; +} + +static inline uint32_t gradient_pixel(const gradient_data_t* gradient, float pos) +{ + int ipos = (int)(pos * (COLOR_TABLE_SIZE - 1) + 0.5f); + return gradient->colortable[gradient_clamp(gradient, ipos)]; +} + +static void fetch_linear_gradient(uint32_t* buffer, const linear_gradient_values_t* v, const gradient_data_t* gradient, int y, int x, int length) +{ + float t, inc; + float rx = 0, ry = 0; + + if(v->l == 0.f) { + t = inc = 0; + } else { + rx = gradient->matrix.c * (y + 0.5f) + gradient->matrix.a * (x + 0.5f) + gradient->matrix.e; + ry = gradient->matrix.d * (y + 0.5f) + gradient->matrix.b * (x + 0.5f) + gradient->matrix.f; + t = v->dx * rx + v->dy * ry + v->off; + inc = v->dx * gradient->matrix.a + v->dy * gradient->matrix.b; + t *= (COLOR_TABLE_SIZE - 1); + inc *= (COLOR_TABLE_SIZE - 1); + } + + const uint32_t* end = buffer + length; + if(inc > -1e-5f && inc < 1e-5f) { + plutovg_memfill32(buffer, length, gradient_pixel_fixed(gradient, (int)(t * FIXPT_SIZE))); + } else { + if(t + inc * length < (float)(INT_MAX >> (FIXPT_BITS + 1)) && t + inc * length > (float)(INT_MIN >> (FIXPT_BITS + 1))) { + int t_fixed = (int)(t * FIXPT_SIZE); + int inc_fixed = (int)(inc * FIXPT_SIZE); + while(buffer < end) { + *buffer = gradient_pixel_fixed(gradient, t_fixed); + t_fixed += inc_fixed; + ++buffer; + } + } else { + while(buffer < end) { + *buffer = gradient_pixel(gradient, t / COLOR_TABLE_SIZE); + t += inc; + ++buffer; + } + } + } +} + +static void fetch_radial_gradient(uint32_t* buffer, const radial_gradient_values_t* v, const gradient_data_t* gradient, int y, int x, int length) +{ + if(v->a == 0.f) { + plutovg_memfill32(buffer, length, 0); + return; + } + + float rx = gradient->matrix.c * (y + 0.5f) + gradient->matrix.e + gradient->matrix.a * (x + 0.5f); + float ry = gradient->matrix.d * (y + 0.5f) + gradient->matrix.f + gradient->matrix.b * (x + 0.5f); + + rx -= gradient->values.radial.fx; + ry -= gradient->values.radial.fy; + + float inv_a = 1.f / (2.f * v->a); + float delta_rx = gradient->matrix.a; + float delta_ry = gradient->matrix.b; + + float b = 2 * (v->dr * gradient->values.radial.fr + rx * v->dx + ry * v->dy); + float delta_b = 2 * (delta_rx * v->dx + delta_ry * v->dy); + float b_delta_b = 2 * b * delta_b; + float delta_b_delta_b = 2 * delta_b * delta_b; + + float bb = b * b; + float delta_bb = delta_b * delta_b; + + b *= inv_a; + delta_b *= inv_a; + + float rxrxryry = rx * rx + ry * ry; + float delta_rxrxryry = delta_rx * delta_rx + delta_ry * delta_ry; + float rx_plus_ry = 2 * (rx * delta_rx + ry * delta_ry); + float delta_rx_plus_ry = 2 * delta_rxrxryry; + + inv_a *= inv_a; + + float det = (bb - 4 * v->a * (v->sqrfr - rxrxryry)) * inv_a; + float delta_det = (b_delta_b + delta_bb + 4 * v->a * (rx_plus_ry + delta_rxrxryry)) * inv_a; + float delta_delta_det = (delta_b_delta_b + 4 * v->a * delta_rx_plus_ry) * inv_a; + + const uint32_t* end = buffer + length; + if(v->extended) { + while(buffer < end) { + uint32_t result = 0; + if(det >= 0) { + float w = sqrtf(det) - b; + if(gradient->values.radial.fr + v->dr * w >= 0) { + result = gradient_pixel(gradient, w); + } + } + + *buffer = result; + det += delta_det; + delta_det += delta_delta_det; + b += delta_b; + ++buffer; + } + } else { + while(buffer < end) { + *buffer++ = gradient_pixel(gradient, sqrtf(det) - b); + det += delta_det; + delta_det += delta_delta_det; + b += delta_b; + } + } +} + +static void composition_solid_clear(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + if(const_alpha == 255) { + plutovg_memfill32(dest, length, 0); + } else { + uint32_t ialpha = 255 - const_alpha; + for(int i = 0; i < length; i++) { + dest[i] = BYTE_MUL(dest[i], ialpha); + } + } +} + +static void composition_solid_source(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + if(const_alpha == 255) { + plutovg_memfill32(dest, length, color); + } else { + uint32_t ialpha = 255 - const_alpha; + color = BYTE_MUL(color, const_alpha); + for(int i = 0; i < length; i++) { + dest[i] = color + BYTE_MUL(dest[i], ialpha); + } + } +} + +static void composition_solid_destination(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ +} + +static void composition_solid_source_over(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + if(const_alpha != 255) + color = BYTE_MUL(color, const_alpha); + uint32_t ialpha = 255 - plutovg_alpha(color); + for(int i = 0; i < length; i++) { + dest[i] = color + BYTE_MUL(dest[i], ialpha); + } +} + +static void composition_solid_destination_over(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + if(const_alpha != 255) + color = BYTE_MUL(color, const_alpha); + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + dest[i] = d + BYTE_MUL(color, plutovg_alpha(~d)); + } +} + +static void composition_solid_source_in(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + dest[i] = BYTE_MUL(color, plutovg_alpha(dest[i])); + } + } else { + color = BYTE_MUL(color, const_alpha); + uint32_t cia = 255 - const_alpha; + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(color, plutovg_alpha(d), d, cia); + } + } +} + +static void composition_solid_destination_in(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + uint32_t a = plutovg_alpha(color); + if(const_alpha != 255) + a = BYTE_MUL(a, const_alpha) + 255 - const_alpha; + for(int i = 0; i < length; i++) { + dest[i] = BYTE_MUL(dest[i], a); + } +} + +static void composition_solid_source_out(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + dest[i] = BYTE_MUL(color, plutovg_alpha(~dest[i])); + } + } else { + color = BYTE_MUL(color, const_alpha); + uint32_t cia = 255 - const_alpha; + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(color, plutovg_alpha(~d), d, cia); + } + } +} + +static void composition_solid_destination_out(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + uint32_t a = plutovg_alpha(~color); + if(const_alpha != 255) + a = BYTE_MUL(a, const_alpha) + 255 - const_alpha; + for(int i = 0; i < length; i++) { + dest[i] = BYTE_MUL(dest[i], a); + } +} + +static void composition_solid_source_atop(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + if(const_alpha != 255) + color = BYTE_MUL(color, const_alpha); + uint32_t sia = plutovg_alpha(~color); + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(color, plutovg_alpha(d), d, sia); + } +} + +static void composition_solid_destination_atop(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + uint32_t a = plutovg_alpha(color); + if(const_alpha != 255) { + color = BYTE_MUL(color, const_alpha); + a = plutovg_alpha(color) + 255 - const_alpha; + } + + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(d, a, color, plutovg_alpha(~d)); + } +} + +static void composition_solid_xor(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + if(const_alpha != 255) + color = BYTE_MUL(color, const_alpha); + uint32_t sia = plutovg_alpha(~color); + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(color, plutovg_alpha(~d), d, sia); + } +} + +typedef void(*composition_solid_function_t)(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha); + +static const composition_solid_function_t composition_solid_table[] = { + composition_solid_clear, + composition_solid_source, + composition_solid_destination, + composition_solid_source_over, + composition_solid_destination_over, + composition_solid_source_in, + composition_solid_destination_in, + composition_solid_source_out, + composition_solid_destination_out, + composition_solid_source_atop, + composition_solid_destination_atop, + composition_solid_xor +}; + +static void composition_clear(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + plutovg_memfill32(dest, length, 0); + } else { + uint32_t ialpha = 255 - const_alpha; + for(int i = 0; i < length; i++) { + dest[i] = BYTE_MUL(dest[i], ialpha); + } + } +} + +static void composition_source(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + memcpy(dest, src, length * sizeof(uint32_t)); + } else { + uint32_t ialpha = 255 - const_alpha; + for(int i = 0; i < length; i++) { + dest[i] = INTERPOLATE_PIXEL_255(src[i], const_alpha, dest[i], ialpha); + } + } +} + +static void composition_destination(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ +} + +static void composition_source_over(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + uint32_t s = src[i]; + if(s >= 0xff000000) { + dest[i] = s; + } else if(s != 0) { + dest[i] = s + BYTE_MUL(dest[i], plutovg_alpha(~s)); + } + } + } else { + for(int i = 0; i < length; i++) { + uint32_t s = BYTE_MUL(src[i], const_alpha); + dest[i] = s + BYTE_MUL(dest[i], plutovg_alpha(~s)); + } + } +} + +static void composition_destination_over(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + dest[i] = d + BYTE_MUL(src[i], plutovg_alpha(~d)); + } + } else { + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + uint32_t s = BYTE_MUL(src[i], const_alpha); + dest[i] = d + BYTE_MUL(s, plutovg_alpha(~d)); + } + } +} + +static void composition_source_in(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + dest[i] = BYTE_MUL(src[i], plutovg_alpha(dest[i])); + } + } else { + uint32_t cia = 255 - const_alpha; + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + uint32_t s = BYTE_MUL(src[i], const_alpha); + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(d), d, cia); + } + } +} + +static void composition_destination_in(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + dest[i] = BYTE_MUL(dest[i], plutovg_alpha(src[i])); + } + } else { + uint32_t cia = 255 - const_alpha; + for(int i = 0; i < length; i++) { + uint32_t a = BYTE_MUL(plutovg_alpha(src[i]), const_alpha) + cia; + dest[i] = BYTE_MUL(dest[i], a); + } + } +} + +static void composition_source_out(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + dest[i] = BYTE_MUL(src[i], plutovg_alpha(~dest[i])); + } + } else { + uint32_t cia = 255 - const_alpha; + for(int i = 0; i < length; i++) { + uint32_t s = BYTE_MUL(src[i], const_alpha); + uint32_t d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(~d), d, cia); + } + } +} + +static void composition_destination_out(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + dest[i] = BYTE_MUL(dest[i], plutovg_alpha(~src[i])); + } + } else { + uint32_t cia = 255 - const_alpha; + for(int i = 0; i < length; i++) { + uint32_t sia = BYTE_MUL(plutovg_alpha(~src[i]), const_alpha) + cia; + dest[i] = BYTE_MUL(dest[i], sia); + } + } +} + +static void composition_source_atop(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + uint32_t s = src[i]; + uint32_t d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(d), d, plutovg_alpha(~s)); + } + } else { + for(int i = 0; i < length; i++) { + uint32_t s = BYTE_MUL(src[i], const_alpha); + uint32_t d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(d), d, plutovg_alpha(~s)); + } + } +} + +static void composition_destination_atop(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + uint32_t s = src[i]; + uint32_t d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(d, plutovg_alpha(s), s, plutovg_alpha(~d)); + } + } else { + uint32_t cia = 255 - const_alpha; + for(int i = 0; i < length; i++) { + uint32_t s = BYTE_MUL(src[i], const_alpha); + uint32_t d = dest[i]; + uint32_t a = plutovg_alpha(s) + cia; + dest[i] = INTERPOLATE_PIXEL_255(d, a, s, plutovg_alpha(~d)); + } + } +} + +static void composition_xor(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) { + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + uint32_t s = src[i]; + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(~d), d, plutovg_alpha(~s)); + } + } else { + for(int i = 0; i < length; i++) { + uint32_t d = dest[i]; + uint32_t s = BYTE_MUL(src[i], const_alpha); + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(~d), d, plutovg_alpha(~s)); + } + } +} + +typedef void(*composition_function_t)(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha); + +static const composition_function_t composition_table[] = { + composition_clear, + composition_source, + composition_destination, + composition_source_over, + composition_destination_over, + composition_source_in, + composition_destination_in, + composition_source_out, + composition_destination_out, + composition_source_atop, + composition_destination_atop, + composition_xor +}; + +static void blend_solid(plutovg_surface_t* surface, plutovg_operator_t op, uint32_t solid, const plutovg_span_buffer_t* span_buffer) +{ + composition_solid_function_t func = composition_solid_table[op]; + int count = span_buffer->spans.size; + const plutovg_span_t* spans = span_buffer->spans.data; + while(count--) { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + func(target, spans->len, solid, spans->coverage); + ++spans; + } +} + +#define BUFFER_SIZE 1024 +static void blend_linear_gradient(plutovg_surface_t* surface, plutovg_operator_t op, const gradient_data_t* gradient, const plutovg_span_buffer_t* span_buffer) +{ + composition_function_t func = composition_table[op]; + unsigned int buffer[BUFFER_SIZE]; + + linear_gradient_values_t v; + v.dx = gradient->values.linear.x2 - gradient->values.linear.x1; + v.dy = gradient->values.linear.y2 - gradient->values.linear.y1; + v.l = v.dx * v.dx + v.dy * v.dy; + v.off = 0.f; + if(v.l != 0.f) { + v.dx /= v.l; + v.dy /= v.l; + v.off = -v.dx * gradient->values.linear.x1 - v.dy * gradient->values.linear.y1; + } + + int count = span_buffer->spans.size; + const plutovg_span_t* spans = span_buffer->spans.data; + while(count--) { + int length = spans->len; + int x = spans->x; + while(length) { + int l = plutovg_min(length, BUFFER_SIZE); + fetch_linear_gradient(buffer, &v, gradient, spans->y, x, l); + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + x; + func(target, l, buffer, spans->coverage); + x += l; + length -= l; + } + + ++spans; + } +} + +static void blend_radial_gradient(plutovg_surface_t* surface, plutovg_operator_t op, const gradient_data_t* gradient, const plutovg_span_buffer_t* span_buffer) +{ + composition_function_t func = composition_table[op]; + unsigned int buffer[BUFFER_SIZE]; + + radial_gradient_values_t v; + v.dx = gradient->values.radial.cx - gradient->values.radial.fx; + v.dy = gradient->values.radial.cy - gradient->values.radial.fy; + v.dr = gradient->values.radial.cr - gradient->values.radial.fr; + v.sqrfr = gradient->values.radial.fr * gradient->values.radial.fr; + v.a = v.dr * v.dr - v.dx * v.dx - v.dy * v.dy; + v.extended = gradient->values.radial.fr != 0.f || v.a <= 0.f; + + int count = span_buffer->spans.size; + const plutovg_span_t* spans = span_buffer->spans.data; + while(count--) { + int length = spans->len; + int x = spans->x; + while(length) { + int l = plutovg_min(length, BUFFER_SIZE); + fetch_radial_gradient(buffer, &v, gradient, spans->y, x, l); + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + x; + func(target, l, buffer, spans->coverage); + x += l; + length -= l; + } + + ++spans; + } +} + +static void blend_untransformed_argb(plutovg_surface_t* surface, plutovg_operator_t op, const texture_data_t* texture, const plutovg_span_buffer_t* span_buffer) +{ + composition_function_t func = composition_table[op]; + + const int image_width = texture->width; + const int image_height = texture->height; + + int xoff = (int)(texture->matrix.e); + int yoff = (int)(texture->matrix.f); + + int count = span_buffer->spans.size; + const plutovg_span_t* spans = span_buffer->spans.data; + while(count--) { + int x = spans->x; + int length = spans->len; + int sx = xoff + x; + int sy = yoff + spans->y; + if(sy >= 0 && sy < image_height && sx < image_width) { + if(sx < 0) { + x -= sx; + length += sx; + sx = 0; + } + + if(sx + length > image_width) + length = image_width - sx; + if(length > 0) { + const int coverage = (spans->coverage * texture->const_alpha) >> 8; + const uint32_t* src = (const uint32_t*)(texture->data + sy * texture->stride) + sx; + uint32_t* dest = (uint32_t*)(surface->data + spans->y * surface->stride) + x; + func(dest, length, src, coverage); + } + } + + ++spans; + } +} + +#define FIXED_SCALE (1 << 16) +static void blend_transformed_argb(plutovg_surface_t* surface, plutovg_operator_t op, const texture_data_t* texture, const plutovg_span_buffer_t* span_buffer) +{ + composition_function_t func = composition_table[op]; + uint32_t buffer[BUFFER_SIZE]; + + int image_width = texture->width; + int image_height = texture->height; + + int fdx = (int)(texture->matrix.a * FIXED_SCALE); + int fdy = (int)(texture->matrix.b * FIXED_SCALE); + + int count = span_buffer->spans.size; + const plutovg_span_t* spans = span_buffer->spans.data; + while(count--) { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + + const float cx = spans->x + 0.5f; + const float cy = spans->y + 0.5f; + + int x = (int)((texture->matrix.c * cy + texture->matrix.a * cx + texture->matrix.e) * FIXED_SCALE); + int y = (int)((texture->matrix.d * cy + texture->matrix.b * cx + texture->matrix.f) * FIXED_SCALE); + + int length = spans->len; + const int coverage = (spans->coverage * texture->const_alpha) >> 8; + while(length) { + int l = plutovg_min(length, BUFFER_SIZE); + const uint32_t* end = buffer + l; + uint32_t* b = buffer; + while(b < end) { + int px = x >> 16; + int py = y >> 16; + if((px < 0) || (px >= image_width) || (py < 0) || (py >= image_height)) { + *b = 0x00000000; + } else { + *b = ((const uint32_t*)(texture->data + py * texture->stride))[px]; + } + + x += fdx; + y += fdy; + ++b; + } + + func(target, l, buffer, coverage); + target += l; + length -= l; + } + + ++spans; + } +} + +static void blend_untransformed_tiled_argb(plutovg_surface_t* surface, plutovg_operator_t op, const texture_data_t* texture, const plutovg_span_buffer_t* span_buffer) +{ + composition_function_t func = composition_table[op]; + + int image_width = texture->width; + int image_height = texture->height; + + int xoff = (int)(texture->matrix.e) % image_width; + int yoff = (int)(texture->matrix.f) % image_height; + + if(xoff < 0) + xoff += image_width; + if(yoff < 0) { + yoff += image_height; + } + + int count = span_buffer->spans.size; + const plutovg_span_t* spans = span_buffer->spans.data; + while(count--) { + int x = spans->x; + int length = spans->len; + int sx = (xoff + spans->x) % image_width; + int sy = (spans->y + yoff) % image_height; + if(sx < 0) + sx += image_width; + if(sy < 0) { + sy += image_height; + } + + const int coverage = (spans->coverage * texture->const_alpha) >> 8; + while(length) { + int l = plutovg_min(image_width - sx, length); + if(BUFFER_SIZE < l) + l = BUFFER_SIZE; + const uint32_t* src = (const uint32_t*)(texture->data + sy * texture->stride) + sx; + uint32_t* dest = (uint32_t*)(surface->data + spans->y * surface->stride) + x; + func(dest, l, src, coverage); + x += l; + sx += l; + length -= l; + if(sx >= image_width) { + sx = 0; + } + } + + ++spans; + } +} + +static void blend_transformed_tiled_argb(plutovg_surface_t* surface, plutovg_operator_t op, const texture_data_t* texture, const plutovg_span_buffer_t* span_buffer) +{ + composition_function_t func = composition_table[op]; + uint32_t buffer[BUFFER_SIZE]; + + int image_width = texture->width; + int image_height = texture->height; + const int scanline_offset = texture->stride / 4; + + int fdx = (int)(texture->matrix.a * FIXED_SCALE); + int fdy = (int)(texture->matrix.b * FIXED_SCALE); + + int count = span_buffer->spans.size; + const plutovg_span_t* spans = span_buffer->spans.data; + while(count--) { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + const uint32_t* image_bits = (const uint32_t*)texture->data; + + const float cx = spans->x + 0.5f; + const float cy = spans->y + 0.5f; + + int x = (int)((texture->matrix.c * cy + texture->matrix.a * cx + texture->matrix.e) * FIXED_SCALE); + int y = (int)((texture->matrix.d * cy + texture->matrix.b * cx + texture->matrix.f) * FIXED_SCALE); + + const int coverage = (spans->coverage * texture->const_alpha) >> 8; + int length = spans->len; + while(length) { + int l = plutovg_min(length, BUFFER_SIZE); + const uint32_t* end = buffer + l; + uint32_t* b = buffer; + while(b < end) { + int px = x >> 16; + int py = y >> 16; + px %= image_width; + py %= image_height; + if(px < 0) px += image_width; + if(py < 0) py += image_height; + int y_offset = py * scanline_offset; + + assert(px >= 0 && px < image_width); + assert(py >= 0 && py < image_height); + + *b = image_bits[y_offset + px]; + x += fdx; + y += fdy; + ++b; + } + + func(target, l, buffer, coverage); + target += l; + length -= l; + } + + ++spans; + } +} + +static inline uint32_t interpolate_4_pixels(uint32_t tl, uint32_t tr, uint32_t bl, uint32_t br, uint32_t distx, uint32_t disty) +{ + uint32_t idistx = 256 - distx; + uint32_t idisty = 256 - disty; + uint32_t xtop = INTERPOLATE_PIXEL_256(tl, idistx, tr, distx); + uint32_t xbot = INTERPOLATE_PIXEL_256(bl, idistx, br, distx); + return INTERPOLATE_PIXEL_256(xtop, idisty, xbot, disty); +} + +#define HALF_POINT (1 << 15) +static void blend_transformed_bilinear_tiled_argb(plutovg_surface_t* surface, plutovg_operator_t op, const texture_data_t* texture, const plutovg_span_buffer_t* span_buffer) +{ + composition_function_t func = composition_table[op]; + uint32_t buffer[BUFFER_SIZE]; + + int image_width = texture->width; + int image_height = texture->height; + + int fdx = (int)(texture->matrix.a * FIXED_SCALE); + int fdy = (int)(texture->matrix.b * FIXED_SCALE); + + int count = span_buffer->spans.size; + const plutovg_span_t* spans = span_buffer->spans.data; + while(count--) { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + + const float cx = spans->x + 0.5f; + const float cy = spans->y + 0.5f; + + int fx = (int)((texture->matrix.c * cy + texture->matrix.a * cx + texture->matrix.e) * FIXED_SCALE); + int fy = (int)((texture->matrix.d * cy + texture->matrix.b * cx + texture->matrix.f) * FIXED_SCALE); + + fx -= HALF_POINT; + fy -= HALF_POINT; + + const int coverage = (spans->coverage * texture->const_alpha) >> 8; + int length = spans->len; + while(length) { + int l = plutovg_min(length, BUFFER_SIZE); + const uint32_t* end = buffer + l; + uint32_t* b = buffer; + while (b < end) { + int x1 = (fx >> 16) % image_width; + int y1 = (fy >> 16) % image_height; + + if(x1 < 0) x1 += image_width; + if(y1 < 0) y1 += image_height; + + int x2 = (x1 + 1) % image_width; + int y2 = (y1 + 1) % image_height; + + const uint32_t* s1 = (const uint32_t*)(texture->data + y1 * texture->stride); + const uint32_t* s2 = (const uint32_t*)(texture->data + y2 * texture->stride); + + uint32_t tl = s1[x1]; + uint32_t tr = s1[x2]; + uint32_t bl = s2[x1]; + uint32_t br = s2[x2]; + + int distx = (fx & 0x0000ffff) >> 8; + int disty = (fy & 0x0000ffff) >> 8; + *b = interpolate_4_pixels(tl, tr, bl, br, distx, disty); + + fx += fdx; + fy += fdy; + ++b; + } + + func(target, l, buffer, coverage); + target += l; + length -= l; + } + + ++spans; + } +} + +static void plutovg_blend_color(plutovg_canvas_t* canvas, const plutovg_color_t* color, const plutovg_span_buffer_t* span_buffer) +{ + plutovg_state_t* state = canvas->state; + uint32_t solid = premultiply_color_with_opacity(color, state->opacity); + uint32_t alpha = plutovg_alpha(solid); + + if(alpha == 255 && state->op == PLUTOVG_OPERATOR_SRC_OVER) { + blend_solid(canvas->surface, PLUTOVG_OPERATOR_SRC, solid, span_buffer); + } else { + blend_solid(canvas->surface, state->op, solid, span_buffer); + } +} + +static void plutovg_blend_gradient(plutovg_canvas_t* canvas, const plutovg_gradient_paint_t* gradient, const plutovg_span_buffer_t* span_buffer) +{ + if(gradient->nstops == 0) + return; + plutovg_state_t* state = canvas->state; + gradient_data_t data; + data.spread = gradient->spread; + data.matrix = gradient->matrix; + plutovg_matrix_multiply(&data.matrix, &data.matrix, &state->matrix); + if(!plutovg_matrix_invert(&data.matrix, &data.matrix)) + return; + int i, pos = 0, nstops = gradient->nstops; + const plutovg_gradient_stop_t *curr, *next, *start, *last; + uint32_t curr_color, next_color, last_color; + uint32_t dist, idist; + float delta, t, incr, fpos; + float opacity = state->opacity; + + start = gradient->stops; + curr = start; + curr_color = premultiply_color_with_opacity(&curr->color, opacity); + + data.colortable[pos++] = curr_color; + incr = 1.0f / COLOR_TABLE_SIZE; + fpos = 1.5f * incr; + + while(fpos <= curr->offset) { + data.colortable[pos] = data.colortable[pos - 1]; + ++pos; + fpos += incr; + } + + for(i = 0; i < nstops - 1; i++) { + curr = (start + i); + next = (start + i + 1); + if(curr->offset == next->offset) + continue; + delta = 1.f / (next->offset - curr->offset); + next_color = premultiply_color_with_opacity(&next->color, opacity); + while(fpos < next->offset && pos < COLOR_TABLE_SIZE) { + t = (fpos - curr->offset) * delta; + dist = (uint32_t)(255 * t); + idist = 255 - dist; + data.colortable[pos] = INTERPOLATE_PIXEL_255(curr_color, idist, next_color, dist); + ++pos; + fpos += incr; + } + + curr_color = next_color; + } + + last = start + nstops - 1; + last_color = premultiply_color_with_opacity(&last->color, opacity); + for(; pos < COLOR_TABLE_SIZE; ++pos) { + data.colortable[pos] = last_color; + } + + if(gradient->type == PLUTOVG_GRADIENT_TYPE_LINEAR) { + data.values.linear.x1 = gradient->values[0]; + data.values.linear.y1 = gradient->values[1]; + data.values.linear.x2 = gradient->values[2]; + data.values.linear.y2 = gradient->values[3]; + blend_linear_gradient(canvas->surface, state->op, &data, span_buffer); + } else { + data.values.radial.cx = gradient->values[0]; + data.values.radial.cy = gradient->values[1]; + data.values.radial.cr = gradient->values[2]; + data.values.radial.fx = gradient->values[3]; + data.values.radial.fy = gradient->values[4]; + data.values.radial.fr = gradient->values[5]; + blend_radial_gradient(canvas->surface, state->op, &data, span_buffer); + } +} + +static void plutovg_blend_texture(plutovg_canvas_t* canvas, const plutovg_texture_paint_t* texture, const plutovg_span_buffer_t* span_buffer) +{ + if(texture->surface == NULL) + return; + plutovg_state_t* state = canvas->state; + texture_data_t data; + data.matrix = texture->matrix; + data.data = texture->surface->data; + data.width = texture->surface->width; + data.height = texture->surface->height; + data.stride = texture->surface->stride; + data.const_alpha = lroundf(state->opacity * texture->opacity * 256); + + plutovg_matrix_multiply(&data.matrix, &data.matrix, &state->matrix); + if(!plutovg_matrix_invert(&data.matrix, &data.matrix)) + return; + const plutovg_matrix_t* matrix = &data.matrix; + if(matrix->a == 1 && matrix->b == 0 && matrix->c == 0 && matrix->d == 1) { + if(texture->type == PLUTOVG_TEXTURE_TYPE_PLAIN) { + blend_untransformed_argb(canvas->surface, state->op, &data, span_buffer); + } else { + blend_untransformed_tiled_argb(canvas->surface, state->op, &data, span_buffer); + } + } else { + if(texture->type == PLUTOVG_TEXTURE_TYPE_PLAIN) { + blend_transformed_argb(canvas->surface, state->op, &data, span_buffer); + } else if(fabsf(matrix->b) > 1e-6f || fabsf(matrix->c) > 1e-6f) { + blend_transformed_bilinear_tiled_argb(canvas->surface, state->op, &data, span_buffer); + } else { + blend_transformed_tiled_argb(canvas->surface, state->op, &data, span_buffer); + } + } +} + +void plutovg_blend(plutovg_canvas_t* canvas, const plutovg_span_buffer_t* span_buffer) +{ + if(span_buffer->spans.size == 0) + return; + if(canvas->state->paint == NULL) { + plutovg_blend_color(canvas, &canvas->state->color, span_buffer); + return; + } + + plutovg_paint_t* paint = canvas->state->paint; + if(paint->type == PLUTOVG_PAINT_TYPE_COLOR) { + plutovg_solid_paint_t* solid = (plutovg_solid_paint_t*)(paint); + plutovg_blend_color(canvas, &solid->color, span_buffer); + } else if(paint->type == PLUTOVG_PAINT_TYPE_GRADIENT) { + plutovg_gradient_paint_t* gradient = (plutovg_gradient_paint_t*)(paint); + plutovg_blend_gradient(canvas, gradient, span_buffer); + } else { + plutovg_texture_paint_t* texture = (plutovg_texture_paint_t*)(paint); + plutovg_blend_texture(canvas, texture, span_buffer); + } +} diff --git a/vendor/lunasvg/plutovg/source/plutovg-canvas.c b/vendor/lunasvg/plutovg/source/plutovg-canvas.c new file mode 100644 index 0000000..908ac4e --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-canvas.c @@ -0,0 +1,759 @@ +#include "plutovg-private.h" +#include "plutovg-utils.h" + +int plutovg_version(void) +{ + return PLUTOVG_VERSION; +} + +const char* plutovg_version_string(void) +{ + return PLUTOVG_VERSION_STRING; +} + +#define PLUTOVG_DEFAULT_STROKE_STYLE ((plutovg_stroke_style_t){1.f, PLUTOVG_LINE_CAP_BUTT, PLUTOVG_LINE_JOIN_MITER, 10.f}) + +static plutovg_state_t* plutovg_state_create(void) +{ + plutovg_state_t* state = malloc(sizeof(plutovg_state_t)); + state->paint = NULL; + state->font_face = NULL; + state->color = PLUTOVG_BLACK_COLOR; + state->matrix = PLUTOVG_IDENTITY_MATRIX; + state->stroke.style = PLUTOVG_DEFAULT_STROKE_STYLE; + state->stroke.dash.offset = 0.f; + plutovg_array_init(state->stroke.dash.array); + plutovg_span_buffer_init(&state->clip_spans); + state->winding = PLUTOVG_FILL_RULE_NON_ZERO; + state->op = PLUTOVG_OPERATOR_SRC_OVER; + state->font_size = 12.f; + state->opacity = 1.f; + state->clipping = false; + state->next = NULL; + return state; +} + +static void plutovg_state_reset(plutovg_state_t* state) +{ + plutovg_paint_destroy(state->paint); + plutovg_font_face_destroy(state->font_face); + state->paint = NULL; + state->font_face = NULL; + state->color = PLUTOVG_BLACK_COLOR; + state->matrix = PLUTOVG_IDENTITY_MATRIX; + state->stroke.style = PLUTOVG_DEFAULT_STROKE_STYLE; + state->stroke.dash.offset = 0.f; + plutovg_array_clear(state->stroke.dash.array); + plutovg_span_buffer_reset(&state->clip_spans); + state->winding = PLUTOVG_FILL_RULE_NON_ZERO; + state->op = PLUTOVG_OPERATOR_SRC_OVER; + state->font_size = 12.f; + state->opacity = 1.f; + state->clipping = false; +} + +static void plutovg_state_copy(plutovg_state_t* state, const plutovg_state_t* source) +{ + state->paint = plutovg_paint_reference(source->paint); + state->font_face = plutovg_font_face_reference(source->font_face); + state->color = source->color; + state->matrix = source->matrix; + state->stroke.style = source->stroke.style; + state->stroke.dash.offset = source->stroke.dash.offset; + plutovg_array_clear(state->stroke.dash.array); + plutovg_array_append(state->stroke.dash.array, source->stroke.dash.array); + plutovg_span_buffer_copy(&state->clip_spans, &source->clip_spans); + state->winding = source->winding; + state->op = source->op; + state->font_size = source->font_size; + state->opacity = source->opacity; + state->clipping = source->clipping; +} + +static void plutovg_state_destroy(plutovg_state_t* state) +{ + plutovg_paint_destroy(state->paint); + plutovg_font_face_destroy(state->font_face); + plutovg_array_destroy(state->stroke.dash.array); + plutovg_span_buffer_destroy(&state->clip_spans); + free(state); +} + +plutovg_canvas_t* plutovg_canvas_create(plutovg_surface_t* surface) +{ + plutovg_canvas_t* canvas = malloc(sizeof(plutovg_canvas_t)); + plutovg_init_reference(canvas); + canvas->surface = plutovg_surface_reference(surface); + canvas->path = plutovg_path_create(); + canvas->state = plutovg_state_create(); + canvas->freed_state = NULL; + canvas->face_cache = NULL; + canvas->clip_rect = PLUTOVG_MAKE_RECT(0, 0, surface->width, surface->height); + plutovg_span_buffer_init(&canvas->clip_spans); + plutovg_span_buffer_init(&canvas->fill_spans); + return canvas; +} + +plutovg_canvas_t* plutovg_canvas_reference(plutovg_canvas_t* canvas) +{ + plutovg_increment_reference(canvas); + return canvas; +} + +void plutovg_canvas_destroy(plutovg_canvas_t* canvas) +{ + if(plutovg_destroy_reference(canvas)) { + while(canvas->state) { + plutovg_state_t* state = canvas->state; + canvas->state = state->next; + plutovg_state_destroy(state); + } + + while(canvas->freed_state) { + plutovg_state_t* state = canvas->freed_state; + canvas->freed_state = state->next; + plutovg_state_destroy(state); + } + + plutovg_font_face_cache_destroy(canvas->face_cache); + plutovg_span_buffer_destroy(&canvas->fill_spans); + plutovg_span_buffer_destroy(&canvas->clip_spans); + plutovg_surface_destroy(canvas->surface); + plutovg_path_destroy(canvas->path); + free(canvas); + } +} + +int plutovg_canvas_get_reference_count(const plutovg_canvas_t* canvas) +{ + return plutovg_get_reference_count(canvas); +} + +plutovg_surface_t* plutovg_canvas_get_surface(const plutovg_canvas_t* canvas) +{ + return canvas->surface; +} + +void plutovg_canvas_save(plutovg_canvas_t* canvas) +{ + plutovg_state_t* new_state = canvas->freed_state; + if(new_state == NULL) + new_state = plutovg_state_create(); + else + canvas->freed_state = new_state->next; + plutovg_state_copy(new_state, canvas->state); + new_state->next = canvas->state; + canvas->state = new_state; +} + +void plutovg_canvas_restore(plutovg_canvas_t* canvas) +{ + if(canvas->state->next == NULL) + return; + plutovg_state_t* old_state = canvas->state; + canvas->state = old_state->next; + plutovg_state_reset(old_state); + old_state->next = canvas->freed_state; + canvas->freed_state = old_state; +} + +void plutovg_canvas_set_rgb(plutovg_canvas_t* canvas, float r, float g, float b) +{ + plutovg_canvas_set_rgba(canvas, r, g, b, 1.f); +} + +void plutovg_canvas_set_rgba(plutovg_canvas_t* canvas, float r, float g, float b, float a) +{ + plutovg_color_init_rgba(&canvas->state->color, r, g, b, a); + plutovg_canvas_set_paint(canvas, NULL); +} + +void plutovg_canvas_set_color(plutovg_canvas_t* canvas, const plutovg_color_t* color) +{ + plutovg_canvas_set_rgba(canvas, color->r, color->g, color->b, color->a); +} + +void plutovg_canvas_set_linear_gradient(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix) +{ + plutovg_paint_t* paint = plutovg_paint_create_linear_gradient(x1, y1, x2, y2, spread, stops, nstops, matrix); + plutovg_canvas_set_paint(canvas, paint); + plutovg_paint_destroy(paint); +} + +void plutovg_canvas_set_radial_gradient(plutovg_canvas_t* canvas, float cx, float cy, float cr, float fx, float fy, float fr, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix) +{ + plutovg_paint_t* paint = plutovg_paint_create_radial_gradient(cx, cy, cr, fx, fy, fr, spread, stops, nstops, matrix); + plutovg_canvas_set_paint(canvas, paint); + plutovg_paint_destroy(paint); +} + +void plutovg_canvas_set_texture(plutovg_canvas_t* canvas, plutovg_surface_t* surface, plutovg_texture_type_t type, float opacity, const plutovg_matrix_t* matrix) +{ + plutovg_paint_t* paint = plutovg_paint_create_texture(surface, type, opacity, matrix); + plutovg_canvas_set_paint(canvas, paint); + plutovg_paint_destroy(paint); +} + +void plutovg_canvas_set_paint(plutovg_canvas_t* canvas, plutovg_paint_t* paint) +{ + paint = plutovg_paint_reference(paint); + plutovg_paint_destroy(canvas->state->paint); + canvas->state->paint = paint; +} + +plutovg_paint_t* plutovg_canvas_get_paint(const plutovg_canvas_t* canvas, plutovg_color_t* color) +{ + if(color) + *color = canvas->state->color; + return canvas->state->paint; +} + +void plutovg_canvas_set_font_face_cache(plutovg_canvas_t* canvas, plutovg_font_face_cache_t* cache) +{ + cache = plutovg_font_face_cache_reference(cache); + plutovg_font_face_cache_destroy(canvas->face_cache); + canvas->face_cache = cache; +} + +plutovg_font_face_cache_t* plutovg_canvas_get_font_face_cache(const plutovg_canvas_t* canvas) +{ + return canvas->face_cache; +} + +void plutovg_canvas_add_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, plutovg_font_face_t* face) +{ + if(canvas->face_cache == NULL) + canvas->face_cache = plutovg_font_face_cache_create(); + plutovg_font_face_cache_add(canvas->face_cache, family, bold, italic, face); +} + +bool plutovg_canvas_add_font_file(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, const char* filename, int ttcindex) +{ + if(canvas->face_cache == NULL) + canvas->face_cache = plutovg_font_face_cache_create(); + return plutovg_font_face_cache_add_file(canvas->face_cache, family, bold, italic, filename, ttcindex); +} + +bool plutovg_canvas_select_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic) +{ + if(canvas->face_cache == NULL) + return false; + plutovg_font_face_t* face = plutovg_font_face_cache_get(canvas->face_cache, family, bold, italic); + if(face == NULL) + return false; + plutovg_canvas_set_font_face(canvas, face); + return true; +} + +void plutovg_canvas_set_font(plutovg_canvas_t* canvas, plutovg_font_face_t* face, float size) +{ + plutovg_canvas_set_font_face(canvas, face); + plutovg_canvas_set_font_size(canvas, size); +} + +void plutovg_canvas_set_font_face(plutovg_canvas_t* canvas, plutovg_font_face_t* face) +{ + face = plutovg_font_face_reference(face); + plutovg_font_face_destroy(canvas->state->font_face); + canvas->state->font_face = face; +} + +plutovg_font_face_t* plutovg_canvas_get_font_face(const plutovg_canvas_t* canvas) +{ + return canvas->state->font_face; +} + +void plutovg_canvas_set_font_size(plutovg_canvas_t* canvas, float size) +{ + canvas->state->font_size = size; +} + +float plutovg_canvas_get_font_size(const plutovg_canvas_t* canvas) +{ + return canvas->state->font_size; +} + +void plutovg_canvas_set_fill_rule(plutovg_canvas_t* canvas, plutovg_fill_rule_t winding) +{ + canvas->state->winding = winding; +} + +plutovg_fill_rule_t plutovg_canvas_get_fill_rule(const plutovg_canvas_t* canvas) +{ + return canvas->state->winding; +} + +void plutovg_canvas_set_operator(plutovg_canvas_t* canvas, plutovg_operator_t op) +{ + canvas->state->op = op; +} + +plutovg_operator_t plutovg_canvas_get_operator(const plutovg_canvas_t* canvas) +{ + return canvas->state->op; +} + +void plutovg_canvas_set_opacity(plutovg_canvas_t* canvas, float opacity) +{ + canvas->state->opacity = plutovg_clamp(opacity, 0.f, 1.f); +} + +float plutovg_canvas_get_opacity(const plutovg_canvas_t* canvas) +{ + return canvas->state->opacity; +} + +void plutovg_canvas_set_line_width(plutovg_canvas_t* canvas, float line_width) +{ + canvas->state->stroke.style.width = line_width; +} + +float plutovg_canvas_get_line_width(const plutovg_canvas_t* canvas) +{ + return canvas->state->stroke.style.width; +} + +void plutovg_canvas_set_line_cap(plutovg_canvas_t* canvas, plutovg_line_cap_t line_cap) +{ + canvas->state->stroke.style.cap = line_cap; +} + +plutovg_line_cap_t plutovg_canvas_get_line_cap(const plutovg_canvas_t* canvas) +{ + return canvas->state->stroke.style.cap; +} + +void plutovg_canvas_set_line_join(plutovg_canvas_t* canvas, plutovg_line_join_t line_join) +{ + canvas->state->stroke.style.join = line_join; +} + +plutovg_line_join_t plutovg_canvas_get_line_join(const plutovg_canvas_t* canvas) +{ + return canvas->state->stroke.style.join; +} + +void plutovg_canvas_set_miter_limit(plutovg_canvas_t* canvas, float miter_limit) +{ + canvas->state->stroke.style.miter_limit = miter_limit; +} + +float plutovg_canvas_get_miter_limit(const plutovg_canvas_t* canvas) +{ + return canvas->state->stroke.style.miter_limit; +} + +void plutovg_canvas_set_dash(plutovg_canvas_t* canvas, float offset, const float* dashes, int ndashes) +{ + plutovg_canvas_set_dash_offset(canvas, offset); + plutovg_canvas_set_dash_array(canvas, dashes, ndashes); +} + +void plutovg_canvas_set_dash_offset(plutovg_canvas_t* canvas, float offset) +{ + canvas->state->stroke.dash.offset = offset; +} + +float plutovg_canvas_get_dash_offset(const plutovg_canvas_t* canvas) +{ + return canvas->state->stroke.dash.offset; +} + +void plutovg_canvas_set_dash_array(plutovg_canvas_t* canvas, const float* dashes, int ndashes) +{ + plutovg_array_clear(canvas->state->stroke.dash.array); + plutovg_array_append_data(canvas->state->stroke.dash.array, dashes, ndashes); +} + +int plutovg_canvas_get_dash_array(const plutovg_canvas_t* canvas, const float** dashes) +{ + if(dashes) + *dashes = canvas->state->stroke.dash.array.data; + return canvas->state->stroke.dash.array.size; +} + +void plutovg_canvas_translate(plutovg_canvas_t* canvas, float tx, float ty) +{ + plutovg_matrix_translate(&canvas->state->matrix, tx, ty); +} + +void plutovg_canvas_scale(plutovg_canvas_t* canvas, float sx, float sy) +{ + plutovg_matrix_scale(&canvas->state->matrix, sx, sy); +} + +void plutovg_canvas_shear(plutovg_canvas_t* canvas, float shx, float shy) +{ + plutovg_matrix_shear(&canvas->state->matrix, shx, shy); +} + +void plutovg_canvas_rotate(plutovg_canvas_t* canvas, float angle) +{ + plutovg_matrix_rotate(&canvas->state->matrix, angle); +} + +void plutovg_canvas_transform(plutovg_canvas_t* canvas, const plutovg_matrix_t* matrix) +{ + plutovg_matrix_multiply(&canvas->state->matrix, matrix, &canvas->state->matrix); +} + +void plutovg_canvas_reset_matrix(plutovg_canvas_t* canvas) +{ + plutovg_matrix_init_identity(&canvas->state->matrix); +} + +void plutovg_canvas_set_matrix(plutovg_canvas_t* canvas, const plutovg_matrix_t* matrix) +{ + canvas->state->matrix = matrix ? *matrix : PLUTOVG_IDENTITY_MATRIX; +} + +void plutovg_canvas_get_matrix(const plutovg_canvas_t* canvas, plutovg_matrix_t* matrix) +{ + *matrix = canvas->state->matrix; +} + +void plutovg_canvas_map(const plutovg_canvas_t* canvas, float x, float y, float* xx, float* yy) +{ + plutovg_matrix_map(&canvas->state->matrix, x, y, xx, yy); +} + +void plutovg_canvas_map_point(const plutovg_canvas_t* canvas, const plutovg_point_t* src, plutovg_point_t* dst) +{ + plutovg_matrix_map_point(&canvas->state->matrix, src, dst); +} + +void plutovg_canvas_map_rect(const plutovg_canvas_t* canvas, const plutovg_rect_t* src, plutovg_rect_t* dst) +{ + plutovg_matrix_map_rect(&canvas->state->matrix, src, dst); +} + +void plutovg_canvas_move_to(plutovg_canvas_t* canvas, float x, float y) +{ + plutovg_path_move_to(canvas->path, x, y); +} + +void plutovg_canvas_line_to(plutovg_canvas_t* canvas, float x, float y) +{ + plutovg_path_line_to(canvas->path, x, y); +} + +void plutovg_canvas_quad_to(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2) +{ + plutovg_path_quad_to(canvas->path, x1, y1, x2, y2); +} + +void plutovg_canvas_cubic_to(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2, float x3, float y3) +{ + plutovg_path_cubic_to(canvas->path, x1, y1, x2, y2, x3, y3); +} + +void plutovg_canvas_arc_to(plutovg_canvas_t* canvas, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y) +{ + plutovg_path_arc_to(canvas->path, rx, ry, angle, large_arc_flag, sweep_flag, x, y); +} + +void plutovg_canvas_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h) +{ + plutovg_path_add_rect(canvas->path, x, y, w, h); +} + +void plutovg_canvas_round_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h, float rx, float ry) +{ + plutovg_path_add_round_rect(canvas->path, x, y, w, h, rx, ry); +} + +void plutovg_canvas_ellipse(plutovg_canvas_t* canvas, float cx, float cy, float rx, float ry) +{ + plutovg_path_add_ellipse(canvas->path, cx, cy, rx, ry); +} + +void plutovg_canvas_circle(plutovg_canvas_t* canvas, float cx, float cy, float r) +{ + plutovg_path_add_circle(canvas->path, cx, cy, r); +} + +void plutovg_canvas_arc(plutovg_canvas_t* canvas, float cx, float cy, float r, float a0, float a1, bool ccw) +{ + plutovg_path_add_arc(canvas->path, cx, cy, r, a0, a1, ccw); +} + +void plutovg_canvas_add_path(plutovg_canvas_t* canvas, const plutovg_path_t* path) +{ + plutovg_path_add_path(canvas->path, path, NULL); +} + +void plutovg_canvas_new_path(plutovg_canvas_t* canvas) +{ + plutovg_path_reset(canvas->path); +} + +void plutovg_canvas_close_path(plutovg_canvas_t* canvas) +{ + plutovg_path_close(canvas->path); +} + +void plutovg_canvas_get_current_point(const plutovg_canvas_t* canvas, float* x, float* y) +{ + plutovg_path_get_current_point(canvas->path, x, y); +} + +plutovg_path_t* plutovg_canvas_get_path(const plutovg_canvas_t* canvas) +{ + return canvas->path; +} + +bool plutovg_canvas_fill_contains(plutovg_canvas_t* canvas, float x, float y) +{ + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, NULL, canvas->state->winding); + return plutovg_span_buffer_contains(&canvas->fill_spans, x, y); +} + +bool plutovg_canvas_stroke_contains(plutovg_canvas_t* canvas, float x, float y) +{ + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, NULL, canvas->state->winding); + return plutovg_span_buffer_contains(&canvas->fill_spans, x, y); +} + +bool plutovg_canvas_clip_contains(plutovg_canvas_t* canvas, float x, float y) +{ + if(canvas->state->clipping) { + return plutovg_span_buffer_contains(&canvas->state->clip_spans, x, y); + } + + float l = canvas->clip_rect.x; + float t = canvas->clip_rect.y; + float r = canvas->clip_rect.x + canvas->clip_rect.w; + float b = canvas->clip_rect.y + canvas->clip_rect.h; + + return x >= l && x <= r && y >= t && y <= b; +} + +void plutovg_canvas_fill_extents(plutovg_canvas_t *canvas, plutovg_rect_t* extents) +{ + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, NULL, canvas->state->winding); + plutovg_span_buffer_extents(&canvas->fill_spans, extents); +} + +void plutovg_canvas_stroke_extents(plutovg_canvas_t *canvas, plutovg_rect_t* extents) +{ + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, &canvas->state->stroke, PLUTOVG_FILL_RULE_NON_ZERO); + plutovg_span_buffer_extents(&canvas->fill_spans, extents); +} + +void plutovg_canvas_clip_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents) +{ + if(canvas->state->clipping) { + plutovg_span_buffer_extents(&canvas->state->clip_spans, extents); + } else { + extents->x = canvas->clip_rect.x; + extents->y = canvas->clip_rect.y; + extents->w = canvas->clip_rect.w; + extents->h = canvas->clip_rect.h; + } +} + +void plutovg_canvas_fill(plutovg_canvas_t* canvas) +{ + plutovg_canvas_fill_preserve(canvas); + plutovg_canvas_new_path(canvas); +} + +void plutovg_canvas_stroke(plutovg_canvas_t* canvas) +{ + plutovg_canvas_stroke_preserve(canvas); + plutovg_canvas_new_path(canvas); +} + +void plutovg_canvas_clip(plutovg_canvas_t* canvas) +{ + plutovg_canvas_clip_preserve(canvas); + plutovg_canvas_new_path(canvas); +} + +void plutovg_canvas_paint(plutovg_canvas_t* canvas) +{ + if(canvas->state->clipping) { + plutovg_blend(canvas, &canvas->state->clip_spans); + } else { + plutovg_span_buffer_init_rect(&canvas->clip_spans, 0, 0, canvas->surface->width, canvas->surface->height); + plutovg_blend(canvas, &canvas->clip_spans); + } +} + +void plutovg_canvas_fill_preserve(plutovg_canvas_t* canvas) +{ + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, NULL, canvas->state->winding); + if(canvas->state->clipping) { + plutovg_span_buffer_intersect(&canvas->clip_spans, &canvas->fill_spans, &canvas->state->clip_spans); + plutovg_blend(canvas, &canvas->clip_spans); + } else { + plutovg_blend(canvas, &canvas->fill_spans); + } +} + +void plutovg_canvas_stroke_preserve(plutovg_canvas_t* canvas) +{ + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, &canvas->state->stroke, PLUTOVG_FILL_RULE_NON_ZERO); + if(canvas->state->clipping) { + plutovg_span_buffer_intersect(&canvas->clip_spans, &canvas->fill_spans, &canvas->state->clip_spans); + plutovg_blend(canvas, &canvas->clip_spans); + } else { + plutovg_blend(canvas, &canvas->fill_spans); + } +} + +void plutovg_canvas_clip_preserve(plutovg_canvas_t* canvas) +{ + if(canvas->state->clipping) { + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, NULL, canvas->state->winding); + plutovg_span_buffer_intersect(&canvas->clip_spans, &canvas->fill_spans, &canvas->state->clip_spans); + plutovg_span_buffer_copy(&canvas->state->clip_spans, &canvas->clip_spans); + } else { + plutovg_rasterize(&canvas->state->clip_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, NULL, canvas->state->winding); + canvas->state->clipping = true; + } +} + +void plutovg_canvas_fill_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h) +{ + plutovg_canvas_new_path(canvas); + plutovg_canvas_rect(canvas, x, y, w, h); + plutovg_canvas_fill(canvas); +} + +void plutovg_canvas_fill_path(plutovg_canvas_t* canvas, const plutovg_path_t* path) +{ + plutovg_canvas_new_path(canvas); + plutovg_canvas_add_path(canvas, path); + plutovg_canvas_fill(canvas); +} + +void plutovg_canvas_stroke_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h) +{ + plutovg_canvas_new_path(canvas); + plutovg_canvas_rect(canvas, x, y, w, h); + plutovg_canvas_stroke(canvas); +} + +void plutovg_canvas_stroke_path(plutovg_canvas_t* canvas, const plutovg_path_t* path) +{ + plutovg_canvas_new_path(canvas); + plutovg_canvas_add_path(canvas, path); + plutovg_canvas_stroke(canvas); +} + +void plutovg_canvas_clip_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h) +{ + plutovg_canvas_new_path(canvas); + plutovg_canvas_rect(canvas, x, y, w, h); + plutovg_canvas_clip(canvas); +} + +void plutovg_canvas_clip_path(plutovg_canvas_t* canvas, const plutovg_path_t* path) +{ + plutovg_canvas_new_path(canvas); + plutovg_canvas_add_path(canvas, path); + plutovg_canvas_clip(canvas); +} + +float plutovg_canvas_add_glyph(plutovg_canvas_t* canvas, plutovg_codepoint_t codepoint, float x, float y) +{ + plutovg_state_t* state = canvas->state; + if(state->font_face && state->font_size > 0.f) + return plutovg_font_face_get_glyph_path(state->font_face, state->font_size, x, y, codepoint, canvas->path); + return 0.f; +} + +float plutovg_canvas_add_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y) +{ + plutovg_state_t* state = canvas->state; + if(state->font_face == NULL || state->font_size <= 0.f) + return 0.f; + plutovg_text_iterator_t it; + plutovg_text_iterator_init(&it, text, length, encoding); + float advance_width = 0.f; + while(plutovg_text_iterator_has_next(&it)) { + plutovg_codepoint_t codepoint = plutovg_text_iterator_next(&it); + advance_width += plutovg_font_face_get_glyph_path(state->font_face, state->font_size, x + advance_width, y, codepoint, canvas->path); + } + + return advance_width; +} + +float plutovg_canvas_fill_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y) +{ + plutovg_canvas_new_path(canvas); + float advance_width = plutovg_canvas_add_text(canvas, text, length, encoding, x, y); + plutovg_canvas_fill(canvas); + return advance_width; +} + +float plutovg_canvas_stroke_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y) +{ + plutovg_canvas_new_path(canvas); + float advance_width = plutovg_canvas_add_text(canvas, text, length, encoding, x, y); + plutovg_canvas_stroke(canvas); + return advance_width; +} + +float plutovg_canvas_clip_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y) +{ + plutovg_canvas_new_path(canvas); + float advance_width = plutovg_canvas_add_text(canvas, text, length, encoding, x, y); + plutovg_canvas_clip(canvas); + return advance_width; +} + +void plutovg_canvas_font_metrics(const plutovg_canvas_t* canvas, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents) +{ + plutovg_state_t* state = canvas->state; + if(state->font_face && state->font_size > 0.f) { + plutovg_font_face_get_metrics(state->font_face, state->font_size, ascent, descent, line_gap, extents); + return; + } + + if(ascent) *ascent = 0.f; + if(descent) *descent = 0.f; + if(line_gap) *line_gap = 0.f; + if(extents) { + extents->x = 0.f; + extents->y = 0.f; + extents->w = 0.f; + extents->h = 0.f; + } +} + +void plutovg_canvas_glyph_metrics(plutovg_canvas_t* canvas, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents) +{ + plutovg_state_t* state = canvas->state; + if(state->font_face && state->font_size > 0.f) { + plutovg_font_face_get_glyph_metrics(state->font_face, state->font_size, codepoint, advance_width, left_side_bearing, extents); + return; + } + + if(advance_width) *advance_width = 0.f; + if(left_side_bearing) *left_side_bearing = 0.f; + if(extents) { + extents->x = 0.f; + extents->y = 0.f; + extents->w = 0.f; + extents->h = 0.f; + } +} + +float plutovg_canvas_text_extents(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents) +{ + plutovg_state_t* state = canvas->state; + if(state->font_face && state->font_size > 0.f) { + return plutovg_font_face_text_extents(state->font_face, state->font_size, text, length, encoding, extents); + } + + if(extents) { + extents->x = 0.f; + extents->y = 0.f; + extents->w = 0.f; + extents->h = 0.f; + } + + return 0.f; +} diff --git a/vendor/lunasvg/plutovg/source/plutovg-font.c b/vendor/lunasvg/plutovg/source/plutovg-font.c new file mode 100644 index 0000000..0ad89b1 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-font.c @@ -0,0 +1,1065 @@ +#include "plutovg-private.h" +#include "plutovg-utils.h" + +#include +#include + +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#include "plutovg-stb-truetype.h" + +static int plutovg_text_iterator_length(const void* data, plutovg_text_encoding_t encoding) +{ + int length = 0; + switch(encoding) { + case PLUTOVG_TEXT_ENCODING_LATIN1: + case PLUTOVG_TEXT_ENCODING_UTF8: { + const uint8_t* text = data; + while(*text++) + length++; + break; + } case PLUTOVG_TEXT_ENCODING_UTF16: { + const uint16_t* text = data; + while(*text++) + length++; + break; + } case PLUTOVG_TEXT_ENCODING_UTF32: { + const uint32_t* text = data; + while(*text++) + length++; + break; + } default: + assert(false); + } + + return length; +} + +void plutovg_text_iterator_init(plutovg_text_iterator_t* it, const void* text, int length, plutovg_text_encoding_t encoding) +{ + if(length == -1) + length = plutovg_text_iterator_length(text, encoding); + it->text = text; + it->length = length; + it->encoding = encoding; + it->index = 0; +} + +bool plutovg_text_iterator_has_next(const plutovg_text_iterator_t* it) +{ + return it->index < it->length; +} + +plutovg_codepoint_t plutovg_text_iterator_next(plutovg_text_iterator_t* it) +{ + plutovg_codepoint_t codepoint = 0; + switch(it->encoding) { + case PLUTOVG_TEXT_ENCODING_LATIN1: { + const uint8_t* text = it->text; + codepoint = text[it->index++]; + break; + } case PLUTOVG_TEXT_ENCODING_UTF8: { + static const uint8_t trailing[256] = { + 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, + 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 + }; + + static const uint32_t offsets[6] = { + 0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080 + }; + + const uint8_t* text = it->text; + uint8_t trailing_offset = trailing[text[it->index]]; + uint32_t offset_value = offsets[trailing_offset]; + while(trailing_offset > 0 && it->index < it->length - 1) { + codepoint += text[it->index++]; + codepoint <<= 6; + trailing_offset--; + } + + codepoint += text[it->index++]; + codepoint -= offset_value; + break; + } case PLUTOVG_TEXT_ENCODING_UTF16: { + const uint16_t* text = it->text; + codepoint = text[it->index++]; + if(((codepoint) & 0xfffffc00) == 0xd800) { + if(it->index < it->length && (((codepoint) & 0xfffffc00) == 0xdc00)) { + uint16_t trail = text[it->index++]; + codepoint = (codepoint << 10) + trail - ((0xD800u << 10) - 0x10000u + 0xDC00u); + } + } + + break; + } case PLUTOVG_TEXT_ENCODING_UTF32: { + const uint32_t* text = it->text; + codepoint = text[it->index++]; + break; + } default: + assert(false); + } + + return codepoint; +} + +#if defined(_WIN32) + +#include + +typedef CRITICAL_SECTION plutovg_mutex_t; + +#define plutovg_mutex_init(mutex) InitializeCriticalSection(mutex) +#define plutovg_mutex_lock(mutex) EnterCriticalSection(mutex) +#define plutovg_mutex_unlock(mutex) LeaveCriticalSection(mutex) +#define plutovg_mutex_destroy(mutex) DeleteCriticalSection(mutex) + +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && defined(HAVE_THREADS_H) && !defined(__STDC_NO_THREADS__) + +#include + +typedef mtx_t plutovg_mutex_t; + +#define plutovg_mutex_init(mutex) mtx_init(mutex, mtx_plain | mtx_recursive) +#define plutovg_mutex_lock(mutex) mtx_lock(mutex) +#define plutovg_mutex_unlock(mutex) mtx_unlock(mutex) +#define plutovg_mutex_destroy(mutex) mtx_destroy(mutex) + +#else + +typedef int plutovg_mutex_t; + +#define plutovg_mutex_init(mutex) ((void)(mutex)) +#define plutovg_mutex_lock(mutex) ((void)(mutex)) +#define plutovg_mutex_unlock(mutex) ((void)(mutex)) +#define plutovg_mutex_destroy(mutex) ((void)(mutex)) + +#endif + +typedef struct plutovg_glyph { + plutovg_codepoint_t codepoint; + stbtt_vertex* vertices; + int nvertices; + int index; + int advance_width; + int left_side_bearing; + int x1; + int y1; + int x2; + int y2; + struct plutovg_glyph* next; +} plutovg_glyph_t; + +typedef struct { + plutovg_glyph_t** glyphs; + size_t size; + size_t capacity; +} plutovg_glyph_cache_t; + +struct plutovg_font_face { + plutovg_ref_count_t ref_count; + int ascent; + int descent; + int line_gap; + int x1; + int y1; + int x2; + int y2; + stbtt_fontinfo info; + plutovg_mutex_t mutex; + plutovg_glyph_cache_t cache; + plutovg_destroy_func_t destroy_func; + void* closure; +}; + +static void plutovg_glyph_cache_init(plutovg_glyph_cache_t* cache) +{ + cache->glyphs = NULL; + cache->size = 0; + cache->capacity = 0; +} + +static void plutovg_glyph_cache_finish(plutovg_glyph_cache_t* cache, plutovg_font_face_t* face) +{ + plutovg_mutex_lock(&face->mutex); + + if(cache->glyphs) { + for(size_t i = 0; i < cache->capacity; ++i) { + plutovg_glyph_t* glyph = cache->glyphs[i]; + while(glyph) { + plutovg_glyph_t* next = glyph->next; + stbtt_FreeShape(&face->info, glyph->vertices); + free(glyph); + glyph = next; + } + } + + free(cache->glyphs); + cache->glyphs = NULL; + cache->capacity = 0; + cache->size = 0; + } + + plutovg_mutex_unlock(&face->mutex); +} + +#define GLYPH_CACHE_INIT_CAPACITY 128 + +static plutovg_glyph_t* plutovg_glyph_cache_get(plutovg_glyph_cache_t* cache, plutovg_font_face_t* face, plutovg_codepoint_t codepoint) +{ + plutovg_mutex_lock(&face->mutex); + + if(cache->glyphs == NULL) { + assert(cache->size == 0); + cache->glyphs = calloc(GLYPH_CACHE_INIT_CAPACITY, sizeof(plutovg_glyph_t*)); + cache->capacity = GLYPH_CACHE_INIT_CAPACITY; + } + + size_t index = codepoint & (cache->capacity - 1); + plutovg_glyph_t* glyph = cache->glyphs[index]; + while(glyph && glyph->codepoint != codepoint) { + glyph = glyph->next; + } + + if(glyph == NULL) { + glyph = malloc(sizeof(plutovg_glyph_t)); + glyph->codepoint = codepoint; + glyph->index = stbtt_FindGlyphIndex(&face->info, codepoint); + glyph->nvertices = stbtt_GetGlyphShape(&face->info, glyph->index, &glyph->vertices); + stbtt_GetGlyphHMetrics(&face->info, glyph->index, &glyph->advance_width, &glyph->left_side_bearing); + if(!stbtt_GetGlyphBox(&face->info, glyph->index, &glyph->x1, &glyph->y1, &glyph->x2, &glyph->y2)) { + glyph->x1 = glyph->y1 = glyph->x2 = glyph->y2 = 0; + } + + glyph->next = cache->glyphs[index]; + cache->glyphs[index] = glyph; + cache->size += 1; + + if(cache->size > (cache->capacity * 3 / 4)) { + size_t newcapacity = cache->capacity << 1; + plutovg_glyph_t** newglyphs = calloc(newcapacity, sizeof(plutovg_glyph_t*)); + + for(size_t i = 0; i < cache->capacity; ++i) { + plutovg_glyph_t* entry = cache->glyphs[i]; + while(entry) { + plutovg_glyph_t* next = entry->next; + size_t newindex = entry->codepoint & (newcapacity - 1); + entry->next = newglyphs[newindex]; + newglyphs[newindex] = entry; + entry = next; + } + } + + free(cache->glyphs); + cache->glyphs = newglyphs; + cache->capacity = newcapacity; + } + } + + plutovg_mutex_unlock(&face->mutex); + return glyph; +} + +plutovg_font_face_t* plutovg_font_face_load_from_file(const char* filename, int ttcindex) +{ + FILE* fp = fopen(filename, "rb"); + if(fp == NULL) { + return NULL; + } + + fseek(fp, 0, SEEK_END); + long length = ftell(fp); + if(length == -1L) { + fclose(fp); + return NULL; + } + + void* data = malloc(length); + if(data == NULL) { + fclose(fp); + return NULL; + } + + fseek(fp, 0, SEEK_SET); + size_t nread = fread(data, 1, length, fp); + fclose(fp); + + if(nread != length) { + free(data); + return NULL; + } + + return plutovg_font_face_load_from_data(data, length, ttcindex, free, data); +} + +plutovg_font_face_t* plutovg_font_face_load_from_data(const void* data, unsigned int length, int ttcindex, plutovg_destroy_func_t destroy_func, void* closure) +{ + stbtt_fontinfo info; + int offset = stbtt_GetFontOffsetForIndex(data, ttcindex); + if(offset == -1 || !stbtt_InitFont(&info, data, offset)) { + if(destroy_func) + destroy_func(closure); + return NULL; + } + + plutovg_font_face_t* face = malloc(sizeof(plutovg_font_face_t)); + plutovg_init_reference(face); + face->info = info; + stbtt_GetFontVMetrics(&face->info, &face->ascent, &face->descent, &face->line_gap); + stbtt_GetFontBoundingBox(&face->info, &face->x1, &face->y1, &face->x2, &face->y2); + plutovg_mutex_init(&face->mutex); + plutovg_glyph_cache_init(&face->cache); + face->destroy_func = destroy_func; + face->closure = closure; + return face; +} + +plutovg_font_face_t* plutovg_font_face_reference(plutovg_font_face_t* face) +{ + plutovg_increment_reference(face); + return face; +} + +void plutovg_font_face_destroy(plutovg_font_face_t* face) +{ + if(plutovg_destroy_reference(face)) { + plutovg_glyph_cache_finish(&face->cache, face); + plutovg_mutex_destroy(&face->mutex); + if(face->destroy_func) + face->destroy_func(face->closure); + free(face); + } +} + +int plutovg_font_face_get_reference_count(const plutovg_font_face_t* face) +{ + return plutovg_get_reference_count(face); +} + +static float plutovg_font_face_get_scale(const plutovg_font_face_t* face, float size) +{ + return stbtt_ScaleForMappingEmToPixels(&face->info, size); +} + +void plutovg_font_face_get_metrics(const plutovg_font_face_t* face, float size, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents) +{ + float scale = plutovg_font_face_get_scale(face, size); + if(ascent) *ascent = face->ascent * scale; + if(descent) *descent = face->descent * scale; + if(line_gap) *line_gap = face->line_gap * scale; + if(extents) { + extents->x = face->x1 * scale; + extents->y = face->y2 * -scale; + extents->w = (face->x2 - face->x1) * scale; + extents->h = (face->y1 - face->y2) * -scale; + } +} + +static plutovg_glyph_t* plutovg_font_face_get_glyph(plutovg_font_face_t* face, plutovg_codepoint_t codepoint) +{ + return plutovg_glyph_cache_get(&face->cache, face, codepoint); +} + +void plutovg_font_face_get_glyph_metrics(plutovg_font_face_t* face, float size, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents) +{ + float scale = plutovg_font_face_get_scale(face, size); + plutovg_glyph_t* glyph = plutovg_font_face_get_glyph(face, codepoint); + if(advance_width) *advance_width = glyph->advance_width * scale; + if(left_side_bearing) *left_side_bearing = glyph->left_side_bearing * scale; + if(extents) { + extents->x = glyph->x1 * scale; + extents->y = glyph->y2 * -scale; + extents->w = (glyph->x2 - glyph->x1) * scale; + extents->h = (glyph->y1 - glyph->y2) * -scale; + } +} + +static void glyph_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) +{ + plutovg_path_t* path = (plutovg_path_t*)(closure); + switch(command) { + case PLUTOVG_PATH_COMMAND_MOVE_TO: + plutovg_path_move_to(path, points[0].x, points[0].y); + break; + case PLUTOVG_PATH_COMMAND_LINE_TO: + plutovg_path_line_to(path, points[0].x, points[0].y); + break; + case PLUTOVG_PATH_COMMAND_CUBIC_TO: + plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); + break; + case PLUTOVG_PATH_COMMAND_CLOSE: + assert(false); + } +} + +float plutovg_font_face_get_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_t* path) +{ + return plutovg_font_face_traverse_glyph_path(face, size, x, y, codepoint, glyph_traverse_func, path); +} + +float plutovg_font_face_traverse_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_traverse_func_t traverse_func, void* closure) +{ + float scale = plutovg_font_face_get_scale(face, size); + plutovg_matrix_t matrix; + plutovg_matrix_init_translate(&matrix, x, y); + plutovg_matrix_scale(&matrix, scale, -scale); + + plutovg_point_t points[3]; + plutovg_point_t current_point = {0, 0}; + plutovg_glyph_t* glyph = plutovg_font_face_get_glyph(face, codepoint); + for(int i = 0; i < glyph->nvertices; i++) { + switch(glyph->vertices[i].type) { + case STBTT_vmove: + points[0].x = glyph->vertices[i].x; + points[0].y = glyph->vertices[i].y; + current_point = points[0]; + plutovg_matrix_map_points(&matrix, points, points, 1); + traverse_func(closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, 1); + break; + case STBTT_vline: + points[0].x = glyph->vertices[i].x; + points[0].y = glyph->vertices[i].y; + current_point = points[0]; + plutovg_matrix_map_points(&matrix, points, points, 1); + traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, points, 1); + break; + case STBTT_vcurve: + points[0].x = 2.f / 3.f * glyph->vertices[i].cx + 1.f / 3.f * current_point.x; + points[0].y = 2.f / 3.f * glyph->vertices[i].cy + 1.f / 3.f * current_point.y; + points[1].x = 2.f / 3.f * glyph->vertices[i].cx + 1.f / 3.f * glyph->vertices[i].x; + points[1].y = 2.f / 3.f * glyph->vertices[i].cy + 1.f / 3.f * glyph->vertices[i].y; + points[2].x = glyph->vertices[i].x; + points[2].y = glyph->vertices[i].y; + current_point = points[2]; + plutovg_matrix_map_points(&matrix, points, points, 3); + traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); + break; + case STBTT_vcubic: + points[0].x = glyph->vertices[i].cx; + points[0].y = glyph->vertices[i].cy; + points[1].x = glyph->vertices[i].cx1; + points[1].y = glyph->vertices[i].cy1; + points[2].x = glyph->vertices[i].x; + points[2].y = glyph->vertices[i].y; + current_point = points[2]; + plutovg_matrix_map_points(&matrix, points, points, 3); + traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); + break; + default: + assert(false); + } + } + + return glyph->advance_width * scale; +} + +float plutovg_font_face_text_extents(plutovg_font_face_t* face, float size, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents) +{ + plutovg_text_iterator_t it; + plutovg_text_iterator_init(&it, text, length, encoding); + plutovg_rect_t* text_extents = NULL; + float total_advance_width = 0.f; + while(plutovg_text_iterator_has_next(&it)) { + plutovg_codepoint_t codepoint = plutovg_text_iterator_next(&it); + + float advance_width; + if(extents == NULL) { + plutovg_font_face_get_glyph_metrics(face, size, codepoint, &advance_width, NULL, NULL); + total_advance_width += advance_width; + continue; + } + + plutovg_rect_t glyph_extents; + plutovg_font_face_get_glyph_metrics(face, size, codepoint, &advance_width, NULL, &glyph_extents); + + glyph_extents.x += total_advance_width; + total_advance_width += advance_width; + if(text_extents == NULL) { + text_extents = extents; + *text_extents = glyph_extents; + continue; + } + + float x1 = plutovg_min(text_extents->x, glyph_extents.x); + float y1 = plutovg_min(text_extents->y, glyph_extents.y); + float x2 = plutovg_max(text_extents->x + text_extents->w, glyph_extents.x + glyph_extents.w); + float y2 = plutovg_max(text_extents->y + text_extents->h, glyph_extents.y + glyph_extents.h); + + text_extents->x = x1; + text_extents->y = y1; + text_extents->w = x2 - x1; + text_extents->h = y2 - y1; + } + + if(extents && !text_extents) { + extents->x = 0; + extents->y = 0; + extents->w = 0; + extents->h = 0; + } + + return total_advance_width; +} + +typedef struct plutovg_font_face_entry { + plutovg_font_face_t* face; + char* family; + char* filename; + int ttcindex; + bool bold; + bool italic; + struct plutovg_font_face_entry* next; +} plutovg_font_face_entry_t; + +struct plutovg_font_face_cache { + plutovg_ref_count_t ref_count; + plutovg_mutex_t mutex; + plutovg_font_face_entry_t** entries; + int size; + int capacity; + bool is_sorted; +}; + +plutovg_font_face_cache_t* plutovg_font_face_cache_create(void) +{ + plutovg_font_face_cache_t* cache = malloc(sizeof(plutovg_font_face_cache_t)); + plutovg_init_reference(cache); + plutovg_mutex_init(&cache->mutex); + cache->entries = NULL; + cache->size = 0; + cache->capacity = 0; + cache->is_sorted = false; + return cache; +} + +plutovg_font_face_cache_t* plutovg_font_face_cache_reference(plutovg_font_face_cache_t* cache) +{ + plutovg_increment_reference(cache); + return cache; +} + +void plutovg_font_face_cache_destroy(plutovg_font_face_cache_t* cache) +{ + if(plutovg_destroy_reference(cache)) { + plutovg_font_face_cache_reset(cache); + plutovg_mutex_destroy(&cache->mutex); + free(cache); + } +} + +int plutovg_font_face_cache_reference_count(const plutovg_font_face_cache_t* cache) +{ + return plutovg_get_reference_count(cache); +} + +void plutovg_font_face_cache_reset(plutovg_font_face_cache_t* cache) +{ + plutovg_mutex_lock(&cache->mutex); + + for(int i = 0; i < cache->size; ++i) { + plutovg_font_face_entry_t* entry = cache->entries[i]; + do { + plutovg_font_face_entry_t* next = entry->next; + plutovg_font_face_destroy(entry->face); + free(entry); + entry = next; + } while(entry); + } + + free(cache->entries); + cache->entries = NULL; + cache->size = 0; + cache->capacity = 0; + cache->is_sorted = false; + + plutovg_mutex_unlock(&cache->mutex); +} + +static void plutovg_font_face_cache_add_entry(plutovg_font_face_cache_t* cache, plutovg_font_face_entry_t* entry) +{ + plutovg_mutex_lock(&cache->mutex); + + for(int i = 0; i < cache->size; ++i) { + if(strcmp(entry->family, cache->entries[i]->family) == 0) { + entry->next = cache->entries[i]; + cache->entries[i] = entry; + goto unlock; + } + } + + if(cache->size >= cache->capacity) { + cache->capacity = cache->capacity == 0 ? 8 : cache->capacity << 2; + cache->entries = realloc(cache->entries, cache->capacity * sizeof(plutovg_font_face_entry_t*)); + } + + entry->next = NULL; + cache->entries[cache->size++] = entry; + cache->is_sorted = false; + +unlock: + plutovg_mutex_unlock(&cache->mutex); +} + +void plutovg_font_face_cache_add(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, plutovg_font_face_t* face) +{ + if(family == NULL) family = ""; + size_t family_length = strlen(family) + 1; + + plutovg_font_face_entry_t* entry = malloc(family_length + sizeof(plutovg_font_face_entry_t)); + entry->face = plutovg_font_face_reference(face); + entry->family = (char*)(entry + 1); + memcpy(entry->family, family, family_length); + + entry->filename = NULL; + entry->ttcindex = 0; + entry->bold = bold; + entry->italic = italic; + + plutovg_font_face_cache_add_entry(cache, entry); +} + +bool plutovg_font_face_cache_add_file(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, const char* filename, int ttcindex) +{ + plutovg_font_face_t* face = plutovg_font_face_load_from_file(filename, ttcindex); + if(face == NULL) + return false; + plutovg_font_face_cache_add(cache, family, bold, italic, face); + plutovg_font_face_destroy(face); + return true; +} + +static plutovg_font_face_entry_t* plutovg_font_face_entry_select(plutovg_font_face_entry_t* a, plutovg_font_face_entry_t* b, bool bold, bool italic) +{ + int a_score = (bold == a->bold) + (italic == a->italic); + int b_score = (bold == b->bold) + (italic == b->italic); + return a_score > b_score ? a : b; +} + +static int plutovg_font_face_entry_compare(const void* a, const void* b) +{ + const plutovg_font_face_entry_t* a_entry = *(const plutovg_font_face_entry_t**)a; + const plutovg_font_face_entry_t* b_entry = *(const plutovg_font_face_entry_t**)b; + return strcmp(a_entry->family, b_entry->family); +} + +plutovg_font_face_t* plutovg_font_face_cache_get(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic) +{ + plutovg_mutex_lock(&cache->mutex); + + if(!cache->is_sorted && cache->size > 0) { + qsort(cache->entries, cache->size, sizeof(cache->entries[0]), plutovg_font_face_entry_compare); + cache->is_sorted = true; + } + + plutovg_font_face_entry_t entry_key; + entry_key.family = (char*)(family); + + plutovg_font_face_entry_t* entry_key_ptr = &entry_key; + plutovg_font_face_entry_t** entry_result = bsearch( + &entry_key_ptr, + cache->entries, + cache->size, + sizeof(cache->entries[0]), + plutovg_font_face_entry_compare + ); + + plutovg_font_face_t* face = NULL; + if(entry_result) { + plutovg_font_face_entry_t* selected = *entry_result; + plutovg_font_face_entry_t* entry = selected->next; + while(entry) { + selected = plutovg_font_face_entry_select(entry, selected, bold, italic); + entry = entry->next; + } + + if(selected->filename && selected->face == NULL) + selected->face = plutovg_font_face_load_from_file(selected->filename, selected->ttcindex); + face = selected->face; + } + + plutovg_mutex_unlock(&cache->mutex); + return face; +} + +#ifndef PLUTOVG_DISABLE_FONT_FACE_CACHE_LOAD + +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include + +#ifdef __linux__ +#include +#else +#include +#endif + +#include +#include +#endif + +#ifdef _WIN32 + +static void* plutovg_mmap(const char* filename, long* length) +{ + HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if(file == INVALID_HANDLE_VALUE) + return NULL; + DWORD size = GetFileSize(file, NULL); + if(size == INVALID_FILE_SIZE) { + CloseHandle(file); + return NULL; + } + + HANDLE mapping = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL); + if(mapping == NULL) { + CloseHandle(file); + return NULL; + } + + void* data = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); + CloseHandle(mapping); + CloseHandle(file); + + if(data == NULL) + return NULL; + *length = size; + return data; +} + +static void plutovg_unmap(void* data, long length) +{ + UnmapViewOfFile(data); +} + +#else + +static void* plutovg_mmap(const char* filename, long* length) +{ + int fd = open(filename, O_RDONLY); + if(fd < 0) + return NULL; + struct stat st; + if(fstat(fd, &st) < 0) { + close(fd); + return NULL; + } + + if(st.st_size == 0) { + close(fd); + return NULL; + } + + void* data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + + if(data == MAP_FAILED) + return NULL; + *length = st.st_size; + return data; +} + +static void plutovg_unmap(void* data, long length) +{ + munmap(data, length); +} + +#endif // _WIN32 + +int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const char* filename) +{ + long length; + stbtt_uint8* data = plutovg_mmap(filename, &length); + if(data == NULL) { + return 0; + } + + int num_faces = 0; + + int num_fonts = stbtt_GetNumberOfFonts(data); + for(int index = 0; index < num_fonts; ++index) { + int offset = stbtt_GetFontOffsetForIndex(data, index); + if(offset == -1 || !stbtt__isfont(data + offset)) { + continue; + } + + stbtt_uint32 nm = stbtt__find_table(data, offset, "name"); + stbtt_uint16 nm_count = ttUSHORT(data + nm + 2); + + const stbtt_uint8* unicode_family_name = NULL; + const stbtt_uint8* roman_family_name = NULL; + + size_t family_length = 0; + for(stbtt_int32 i = 0; i < nm_count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_uint16 nm_id = ttUSHORT(data + loc + 6); + if(nm_id != 1) { + continue; + } + + stbtt_uint16 platform = ttUSHORT(data + loc + 0); + stbtt_uint16 encoding = ttUSHORT(data + loc + 2); + + const stbtt_uint8* family_name = data + nm + ttUSHORT(data + nm + 4) + ttUSHORT(data + loc + 10); + if(platform == 1 && encoding == 0) { + family_length = ttUSHORT(data + loc + 8); + roman_family_name = family_name; + continue; + } + + if(platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + family_length = ttUSHORT(data + loc + 8); + unicode_family_name = family_name; + break; + } + } + + if(unicode_family_name == NULL && roman_family_name == NULL) + continue; + size_t filename_length = strlen(filename) + 1; + size_t max_family_length = (unicode_family_name ? 3 * (family_length / 2) : family_length * 3) + 1; + + plutovg_font_face_entry_t* entry = malloc(max_family_length + filename_length + sizeof(plutovg_font_face_entry_t)); + entry->family = (char*)(entry + 1); + entry->filename = entry->family + max_family_length; + memcpy(entry->filename, filename, filename_length); + + size_t family_index = 0; + if(unicode_family_name) { + const stbtt_uint8* family_name = unicode_family_name; + while(family_length) { + stbtt_uint16 ch = family_name[0] * 256 + family_name[1]; + if(ch < 0x80) { + entry->family[family_index++] = ch; + } else if(ch < 0x800) { + entry->family[family_index++] = (0xc0 + (ch >> 6)); + entry->family[family_index++] = (0x80 + (ch & 0x3f)); + } else if(ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint16 ch2 = family_name[2] * 256 + family_name[3]; + stbtt_uint32 c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + + entry->family[family_index++] = (0xf0 + (c >> 18)); + entry->family[family_index++] = (0x80 + ((c >> 12) & 0x3f)); + entry->family[family_index++] = (0x80 + ((c >> 6) & 0x3f)); + entry->family[family_index++] = (0x80 + ((c) & 0x3f)); + + family_name += 2; + family_length -= 2; + } else { + entry->family[family_index++] = (0xe0 + (ch >> 12)); + entry->family[family_index++] = (0x80 + ((ch >> 6) & 0x3f)); + entry->family[family_index++] = (0x80 + ((ch) & 0x3f)); + } + + family_name += 2; + family_length -= 2; + } + + entry->family[family_index] = '\0'; + } else { + static const stbtt_uint16 MAC_ROMAN_TABLE[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + 0x0010, 0x2318, 0x21E7, 0x2325, 0x2303, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, + 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, + 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, + 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, + 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, + 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, + 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, + 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, + 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, + 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, + 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, + 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, + 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, + 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, + 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, + 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7, + }; + + const stbtt_uint8* family_name = roman_family_name; + while(family_length) { + stbtt_uint16 ch = MAC_ROMAN_TABLE[family_name[0]]; + if(ch < 0x80) { + entry->family[family_index++] = ch; + } else if(ch < 0x800) { + entry->family[family_index++] = (0xc0 + (ch >> 6)); + entry->family[family_index++] = (0x80 + (ch & 0x3f)); + } else { + entry->family[family_index++] = (0xe0 + (ch >> 12)); + entry->family[family_index++] = (0x80 + ((ch >> 6) & 0x3f)); + entry->family[family_index++] = (0x80 + ((ch) & 0x3f)); + } + + family_name += 1; + family_length -= 1; + } + + entry->family[family_index] = '\0'; + } + + entry->face = NULL; + entry->bold = false; + entry->italic = false; + entry->ttcindex = index; + + stbtt_uint32 hd = stbtt__find_table(data, offset, "head"); + stbtt_uint16 style = ttUSHORT(data + hd + 44); + if(style & 0x1) + entry->bold = true; + if(style & 0x2) { + entry->italic = true; + } + + plutovg_font_face_cache_add_entry(cache, entry); + num_faces++; + } + + plutovg_unmap(data, length); + return num_faces; +} + +static bool plutovg_font_face_supports_file(const char* filename) +{ + const char* extension = strrchr(filename, '.'); + if(extension) { + char ext[5]; + size_t length = strlen(extension); + if(length == 4) { + for(size_t i = 0; i < length; ++i) + ext[i] = tolower(extension[i]); + ext[length] = '\0'; + return strcmp(ext, ".ttf") == 0 + || strcmp(ext, ".otf") == 0 + || strcmp(ext, ".ttc") == 0 + || strcmp(ext, ".otc") == 0; + } + } + + return false; +} + +#ifdef _WIN32 + +int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname) +{ + char search_path[MAX_PATH]; + snprintf(search_path, sizeof(search_path), "%s\\*", dirname); + + WIN32_FIND_DATAA find_data; + HANDLE handle = FindFirstFileA(search_path, &find_data); + if(handle == INVALID_HANDLE_VALUE) { + return 0; + } + + int num_faces = 0; + + do { + const char* name = find_data.cFileName; + if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) + continue; + char path[MAX_PATH * 2]; + snprintf(path, sizeof(path), "%s\\%s", dirname, name); + + if(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + num_faces += plutovg_font_face_cache_load_dir(cache, path); + } else if(plutovg_font_face_supports_file(path)) { + num_faces += plutovg_font_face_cache_load_file(cache, path); + } + } while(FindNextFileA(handle, &find_data)); + + FindClose(handle); + return num_faces; +} + +#else + +int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname) +{ + DIR* dir = opendir(dirname); + if(dir == NULL) { + return 0; + } + + int num_faces = 0; + + struct dirent* entry; + while((entry = readdir(dir)) != NULL) { + if(strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name); + + struct stat st; + if(stat(path, &st) == -1) + continue; + if(S_ISDIR(st.st_mode)) { + num_faces += plutovg_font_face_cache_load_dir(cache, path); + } else if(S_ISREG(st.st_mode) && plutovg_font_face_supports_file(path)) { + num_faces += plutovg_font_face_cache_load_file(cache, path); + } + } + + closedir(dir); + return num_faces; +} + +#endif // _WIN32 + +int plutovg_font_face_cache_load_sys(plutovg_font_face_cache_t* cache) +{ + int num_faces = 0; +#if defined(_WIN32) + num_faces += plutovg_font_face_cache_load_dir(cache, "C:\\Windows\\Fonts"); +#elif defined(__APPLE__) + num_faces += plutovg_font_face_cache_load_dir(cache, "/Library/Fonts"); + num_faces += plutovg_font_face_cache_load_dir(cache, "/System/Library/Fonts"); +#elif defined(__linux__) + num_faces += plutovg_font_face_cache_load_dir(cache, "/usr/share/fonts"); + num_faces += plutovg_font_face_cache_load_dir(cache, "/usr/local/share/fonts"); +#endif + return num_faces; +} + +#else + +int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const char* filename) +{ + return -1; +} + +int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname) +{ + return -1; +} + +int plutovg_font_face_cache_load_sys(plutovg_font_face_cache_t* cache) +{ + return -1; +} + +#endif // PLUTOVG_DISABLE_FONT_FACE_CACHE_LOAD diff --git a/vendor/lunasvg/plutovg/source/plutovg-ft-math.c b/vendor/lunasvg/plutovg/source/plutovg-ft-math.c new file mode 100644 index 0000000..c4a1af8 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-ft-math.c @@ -0,0 +1,441 @@ +/***************************************************************************/ +/* */ +/* fttrigon.c */ +/* */ +/* FreeType trigonometric functions (body). */ +/* */ +/* Copyright 2001-2005, 2012-2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, FTL.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "plutovg-ft-math.h" + +#if defined(_MSC_VER) +#include +static inline int clz(unsigned int x) { + unsigned long r = 0; + if (_BitScanReverse(&r, x)) + return 31 - r; + return 32; +} +#define PVG_FT_MSB(x) (31 - clz(x)) +#elif defined(__GNUC__) +#define PVG_FT_MSB(x) (31 - __builtin_clz(x)) +#else +static inline int clz(unsigned int x) { + int n = 0; + if (x == 0) return 32; + if (x <= 0x0000FFFFU) { n += 16; x <<= 16; } + if (x <= 0x00FFFFFFU) { n += 8; x <<= 8; } + if (x <= 0x0FFFFFFFU) { n += 4; x <<= 4; } + if (x <= 0x3FFFFFFFU) { n += 2; x <<= 2; } + if (x <= 0x7FFFFFFFU) { n += 1; } + return n; +} +#define PVG_FT_MSB(x) (31 - clz(x)) +#endif + +#define PVG_FT_PAD_FLOOR(x, n) ((x) & ~((n)-1)) +#define PVG_FT_PAD_ROUND(x, n) PVG_FT_PAD_FLOOR((x) + ((n) / 2), n) +#define PVG_FT_PAD_CEIL(x, n) PVG_FT_PAD_FLOOR((x) + ((n)-1), n) + +/* transfer sign leaving a positive number */ +#define PVG_FT_MOVE_SIGN(x, s) \ + PVG_FT_BEGIN_STMNT \ + if (x < 0) { \ + x = -x; \ + s = -s; \ + } \ + PVG_FT_END_STMNT + +PVG_FT_Long PVG_FT_MulFix(PVG_FT_Long a, PVG_FT_Long b) +{ + PVG_FT_Int s = 1; + PVG_FT_Long c; + + PVG_FT_MOVE_SIGN(a, s); + PVG_FT_MOVE_SIGN(b, s); + + c = (PVG_FT_Long)(((PVG_FT_Int64)a * b + 0x8000L) >> 16); + + return (s > 0) ? c : -c; +} + +PVG_FT_Long PVG_FT_MulDiv(PVG_FT_Long a, PVG_FT_Long b, PVG_FT_Long c) +{ + PVG_FT_Int s = 1; + PVG_FT_Long d; + + PVG_FT_MOVE_SIGN(a, s); + PVG_FT_MOVE_SIGN(b, s); + PVG_FT_MOVE_SIGN(c, s); + + d = (PVG_FT_Long)(c > 0 ? ((PVG_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL); + + return (s > 0) ? d : -d; +} + +PVG_FT_Long PVG_FT_DivFix(PVG_FT_Long a, PVG_FT_Long b) +{ + PVG_FT_Int s = 1; + PVG_FT_Long q; + + PVG_FT_MOVE_SIGN(a, s); + PVG_FT_MOVE_SIGN(b, s); + + q = (PVG_FT_Long)(b > 0 ? (((PVG_FT_UInt64)a << 16) + (b >> 1)) / b + : 0x7FFFFFFFL); + + return (s < 0 ? -q : q); +} + +/*************************************************************************/ +/* */ +/* This is a fixed-point CORDIC implementation of trigonometric */ +/* functions as well as transformations between Cartesian and polar */ +/* coordinates. The angles are represented as 16.16 fixed-point values */ +/* in degrees, i.e., the angular resolution is 2^-16 degrees. Note that */ +/* only vectors longer than 2^16*180/pi (or at least 22 bits) on a */ +/* discrete Cartesian grid can have the same or better angular */ +/* resolution. Therefore, to maintain this precision, some functions */ +/* require an interim upscaling of the vectors, whereas others operate */ +/* with 24-bit long vectors directly. */ +/* */ +/*************************************************************************/ + +/* the Cordic shrink factor 0.858785336480436 * 2^32 */ +#define PVG_FT_TRIG_SCALE 0xDBD95B16UL + +/* the highest bit in overflow-safe vector components, */ +/* MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ +#define PVG_FT_TRIG_SAFE_MSB 29 + +/* this table was generated for PVG_FT_PI = 180L << 16, i.e. degrees */ +#define PVG_FT_TRIG_MAX_ITERS 23 + +static const PVG_FT_Fixed ft_trig_arctan_table[] = { + 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, 14668L, + 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, 57L, + 29L, 14L, 7L, 4L, 2L, 1L}; + +/* multiply a given value by the CORDIC shrink factor */ +static PVG_FT_Fixed ft_trig_downscale(PVG_FT_Fixed val) +{ + PVG_FT_Fixed s; + PVG_FT_Int64 v; + + s = val; + val = PVG_FT_ABS(val); + + v = (val * (PVG_FT_Int64)PVG_FT_TRIG_SCALE) + 0x100000000UL; + val = (PVG_FT_Fixed)(v >> 32); + + return (s >= 0) ? val : -val; +} + +/* undefined and never called for zero vector */ +static PVG_FT_Int ft_trig_prenorm(PVG_FT_Vector* vec) +{ + PVG_FT_Pos x, y; + PVG_FT_Int shift; + + x = vec->x; + y = vec->y; + + shift = PVG_FT_MSB(PVG_FT_ABS(x) | PVG_FT_ABS(y)); + + if (shift <= PVG_FT_TRIG_SAFE_MSB) { + shift = PVG_FT_TRIG_SAFE_MSB - shift; + vec->x = (PVG_FT_Pos)((PVG_FT_ULong)x << shift); + vec->y = (PVG_FT_Pos)((PVG_FT_ULong)y << shift); + } else { + shift -= PVG_FT_TRIG_SAFE_MSB; + vec->x = x >> shift; + vec->y = y >> shift; + shift = -shift; + } + + return shift; +} + +static void ft_trig_pseudo_rotate(PVG_FT_Vector* vec, PVG_FT_Angle theta) +{ + PVG_FT_Int i; + PVG_FT_Fixed x, y, xtemp, b; + const PVG_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Rotate inside [-PI/4,PI/4] sector */ + while (theta < -PVG_FT_ANGLE_PI4) { + xtemp = y; + y = -x; + x = xtemp; + theta += PVG_FT_ANGLE_PI2; + } + + while (theta > PVG_FT_ANGLE_PI4) { + xtemp = -y; + y = x; + x = xtemp; + theta -= PVG_FT_ANGLE_PI2; + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < PVG_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + PVG_FT_Fixed v1 = ((y + b) >> i); + PVG_FT_Fixed v2 = ((x + b) >> i); + if (theta < 0) { + xtemp = x + v1; + y = y - v2; + x = xtemp; + theta += *arctanptr++; + } else { + xtemp = x - v1; + y = y + v2; + x = xtemp; + theta -= *arctanptr++; + } + } + + vec->x = x; + vec->y = y; +} + +static void ft_trig_pseudo_polarize(PVG_FT_Vector* vec) +{ + PVG_FT_Angle theta; + PVG_FT_Int i; + PVG_FT_Fixed x, y, xtemp, b; + const PVG_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Get the vector into [-PI/4,PI/4] sector */ + if (y > x) { + if (y > -x) { + theta = PVG_FT_ANGLE_PI2; + xtemp = y; + y = -x; + x = xtemp; + } else { + theta = y > 0 ? PVG_FT_ANGLE_PI : -PVG_FT_ANGLE_PI; + x = -x; + y = -y; + } + } else { + if (y < -x) { + theta = -PVG_FT_ANGLE_PI2; + xtemp = -y; + y = x; + x = xtemp; + } else { + theta = 0; + } + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < PVG_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + PVG_FT_Fixed v1 = ((y + b) >> i); + PVG_FT_Fixed v2 = ((x + b) >> i); + if (y > 0) { + xtemp = x + v1; + y = y - v2; + x = xtemp; + theta += *arctanptr++; + } else { + xtemp = x - v1; + y = y + v2; + x = xtemp; + theta -= *arctanptr++; + } + } + + /* round theta */ + if (theta >= 0) + theta = PVG_FT_PAD_ROUND(theta, 32); + else + theta = -PVG_FT_PAD_ROUND(-theta, 32); + + vec->x = x; + vec->y = theta; +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Fixed PVG_FT_Cos(PVG_FT_Angle angle) +{ + PVG_FT_Vector v; + + v.x = PVG_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return (v.x + 0x80L) >> 8; +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Fixed PVG_FT_Sin(PVG_FT_Angle angle) +{ + return PVG_FT_Cos(PVG_FT_ANGLE_PI2 - angle); +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Fixed PVG_FT_Tan(PVG_FT_Angle angle) +{ + PVG_FT_Vector v; + + v.x = PVG_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return PVG_FT_DivFix(v.y, v.x); +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Angle PVG_FT_Atan2(PVG_FT_Fixed dx, PVG_FT_Fixed dy) +{ + PVG_FT_Vector v; + + if (dx == 0 && dy == 0) return 0; + + v.x = dx; + v.y = dy; + ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + return v.y; +} + +/* documentation is in fttrigon.h */ + +void PVG_FT_Vector_Unit(PVG_FT_Vector* vec, PVG_FT_Angle angle) +{ + vec->x = PVG_FT_TRIG_SCALE >> 8; + vec->y = 0; + ft_trig_pseudo_rotate(vec, angle); + vec->x = (vec->x + 0x80L) >> 8; + vec->y = (vec->y + 0x80L) >> 8; +} + +void PVG_FT_Vector_Rotate(PVG_FT_Vector* vec, PVG_FT_Angle angle) +{ + PVG_FT_Int shift; + PVG_FT_Vector v = *vec; + + if ( v.x == 0 && v.y == 0 ) + return; + + shift = ft_trig_prenorm( &v ); + ft_trig_pseudo_rotate( &v, angle ); + v.x = ft_trig_downscale( v.x ); + v.y = ft_trig_downscale( v.y ); + + if ( shift > 0 ) + { + PVG_FT_Int32 half = (PVG_FT_Int32)1L << ( shift - 1 ); + + + vec->x = ( v.x + half - ( v.x < 0 ) ) >> shift; + vec->y = ( v.y + half - ( v.y < 0 ) ) >> shift; + } + else + { + shift = -shift; + vec->x = (PVG_FT_Pos)( (PVG_FT_ULong)v.x << shift ); + vec->y = (PVG_FT_Pos)( (PVG_FT_ULong)v.y << shift ); + } +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Fixed PVG_FT_Vector_Length(PVG_FT_Vector* vec) +{ + PVG_FT_Int shift; + PVG_FT_Vector v; + + v = *vec; + + /* handle trivial cases */ + if (v.x == 0) { + return PVG_FT_ABS(v.y); + } else if (v.y == 0) { + return PVG_FT_ABS(v.x); + } + + /* general case */ + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + v.x = ft_trig_downscale(v.x); + + if (shift > 0) return (v.x + (1 << (shift - 1))) >> shift; + + return (PVG_FT_Fixed)((PVG_FT_UInt32)v.x << -shift); +} + +/* documentation is in fttrigon.h */ + +void PVG_FT_Vector_Polarize(PVG_FT_Vector* vec, PVG_FT_Fixed* length, + PVG_FT_Angle* angle) +{ + PVG_FT_Int shift; + PVG_FT_Vector v; + + v = *vec; + + if (v.x == 0 && v.y == 0) return; + + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + v.x = ft_trig_downscale(v.x); + + *length = (shift >= 0) ? (v.x >> shift) + : (PVG_FT_Fixed)((PVG_FT_UInt32)v.x << -shift); + *angle = v.y; +} + +/* documentation is in fttrigon.h */ + +void PVG_FT_Vector_From_Polar(PVG_FT_Vector* vec, PVG_FT_Fixed length, + PVG_FT_Angle angle) +{ + vec->x = length; + vec->y = 0; + + PVG_FT_Vector_Rotate(vec, angle); +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Angle PVG_FT_Angle_Diff( PVG_FT_Angle angle1, PVG_FT_Angle angle2 ) +{ + PVG_FT_Angle delta = angle2 - angle1; + + while ( delta <= -PVG_FT_ANGLE_PI ) + delta += PVG_FT_ANGLE_2PI; + + while ( delta > PVG_FT_ANGLE_PI ) + delta -= PVG_FT_ANGLE_2PI; + + return delta; +} + +/* END */ diff --git a/vendor/lunasvg/plutovg/source/plutovg-ft-math.h b/vendor/lunasvg/plutovg/source/plutovg-ft-math.h new file mode 100644 index 0000000..2d15c5b --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-ft-math.h @@ -0,0 +1,436 @@ +/***************************************************************************/ +/* */ +/* fttrigon.h */ +/* */ +/* FreeType trigonometric functions (specification). */ +/* */ +/* Copyright 2001, 2003, 2005, 2007, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, FTL.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#ifndef PLUTOVG_FT_MATH_H +#define PLUTOVG_FT_MATH_H + +#include "plutovg-ft-types.h" + +/*************************************************************************/ +/* */ +/* The min and max functions missing in C. As usual, be careful not to */ +/* write things like PVG_FT_MIN( a++, b++ ) to avoid side effects. */ +/* */ +#define PVG_FT_MIN( a, b ) ( (a) < (b) ? (a) : (b) ) +#define PVG_FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) ) + +#define PVG_FT_ABS( a ) ( (a) < 0 ? -(a) : (a) ) + +/* + * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' + * algorithm. We use alpha = 1, beta = 3/8, giving us results with a + * largest error less than 7% compared to the exact value. + */ +#define PVG_FT_HYPOT( x, y ) \ + ( x = PVG_FT_ABS( x ), \ + y = PVG_FT_ABS( y ), \ + x > y ? x + ( 3 * y >> 3 ) \ + : y + ( 3 * x >> 3 ) ) + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_MulFix */ +/* */ +/* */ +/* A very simple function used to perform the computation */ +/* `(a*b)/0x10000' with maximum accuracy. Most of the time this is */ +/* used to multiply a given value by a 16.16 fixed-point factor. */ +/* */ +/* */ +/* a :: The first multiplier. */ +/* b :: The second multiplier. Use a 16.16 factor here whenever */ +/* possible (see note below). */ +/* */ +/* */ +/* The result of `(a*b)/0x10000'. */ +/* */ +/* */ +/* This function has been optimized for the case where the absolute */ +/* value of `a' is less than 2048, and `b' is a 16.16 scaling factor. */ +/* As this happens mainly when scaling from notional units to */ +/* fractional pixels in FreeType, it resulted in noticeable speed */ +/* improvements between versions 2.x and 1.x. */ +/* */ +/* As a conclusion, always try to place a 16.16 factor as the */ +/* _second_ argument of this function; this can make a great */ +/* difference. */ +/* */ +PVG_FT_Long +PVG_FT_MulFix( PVG_FT_Long a, + PVG_FT_Long b ); + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_MulDiv */ +/* */ +/* */ +/* A very simple function used to perform the computation `(a*b)/c' */ +/* with maximum accuracy (it uses a 64-bit intermediate integer */ +/* whenever necessary). */ +/* */ +/* This function isn't necessarily as fast as some processor specific */ +/* operations, but is at least completely portable. */ +/* */ +/* */ +/* a :: The first multiplier. */ +/* b :: The second multiplier. */ +/* c :: The divisor. */ +/* */ +/* */ +/* The result of `(a*b)/c'. This function never traps when trying to */ +/* divide by zero; it simply returns `MaxInt' or `MinInt' depending */ +/* on the signs of `a' and `b'. */ +/* */ +PVG_FT_Long +PVG_FT_MulDiv( PVG_FT_Long a, + PVG_FT_Long b, + PVG_FT_Long c ); + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_DivFix */ +/* */ +/* */ +/* A very simple function used to perform the computation */ +/* `(a*0x10000)/b' with maximum accuracy. Most of the time, this is */ +/* used to divide a given value by a 16.16 fixed-point factor. */ +/* */ +/* */ +/* a :: The numerator. */ +/* b :: The denominator. Use a 16.16 factor here. */ +/* */ +/* */ +/* The result of `(a*0x10000)/b'. */ +/* */ +PVG_FT_Long +PVG_FT_DivFix( PVG_FT_Long a, + PVG_FT_Long b ); + + + +/*************************************************************************/ +/* */ +/*

*/ +/* computations */ +/* */ +/*************************************************************************/ + + +/************************************************************************* + * + * @type: + * PVG_FT_Angle + * + * @description: + * This type is used to model angle values in FreeType. Note that the + * angle is a 16.16 fixed-point value expressed in degrees. + * + */ +typedef PVG_FT_Fixed PVG_FT_Angle; + + +/************************************************************************* + * + * @macro: + * PVG_FT_ANGLE_PI + * + * @description: + * The angle pi expressed in @PVG_FT_Angle units. + * + */ +#define PVG_FT_ANGLE_PI ( 180L << 16 ) + + +/************************************************************************* + * + * @macro: + * PVG_FT_ANGLE_2PI + * + * @description: + * The angle 2*pi expressed in @PVG_FT_Angle units. + * + */ +#define PVG_FT_ANGLE_2PI ( PVG_FT_ANGLE_PI * 2 ) + + +/************************************************************************* + * + * @macro: + * PVG_FT_ANGLE_PI2 + * + * @description: + * The angle pi/2 expressed in @PVG_FT_Angle units. + * + */ +#define PVG_FT_ANGLE_PI2 ( PVG_FT_ANGLE_PI / 2 ) + + +/************************************************************************* + * + * @macro: + * PVG_FT_ANGLE_PI4 + * + * @description: + * The angle pi/4 expressed in @PVG_FT_Angle units. + * + */ +#define PVG_FT_ANGLE_PI4 ( PVG_FT_ANGLE_PI / 4 ) + + +/************************************************************************* + * + * @function: + * PVG_FT_Sin + * + * @description: + * Return the sinus of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The sinus value. + * + * @note: + * If you need both the sinus and cosinus for a given angle, use the + * function @PVG_FT_Vector_Unit. + * + */ +PVG_FT_Fixed +PVG_FT_Sin( PVG_FT_Angle angle ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Cos + * + * @description: + * Return the cosinus of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The cosinus value. + * + * @note: + * If you need both the sinus and cosinus for a given angle, use the + * function @PVG_FT_Vector_Unit. + * + */ +PVG_FT_Fixed +PVG_FT_Cos( PVG_FT_Angle angle ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Tan + * + * @description: + * Return the tangent of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The tangent value. + * + */ +PVG_FT_Fixed +PVG_FT_Tan( PVG_FT_Angle angle ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Atan2 + * + * @description: + * Return the arc-tangent corresponding to a given vector (x,y) in + * the 2d plane. + * + * @input: + * x :: + * The horizontal vector coordinate. + * + * y :: + * The vertical vector coordinate. + * + * @return: + * The arc-tangent value (i.e. angle). + * + */ +PVG_FT_Angle +PVG_FT_Atan2( PVG_FT_Fixed x, + PVG_FT_Fixed y ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Angle_Diff + * + * @description: + * Return the difference between two angles. The result is always + * constrained to the ]-PI..PI] interval. + * + * @input: + * angle1 :: + * First angle. + * + * angle2 :: + * Second angle. + * + * @return: + * Constrained value of `value2-value1'. + * + */ +PVG_FT_Angle +PVG_FT_Angle_Diff( PVG_FT_Angle angle1, + PVG_FT_Angle angle2 ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Vector_Unit + * + * @description: + * Return the unit vector corresponding to a given angle. After the + * call, the value of `vec.x' will be `sin(angle)', and the value of + * `vec.y' will be `cos(angle)'. + * + * This function is useful to retrieve both the sinus and cosinus of a + * given angle quickly. + * + * @output: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ +void +PVG_FT_Vector_Unit( PVG_FT_Vector* vec, + PVG_FT_Angle angle ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Vector_Rotate + * + * @description: + * Rotate a vector by a given angle. + * + * @inout: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ +void +PVG_FT_Vector_Rotate( PVG_FT_Vector* vec, + PVG_FT_Angle angle ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Vector_Length + * + * @description: + * Return the length of a given vector. + * + * @input: + * vec :: + * The address of target vector. + * + * @return: + * The vector length, expressed in the same units that the original + * vector coordinates. + * + */ +PVG_FT_Fixed +PVG_FT_Vector_Length( PVG_FT_Vector* vec ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Vector_Polarize + * + * @description: + * Compute both the length and angle of a given vector. + * + * @input: + * vec :: + * The address of source vector. + * + * @output: + * length :: + * The vector length. + * + * angle :: + * The vector angle. + * + */ +void +PVG_FT_Vector_Polarize( PVG_FT_Vector* vec, + PVG_FT_Fixed *length, + PVG_FT_Angle *angle ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Vector_From_Polar + * + * @description: + * Compute vector coordinates from a length and angle. + * + * @output: + * vec :: + * The address of source vector. + * + * @input: + * length :: + * The vector length. + * + * angle :: + * The vector angle. + * + */ +void +PVG_FT_Vector_From_Polar( PVG_FT_Vector* vec, + PVG_FT_Fixed length, + PVG_FT_Angle angle ); + +#endif /* PLUTOVG_FT_MATH_H */ diff --git a/vendor/lunasvg/plutovg/source/plutovg-ft-raster.c b/vendor/lunasvg/plutovg/source/plutovg-ft-raster.c new file mode 100644 index 0000000..d746b78 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-ft-raster.c @@ -0,0 +1,1889 @@ +/***************************************************************************/ +/* */ +/* ftgrays.c */ +/* */ +/* A new `perfect' anti-aliasing renderer (body). */ +/* */ +/* Copyright 2000-2003, 2005-2014 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, FTL.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +/*************************************************************************/ +/* */ +/* This is a new anti-aliasing scan-converter for FreeType 2. The */ +/* algorithm used here is _very_ different from the one in the standard */ +/* `ftraster' module. Actually, `ftgrays' computes the _exact_ */ +/* coverage of the outline on each pixel cell. */ +/* */ +/* It is based on ideas that I initially found in Raph Levien's */ +/* excellent LibArt graphics library (see http://www.levien.com/libart */ +/* for more information, though the web pages do not tell anything */ +/* about the renderer; you'll have to dive into the source code to */ +/* understand how it works). */ +/* */ +/* Note, however, that this is a _very_ different implementation */ +/* compared to Raph's. Coverage information is stored in a very */ +/* different way, and I don't use sorted vector paths. Also, it doesn't */ +/* use floating point values. */ +/* */ +/* This renderer has the following advantages: */ +/* */ +/* - It doesn't need an intermediate bitmap. Instead, one can supply a */ +/* callback function that will be called by the renderer to draw gray */ +/* spans on any target surface. You can thus do direct composition on */ +/* any kind of bitmap, provided that you give the renderer the right */ +/* callback. */ +/* */ +/* - A perfect anti-aliaser, i.e., it computes the _exact_ coverage on */ +/* each pixel cell. */ +/* */ +/* - It performs a single pass on the outline (the `standard' FT2 */ +/* renderer makes two passes). */ +/* */ +/* - It can easily be modified to render to _any_ number of gray levels */ +/* cheaply. */ +/* */ +/* - For small (< 20) pixel sizes, it is faster than the standard */ +/* renderer. */ +/* */ +/*************************************************************************/ + +#include "plutovg-ft-raster.h" +#include "plutovg-ft-math.h" + +#include + +#define pvg_ft_setjmp setjmp +#define pvg_ft_longjmp longjmp +#define pvg_ft_jmp_buf jmp_buf + +#include + +typedef ptrdiff_t PVG_FT_PtrDist; + +#define ErrRaster_Invalid_Mode -2 +#define ErrRaster_Invalid_Outline -1 +#define ErrRaster_Invalid_Argument -3 +#define ErrRaster_Memory_Overflow -4 +#define ErrRaster_OutOfMemory -6 + +#include +#include + +#define PVG_FT_MINIMUM_POOL_SIZE 8192 + +#define RAS_ARG PWorker worker +#define RAS_ARG_ PWorker worker, + +#define RAS_VAR worker +#define RAS_VAR_ worker, + +#define ras (*worker) + + /* must be at least 6 bits! */ +#define PIXEL_BITS 8 + +#define ONE_PIXEL ( 1L << PIXEL_BITS ) +#define TRUNC( x ) (TCoord)( (x) >> PIXEL_BITS ) +#define FRACT( x ) (TCoord)( (x) & ( ONE_PIXEL - 1 ) ) + +#if PIXEL_BITS >= 6 +#define UPSCALE( x ) ( (x) * ( ONE_PIXEL >> 6 ) ) +#define DOWNSCALE( x ) ( (x) >> ( PIXEL_BITS - 6 ) ) +#else +#define UPSCALE( x ) ( (x) >> ( 6 - PIXEL_BITS ) ) +#define DOWNSCALE( x ) ( (x) * ( 64 >> PIXEL_BITS ) ) +#endif + +/* Compute `dividend / divisor' and return both its quotient and */ +/* remainder, cast to a specific type. This macro also ensures that */ +/* the remainder is always positive. */ +#define PVG_FT_DIV_MOD( type, dividend, divisor, quotient, remainder ) \ +PVG_FT_BEGIN_STMNT \ + (quotient) = (type)( (dividend) / (divisor) ); \ + (remainder) = (type)( (dividend) % (divisor) ); \ + if ( (remainder) < 0 ) \ + { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ +PVG_FT_END_STMNT + + /* These macros speed up repetitive divisions by replacing them */ + /* with multiplications and right shifts. */ +#define PVG_FT_UDIVPREP( b ) \ + long b ## _r = (long)( ULONG_MAX >> PIXEL_BITS ) / ( b ) +#define PVG_FT_UDIV( a, b ) \ + ( ( (unsigned long)( a ) * (unsigned long)( b ## _r ) ) >> \ + ( sizeof( long ) * CHAR_BIT - PIXEL_BITS ) ) + + + /*************************************************************************/ + /* */ + /* TYPE DEFINITIONS */ + /* */ + + /* don't change the following types to PVG_FT_Int or PVG_FT_Pos, since we might */ + /* need to define them to "float" or "double" when experimenting with */ + /* new algorithms */ + + typedef long TCoord; /* integer scanline/pixel coordinate */ + typedef long TPos; /* sub-pixel coordinate */ + typedef long TArea ; /* cell areas, coordinate products */ + + /* maximal number of gray spans in a call to the span callback */ +#define PVG_FT_MAX_GRAY_SPANS 256 + + + typedef struct TCell_* PCell; + + typedef struct TCell_ + { + int x; + int cover; + TArea area; + PCell next; + + } TCell; + + + typedef struct TWorker_ + { + TCoord ex, ey; + TPos min_ex, max_ex; + TPos min_ey, max_ey; + TPos count_ex, count_ey; + + TArea area; + int cover; + int invalid; + + PCell cells; + PVG_FT_PtrDist max_cells; + PVG_FT_PtrDist num_cells; + + TPos x, y; + + PVG_FT_Outline outline; + PVG_FT_BBox clip_box; + + int clip_flags; + int clipping; + + PVG_FT_Span gray_spans[PVG_FT_MAX_GRAY_SPANS]; + int num_gray_spans; + int skip_spans; + + PVG_FT_Raster_Span_Func render_span; + void* render_span_data; + + int band_size; + int band_shoot; + + pvg_ft_jmp_buf jump_buffer; + + void* buffer; + long buffer_size; + + PCell* ycells; + TPos ycount; + } TWorker, *PWorker; + + + /*************************************************************************/ + /* */ + /* Initialize the cells table. */ + /* */ + static void + gray_init_cells( RAS_ARG_ void* buffer, + long byte_size ) + { + ras.buffer = buffer; + ras.buffer_size = byte_size; + + ras.ycells = (PCell*) buffer; + ras.cells = NULL; + ras.max_cells = 0; + ras.num_cells = 0; + ras.area = 0; + ras.cover = 0; + ras.invalid = 1; + } + + + /*************************************************************************/ + /* */ + /* Compute the outline bounding box. */ + /* */ + static void + gray_compute_cbox( RAS_ARG ) + { + PVG_FT_Outline* outline = &ras.outline; + PVG_FT_Vector* vec = outline->points; + PVG_FT_Vector* limit = vec + outline->n_points; + + + if ( outline->n_points <= 0 ) + { + ras.min_ex = ras.max_ex = 0; + ras.min_ey = ras.max_ey = 0; + return; + } + + ras.min_ex = ras.max_ex = vec->x; + ras.min_ey = ras.max_ey = vec->y; + + vec++; + + for ( ; vec < limit; vec++ ) + { + TPos x = vec->x; + TPos y = vec->y; + + + if ( x < ras.min_ex ) ras.min_ex = x; + if ( x > ras.max_ex ) ras.max_ex = x; + if ( y < ras.min_ey ) ras.min_ey = y; + if ( y > ras.max_ey ) ras.max_ey = y; + } + + /* truncate the bounding box to integer pixels */ + ras.min_ex = ras.min_ex >> 6; + ras.min_ey = ras.min_ey >> 6; + ras.max_ex = ( ras.max_ex + 63 ) >> 6; + ras.max_ey = ( ras.max_ey + 63 ) >> 6; + } + + + /*************************************************************************/ + /* */ + /* Record the current cell in the table. */ + /* */ + static PCell + gray_find_cell( RAS_ARG ) + { + PCell *pcell, cell; + TPos x = ras.ex; + + + if ( x > ras.count_ex ) + x = ras.count_ex; + + pcell = &ras.ycells[ras.ey]; + for (;;) + { + cell = *pcell; + if ( cell == NULL || cell->x > x ) + break; + + if ( cell->x == x ) + goto Exit; + + pcell = &cell->next; + } + + if ( ras.num_cells >= ras.max_cells ) + pvg_ft_longjmp( ras.jump_buffer, 1 ); + + cell = ras.cells + ras.num_cells++; + cell->x = x; + cell->area = 0; + cell->cover = 0; + + cell->next = *pcell; + *pcell = cell; + + Exit: + return cell; + } + + + static void + gray_record_cell( RAS_ARG ) + { + if ( ras.area | ras.cover ) + { + PCell cell = gray_find_cell( RAS_VAR ); + + + cell->area += ras.area; + cell->cover += ras.cover; + } + } + + + /*************************************************************************/ + /* */ + /* Set the current cell to a new position. */ + /* */ + static void + gray_set_cell( RAS_ARG_ TCoord ex, + TCoord ey ) + { + /* Move the cell pointer to a new position. We set the `invalid' */ + /* flag to indicate that the cell isn't part of those we're interested */ + /* in during the render phase. This means that: */ + /* */ + /* . the new vertical position must be within min_ey..max_ey-1. */ + /* . the new horizontal position must be strictly less than max_ex */ + /* */ + /* Note that if a cell is to the left of the clipping region, it is */ + /* actually set to the (min_ex-1) horizontal position. */ + + /* All cells that are on the left of the clipping region go to the */ + /* min_ex - 1 horizontal position. */ + ey -= ras.min_ey; + + if ( ex > ras.max_ex ) + ex = ras.max_ex; + + ex -= ras.min_ex; + if ( ex < 0 ) + ex = -1; + + /* are we moving to a different cell ? */ + if ( ex != ras.ex || ey != ras.ey ) + { + /* record the current one if it is valid */ + if ( !ras.invalid ) + gray_record_cell( RAS_VAR ); + + ras.area = 0; + ras.cover = 0; + ras.ex = ex; + ras.ey = ey; + } + + ras.invalid = ( (unsigned int)ey >= (unsigned int)ras.count_ey || + ex >= ras.count_ex ); + } + + + /*************************************************************************/ + /* */ + /* Start a new contour at a given cell. */ + /* */ + static void + gray_start_cell( RAS_ARG_ TCoord ex, + TCoord ey ) + { + if ( ex > ras.max_ex ) + ex = (TCoord)( ras.max_ex ); + + if ( ex < ras.min_ex ) + ex = (TCoord)( ras.min_ex - 1 ); + + ras.area = 0; + ras.cover = 0; + ras.ex = ex - ras.min_ex; + ras.ey = ey - ras.min_ey; + ras.invalid = 0; + + gray_set_cell( RAS_VAR_ ex, ey ); + } + +// The new render-line implementation is not yet used +#if 1 + + /*************************************************************************/ + /* */ + /* Render a scanline as one or more cells. */ + /* */ + static void + gray_render_scanline( RAS_ARG_ TCoord ey, + TPos x1, + TCoord y1, + TPos x2, + TCoord y2 ) + { + TCoord ex1, ex2, fx1, fx2, first, dy, delta, mod; + TPos p, dx; + int incr; + + + ex1 = TRUNC( x1 ); + ex2 = TRUNC( x2 ); + + /* trivial case. Happens often */ + if ( y1 == y2 ) + { + gray_set_cell( RAS_VAR_ ex2, ey ); + return; + } + + fx1 = FRACT( x1 ); + fx2 = FRACT( x2 ); + + /* everything is located in a single cell. That is easy! */ + /* */ + if ( ex1 == ex2 ) + goto End; + + /* ok, we'll have to render a run of adjacent cells on the same */ + /* scanline... */ + /* */ + dx = x2 - x1; + dy = y2 - y1; + + if ( dx > 0 ) + { + p = ( ONE_PIXEL - fx1 ) * dy; + first = ONE_PIXEL; + incr = 1; + } else { + p = fx1 * dy; + first = 0; + incr = -1; + dx = -dx; + } + + PVG_FT_DIV_MOD( TCoord, p, dx, delta, mod ); + + ras.area += (TArea)( fx1 + first ) * delta; + ras.cover += delta; + y1 += delta; + ex1 += incr; + gray_set_cell( RAS_VAR_ ex1, ey ); + + if ( ex1 != ex2 ) + { + TCoord lift, rem; + + + p = ONE_PIXEL * dy; + PVG_FT_DIV_MOD( TCoord, p, dx, lift, rem ); + + do + { + delta = lift; + mod += rem; + if ( mod >= (TCoord)dx ) + { + mod -= (TCoord)dx; + delta++; + } + + ras.area += (TArea)( ONE_PIXEL * delta ); + ras.cover += delta; + y1 += delta; + ex1 += incr; + gray_set_cell( RAS_VAR_ ex1, ey ); + } while ( ex1 != ex2 ); + } + fx1 = ONE_PIXEL - first; + + End: + dy = y2 - y1; + + ras.area += (TArea)( ( fx1 + fx2 ) * dy ); + ras.cover += dy; + } + + + /*************************************************************************/ + /* */ + /* Render a given line as a series of scanlines. */ + /* */ + static void + gray_render_line( RAS_ARG_ TPos from_x, TPos from_y, TPos to_x, TPos to_y ) + { + TCoord ey1, ey2, fy1, fy2, first, delta, mod; + TPos p, dx, dy, x, x2; + int incr; + + ey1 = TRUNC( from_y ); + ey2 = TRUNC( to_y ); /* if (ey2 >= ras.max_ey) ey2 = ras.max_ey-1; */ + + /* perform vertical clipping */ + if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) || + ( ey1 < ras.min_ey && ey2 < ras.min_ey ) ) + return; + + fy1 = FRACT( from_y ); + fy2 = FRACT( to_y ); + + /* everything is on a single scanline */ + if ( ey1 == ey2 ) + { + gray_render_scanline( RAS_VAR_ ey1, from_x, fy1, to_x, fy2 ); + return; + } + + dx = to_x - from_x; + dy = to_y - from_y; + + /* vertical line - avoid calling gray_render_scanline */ + if ( dx == 0 ) + { + TCoord ex = TRUNC( from_x ); + TCoord two_fx = FRACT( from_x ) << 1; + TPos area, max_ey1; + + + if ( dy > 0) + { + first = ONE_PIXEL; + } + else + { + first = 0; + } + + delta = first - fy1; + ras.area += (TArea)two_fx * delta; + ras.cover += delta; + + delta = first + first - ONE_PIXEL; + area = (TArea)two_fx * delta; + max_ey1 = ras.count_ey + ras.min_ey; + if (dy < 0) { + if (ey1 > max_ey1) { + ey1 = (max_ey1 > ey2) ? max_ey1 : ey2; + gray_set_cell( &ras, ex, ey1 ); + } else { + ey1--; + gray_set_cell( &ras, ex, ey1 ); + } + while ( ey1 > ey2 && ey1 >= ras.min_ey) + { + ras.area += area; + ras.cover += delta; + ey1--; + + gray_set_cell( &ras, ex, ey1 ); + } + if (ey1 != ey2) { + ey1 = ey2; + gray_set_cell( &ras, ex, ey1 ); + } + } else { + if (ey1 < ras.min_ey) { + ey1 = (ras.min_ey < ey2) ? ras.min_ey : ey2; + gray_set_cell( &ras, ex, ey1 ); + } else { + ey1++; + gray_set_cell( &ras, ex, ey1 ); + } + while ( ey1 < ey2 && ey1 < max_ey1) + { + ras.area += area; + ras.cover += delta; + ey1++; + + gray_set_cell( &ras, ex, ey1 ); + } + if (ey1 != ey2) { + ey1 = ey2; + gray_set_cell( &ras, ex, ey1 ); + } + } + + delta = (int)( fy2 - ONE_PIXEL + first ); + ras.area += (TArea)two_fx * delta; + ras.cover += delta; + + return; + } + + /* ok, we have to render several scanlines */ + if ( dy > 0) + { + p = ( ONE_PIXEL - fy1 ) * dx; + first = ONE_PIXEL; + incr = 1; + } + else + { + p = fy1 * dx; + first = 0; + incr = -1; + dy = -dy; + } + + /* the fractional part of x-delta is mod/dy. It is essential to */ + /* keep track of its accumulation for accurate rendering. */ + PVG_FT_DIV_MOD( TCoord, p, dy, delta, mod ); + + x = from_x + delta; + gray_render_scanline( RAS_VAR_ ey1, from_x, fy1, x, (TCoord)first ); + + ey1 += incr; + gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 ); + + if ( ey1 != ey2 ) + { + TCoord lift, rem; + + + p = ONE_PIXEL * dx; + PVG_FT_DIV_MOD( TCoord, p, dy, lift, rem ); + + do + { + delta = lift; + mod += rem; + if ( mod >= (TCoord)dy ) + { + mod -= (TCoord)dy; + delta++; + } + + x2 = x + delta; + gray_render_scanline( RAS_VAR_ ey1, + x, ONE_PIXEL - first, + x2, first ); + x = x2; + + ey1 += incr; + gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 ); + } while ( ey1 != ey2 ); + } + + gray_render_scanline( RAS_VAR_ ey1, + x, ONE_PIXEL - first, + to_x, fy2 ); + } + + +#else + + /*************************************************************************/ + /* */ + /* Render a straight line across multiple cells in any direction. */ + /* */ + static void + gray_render_line( RAS_ARG_ TPos from_x, TPos from_y, TPos to_x, TPos to_y ) + { + TPos dx, dy, fx1, fy1, fx2, fy2; + TCoord ex1, ex2, ey1, ey2; + + + ex1 = TRUNC( from_x ); + ex2 = TRUNC( to_x ); + ey1 = TRUNC( from_y ); + ey2 = TRUNC( to_y ); + + /* perform vertical clipping */ + if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) || + ( ey1 < ras.min_ey && ey2 < ras.min_ey ) ) + return; + + dx = to_x - from_x; + dy = to_y - from_y; + + fx1 = FRACT( from_x ); + fy1 = FRACT( from_y ); + + if ( ex1 == ex2 && ey1 == ey2 ) /* inside one cell */ + ; + else if ( dy == 0 ) /* ex1 != ex2 */ /* any horizontal line */ + { + ex1 = ex2; + gray_set_cell( RAS_VAR_ ex1, ey1 ); + } + else if ( dx == 0 ) + { + if ( dy > 0 ) /* vertical line up */ + do + { + fy2 = ONE_PIXEL; + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * fx1 * 2; + fy1 = 0; + ey1++; + gray_set_cell( RAS_VAR_ ex1, ey1 ); + } while ( ey1 != ey2 ); + else /* vertical line down */ + do + { + fy2 = 0; + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * fx1 * 2; + fy1 = ONE_PIXEL; + ey1--; + gray_set_cell( RAS_VAR_ ex1, ey1 ); + } while ( ey1 != ey2 ); + } + else /* any other line */ + { + TArea prod = dx * fy1 - dy * fx1; + PVG_FT_UDIVPREP( dx ); + PVG_FT_UDIVPREP( dy ); + + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ + do + { + if ( prod <= 0 && + prod - dx * ONE_PIXEL > 0 ) /* left */ + { + fx2 = 0; + fy2 = (TPos)PVG_FT_UDIV( -prod, -dx ); + prod -= dy * ONE_PIXEL; + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + fx1 = ONE_PIXEL; + fy1 = fy2; + ex1--; + } + else if ( prod - dx * ONE_PIXEL <= 0 && + prod - dx * ONE_PIXEL + dy * ONE_PIXEL > 0 ) /* up */ + { + prod -= dx * ONE_PIXEL; + fx2 = (TPos)PVG_FT_UDIV( -prod, dy ); + fy2 = ONE_PIXEL; + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + fx1 = fx2; + fy1 = 0; + ey1++; + } + else if ( prod - dx * ONE_PIXEL + dy * ONE_PIXEL <= 0 && + prod + dy * ONE_PIXEL >= 0 ) /* right */ + { + prod += dy * ONE_PIXEL; + fx2 = ONE_PIXEL; + fy2 = (TPos)PVG_FT_UDIV( prod, dx ); + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + fx1 = 0; + fy1 = fy2; + ex1++; + } + else /* ( prod + dy * ONE_PIXEL < 0 && + prod > 0 ) down */ + { + fx2 = (TPos)PVG_FT_UDIV( prod, -dy ); + fy2 = 0; + prod += dx * ONE_PIXEL; + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + fx1 = fx2; + fy1 = ONE_PIXEL; + ey1--; + } + + gray_set_cell( RAS_VAR_ ex1, ey1 ); + } while ( ex1 != ex2 || ey1 != ey2 ); + } + + fx2 = FRACT( to_x ); + fy2 = FRACT( to_y ); + + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + } + +#endif + + static int + gray_clip_flags( RAS_ARG_ TPos x, TPos y ) + { + return ((x > ras.clip_box.xMax) << 0) | ((y > ras.clip_box.yMax) << 1) | + ((x < ras.clip_box.xMin) << 2) | ((y < ras.clip_box.yMin) << 3); + } + + static int + gray_clip_vflags( RAS_ARG_ TPos y ) + { + return ((y > ras.clip_box.yMax) << 1) | ((y < ras.clip_box.yMin) << 3); + } + + static void + gray_vline( RAS_ARG_ TPos x1, TPos y1, TPos x2, TPos y2, int f1, int f2 ) + { + f1 &= 10; + f2 &= 10; + if((f1 | f2) == 0) /* Fully visible */ + { + gray_render_line( RAS_VAR_ x1, y1, x2, y2 ); + } + else if(f1 == f2) /* Invisible by Y */ + { + return; + } + else + { + TPos tx1, ty1, tx2, ty2; + TPos clip_y1, clip_y2; + + tx1 = x1; + ty1 = y1; + tx2 = x2; + ty2 = y2; + + clip_y1 = ras.clip_box.yMin; + clip_y2 = ras.clip_box.yMax; + + if(f1 & 8) /* y1 < clip_y1 */ + { + tx1 = x1 + PVG_FT_MulDiv(clip_y1-y1, x2-x1, y2-y1); + ty1 = clip_y1; + } + + if(f1 & 2) /* y1 > clip_y2 */ + { + tx1 = x1 + PVG_FT_MulDiv(clip_y2-y1, x2-x1, y2-y1); + ty1 = clip_y2; + } + + if(f2 & 8) /* y2 < clip_y1 */ + { + tx2 = x1 + PVG_FT_MulDiv(clip_y1-y1, x2-x1, y2-y1); + ty2 = clip_y1; + } + + if(f2 & 2) /* y2 > clip_y2 */ + { + tx2 = x1 + PVG_FT_MulDiv(clip_y2-y1, x2-x1, y2-y1); + ty2 = clip_y2; + } + + gray_render_line( RAS_VAR_ tx1, ty1, tx2, ty2 ); + } + } + + static void + gray_line_to( RAS_ARG_ TPos x2, TPos y2 ) + { + if ( !ras.clipping ) + { + gray_render_line( RAS_VAR_ ras.x, ras.y, x2, y2 ); + } + else + { + TPos x1, y1, y3, y4; + TPos clip_x1, clip_x2; + int f1, f2, f3, f4; + + f1 = ras.clip_flags; + f2 = gray_clip_flags( RAS_VAR_ x2, y2 ); + + if((f1 & 10) == (f2 & 10) && (f1 & 10) != 0) /* Invisible by Y */ + { + ras.clip_flags = f2; + goto End; + } + + x1 = ras.x; + y1 = ras.y; + + clip_x1 = ras.clip_box.xMin; + clip_x2 = ras.clip_box.xMax; + + switch(((f1 & 5) << 1) | (f2 & 5)) + { + case 0: /* Visible by X */ + gray_vline( RAS_VAR_ x1, y1, x2, y2, f1, f2); + break; + + case 1: /* x2 > clip_x2 */ + y3 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + gray_vline( RAS_VAR_ x1, y1, clip_x2, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x2, y3, clip_x2, y2, f3, f2); + break; + + case 2: /* x1 > clip_x2 */ + y3 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + gray_vline( RAS_VAR_ clip_x2, y1, clip_x2, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x2, y3, x2, y2, f3, f2); + break; + + case 3: /* x1 > clip_x2 && x2 > clip_x2 */ + gray_vline( RAS_VAR_ clip_x2, y1, clip_x2, y2, f1, f2); + break; + + case 4: /* x2 < clip_x1 */ + y3 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + gray_vline( RAS_VAR_ x1, y1, clip_x1, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x1, y3, clip_x1, y2, f3, f2); + break; + + case 6: /* x1 > clip_x2 && x2 < clip_x1 */ + y3 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); + y4 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + f4 = gray_clip_vflags( RAS_VAR_ y4 ); + gray_vline( RAS_VAR_ clip_x2, y1, clip_x2, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x2, y3, clip_x1, y4, f3, f4); + gray_vline( RAS_VAR_ clip_x1, y4, clip_x1, y2, f4, f2); + break; + + case 8: /* x1 < clip_x1 */ + y3 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + gray_vline( RAS_VAR_ clip_x1, y1, clip_x1, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x1, y3, x2, y2, f3, f2); + break; + + case 9: /* x1 < clip_x1 && x2 > clip_x2 */ + y3 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); + y4 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + f4 = gray_clip_vflags( RAS_VAR_ y4 ); + gray_vline( RAS_VAR_ clip_x1, y1, clip_x1, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x1, y3, clip_x2, y4, f3, f4); + gray_vline( RAS_VAR_ clip_x2, y4, clip_x2, y2, f4, f2); + break; + + case 12: /* x1 < clip_x1 && x2 < clip_x1 */ + gray_vline( RAS_VAR_ clip_x1, y1, clip_x1, y2, f1, f2); + break; + } + + ras.clip_flags = f2; + } + + End: + ras.x = x2; + ras.y = y2; + } + + static void + gray_split_conic( PVG_FT_Vector* base ) + { + TPos a, b; + + + base[4].x = base[2].x; + b = base[1].x; + a = base[3].x = ( base[2].x + b ) / 2; + b = base[1].x = ( base[0].x + b ) / 2; + base[2].x = ( a + b ) / 2; + + base[4].y = base[2].y; + b = base[1].y; + a = base[3].y = ( base[2].y + b ) / 2; + b = base[1].y = ( base[0].y + b ) / 2; + base[2].y = ( a + b ) / 2; + } + + + static void + gray_render_conic( RAS_ARG_ const PVG_FT_Vector* control, + const PVG_FT_Vector* to ) + { + PVG_FT_Vector bez_stack[16 * 2 + 1]; /* enough to accommodate bisections */ + PVG_FT_Vector* arc = bez_stack; + TPos dx, dy; + int draw, split; + + + arc[0].x = UPSCALE( to->x ); + arc[0].y = UPSCALE( to->y ); + arc[1].x = UPSCALE( control->x ); + arc[1].y = UPSCALE( control->y ); + arc[2].x = ras.x; + arc[2].y = ras.y; + + /* short-cut the arc that crosses the current band */ + if ( ( TRUNC( arc[0].y ) >= ras.max_ey && + TRUNC( arc[1].y ) >= ras.max_ey && + TRUNC( arc[2].y ) >= ras.max_ey ) || + ( TRUNC( arc[0].y ) < ras.min_ey && + TRUNC( arc[1].y ) < ras.min_ey && + TRUNC( arc[2].y ) < ras.min_ey ) ) + { + if ( ras.clipping ) + ras.clip_flags = gray_clip_flags ( RAS_VAR_ arc[0].x, arc[0].y ); + ras.x = arc[0].x; + ras.y = arc[0].y; + return; + } + + dx = PVG_FT_ABS( arc[2].x + arc[0].x - 2 * arc[1].x ); + dy = PVG_FT_ABS( arc[2].y + arc[0].y - 2 * arc[1].y ); + if ( dx < dy ) + dx = dy; + + /* We can calculate the number of necessary bisections because */ + /* each bisection predictably reduces deviation exactly 4-fold. */ + /* Even 32-bit deviation would vanish after 16 bisections. */ + draw = 1; + while ( dx > ONE_PIXEL / 4 ) + { + dx >>= 2; + draw <<= 1; + } + + /* We use decrement counter to count the total number of segments */ + /* to draw starting from 2^level. Before each draw we split as */ + /* many times as there are trailing zeros in the counter. */ + do + { + split = 1; + while ( ( draw & split ) == 0 ) + { + gray_split_conic( arc ); + arc += 2; + split <<= 1; + } + + gray_line_to( RAS_VAR_ arc[0].x, arc[0].y ); + arc -= 2; + + } while ( --draw ); + } + + + static void + gray_split_cubic( PVG_FT_Vector* base ) + { + TPos a, b, c, d; + + + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = ( base[0].x + c ) / 2; + base[5].x = b = ( base[3].x + d ) / 2; + c = ( c + d ) / 2; + base[2].x = a = ( a + c ) / 2; + base[4].x = b = ( b + c ) / 2; + base[3].x = ( a + b ) / 2; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = ( base[0].y + c ) / 2; + base[5].y = b = ( base[3].y + d ) / 2; + c = ( c + d ) / 2; + base[2].y = a = ( a + c ) / 2; + base[4].y = b = ( b + c ) / 2; + base[3].y = ( a + b ) / 2; + } + + + static void + gray_render_cubic( RAS_ARG_ const PVG_FT_Vector* control1, + const PVG_FT_Vector* control2, + const PVG_FT_Vector* to ) + { + PVG_FT_Vector bez_stack[16 * 3 + 1]; /* enough to accommodate bisections */ + PVG_FT_Vector* arc = bez_stack; + PVG_FT_Vector* limit = bez_stack + 45; + TPos dx, dy, dx_, dy_; + TPos dx1, dy1, dx2, dy2; + TPos L, s, s_limit; + + + arc[0].x = UPSCALE( to->x ); + arc[0].y = UPSCALE( to->y ); + arc[1].x = UPSCALE( control2->x ); + arc[1].y = UPSCALE( control2->y ); + arc[2].x = UPSCALE( control1->x ); + arc[2].y = UPSCALE( control1->y ); + arc[3].x = ras.x; + arc[3].y = ras.y; + + /* short-cut the arc that crosses the current band */ + if ( ( TRUNC( arc[0].y ) >= ras.max_ey && + TRUNC( arc[1].y ) >= ras.max_ey && + TRUNC( arc[2].y ) >= ras.max_ey && + TRUNC( arc[3].y ) >= ras.max_ey ) || + ( TRUNC( arc[0].y ) < ras.min_ey && + TRUNC( arc[1].y ) < ras.min_ey && + TRUNC( arc[2].y ) < ras.min_ey && + TRUNC( arc[3].y ) < ras.min_ey ) ) + { + if ( ras.clipping ) + ras.clip_flags = gray_clip_flags ( RAS_VAR_ arc[0].x, arc[0].y ); + ras.x = arc[0].x; + ras.y = arc[0].y; + return; + } + + for (;;) + { + /* Decide whether to split or draw. See `Rapid Termination */ + /* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */ + /* F. Hain, at */ + /* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf */ + + + /* dx and dy are x and y components of the P0-P3 chord vector. */ + dx = dx_ = arc[3].x - arc[0].x; + dy = dy_ = arc[3].y - arc[0].y; + + L = PVG_FT_HYPOT( dx_, dy_ ); + + /* Avoid possible arithmetic overflow below by splitting. */ + if ( L >= (1 << 23) ) + goto Split; + + /* Max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1). */ + s_limit = L * (TPos)( ONE_PIXEL / 6 ); + + /* s is L * the perpendicular distance from P1 to the line P0-P3. */ + dx1 = arc[1].x - arc[0].x; + dy1 = arc[1].y - arc[0].y; + s = PVG_FT_ABS( dy * dx1 - dx * dy1 ); + + if ( s > s_limit ) + goto Split; + + /* s is L * the perpendicular distance from P2 to the line P0-P3. */ + dx2 = arc[2].x - arc[0].x; + dy2 = arc[2].y - arc[0].y; + s = PVG_FT_ABS( dy * dx2 - dx * dy2 ); + + if ( s > s_limit ) + goto Split; + + /* Split super curvy segments where the off points are so far + from the chord that the angles P0-P1-P3 or P0-P2-P3 become + acute as detected by appropriate dot products. */ + if ( dx1 * ( dx1 - dx ) + dy1 * ( dy1 - dy ) > 0 || + dx2 * ( dx2 - dx ) + dy2 * ( dy2 - dy ) > 0 ) + goto Split; + + gray_line_to( RAS_VAR_ arc[0].x, arc[0].y ); + + if ( arc == bez_stack ) + return; + + arc -= 3; + continue; + + Split: + if( arc == limit ) + return; + gray_split_cubic( arc ); + arc += 3; + } + } + + + + static int + gray_move_to( const PVG_FT_Vector* to, + PWorker worker ) + { + TPos x, y; + + + /* record current cell, if any */ + if ( !ras.invalid ) + gray_record_cell( worker ); + + /* start to a new position */ + x = UPSCALE( to->x ); + y = UPSCALE( to->y ); + + gray_start_cell( worker, TRUNC( x ), TRUNC( y ) ); + + if ( ras.clipping ) + ras.clip_flags = gray_clip_flags( worker, x, y ); + ras.x = x; + ras.y = y; + return 0; + } + + + static void + gray_hline( RAS_ARG_ TCoord x, + TCoord y, + TPos area, + int acount ) + { + int coverage; + + + /* compute the coverage line's coverage, depending on the */ + /* outline fill rule */ + /* */ + /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ + /* */ + coverage = (int)( area >> ( PIXEL_BITS * 2 + 1 - 8 ) ); + /* use range 0..256 */ + if ( coverage < 0 ) + coverage = -coverage; + + if ( ras.outline.flags & PVG_FT_OUTLINE_EVEN_ODD_FILL ) + { + coverage &= 511; + + if ( coverage > 256 ) + coverage = 512 - coverage; + else if ( coverage == 256 ) + coverage = 255; + } + else + { + /* normal non-zero winding rule */ + if ( coverage >= 256 ) + coverage = 255; + } + + y += (TCoord)ras.min_ey; + x += (TCoord)ras.min_ex; + + /* PVG_FT_Span.x is an int, so limit our coordinates appropriately */ + if ( x >= (1 << 23) ) + x = (1 << 23) - 1; + + /* PVG_FT_Span.y is an int, so limit our coordinates appropriately */ + if ( y >= (1 << 23) ) + y = (1 << 23) - 1; + + if ( coverage ) + { + PVG_FT_Span* span; + int count; + int skip; + + /* see whether we can add this span to the current list */ + count = ras.num_gray_spans; + span = ras.gray_spans + count - 1; + if ( count > 0 && + span->y == y && + span->x + span->len == x && + span->coverage == coverage ) + { + span->len = span->len + acount; + return; + } + + if ( count >= PVG_FT_MAX_GRAY_SPANS ) + { + if ( ras.render_span && count > ras.skip_spans ) + { + skip = ras.skip_spans > 0 ? ras.skip_spans : 0; + ras.render_span( ras.num_gray_spans - skip, + ras.gray_spans + skip, + ras.render_span_data ); + } + + ras.skip_spans -= ras.num_gray_spans; + /* ras.render_span( span->y, ras.gray_spans, count ); */ + ras.num_gray_spans = 0; + + span = ras.gray_spans; + } + else + span++; + + /* add a gray span to the current list */ + span->x = x; + span->len = acount; + span->y = y; + span->coverage = (unsigned char)coverage; + + ras.num_gray_spans++; + } + } + + + + static void + gray_sweep( RAS_ARG) + { + int yindex; + + if ( ras.num_cells == 0 ) + return; + + for ( yindex = 0; yindex < ras.ycount; yindex++ ) + { + PCell cell = ras.ycells[yindex]; + TCoord cover = 0; + TCoord x = 0; + + + for ( ; cell != NULL; cell = cell->next ) + { + TArea area; + + + if ( cell->x > x && cover != 0 ) + gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2 ), + cell->x - x ); + + cover += cell->cover; + area = cover * ( ONE_PIXEL * 2 ) - cell->area; + + if ( area != 0 && cell->x >= 0 ) + gray_hline( RAS_VAR_ cell->x, yindex, area, 1 ); + + x = cell->x + 1; + } + + if ( ras.count_ex > x && cover != 0 ) + gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2 ), + ras.count_ex - x ); + } + } + +PVG_FT_Error PVG_FT_Outline_Check(PVG_FT_Outline* outline) +{ + if (outline) { + PVG_FT_Int n_points = outline->n_points; + PVG_FT_Int n_contours = outline->n_contours; + PVG_FT_Int end0, end; + PVG_FT_Int n; + + /* empty glyph? */ + if (n_points == 0 && n_contours == 0) return 0; + + /* check point and contour counts */ + if (n_points <= 0 || n_contours <= 0) goto Bad; + + end0 = end = -1; + for (n = 0; n < n_contours; n++) { + end = outline->contours[n]; + + /* note that we don't accept empty contours */ + if (end <= end0 || end >= n_points) goto Bad; + + end0 = end; + } + + if (end != n_points - 1) goto Bad; + + /* XXX: check the tags array */ + return 0; + } + +Bad: + return ErrRaster_Invalid_Outline; +} + +void PVG_FT_Outline_Get_CBox(const PVG_FT_Outline* outline, PVG_FT_BBox* acbox) +{ + PVG_FT_Pos xMin, yMin, xMax, yMax; + + if (outline && acbox) { + if (outline->n_points == 0) { + xMin = 0; + yMin = 0; + xMax = 0; + yMax = 0; + } else { + PVG_FT_Vector* vec = outline->points; + PVG_FT_Vector* limit = vec + outline->n_points; + + xMin = xMax = vec->x; + yMin = yMax = vec->y; + vec++; + + for (; vec < limit; vec++) { + PVG_FT_Pos x, y; + + x = vec->x; + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + + y = vec->y; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } + acbox->xMin = xMin; + acbox->xMax = xMax; + acbox->yMin = yMin; + acbox->yMax = yMax; + } +} + + /*************************************************************************/ + /* */ + /* The following function should only compile in stand_alone mode, */ + /* i.e., when building this component without the rest of FreeType. */ + /* */ + /*************************************************************************/ + + /*************************************************************************/ + /* */ + /* */ + /* PVG_FT_Outline_Decompose */ + /* */ + /* */ + /* Walks over an outline's structure to decompose it into individual */ + /* segments and Bezier arcs. This function is also able to emit */ + /* `move to' and `close to' operations to indicate the start and end */ + /* of new contours in the outline. */ + /* */ + /* */ + /* outline :: A pointer to the source target. */ + /* */ + /* user :: A typeless pointer which is passed to each */ + /* emitter during the decomposition. It can be */ + /* used to store the state during the */ + /* decomposition. */ + /* */ + /* */ + /* Error code. 0 means success. */ + /* */ + static + int PVG_FT_Outline_Decompose( const PVG_FT_Outline* outline, + void* user ) + { +#undef SCALED +#define SCALED( x ) (x) + + PVG_FT_Vector v_last; + PVG_FT_Vector v_control; + PVG_FT_Vector v_start; + + PVG_FT_Vector* point; + PVG_FT_Vector* limit; + char* tags; + + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + int error; + char tag; /* current point's state */ + + if ( !outline ) + return ErrRaster_Invalid_Outline; + + first = 0; + + for ( n = 0; n < outline->n_contours; n++ ) + { + int last; /* index of last point in contour */ + + + last = outline->contours[n]; + if ( last < 0 ) + goto Invalid_Outline; + limit = outline->points + last; + + v_start = outline->points[first]; + v_start.x = SCALED( v_start.x ); + v_start.y = SCALED( v_start.y ); + + v_last = outline->points[last]; + v_last.x = SCALED( v_last.x ); + v_last.y = SCALED( v_last.y ); + + v_control = v_start; + + point = outline->points + first; + tags = outline->tags + first; + tag = PVG_FT_CURVE_TAG( tags[0] ); + + /* A contour cannot start with a cubic control point! */ + if ( tag == PVG_FT_CURVE_TAG_CUBIC ) + goto Invalid_Outline; + + /* check first point to determine origin */ + if ( tag == PVG_FT_CURVE_TAG_CONIC ) + { + /* first point is conic control. Yes, this happens. */ + if ( PVG_FT_CURVE_TAG( outline->tags[last] ) == PVG_FT_CURVE_TAG_ON ) + { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } + else + { + /* if both first and last points are conic, */ + /* start at their middle and record its position */ + /* for closure */ + v_start.x = ( v_start.x + v_last.x ) / 2; + v_start.y = ( v_start.y + v_last.y ) / 2; + + v_last = v_start; + } + point--; + tags--; + } + + error = gray_move_to( &v_start, user ); + if ( error ) + goto Exit; + + while ( point < limit ) + { + point++; + tags++; + + tag = PVG_FT_CURVE_TAG( tags[0] ); + switch ( tag ) + { + case PVG_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + PVG_FT_Vector vec; + + + vec.x = SCALED( point->x ); + vec.y = SCALED( point->y ); + + gray_line_to(user, UPSCALE(vec.x), UPSCALE(vec.y)); + continue; + } + + case PVG_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + { + v_control.x = SCALED( point->x ); + v_control.y = SCALED( point->y ); + + Do_Conic: + if ( point < limit ) + { + PVG_FT_Vector vec; + PVG_FT_Vector v_middle; + + + point++; + tags++; + tag = PVG_FT_CURVE_TAG( tags[0] ); + + vec.x = SCALED( point->x ); + vec.y = SCALED( point->y ); + + if ( tag == PVG_FT_CURVE_TAG_ON ) + { + gray_render_conic(user, &v_control, &vec); + continue; + } + + if ( tag != PVG_FT_CURVE_TAG_CONIC ) + goto Invalid_Outline; + + v_middle.x = ( v_control.x + vec.x ) / 2; + v_middle.y = ( v_control.y + vec.y ) / 2; + + gray_render_conic(user, &v_control, &v_middle); + + v_control = vec; + goto Do_Conic; + } + + gray_render_conic(user, &v_control, &v_start); + goto Close; + } + + default: /* PVG_FT_CURVE_TAG_CUBIC */ + { + PVG_FT_Vector vec1, vec2; + + + if ( point + 1 > limit || + PVG_FT_CURVE_TAG( tags[1] ) != PVG_FT_CURVE_TAG_CUBIC ) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1.x = SCALED( point[-2].x ); + vec1.y = SCALED( point[-2].y ); + + vec2.x = SCALED( point[-1].x ); + vec2.y = SCALED( point[-1].y ); + + if ( point <= limit ) + { + PVG_FT_Vector vec; + + + vec.x = SCALED( point->x ); + vec.y = SCALED( point->y ); + + gray_render_cubic(user, &vec1, &vec2, &vec); + continue; + } + + gray_render_cubic(user, &vec1, &vec2, &v_start); + goto Close; + } + } + } + + /* close the contour with a line segment */ + gray_line_to(user, UPSCALE(v_start.x), UPSCALE(v_start.y)); + + Close: + first = last + 1; + } + + return 0; + + Exit: + return error; + + Invalid_Outline: + return ErrRaster_Invalid_Outline; + } + + typedef struct TBand_ + { + TPos min, max; + + } TBand; + + static int + gray_convert_glyph_inner( RAS_ARG ) + { + volatile int error = 0; + + if ( pvg_ft_setjmp( ras.jump_buffer ) == 0 ) + { + error = PVG_FT_Outline_Decompose( &ras.outline, &ras ); + if ( !ras.invalid ) + gray_record_cell( RAS_VAR ); + } + else + { + error = ErrRaster_Memory_Overflow; + } + + return error; + } + + + static int + gray_convert_glyph( RAS_ARG ) + { + TBand bands[40]; + TBand* volatile band; + int volatile n, num_bands; + TPos volatile min, max, max_y; + PVG_FT_BBox* clip; + int skip; + + ras.num_gray_spans = 0; + + /* Set up state in the raster object */ + gray_compute_cbox( RAS_VAR ); + + /* clip to target bitmap, exit if nothing to do */ + clip = &ras.clip_box; + + if ( ras.max_ex <= clip->xMin || ras.min_ex >= clip->xMax || + ras.max_ey <= clip->yMin || ras.min_ey >= clip->yMax ) + return 0; + + ras.clip_flags = ras.clipping = 0; + + if ( ras.min_ex < clip->xMin ) { + ras.min_ex = clip->xMin; + ras.clipping = 1; + } + + if ( ras.min_ey < clip->yMin ) { + ras.min_ey = clip->yMin; + ras.clipping = 1; + } + + if ( ras.max_ex > clip->xMax ) { + ras.max_ex = clip->xMax; + ras.clipping = 1; + } + + if ( ras.max_ey > clip->yMax ) { + ras.max_ey = clip->yMax; + ras.clipping = 1; + } + + clip->xMin = (ras.min_ex - 1) * ONE_PIXEL; + clip->yMin = (ras.min_ey - 1) * ONE_PIXEL; + clip->xMax = (ras.max_ex + 1) * ONE_PIXEL; + clip->yMax = (ras.max_ey + 1) * ONE_PIXEL; + + ras.count_ex = ras.max_ex - ras.min_ex; + ras.count_ey = ras.max_ey - ras.min_ey; + + /* set up vertical bands */ + num_bands = (int)( ( ras.max_ey - ras.min_ey ) / ras.band_size ); + if ( num_bands == 0 ) + num_bands = 1; + if ( num_bands >= 39 ) + num_bands = 39; + + ras.band_shoot = 0; + + min = ras.min_ey; + max_y = ras.max_ey; + + for ( n = 0; n < num_bands; n++, min = max ) + { + max = min + ras.band_size; + if ( n == num_bands - 1 || max > max_y ) + max = max_y; + + bands[0].min = min; + bands[0].max = max; + band = bands; + + while ( band >= bands ) + { + TPos bottom, top, middle; + int error; + + { + PCell cells_max; + int yindex; + int cell_start, cell_end, cell_mod; + + + ras.ycells = (PCell*)ras.buffer; + ras.ycount = band->max - band->min; + + cell_start = sizeof ( PCell ) * ras.ycount; + cell_mod = cell_start % sizeof ( TCell ); + if ( cell_mod > 0 ) + cell_start += sizeof ( TCell ) - cell_mod; + + cell_end = ras.buffer_size; + cell_end -= cell_end % sizeof( TCell ); + + cells_max = (PCell)( (char*)ras.buffer + cell_end ); + ras.cells = (PCell)( (char*)ras.buffer + cell_start ); + if ( ras.cells >= cells_max ) + goto ReduceBands; + + ras.max_cells = (int)(cells_max - ras.cells); + if ( ras.max_cells < 2 ) + goto ReduceBands; + + for ( yindex = 0; yindex < ras.ycount; yindex++ ) + ras.ycells[yindex] = NULL; + } + + ras.num_cells = 0; + ras.invalid = 1; + ras.min_ey = band->min; + ras.max_ey = band->max; + ras.count_ey = band->max - band->min; + + error = gray_convert_glyph_inner( RAS_VAR ); + + if ( !error ) + { + gray_sweep( RAS_VAR); + band--; + continue; + } + else if ( error != ErrRaster_Memory_Overflow ) + return 1; + + ReduceBands: + /* render pool overflow; we will reduce the render band by half */ + bottom = band->min; + top = band->max; + middle = bottom + ( ( top - bottom ) >> 1 ); + + /* This is too complex for a single scanline; there must */ + /* be some problems. */ + if ( middle == bottom ) + { + return ErrRaster_OutOfMemory; + } + + if ( bottom-top >= ras.band_size ) + ras.band_shoot++; + + band[1].min = bottom; + band[1].max = middle; + band[0].min = middle; + band[0].max = top; + band++; + } + } + + if ( ras.render_span && ras.num_gray_spans > ras.skip_spans ) + { + skip = ras.skip_spans > 0 ? ras.skip_spans : 0; + ras.render_span( ras.num_gray_spans - skip, + ras.gray_spans + skip, + ras.render_span_data ); + } + + ras.skip_spans -= ras.num_gray_spans; + + if ( ras.band_shoot > 8 && ras.band_size > 16 ) + ras.band_size = ras.band_size / 2; + + return 0; + } + + + static int + gray_raster_render( RAS_ARG_ void* buffer, long buffer_size, + const PVG_FT_Raster_Params* params ) + { + const PVG_FT_Outline* outline = (const PVG_FT_Outline*)params->source; + if ( outline == NULL ) + return ErrRaster_Invalid_Outline; + + /* return immediately if the outline is empty */ + if ( outline->n_points == 0 || outline->n_contours <= 0 ) + return 0; + + if ( !outline->contours || !outline->points ) + return ErrRaster_Invalid_Outline; + + if ( outline->n_points != + outline->contours[outline->n_contours - 1] + 1 ) + return ErrRaster_Invalid_Outline; + + /* this version does not support monochrome rendering */ + if ( !( params->flags & PVG_FT_RASTER_FLAG_AA ) ) + return ErrRaster_Invalid_Mode; + + if ( !( params->flags & PVG_FT_RASTER_FLAG_DIRECT ) ) + return ErrRaster_Invalid_Mode; + + /* compute clipping box */ + if ( params->flags & PVG_FT_RASTER_FLAG_CLIP ) + { + ras.clip_box = params->clip_box; + } + else + { + ras.clip_box.xMin = -(1 << 23); + ras.clip_box.yMin = -(1 << 23); + ras.clip_box.xMax = (1 << 23) - 1; + ras.clip_box.yMax = (1 << 23) - 1; + } + + gray_init_cells( RAS_VAR_ buffer, buffer_size ); + + ras.outline = *outline; + ras.num_cells = 0; + ras.invalid = 1; + ras.band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8)); + + ras.render_span = (PVG_FT_Raster_Span_Func)params->gray_spans; + ras.render_span_data = params->user; + + return gray_convert_glyph( RAS_VAR ); + } + + void + PVG_FT_Raster_Render(const PVG_FT_Raster_Params *params) + { + char stack[PVG_FT_MINIMUM_POOL_SIZE]; + size_t length = PVG_FT_MINIMUM_POOL_SIZE; + + TWorker worker; + worker.skip_spans = 0; + int rendered_spans = 0; + int error = gray_raster_render(&worker, stack, length, params); + while(error == ErrRaster_OutOfMemory) { + if(worker.skip_spans < 0) + rendered_spans += -worker.skip_spans; + worker.skip_spans = rendered_spans; + length *= 2; + void* heap = malloc(length); + error = gray_raster_render(&worker, heap, length, params); + free(heap); + } + } + +/* END */ diff --git a/vendor/lunasvg/plutovg/source/plutovg-ft-raster.h b/vendor/lunasvg/plutovg/source/plutovg-ft-raster.h new file mode 100644 index 0000000..e83ccc6 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-ft-raster.h @@ -0,0 +1,420 @@ +/***************************************************************************/ +/* */ +/* ftimage.h */ +/* */ +/* FreeType glyph image formats and default raster interface */ +/* (specification). */ +/* */ +/* Copyright 1996-2010, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, FTL.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#ifndef PLUTOVG_FT_RASTER_H +#define PLUTOVG_FT_RASTER_H + +#include "plutovg-ft-types.h" + +/*************************************************************************/ +/* */ +/* */ +/* FT_BBox */ +/* */ +/* */ +/* A structure used to hold an outline's bounding box, i.e., the */ +/* coordinates of its extrema in the horizontal and vertical */ +/* directions. */ +/* */ +/* */ +/* xMin :: The horizontal minimum (left-most). */ +/* */ +/* yMin :: The vertical minimum (bottom-most). */ +/* */ +/* xMax :: The horizontal maximum (right-most). */ +/* */ +/* yMax :: The vertical maximum (top-most). */ +/* */ +/* */ +/* The bounding box is specified with the coordinates of the lower */ +/* left and the upper right corner. In PostScript, those values are */ +/* often called (llx,lly) and (urx,ury), respectively. */ +/* */ +/* If `yMin' is negative, this value gives the glyph's descender. */ +/* Otherwise, the glyph doesn't descend below the baseline. */ +/* Similarly, if `ymax' is positive, this value gives the glyph's */ +/* ascender. */ +/* */ +/* `xMin' gives the horizontal distance from the glyph's origin to */ +/* the left edge of the glyph's bounding box. If `xMin' is negative, */ +/* the glyph extends to the left of the origin. */ +/* */ +typedef struct PVG_FT_BBox_ +{ + PVG_FT_Pos xMin, yMin; + PVG_FT_Pos xMax, yMax; + +} PVG_FT_BBox; + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Outline */ +/* */ +/* */ +/* This structure is used to describe an outline to the scan-line */ +/* converter. */ +/* */ +/* */ +/* n_contours :: The number of contours in the outline. */ +/* */ +/* n_points :: The number of points in the outline. */ +/* */ +/* points :: A pointer to an array of `n_points' @PVG_FT_Vector */ +/* elements, giving the outline's point coordinates. */ +/* */ +/* tags :: A pointer to an array of `n_points' chars, giving */ +/* each outline point's type. */ +/* */ +/* If bit~0 is unset, the point is `off' the curve, */ +/* i.e., a Bézier control point, while it is `on' if */ +/* set. */ +/* */ +/* Bit~1 is meaningful for `off' points only. If set, */ +/* it indicates a third-order Bézier arc control point; */ +/* and a second-order control point if unset. */ +/* */ +/* If bit~2 is set, bits 5-7 contain the drop-out mode */ +/* (as defined in the OpenType specification; the value */ +/* is the same as the argument to the SCANMODE */ +/* instruction). */ +/* */ +/* Bits 3 and~4 are reserved for internal purposes. */ +/* */ +/* contours :: An array of `n_contours' shorts, giving the end */ +/* point of each contour within the outline. For */ +/* example, the first contour is defined by the points */ +/* `0' to `contours[0]', the second one is defined by */ +/* the points `contours[0]+1' to `contours[1]', etc. */ +/* */ +/* flags :: A set of bit flags used to characterize the outline */ +/* and give hints to the scan-converter and hinter on */ +/* how to convert/grid-fit it. See @PVG_FT_OUTLINE_FLAGS.*/ +/* */ +typedef struct PVG_FT_Outline_ +{ + int n_contours; /* number of contours in glyph */ + int n_points; /* number of points in the glyph */ + + PVG_FT_Vector* points; /* the outline's points */ + char* tags; /* the points flags */ + int* contours; /* the contour end points */ + char* contours_flag; /* the contour open flags */ + + int flags; /* outline masks */ + +} PVG_FT_Outline; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_OUTLINE_FLAGS */ +/* */ +/* */ +/* A list of bit-field constants use for the flags in an outline's */ +/* `flags' field. */ +/* */ +/* */ +/* PVG_FT_OUTLINE_NONE :: */ +/* Value~0 is reserved. */ +/* */ +/* PVG_FT_OUTLINE_OWNER :: */ +/* If set, this flag indicates that the outline's field arrays */ +/* (i.e., `points', `flags', and `contours') are `owned' by the */ +/* outline object, and should thus be freed when it is destroyed. */ +/* */ +/* PVG_FT_OUTLINE_EVEN_ODD_FILL :: */ +/* By default, outlines are filled using the non-zero winding rule. */ +/* If set to 1, the outline will be filled using the even-odd fill */ +/* rule (only works with the smooth rasterizer). */ +/* */ +/* PVG_FT_OUTLINE_REVERSE_FILL :: */ +/* By default, outside contours of an outline are oriented in */ +/* clock-wise direction, as defined in the TrueType specification. */ +/* This flag is set if the outline uses the opposite direction */ +/* (typically for Type~1 fonts). This flag is ignored by the scan */ +/* converter. */ +/* */ +/* */ +/* */ +/* There exists a second mechanism to pass the drop-out mode to the */ +/* B/W rasterizer; see the `tags' field in @PVG_FT_Outline. */ +/* */ +/* Please refer to the description of the `SCANTYPE' instruction in */ +/* the OpenType specification (in file `ttinst1.doc') how simple */ +/* drop-outs, smart drop-outs, and stubs are defined. */ +/* */ +#define PVG_FT_OUTLINE_NONE 0x0 +#define PVG_FT_OUTLINE_OWNER 0x1 +#define PVG_FT_OUTLINE_EVEN_ODD_FILL 0x2 +#define PVG_FT_OUTLINE_REVERSE_FILL 0x4 + +/* */ + +#define PVG_FT_CURVE_TAG( flag ) ( flag & 3 ) + +#define PVG_FT_CURVE_TAG_ON 1 +#define PVG_FT_CURVE_TAG_CONIC 0 +#define PVG_FT_CURVE_TAG_CUBIC 2 + + +#define PVG_FT_Curve_Tag_On PVG_FT_CURVE_TAG_ON +#define PVG_FT_Curve_Tag_Conic PVG_FT_CURVE_TAG_CONIC +#define PVG_FT_Curve_Tag_Cubic PVG_FT_CURVE_TAG_CUBIC + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Outline_Check */ +/* */ +/* */ +/* Check the contents of an outline descriptor. */ +/* */ +/* */ +/* outline :: A handle to a source outline. */ +/* */ +/* */ +/* FreeType error code. 0~means success. */ +/* */ +PVG_FT_Error +PVG_FT_Outline_Check( PVG_FT_Outline* outline ); + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Outline_Get_CBox */ +/* */ +/* */ +/* Return an outline's `control box'. The control box encloses all */ +/* the outline's points, including Bézier control points. Though it */ +/* coincides with the exact bounding box for most glyphs, it can be */ +/* slightly larger in some situations (like when rotating an outline */ +/* that contains Bézier outside arcs). */ +/* */ +/* Computing the control box is very fast, while getting the bounding */ +/* box can take much more time as it needs to walk over all segments */ +/* and arcs in the outline. To get the latter, you can use the */ +/* `ftbbox' component, which is dedicated to this single task. */ +/* */ +/* */ +/* outline :: A pointer to the source outline descriptor. */ +/* */ +/* */ +/* acbox :: The outline's control box. */ +/* */ +/* */ +/* See @PVG_FT_Glyph_Get_CBox for a discussion of tricky fonts. */ +/* */ +void +PVG_FT_Outline_Get_CBox( const PVG_FT_Outline* outline, + PVG_FT_BBox *acbox ); + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Span */ +/* */ +/* */ +/* A structure used to model a single span of gray (or black) pixels */ +/* when rendering a monochrome or anti-aliased bitmap. */ +/* */ +/* */ +/* x :: The span's horizontal start position. */ +/* */ +/* len :: The span's length in pixels. */ +/* */ +/* coverage :: The span color/coverage, ranging from 0 (background) */ +/* to 255 (foreground). Only used for anti-aliased */ +/* rendering. */ +/* */ +/* */ +/* This structure is used by the span drawing callback type named */ +/* @PVG_FT_SpanFunc that takes the y~coordinate of the span as a */ +/* parameter. */ +/* */ +/* The coverage value is always between 0 and 255. If you want less */ +/* gray values, the callback function has to reduce them. */ +/* */ +typedef struct PVG_FT_Span_ +{ + int x; + int len; + int y; + unsigned char coverage; + +} PVG_FT_Span; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_SpanFunc */ +/* */ +/* */ +/* A function used as a call-back by the anti-aliased renderer in */ +/* order to let client applications draw themselves the gray pixel */ +/* spans on each scan line. */ +/* */ +/* */ +/* y :: The scanline's y~coordinate. */ +/* */ +/* count :: The number of spans to draw on this scanline. */ +/* */ +/* spans :: A table of `count' spans to draw on the scanline. */ +/* */ +/* user :: User-supplied data that is passed to the callback. */ +/* */ +/* */ +/* This callback allows client applications to directly render the */ +/* gray spans of the anti-aliased bitmap to any kind of surfaces. */ +/* */ +/* This can be used to write anti-aliased outlines directly to a */ +/* given background bitmap, and even perform translucency. */ +/* */ +/* Note that the `count' field cannot be greater than a fixed value */ +/* defined by the `PVG_FT_MAX_GRAY_SPANS' configuration macro in */ +/* `ftoption.h'. By default, this value is set to~32, which means */ +/* that if there are more than 32~spans on a given scanline, the */ +/* callback is called several times with the same `y' parameter in */ +/* order to draw all callbacks. */ +/* */ +/* Otherwise, the callback is only called once per scan-line, and */ +/* only for those scanlines that do have `gray' pixels on them. */ +/* */ +typedef void + (*PVG_FT_SpanFunc)( int count, + const PVG_FT_Span* spans, + void* user ); + +#define PVG_FT_Raster_Span_Func PVG_FT_SpanFunc + + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_RASTER_FLAG_XXX */ +/* */ +/* */ +/* A list of bit flag constants as used in the `flags' field of a */ +/* @PVG_FT_Raster_Params structure. */ +/* */ +/* */ +/* PVG_FT_RASTER_FLAG_DEFAULT :: This value is 0. */ +/* */ +/* PVG_FT_RASTER_FLAG_AA :: This flag is set to indicate that an */ +/* anti-aliased glyph image should be */ +/* generated. Otherwise, it will be */ +/* monochrome (1-bit). */ +/* */ +/* PVG_FT_RASTER_FLAG_DIRECT :: This flag is set to indicate direct */ +/* rendering. In this mode, client */ +/* applications must provide their own span */ +/* callback. This lets them directly */ +/* draw or compose over an existing bitmap. */ +/* If this bit is not set, the target */ +/* pixmap's buffer _must_ be zeroed before */ +/* rendering. */ +/* */ +/* Note that for now, direct rendering is */ +/* only possible with anti-aliased glyphs. */ +/* */ +/* PVG_FT_RASTER_FLAG_CLIP :: This flag is only used in direct */ +/* rendering mode. If set, the output will */ +/* be clipped to a box specified in the */ +/* `clip_box' field of the */ +/* @PVG_FT_Raster_Params structure. */ +/* */ +/* Note that by default, the glyph bitmap */ +/* is clipped to the target pixmap, except */ +/* in direct rendering mode where all spans */ +/* are generated if no clipping box is set. */ +/* */ +#define PVG_FT_RASTER_FLAG_DEFAULT 0x0 +#define PVG_FT_RASTER_FLAG_AA 0x1 +#define PVG_FT_RASTER_FLAG_DIRECT 0x2 +#define PVG_FT_RASTER_FLAG_CLIP 0x4 + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Raster_Params */ +/* */ +/* */ +/* A structure to hold the arguments used by a raster's render */ +/* function. */ +/* */ +/* */ +/* target :: The target bitmap. */ +/* */ +/* source :: A pointer to the source glyph image (e.g., an */ +/* @PVG_FT_Outline). */ +/* */ +/* flags :: The rendering flags. */ +/* */ +/* gray_spans :: The gray span drawing callback. */ +/* */ +/* black_spans :: The black span drawing callback. UNIMPLEMENTED! */ +/* */ +/* bit_test :: The bit test callback. UNIMPLEMENTED! */ +/* */ +/* bit_set :: The bit set callback. UNIMPLEMENTED! */ +/* */ +/* user :: User-supplied data that is passed to each drawing */ +/* callback. */ +/* */ +/* clip_box :: An optional clipping box. It is only used in */ +/* direct rendering mode. Note that coordinates here */ +/* should be expressed in _integer_ pixels (and not in */ +/* 26.6 fixed-point units). */ +/* */ +/* */ +/* An anti-aliased glyph bitmap is drawn if the @PVG_FT_RASTER_FLAG_AA */ +/* bit flag is set in the `flags' field, otherwise a monochrome */ +/* bitmap is generated. */ +/* */ +/* If the @PVG_FT_RASTER_FLAG_DIRECT bit flag is set in `flags', the */ +/* raster will call the `gray_spans' callback to draw gray pixel */ +/* spans, in the case of an aa glyph bitmap, it will call */ +/* `black_spans', and `bit_test' and `bit_set' in the case of a */ +/* monochrome bitmap. This allows direct composition over a */ +/* pre-existing bitmap through user-provided callbacks to perform the */ +/* span drawing/composition. */ +/* */ +/* Note that the `bit_test' and `bit_set' callbacks are required when */ +/* rendering a monochrome bitmap, as they are crucial to implement */ +/* correct drop-out control as defined in the TrueType specification. */ +/* */ +typedef struct PVG_FT_Raster_Params_ +{ + const void* source; + int flags; + PVG_FT_SpanFunc gray_spans; + void* user; + PVG_FT_BBox clip_box; + +} PVG_FT_Raster_Params; + + +void +PVG_FT_Raster_Render(const PVG_FT_Raster_Params *params); + +#endif // PLUTOVG_FT_RASTER_H diff --git a/vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c b/vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c new file mode 100644 index 0000000..b20cfea --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c @@ -0,0 +1,1873 @@ + +/***************************************************************************/ +/* */ +/* ftstroke.c */ +/* */ +/* FreeType path stroker (body). */ +/* */ +/* Copyright 2002-2006, 2008-2011, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, FTL.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "plutovg-ft-stroker.h" +#include "plutovg-ft-math.h" + +#include +#include +#include + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** BEZIER COMPUTATIONS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define PVG_FT_SMALL_CONIC_THRESHOLD (PVG_FT_ANGLE_PI / 6) +#define PVG_FT_SMALL_CUBIC_THRESHOLD (PVG_FT_ANGLE_PI / 8) + +#define PVG_FT_EPSILON 2 + +#define PVG_FT_IS_SMALL(x) ((x) > -PVG_FT_EPSILON && (x) < PVG_FT_EPSILON) + +static PVG_FT_Pos ft_pos_abs(PVG_FT_Pos x) +{ + return x >= 0 ? x : -x; +} + +static void ft_conic_split(PVG_FT_Vector* base) +{ + PVG_FT_Pos a, b; + + base[4].x = base[2].x; + a = base[0].x + base[1].x; + b = base[1].x + base[2].x; + base[3].x = b >> 1; + base[2].x = ( a + b ) >> 2; + base[1].x = a >> 1; + + base[4].y = base[2].y; + a = base[0].y + base[1].y; + b = base[1].y + base[2].y; + base[3].y = b >> 1; + base[2].y = ( a + b ) >> 2; + base[1].y = a >> 1; +} + +static PVG_FT_Bool ft_conic_is_small_enough(PVG_FT_Vector* base, + PVG_FT_Angle* angle_in, + PVG_FT_Angle* angle_out) +{ + PVG_FT_Vector d1, d2; + PVG_FT_Angle theta; + PVG_FT_Int close1, close2; + + d1.x = base[1].x - base[2].x; + d1.y = base[1].y - base[2].y; + d2.x = base[0].x - base[1].x; + d2.y = base[0].y - base[1].y; + + close1 = PVG_FT_IS_SMALL(d1.x) && PVG_FT_IS_SMALL(d1.y); + close2 = PVG_FT_IS_SMALL(d2.x) && PVG_FT_IS_SMALL(d2.y); + + if (close1) { + if (close2) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else { + *angle_in = *angle_out = PVG_FT_Atan2(d2.x, d2.y); + } + } else /* !close1 */ + { + if (close2) { + *angle_in = *angle_out = PVG_FT_Atan2(d1.x, d1.y); + } else { + *angle_in = PVG_FT_Atan2(d1.x, d1.y); + *angle_out = PVG_FT_Atan2(d2.x, d2.y); + } + } + + theta = ft_pos_abs(PVG_FT_Angle_Diff(*angle_in, *angle_out)); + + return PVG_FT_BOOL(theta < PVG_FT_SMALL_CONIC_THRESHOLD); +} + +static void ft_cubic_split(PVG_FT_Vector* base) +{ + PVG_FT_Pos a, b, c; + + base[6].x = base[3].x; + a = base[0].x + base[1].x; + b = base[1].x + base[2].x; + c = base[2].x + base[3].x; + base[5].x = c >> 1; + c += b; + base[4].x = c >> 2; + base[1].x = a >> 1; + a += b; + base[2].x = a >> 2; + base[3].x = ( a + c ) >> 3; + + base[6].y = base[3].y; + a = base[0].y + base[1].y; + b = base[1].y + base[2].y; + c = base[2].y + base[3].y; + base[5].y = c >> 1; + c += b; + base[4].y = c >> 2; + base[1].y = a >> 1; + a += b; + base[2].y = a >> 2; + base[3].y = ( a + c ) >> 3; +} + +/* Return the average of `angle1' and `angle2'. */ +/* This gives correct result even if `angle1' and `angle2' */ +/* have opposite signs. */ +static PVG_FT_Angle ft_angle_mean(PVG_FT_Angle angle1, PVG_FT_Angle angle2) +{ + return angle1 + PVG_FT_Angle_Diff(angle1, angle2) / 2; +} + +static PVG_FT_Bool ft_cubic_is_small_enough(PVG_FT_Vector* base, + PVG_FT_Angle* angle_in, + PVG_FT_Angle* angle_mid, + PVG_FT_Angle* angle_out) +{ + PVG_FT_Vector d1, d2, d3; + PVG_FT_Angle theta1, theta2; + PVG_FT_Int close1, close2, close3; + + d1.x = base[2].x - base[3].x; + d1.y = base[2].y - base[3].y; + d2.x = base[1].x - base[2].x; + d2.y = base[1].y - base[2].y; + d3.x = base[0].x - base[1].x; + d3.y = base[0].y - base[1].y; + + close1 = PVG_FT_IS_SMALL(d1.x) && PVG_FT_IS_SMALL(d1.y); + close2 = PVG_FT_IS_SMALL(d2.x) && PVG_FT_IS_SMALL(d2.y); + close3 = PVG_FT_IS_SMALL(d3.x) && PVG_FT_IS_SMALL(d3.y); + + if (close1) { + if (close2) { + if (close3) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else /* !close3 */ + { + *angle_in = *angle_mid = *angle_out = PVG_FT_Atan2(d3.x, d3.y); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = *angle_mid = *angle_out = PVG_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = *angle_mid = PVG_FT_Atan2(d2.x, d2.y); + *angle_out = PVG_FT_Atan2(d3.x, d3.y); + } + } + } else /* !close1 */ + { + if (close2) { + if (close3) { + *angle_in = *angle_mid = *angle_out = PVG_FT_Atan2(d1.x, d1.y); + } else /* !close3 */ + { + *angle_in = PVG_FT_Atan2(d1.x, d1.y); + *angle_out = PVG_FT_Atan2(d3.x, d3.y); + *angle_mid = ft_angle_mean(*angle_in, *angle_out); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = PVG_FT_Atan2(d1.x, d1.y); + *angle_mid = *angle_out = PVG_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = PVG_FT_Atan2(d1.x, d1.y); + *angle_mid = PVG_FT_Atan2(d2.x, d2.y); + *angle_out = PVG_FT_Atan2(d3.x, d3.y); + } + } + } + + theta1 = ft_pos_abs(PVG_FT_Angle_Diff(*angle_in, *angle_mid)); + theta2 = ft_pos_abs(PVG_FT_Angle_Diff(*angle_mid, *angle_out)); + + return PVG_FT_BOOL(theta1 < PVG_FT_SMALL_CUBIC_THRESHOLD && + theta2 < PVG_FT_SMALL_CUBIC_THRESHOLD); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKE BORDERS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +typedef enum PVG_FT_StrokeTags_ { + PVG_FT_STROKE_TAG_ON = 1, /* on-curve point */ + PVG_FT_STROKE_TAG_CUBIC = 2, /* cubic off-point */ + PVG_FT_STROKE_TAG_BEGIN = 4, /* sub-path start */ + PVG_FT_STROKE_TAG_END = 8 /* sub-path end */ + +} PVG_FT_StrokeTags; + +#define PVG_FT_STROKE_TAG_BEGIN_END \ + (PVG_FT_STROKE_TAG_BEGIN | PVG_FT_STROKE_TAG_END) + +typedef struct PVG_FT_StrokeBorderRec_ { + PVG_FT_UInt num_points; + PVG_FT_UInt max_points; + PVG_FT_Vector* points; + PVG_FT_Byte* tags; + PVG_FT_Bool movable; /* TRUE for ends of lineto borders */ + PVG_FT_Int start; /* index of current sub-path start point */ + PVG_FT_Bool valid; + +} PVG_FT_StrokeBorderRec, *PVG_FT_StrokeBorder; + +static PVG_FT_Error ft_stroke_border_grow(PVG_FT_StrokeBorder border, + PVG_FT_UInt new_points) +{ + PVG_FT_UInt old_max = border->max_points; + PVG_FT_UInt new_max = border->num_points + new_points; + PVG_FT_Error error = 0; + + if (new_max > old_max) { + PVG_FT_UInt cur_max = old_max; + + while (cur_max < new_max) cur_max += (cur_max >> 1) + 16; + + border->points = (PVG_FT_Vector*)realloc(border->points, + cur_max * sizeof(PVG_FT_Vector)); + border->tags = + (PVG_FT_Byte*)realloc(border->tags, cur_max * sizeof(PVG_FT_Byte)); + + if (!border->points || !border->tags) goto Exit; + + border->max_points = cur_max; + } + +Exit: + return error; +} + +static void ft_stroke_border_close(PVG_FT_StrokeBorder border, + PVG_FT_Bool reverse) +{ + PVG_FT_UInt start = border->start; + PVG_FT_UInt count = border->num_points; + + assert(border->start >= 0); + + /* don't record empty paths! */ + if (count <= start + 1U) + border->num_points = start; + else { + /* copy the last point to the start of this sub-path, since */ + /* it contains the `adjusted' starting coordinates */ + border->num_points = --count; + border->points[start] = border->points[count]; + border->tags[start] = border->tags[count]; + + if (reverse) { + /* reverse the points */ + { + PVG_FT_Vector* vec1 = border->points + start + 1; + PVG_FT_Vector* vec2 = border->points + count - 1; + + for (; vec1 < vec2; vec1++, vec2--) { + PVG_FT_Vector tmp; + + tmp = *vec1; + *vec1 = *vec2; + *vec2 = tmp; + } + } + + /* then the tags */ + { + PVG_FT_Byte* tag1 = border->tags + start + 1; + PVG_FT_Byte* tag2 = border->tags + count - 1; + + for (; tag1 < tag2; tag1++, tag2--) { + PVG_FT_Byte tmp; + + tmp = *tag1; + *tag1 = *tag2; + *tag2 = tmp; + } + } + } + + border->tags[start] |= PVG_FT_STROKE_TAG_BEGIN; + border->tags[count - 1] |= PVG_FT_STROKE_TAG_END; + } + + border->start = -1; + border->movable = FALSE; +} + +static PVG_FT_Error ft_stroke_border_lineto(PVG_FT_StrokeBorder border, + PVG_FT_Vector* to, PVG_FT_Bool movable) +{ + PVG_FT_Error error = 0; + + assert(border->start >= 0); + + if (border->movable) { + /* move last point */ + border->points[border->num_points - 1] = *to; + } else { + /* don't add zero-length lineto, but always add moveto */ + if (border->num_points > border->start && + PVG_FT_IS_SMALL(border->points[border->num_points - 1].x - to->x) && + PVG_FT_IS_SMALL(border->points[border->num_points - 1].y - to->y)) + return error; + + /* add one point */ + error = ft_stroke_border_grow(border, 1); + if (!error) { + PVG_FT_Vector* vec = border->points + border->num_points; + PVG_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *to; + tag[0] = PVG_FT_STROKE_TAG_ON; + + border->num_points += 1; + } + } + border->movable = movable; + return error; +} + +static PVG_FT_Error ft_stroke_border_conicto(PVG_FT_StrokeBorder border, + PVG_FT_Vector* control, + PVG_FT_Vector* to) +{ + PVG_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 2); + if (!error) { + PVG_FT_Vector* vec = border->points + border->num_points; + PVG_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control; + vec[1] = *to; + + tag[0] = 0; + tag[1] = PVG_FT_STROKE_TAG_ON; + + border->num_points += 2; + } + + border->movable = FALSE; + + return error; +} + +static PVG_FT_Error ft_stroke_border_cubicto(PVG_FT_StrokeBorder border, + PVG_FT_Vector* control1, + PVG_FT_Vector* control2, + PVG_FT_Vector* to) +{ + PVG_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 3); + if (!error) { + PVG_FT_Vector* vec = border->points + border->num_points; + PVG_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control1; + vec[1] = *control2; + vec[2] = *to; + + tag[0] = PVG_FT_STROKE_TAG_CUBIC; + tag[1] = PVG_FT_STROKE_TAG_CUBIC; + tag[2] = PVG_FT_STROKE_TAG_ON; + + border->num_points += 3; + } + + border->movable = FALSE; + + return error; +} + +#define PVG_FT_ARC_CUBIC_ANGLE (PVG_FT_ANGLE_PI / 2) + + +static PVG_FT_Error +ft_stroke_border_arcto( PVG_FT_StrokeBorder border, + PVG_FT_Vector* center, + PVG_FT_Fixed radius, + PVG_FT_Angle angle_start, + PVG_FT_Angle angle_diff ) +{ + PVG_FT_Fixed coef; + PVG_FT_Vector a0, a1, a2, a3; + PVG_FT_Int i, arcs = 1; + PVG_FT_Error error = 0; + + + /* number of cubic arcs to draw */ + while ( angle_diff > PVG_FT_ARC_CUBIC_ANGLE * arcs || + -angle_diff > PVG_FT_ARC_CUBIC_ANGLE * arcs ) + arcs++; + + /* control tangents */ + coef = PVG_FT_Tan( angle_diff / ( 4 * arcs ) ); + coef += coef / 3; + + /* compute start and first control point */ + PVG_FT_Vector_From_Polar( &a0, radius, angle_start ); + a1.x = PVG_FT_MulFix( -a0.y, coef ); + a1.y = PVG_FT_MulFix( a0.x, coef ); + + a0.x += center->x; + a0.y += center->y; + a1.x += a0.x; + a1.y += a0.y; + + for ( i = 1; i <= arcs; i++ ) + { + /* compute end and second control point */ + PVG_FT_Vector_From_Polar( &a3, radius, + angle_start + i * angle_diff / arcs ); + a2.x = PVG_FT_MulFix( a3.y, coef ); + a2.y = PVG_FT_MulFix( -a3.x, coef ); + + a3.x += center->x; + a3.y += center->y; + a2.x += a3.x; + a2.y += a3.y; + + /* add cubic arc */ + error = ft_stroke_border_cubicto( border, &a1, &a2, &a3 ); + if ( error ) + break; + + /* a0 = a3; */ + a1.x = a3.x - a2.x + a3.x; + a1.y = a3.y - a2.y + a3.y; + } + + return error; +} + +static PVG_FT_Error ft_stroke_border_moveto(PVG_FT_StrokeBorder border, + PVG_FT_Vector* to) +{ + /* close current open path if any ? */ + if (border->start >= 0) ft_stroke_border_close(border, FALSE); + + border->start = border->num_points; + border->movable = FALSE; + + return ft_stroke_border_lineto(border, to, FALSE); +} + +static void ft_stroke_border_init(PVG_FT_StrokeBorder border) +{ + border->points = NULL; + border->tags = NULL; + + border->num_points = 0; + border->max_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static void ft_stroke_border_reset(PVG_FT_StrokeBorder border) +{ + border->num_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static void ft_stroke_border_done(PVG_FT_StrokeBorder border) +{ + free(border->points); + free(border->tags); + + border->num_points = 0; + border->max_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static PVG_FT_Error ft_stroke_border_get_counts(PVG_FT_StrokeBorder border, + PVG_FT_UInt* anum_points, + PVG_FT_UInt* anum_contours) +{ + PVG_FT_Error error = 0; + PVG_FT_UInt num_points = 0; + PVG_FT_UInt num_contours = 0; + + PVG_FT_UInt count = border->num_points; + PVG_FT_Vector* point = border->points; + PVG_FT_Byte* tags = border->tags; + PVG_FT_Int in_contour = 0; + + for (; count > 0; count--, num_points++, point++, tags++) { + if (tags[0] & PVG_FT_STROKE_TAG_BEGIN) { + if (in_contour != 0) goto Fail; + + in_contour = 1; + } else if (in_contour == 0) + goto Fail; + + if (tags[0] & PVG_FT_STROKE_TAG_END) { + in_contour = 0; + num_contours++; + } + } + + if (in_contour != 0) goto Fail; + + border->valid = TRUE; + +Exit: + *anum_points = num_points; + *anum_contours = num_contours; + return error; + +Fail: + num_points = 0; + num_contours = 0; + goto Exit; +} + +static void ft_stroke_border_export(PVG_FT_StrokeBorder border, + PVG_FT_Outline* outline) +{ + /* copy point locations */ + memcpy(outline->points + outline->n_points, border->points, + border->num_points * sizeof(PVG_FT_Vector)); + + /* copy tags */ + { + PVG_FT_UInt count = border->num_points; + PVG_FT_Byte* read = border->tags; + PVG_FT_Byte* write = (PVG_FT_Byte*)outline->tags + outline->n_points; + + for (; count > 0; count--, read++, write++) { + if (*read & PVG_FT_STROKE_TAG_ON) + *write = PVG_FT_CURVE_TAG_ON; + else if (*read & PVG_FT_STROKE_TAG_CUBIC) + *write = PVG_FT_CURVE_TAG_CUBIC; + else + *write = PVG_FT_CURVE_TAG_CONIC; + } + } + + /* copy contours */ + { + PVG_FT_UInt count = border->num_points; + PVG_FT_Byte* tags = border->tags; + PVG_FT_Int* write = outline->contours + outline->n_contours; + PVG_FT_Int idx = (PVG_FT_Int)outline->n_points; + + for (; count > 0; count--, tags++, idx++) { + if (*tags & PVG_FT_STROKE_TAG_END) { + *write++ = idx; + outline->n_contours++; + } + } + } + + outline->n_points = (int)(outline->n_points + border->num_points); + + assert(PVG_FT_Outline_Check(outline) == 0); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKER *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define PVG_FT_SIDE_TO_ROTATE(s) (PVG_FT_ANGLE_PI2 - (s)*PVG_FT_ANGLE_PI) + +typedef struct PVG_FT_StrokerRec_ { + PVG_FT_Angle angle_in; /* direction into curr join */ + PVG_FT_Angle angle_out; /* direction out of join */ + PVG_FT_Vector center; /* current position */ + PVG_FT_Fixed line_length; /* length of last lineto */ + PVG_FT_Bool first_point; /* is this the start? */ + PVG_FT_Bool subpath_open; /* is the subpath open? */ + PVG_FT_Angle subpath_angle; /* subpath start direction */ + PVG_FT_Vector subpath_start; /* subpath start position */ + PVG_FT_Fixed subpath_line_length; /* subpath start lineto len */ + PVG_FT_Bool handle_wide_strokes; /* use wide strokes logic? */ + + PVG_FT_Stroker_LineCap line_cap; + PVG_FT_Stroker_LineJoin line_join; + PVG_FT_Stroker_LineJoin line_join_saved; + PVG_FT_Fixed miter_limit; + PVG_FT_Fixed radius; + + PVG_FT_StrokeBorderRec borders[2]; +} PVG_FT_StrokerRec; + +/* documentation is in ftstroke.h */ + +PVG_FT_Error PVG_FT_Stroker_New(PVG_FT_Stroker* astroker) +{ + PVG_FT_Error error = 0; /* assigned in PVG_FT_NEW */ + PVG_FT_Stroker stroker = NULL; + + stroker = (PVG_FT_StrokerRec*)calloc(1, sizeof(PVG_FT_StrokerRec)); + if (stroker) { + ft_stroke_border_init(&stroker->borders[0]); + ft_stroke_border_init(&stroker->borders[1]); + } + + *astroker = stroker; + + return error; +} + +void PVG_FT_Stroker_Rewind(PVG_FT_Stroker stroker) +{ + if (stroker) { + ft_stroke_border_reset(&stroker->borders[0]); + ft_stroke_border_reset(&stroker->borders[1]); + } +} + +/* documentation is in ftstroke.h */ + +void PVG_FT_Stroker_Set(PVG_FT_Stroker stroker, PVG_FT_Fixed radius, + PVG_FT_Stroker_LineCap line_cap, + PVG_FT_Stroker_LineJoin line_join, + PVG_FT_Fixed miter_limit) +{ + stroker->radius = radius; + stroker->line_cap = line_cap; + stroker->line_join = line_join; + stroker->miter_limit = miter_limit; + + /* ensure miter limit has sensible value */ + if (stroker->miter_limit < 0x10000) stroker->miter_limit = 0x10000; + + /* save line join style: */ + /* line join style can be temporarily changed when stroking curves */ + stroker->line_join_saved = line_join; + + PVG_FT_Stroker_Rewind(stroker); +} + +/* documentation is in ftstroke.h */ + +void PVG_FT_Stroker_Done(PVG_FT_Stroker stroker) +{ + if (stroker) { + ft_stroke_border_done(&stroker->borders[0]); + ft_stroke_border_done(&stroker->borders[1]); + + free(stroker); + } +} + +/* create a circular arc at a corner or cap */ +static PVG_FT_Error ft_stroker_arcto(PVG_FT_Stroker stroker, PVG_FT_Int side) +{ + PVG_FT_Angle total, rotate; + PVG_FT_Fixed radius = stroker->radius; + PVG_FT_Error error = 0; + PVG_FT_StrokeBorder border = stroker->borders + side; + + rotate = PVG_FT_SIDE_TO_ROTATE(side); + + total = PVG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + if (total == PVG_FT_ANGLE_PI) total = -rotate * 2; + + error = ft_stroke_border_arcto(border, &stroker->center, radius, + stroker->angle_in + rotate, total); + border->movable = FALSE; + return error; +} + +/* add a cap at the end of an opened path */ +static PVG_FT_Error +ft_stroker_cap(PVG_FT_Stroker stroker, + PVG_FT_Angle angle, + PVG_FT_Int side) +{ + PVG_FT_Error error = 0; + + if (stroker->line_cap == PVG_FT_STROKER_LINECAP_ROUND) + { + /* add a round cap */ + stroker->angle_in = angle; + stroker->angle_out = angle + PVG_FT_ANGLE_PI; + + error = ft_stroker_arcto(stroker, side); + } + else + { + /* add a square or butt cap */ + PVG_FT_Vector middle, delta; + PVG_FT_Fixed radius = stroker->radius; + PVG_FT_StrokeBorder border = stroker->borders + side; + + /* compute middle point and first angle point */ + PVG_FT_Vector_From_Polar( &middle, radius, angle ); + delta.x = side ? middle.y : -middle.y; + delta.y = side ? -middle.x : middle.x; + + if ( stroker->line_cap == PVG_FT_STROKER_LINECAP_SQUARE ) + { + middle.x += stroker->center.x; + middle.y += stroker->center.y; + } + else /* PVG_FT_STROKER_LINECAP_BUTT */ + { + middle.x = stroker->center.x; + middle.y = stroker->center.y; + } + + delta.x += middle.x; + delta.y += middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* compute second angle point */ + delta.x = middle.x - delta.x + middle.x; + delta.y = middle.y - delta.y + middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + +Exit: + return error; +} + +/* process an inside corner, i.e. compute intersection */ +static PVG_FT_Error ft_stroker_inside(PVG_FT_Stroker stroker, PVG_FT_Int side, + PVG_FT_Fixed line_length) +{ + PVG_FT_StrokeBorder border = stroker->borders + side; + PVG_FT_Angle phi, theta, rotate; + PVG_FT_Fixed length; + PVG_FT_Vector sigma = {0, 0}; + PVG_FT_Vector delta; + PVG_FT_Error error = 0; + PVG_FT_Bool intersect; /* use intersection of lines? */ + + rotate = PVG_FT_SIDE_TO_ROTATE(side); + + theta = PVG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2; + + /* Only intersect borders if between two lineto's and both */ + /* lines are long enough (line_length is zero for curves). */ + if (!border->movable || line_length == 0 || + theta > 0x59C000 || theta < -0x59C000 ) + intersect = FALSE; + else { + /* compute minimum required length of lines */ + PVG_FT_Fixed min_length; + + + PVG_FT_Vector_Unit( &sigma, theta ); + min_length = + ft_pos_abs( PVG_FT_MulDiv( stroker->radius, sigma.y, sigma.x ) ); + + intersect = PVG_FT_BOOL( min_length && + stroker->line_length >= min_length && + line_length >= min_length ); + } + + if (!intersect) { + PVG_FT_Vector_From_Polar(&delta, stroker->radius, + stroker->angle_out + rotate); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + border->movable = FALSE; + } else { + /* compute median angle */ + phi = stroker->angle_in + theta + rotate; + + length = PVG_FT_DivFix( stroker->radius, sigma.x ); + + PVG_FT_Vector_From_Polar( &delta, length, phi ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + } + + error = ft_stroke_border_lineto(border, &delta, FALSE); + + return error; +} + + /* process an outside corner, i.e. compute bevel/miter/round */ +static PVG_FT_Error +ft_stroker_outside( PVG_FT_Stroker stroker, + PVG_FT_Int side, + PVG_FT_Fixed line_length ) +{ + PVG_FT_StrokeBorder border = stroker->borders + side; + PVG_FT_Error error; + PVG_FT_Angle rotate; + + + if ( stroker->line_join == PVG_FT_STROKER_LINEJOIN_ROUND ) + error = ft_stroker_arcto( stroker, side ); + else + { + /* this is a mitered (pointed) or beveled (truncated) corner */ + PVG_FT_Fixed radius = stroker->radius; + PVG_FT_Vector sigma = {0, 0}; + PVG_FT_Angle theta = 0, phi = 0; + PVG_FT_Bool bevel, fixed_bevel; + + + rotate = PVG_FT_SIDE_TO_ROTATE( side ); + + bevel = + PVG_FT_BOOL( stroker->line_join == PVG_FT_STROKER_LINEJOIN_BEVEL ); + + fixed_bevel = + PVG_FT_BOOL( stroker->line_join != PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE ); + + /* check miter limit first */ + if ( !bevel ) + { + theta = PVG_FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2; + + if ( theta == PVG_FT_ANGLE_PI2 ) + theta = -rotate; + + phi = stroker->angle_in + theta + rotate; + + PVG_FT_Vector_From_Polar( &sigma, stroker->miter_limit, theta ); + + /* is miter limit exceeded? */ + if ( sigma.x < 0x10000L ) + { + /* don't create variable bevels for very small deviations; */ + /* FT_Sin(x) = 0 for x <= 57 */ + if ( fixed_bevel || ft_pos_abs( theta ) > 57 ) + bevel = TRUE; + } + } + + if ( bevel ) /* this is a bevel (broken angle) */ + { + if ( fixed_bevel ) + { + /* the outer corners are simply joined together */ + PVG_FT_Vector delta; + + + /* add bevel */ + PVG_FT_Vector_From_Polar( &delta, + radius, + stroker->angle_out + rotate ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + border->movable = FALSE; + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + else /* variable bevel or clipped miter */ + { + /* the miter is truncated */ + PVG_FT_Vector middle, delta; + PVG_FT_Fixed coef; + + + /* compute middle point and first angle point */ + PVG_FT_Vector_From_Polar( &middle, + PVG_FT_MulFix( radius, stroker->miter_limit ), + phi ); + + coef = PVG_FT_DivFix( 0x10000L - sigma.x, sigma.y ); + delta.x = PVG_FT_MulFix( middle.y, coef ); + delta.y = PVG_FT_MulFix( -middle.x, coef ); + + middle.x += stroker->center.x; + middle.y += stroker->center.y; + delta.x += middle.x; + delta.y += middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* compute second angle point */ + delta.x = middle.x - delta.x + middle.x; + delta.y = middle.y - delta.y + middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* finally, add an end point; only needed if not lineto */ + /* (line_length is zero for curves) */ + if ( line_length == 0 ) + { + PVG_FT_Vector_From_Polar( &delta, + radius, + stroker->angle_out + rotate ); + + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + } + } + else /* this is a miter (intersection) */ + { + PVG_FT_Fixed length; + PVG_FT_Vector delta; + + + length = PVG_FT_MulDiv( stroker->radius, stroker->miter_limit, sigma.x ); + + PVG_FT_Vector_From_Polar( &delta, length, phi ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* now add an end point; only needed if not lineto */ + /* (line_length is zero for curves) */ + if ( line_length == 0 ) + { + PVG_FT_Vector_From_Polar( &delta, + stroker->radius, + stroker->angle_out + rotate ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + } + } + + Exit: + return error; +} + +static PVG_FT_Error ft_stroker_process_corner(PVG_FT_Stroker stroker, + PVG_FT_Fixed line_length) +{ + PVG_FT_Error error = 0; + PVG_FT_Angle turn; + PVG_FT_Int inside_side; + + turn = PVG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + /* no specific corner processing is required if the turn is 0 */ + if (turn == 0) goto Exit; + + /* when we turn to the right, the inside side is 0 */ + inside_side = 0; + + /* otherwise, the inside side is 1 */ + if (turn < 0) inside_side = 1; + + /* process the inside side */ + error = ft_stroker_inside(stroker, inside_side, line_length); + if (error) goto Exit; + + /* process the outside side */ + error = ft_stroker_outside(stroker, 1 - inside_side, line_length); + +Exit: + return error; +} + +/* add two points to the left and right borders corresponding to the */ +/* start of the subpath */ +static PVG_FT_Error ft_stroker_subpath_start(PVG_FT_Stroker stroker, + PVG_FT_Angle start_angle, + PVG_FT_Fixed line_length) +{ + PVG_FT_Vector delta; + PVG_FT_Vector point; + PVG_FT_Error error; + PVG_FT_StrokeBorder border; + + PVG_FT_Vector_From_Polar(&delta, stroker->radius, + start_angle + PVG_FT_ANGLE_PI2); + + point.x = stroker->center.x + delta.x; + point.y = stroker->center.y + delta.y; + + border = stroker->borders; + error = ft_stroke_border_moveto(border, &point); + if (error) goto Exit; + + point.x = stroker->center.x - delta.x; + point.y = stroker->center.y - delta.y; + + border++; + error = ft_stroke_border_moveto(border, &point); + + /* save angle, position, and line length for last join */ + /* (line_length is zero for curves) */ + stroker->subpath_angle = start_angle; + stroker->first_point = FALSE; + stroker->subpath_line_length = line_length; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +PVG_FT_Error PVG_FT_Stroker_LineTo(PVG_FT_Stroker stroker, PVG_FT_Vector* to) +{ + PVG_FT_Error error = 0; + PVG_FT_StrokeBorder border; + PVG_FT_Vector delta; + PVG_FT_Angle angle; + PVG_FT_Int side; + PVG_FT_Fixed line_length; + + delta.x = to->x - stroker->center.x; + delta.y = to->y - stroker->center.y; + + /* a zero-length lineto is a no-op; avoid creating a spurious corner */ + if (delta.x == 0 && delta.y == 0) goto Exit; + + /* compute length of line */ + line_length = PVG_FT_Vector_Length(&delta); + + angle = PVG_FT_Atan2(delta.x, delta.y); + PVG_FT_Vector_From_Polar(&delta, stroker->radius, angle + PVG_FT_ANGLE_PI2); + + /* process corner if necessary */ + if (stroker->first_point) { + /* This is the first segment of a subpath. We need to */ + /* add a point to each border at their respective starting */ + /* point locations. */ + error = ft_stroker_subpath_start(stroker, angle, line_length); + if (error) goto Exit; + } else { + /* process the current corner */ + stroker->angle_out = angle; + error = ft_stroker_process_corner(stroker, line_length); + if (error) goto Exit; + } + + /* now add a line segment to both the `inside' and `outside' paths */ + for (border = stroker->borders, side = 1; side >= 0; side--, border++) { + PVG_FT_Vector point; + + point.x = to->x + delta.x; + point.y = to->y + delta.y; + + /* the ends of lineto borders are movable */ + error = ft_stroke_border_lineto(border, &point, TRUE); + if (error) goto Exit; + + delta.x = -delta.x; + delta.y = -delta.y; + } + + stroker->angle_in = angle; + stroker->center = *to; + stroker->line_length = line_length; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +PVG_FT_Error PVG_FT_Stroker_ConicTo(PVG_FT_Stroker stroker, PVG_FT_Vector* control, + PVG_FT_Vector* to) +{ + PVG_FT_Error error = 0; + PVG_FT_Vector bez_stack[34]; + PVG_FT_Vector* arc; + PVG_FT_Vector* limit = bez_stack + 30; + PVG_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (PVG_FT_IS_SMALL(stroker->center.x - control->x) && + PVG_FT_IS_SMALL(stroker->center.y - control->y) && + PVG_FT_IS_SMALL(control->x - to->x) && + PVG_FT_IS_SMALL(control->y - to->y)) { + stroker->center = *to; + goto Exit; + } + + arc = bez_stack; + arc[0] = *to; + arc[1] = *control; + arc[2] = stroker->center; + + while (arc >= bez_stack) { + PVG_FT_Angle angle_in, angle_out; + + /* initialize with current direction */ + angle_in = angle_out = stroker->angle_in; + + if (arc < limit && + !ft_conic_is_small_enough(arc, &angle_in, &angle_out)) { + if (stroker->first_point) stroker->angle_in = angle_in; + + ft_conic_split(arc); + arc += 2; + continue; + } + + if (first_arc) { + first_arc = FALSE; + + /* process corner if necessary */ + if (stroker->first_point) + error = ft_stroker_subpath_start(stroker, angle_in, 0); + else { + stroker->angle_out = angle_in; + error = ft_stroker_process_corner(stroker, 0); + } + } else if (ft_pos_abs(PVG_FT_Angle_Diff(stroker->angle_in, angle_in)) > + PVG_FT_SMALL_CONIC_THRESHOLD / 4) { + /* if the deviation from one arc to the next is too great, */ + /* add a round corner */ + stroker->center = arc[2]; + stroker->angle_out = angle_in; + stroker->line_join = PVG_FT_STROKER_LINEJOIN_ROUND; + + error = ft_stroker_process_corner(stroker, 0); + + /* reinstate line join style */ + stroker->line_join = stroker->line_join_saved; + } + + if (error) goto Exit; + + /* the arc's angle is small enough; we can add it directly to each */ + /* border */ + { + PVG_FT_Vector ctrl, end; + PVG_FT_Angle theta, phi, rotate, alpha0 = 0; + PVG_FT_Fixed length; + PVG_FT_StrokeBorder border; + PVG_FT_Int side; + + theta = PVG_FT_Angle_Diff(angle_in, angle_out) / 2; + phi = angle_in + theta; + length = PVG_FT_DivFix(stroker->radius, PVG_FT_Cos(theta)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = PVG_FT_Atan2(arc[0].x - arc[2].x, arc[0].y - arc[2].y); + + for (border = stroker->borders, side = 0; side <= 1; + side++, border++) { + rotate = PVG_FT_SIDE_TO_ROTATE(side); + + /* compute control point */ + PVG_FT_Vector_From_Polar(&ctrl, length, phi + rotate); + ctrl.x += arc[1].x; + ctrl.y += arc[1].y; + + /* compute end point */ + PVG_FT_Vector_From_Polar(&end, stroker->radius, + angle_out + rotate); + end.x += arc[0].x; + end.y += arc[0].y; + + if (stroker->handle_wide_strokes) { + PVG_FT_Vector start; + PVG_FT_Angle alpha1; + + /* determine whether the border radius is greater than the + */ + /* radius of curvature of the original arc */ + start = border->points[border->num_points - 1]; + + alpha1 = PVG_FT_Atan2(end.x - start.x, end.y - start.y); + + /* is the direction of the border arc opposite to */ + /* that of the original arc? */ + if (ft_pos_abs(PVG_FT_Angle_Diff(alpha0, alpha1)) > + PVG_FT_ANGLE_PI / 2) { + PVG_FT_Angle beta, gamma; + PVG_FT_Vector bvec, delta; + PVG_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + PVG_FT_Atan2(arc[2].x - start.x, arc[2].y - start.y); + gamma = PVG_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); + + bvec.x = end.x - start.x; + bvec.y = end.y - start.y; + + blen = PVG_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(PVG_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(PVG_FT_Sin(beta - gamma)); + + alen = PVG_FT_MulDiv(blen, sinA, sinB); + + PVG_FT_Vector_From_Polar(&delta, alen, beta); + delta.x += start.x; + delta.y += start.y; + + /* circumnavigate the negative sector backwards */ + border->movable = FALSE; + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + error = ft_stroke_border_conicto(border, &ctrl, &start); + if (error) goto Exit; + /* and then move to the endpoint */ + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + + continue; + } + + /* else fall through */ + } + + /* simply add an arc */ + error = ft_stroke_border_conicto(border, &ctrl, &end); + if (error) goto Exit; + } + } + + arc -= 2; + + stroker->angle_in = angle_out; + } + + stroker->center = *to; + stroker->line_length = 0; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +PVG_FT_Error PVG_FT_Stroker_CubicTo(PVG_FT_Stroker stroker, PVG_FT_Vector* control1, + PVG_FT_Vector* control2, PVG_FT_Vector* to) +{ + PVG_FT_Error error = 0; + PVG_FT_Vector bez_stack[37]; + PVG_FT_Vector* arc; + PVG_FT_Vector* limit = bez_stack + 32; + PVG_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (PVG_FT_IS_SMALL(stroker->center.x - control1->x) && + PVG_FT_IS_SMALL(stroker->center.y - control1->y) && + PVG_FT_IS_SMALL(control1->x - control2->x) && + PVG_FT_IS_SMALL(control1->y - control2->y) && + PVG_FT_IS_SMALL(control2->x - to->x) && + PVG_FT_IS_SMALL(control2->y - to->y)) { + stroker->center = *to; + goto Exit; + } + + arc = bez_stack; + arc[0] = *to; + arc[1] = *control2; + arc[2] = *control1; + arc[3] = stroker->center; + + while (arc >= bez_stack) { + PVG_FT_Angle angle_in, angle_mid, angle_out; + + /* initialize with current direction */ + angle_in = angle_out = angle_mid = stroker->angle_in; + + if (arc < limit && + !ft_cubic_is_small_enough(arc, &angle_in, &angle_mid, &angle_out)) { + if (stroker->first_point) stroker->angle_in = angle_in; + + ft_cubic_split(arc); + arc += 3; + continue; + } + + if (first_arc) { + first_arc = FALSE; + + /* process corner if necessary */ + if (stroker->first_point) + error = ft_stroker_subpath_start(stroker, angle_in, 0); + else { + stroker->angle_out = angle_in; + error = ft_stroker_process_corner(stroker, 0); + } + } else if (ft_pos_abs(PVG_FT_Angle_Diff(stroker->angle_in, angle_in)) > + PVG_FT_SMALL_CUBIC_THRESHOLD / 4) { + /* if the deviation from one arc to the next is too great, */ + /* add a round corner */ + stroker->center = arc[3]; + stroker->angle_out = angle_in; + stroker->line_join = PVG_FT_STROKER_LINEJOIN_ROUND; + + error = ft_stroker_process_corner(stroker, 0); + + /* reinstate line join style */ + stroker->line_join = stroker->line_join_saved; + } + + if (error) goto Exit; + + /* the arc's angle is small enough; we can add it directly to each */ + /* border */ + { + PVG_FT_Vector ctrl1, ctrl2, end; + PVG_FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0; + PVG_FT_Fixed length1, length2; + PVG_FT_StrokeBorder border; + PVG_FT_Int side; + + theta1 = PVG_FT_Angle_Diff(angle_in, angle_mid) / 2; + theta2 = PVG_FT_Angle_Diff(angle_mid, angle_out) / 2; + phi1 = ft_angle_mean(angle_in, angle_mid); + phi2 = ft_angle_mean(angle_mid, angle_out); + length1 = PVG_FT_DivFix(stroker->radius, PVG_FT_Cos(theta1)); + length2 = PVG_FT_DivFix(stroker->radius, PVG_FT_Cos(theta2)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = PVG_FT_Atan2(arc[0].x - arc[3].x, arc[0].y - arc[3].y); + + for (border = stroker->borders, side = 0; side <= 1; + side++, border++) { + rotate = PVG_FT_SIDE_TO_ROTATE(side); + + /* compute control points */ + PVG_FT_Vector_From_Polar(&ctrl1, length1, phi1 + rotate); + ctrl1.x += arc[2].x; + ctrl1.y += arc[2].y; + + PVG_FT_Vector_From_Polar(&ctrl2, length2, phi2 + rotate); + ctrl2.x += arc[1].x; + ctrl2.y += arc[1].y; + + /* compute end point */ + PVG_FT_Vector_From_Polar(&end, stroker->radius, + angle_out + rotate); + end.x += arc[0].x; + end.y += arc[0].y; + + if (stroker->handle_wide_strokes) { + PVG_FT_Vector start; + PVG_FT_Angle alpha1; + + /* determine whether the border radius is greater than the + */ + /* radius of curvature of the original arc */ + start = border->points[border->num_points - 1]; + + alpha1 = PVG_FT_Atan2(end.x - start.x, end.y - start.y); + + /* is the direction of the border arc opposite to */ + /* that of the original arc? */ + if (ft_pos_abs(PVG_FT_Angle_Diff(alpha0, alpha1)) > + PVG_FT_ANGLE_PI / 2) { + PVG_FT_Angle beta, gamma; + PVG_FT_Vector bvec, delta; + PVG_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + PVG_FT_Atan2(arc[3].x - start.x, arc[3].y - start.y); + gamma = PVG_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); + + bvec.x = end.x - start.x; + bvec.y = end.y - start.y; + + blen = PVG_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(PVG_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(PVG_FT_Sin(beta - gamma)); + + alen = PVG_FT_MulDiv(blen, sinA, sinB); + + PVG_FT_Vector_From_Polar(&delta, alen, beta); + delta.x += start.x; + delta.y += start.y; + + /* circumnavigate the negative sector backwards */ + border->movable = FALSE; + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + error = ft_stroke_border_cubicto(border, &ctrl2, &ctrl1, + &start); + if (error) goto Exit; + /* and then move to the endpoint */ + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + + continue; + } + + /* else fall through */ + } + + /* simply add an arc */ + error = ft_stroke_border_cubicto(border, &ctrl1, &ctrl2, &end); + if (error) goto Exit; + } + } + + arc -= 3; + + stroker->angle_in = angle_out; + } + + stroker->center = *to; + stroker->line_length = 0; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +PVG_FT_Error PVG_FT_Stroker_BeginSubPath(PVG_FT_Stroker stroker, PVG_FT_Vector* to, + PVG_FT_Bool open) +{ + /* We cannot process the first point, because there is not enough */ + /* information regarding its corner/cap. The latter will be processed */ + /* in the `PVG_FT_Stroker_EndSubPath' routine. */ + /* */ + stroker->first_point = TRUE; + stroker->center = *to; + stroker->subpath_open = open; + + /* Determine if we need to check whether the border radius is greater */ + /* than the radius of curvature of a curve, to handle this case */ + /* specially. This is only required if bevel joins or butt caps may */ + /* be created, because round & miter joins and round & square caps */ + /* cover the negative sector created with wide strokes. */ + stroker->handle_wide_strokes = + PVG_FT_BOOL(stroker->line_join != PVG_FT_STROKER_LINEJOIN_ROUND || + (stroker->subpath_open && + stroker->line_cap == PVG_FT_STROKER_LINECAP_BUTT)); + + /* record the subpath start point for each border */ + stroker->subpath_start = *to; + + stroker->angle_in = 0; + + return 0; +} + +static PVG_FT_Error ft_stroker_add_reverse_left(PVG_FT_Stroker stroker, + PVG_FT_Bool open) +{ + PVG_FT_StrokeBorder right = stroker->borders + 0; + PVG_FT_StrokeBorder left = stroker->borders + 1; + PVG_FT_Int new_points; + PVG_FT_Error error = 0; + + assert(left->start >= 0); + + new_points = left->num_points - left->start; + if (new_points > 0) { + error = ft_stroke_border_grow(right, (PVG_FT_UInt)new_points); + if (error) goto Exit; + + { + PVG_FT_Vector* dst_point = right->points + right->num_points; + PVG_FT_Byte* dst_tag = right->tags + right->num_points; + PVG_FT_Vector* src_point = left->points + left->num_points - 1; + PVG_FT_Byte* src_tag = left->tags + left->num_points - 1; + + while (src_point >= left->points + left->start) { + *dst_point = *src_point; + *dst_tag = *src_tag; + + if (open) + dst_tag[0] &= ~PVG_FT_STROKE_TAG_BEGIN_END; + else { + PVG_FT_Byte ttag = + (PVG_FT_Byte)(dst_tag[0] & PVG_FT_STROKE_TAG_BEGIN_END); + + /* switch begin/end tags if necessary */ + if (ttag == PVG_FT_STROKE_TAG_BEGIN || + ttag == PVG_FT_STROKE_TAG_END) + dst_tag[0] ^= PVG_FT_STROKE_TAG_BEGIN_END; + } + + src_point--; + src_tag--; + dst_point++; + dst_tag++; + } + } + + left->num_points = left->start; + right->num_points += new_points; + + right->movable = FALSE; + left->movable = FALSE; + } + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +/* there's a lot of magic in this function! */ +PVG_FT_Error PVG_FT_Stroker_EndSubPath(PVG_FT_Stroker stroker) +{ + PVG_FT_Error error = 0; + + if (stroker->subpath_open) { + PVG_FT_StrokeBorder right = stroker->borders; + + /* All right, this is an opened path, we need to add a cap between */ + /* right & left, add the reverse of left, then add a final cap */ + /* between left & right. */ + error = ft_stroker_cap(stroker, stroker->angle_in, 0); + if (error) goto Exit; + + /* add reversed points from `left' to `right' */ + error = ft_stroker_add_reverse_left(stroker, TRUE); + if (error) goto Exit; + + /* now add the final cap */ + stroker->center = stroker->subpath_start; + error = + ft_stroker_cap(stroker, stroker->subpath_angle + PVG_FT_ANGLE_PI, 0); + if (error) goto Exit; + + /* Now end the right subpath accordingly. The left one is */ + /* rewind and doesn't need further processing. */ + ft_stroke_border_close(right, FALSE); + } else { + PVG_FT_Angle turn; + PVG_FT_Int inside_side; + + /* close the path if needed */ + if (stroker->center.x != stroker->subpath_start.x || + stroker->center.y != stroker->subpath_start.y) { + error = PVG_FT_Stroker_LineTo(stroker, &stroker->subpath_start); + if (error) goto Exit; + } + + /* process the corner */ + stroker->angle_out = stroker->subpath_angle; + turn = PVG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + /* no specific corner processing is required if the turn is 0 */ + if (turn != 0) { + /* when we turn to the right, the inside side is 0 */ + inside_side = 0; + + /* otherwise, the inside side is 1 */ + if (turn < 0) inside_side = 1; + + error = ft_stroker_inside(stroker, inside_side, + stroker->subpath_line_length); + if (error) goto Exit; + + /* process the outside side */ + error = ft_stroker_outside(stroker, 1 - inside_side, + stroker->subpath_line_length); + if (error) goto Exit; + } + + /* then end our two subpaths */ + ft_stroke_border_close(stroker->borders + 0, FALSE); + ft_stroke_border_close(stroker->borders + 1, TRUE); + } + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +PVG_FT_Error PVG_FT_Stroker_GetBorderCounts(PVG_FT_Stroker stroker, + PVG_FT_StrokerBorder border, + PVG_FT_UInt* anum_points, + PVG_FT_UInt* anum_contours) +{ + PVG_FT_UInt num_points = 0, num_contours = 0; + PVG_FT_Error error; + + if (!stroker || border > 1) { + error = -1; // PVG_FT_THROW( Invalid_Argument ); + goto Exit; + } + + error = ft_stroke_border_get_counts(stroker->borders + border, &num_points, + &num_contours); +Exit: + if (anum_points) *anum_points = num_points; + + if (anum_contours) *anum_contours = num_contours; + + return error; +} + +/* documentation is in ftstroke.h */ + +PVG_FT_Error PVG_FT_Stroker_GetCounts(PVG_FT_Stroker stroker, + PVG_FT_UInt* anum_points, + PVG_FT_UInt* anum_contours) +{ + PVG_FT_UInt count1, count2, num_points = 0; + PVG_FT_UInt count3, count4, num_contours = 0; + PVG_FT_Error error; + + error = ft_stroke_border_get_counts(stroker->borders + 0, &count1, &count2); + if (error) goto Exit; + + error = ft_stroke_border_get_counts(stroker->borders + 1, &count3, &count4); + if (error) goto Exit; + + num_points = count1 + count3; + num_contours = count2 + count4; + +Exit: + *anum_points = num_points; + *anum_contours = num_contours; + return error; +} + +/* documentation is in ftstroke.h */ + +void PVG_FT_Stroker_ExportBorder(PVG_FT_Stroker stroker, + PVG_FT_StrokerBorder border, + PVG_FT_Outline* outline) +{ + if (border == PVG_FT_STROKER_BORDER_LEFT || + border == PVG_FT_STROKER_BORDER_RIGHT) { + PVG_FT_StrokeBorder sborder = &stroker->borders[border]; + + if (sborder->valid) ft_stroke_border_export(sborder, outline); + } +} + +/* documentation is in ftstroke.h */ + +void PVG_FT_Stroker_Export(PVG_FT_Stroker stroker, PVG_FT_Outline* outline) +{ + PVG_FT_Stroker_ExportBorder(stroker, PVG_FT_STROKER_BORDER_LEFT, outline); + PVG_FT_Stroker_ExportBorder(stroker, PVG_FT_STROKER_BORDER_RIGHT, outline); +} + +/* documentation is in ftstroke.h */ + +/* + * The following is very similar to PVG_FT_Outline_Decompose, except + * that we do support opened paths, and do not scale the outline. + */ +PVG_FT_Error PVG_FT_Stroker_ParseOutline(PVG_FT_Stroker stroker, + const PVG_FT_Outline* outline) +{ + PVG_FT_Vector v_last; + PVG_FT_Vector v_control; + PVG_FT_Vector v_start; + + PVG_FT_Vector* point; + PVG_FT_Vector* limit; + char* tags; + + PVG_FT_Error error; + + PVG_FT_Int n; /* index of contour in outline */ + PVG_FT_UInt first; /* index of first point in contour */ + PVG_FT_Int tag; /* current point's state */ + + if (!outline || !stroker) return -1; // PVG_FT_THROW( Invalid_Argument ); + + PVG_FT_Stroker_Rewind(stroker); + + first = 0; + + for (n = 0; n < outline->n_contours; n++) { + PVG_FT_UInt last; /* index of last point in contour */ + + last = outline->contours[n]; + limit = outline->points + last; + + /* skip empty points; we don't stroke these */ + if (last <= first) { + first = last + 1; + continue; + } + + v_start = outline->points[first]; + v_last = outline->points[last]; + + v_control = v_start; + + point = outline->points + first; + tags = outline->tags + first; + tag = PVG_FT_CURVE_TAG(tags[0]); + + /* A contour cannot start with a cubic control point! */ + if (tag == PVG_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; + + /* check first point to determine origin */ + if (tag == PVG_FT_CURVE_TAG_CONIC) { + /* First point is conic control. Yes, this happens. */ + if (PVG_FT_CURVE_TAG(outline->tags[last]) == PVG_FT_CURVE_TAG_ON) { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } else { + /* if both first and last points are conic, */ + /* start at their middle */ + v_start.x = (v_start.x + v_last.x) / 2; + v_start.y = (v_start.y + v_last.y) / 2; + } + point--; + tags--; + } + + error = PVG_FT_Stroker_BeginSubPath(stroker, &v_start, outline->contours_flag[n]); + if (error) goto Exit; + + while (point < limit) { + point++; + tags++; + + tag = PVG_FT_CURVE_TAG(tags[0]); + switch (tag) { + case PVG_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + PVG_FT_Vector vec; + + vec.x = point->x; + vec.y = point->y; + + error = PVG_FT_Stroker_LineTo(stroker, &vec); + if (error) goto Exit; + continue; + } + + case PVG_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + v_control.x = point->x; + v_control.y = point->y; + + Do_Conic: + if (point < limit) { + PVG_FT_Vector vec; + PVG_FT_Vector v_middle; + + point++; + tags++; + tag = PVG_FT_CURVE_TAG(tags[0]); + + vec = point[0]; + + if (tag == PVG_FT_CURVE_TAG_ON) { + error = + PVG_FT_Stroker_ConicTo(stroker, &v_control, &vec); + if (error) goto Exit; + continue; + } + + if (tag != PVG_FT_CURVE_TAG_CONIC) goto Invalid_Outline; + + v_middle.x = (v_control.x + vec.x) / 2; + v_middle.y = (v_control.y + vec.y) / 2; + + error = + PVG_FT_Stroker_ConicTo(stroker, &v_control, &v_middle); + if (error) goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = PVG_FT_Stroker_ConicTo(stroker, &v_control, &v_start); + goto Close; + + default: /* PVG_FT_CURVE_TAG_CUBIC */ + { + PVG_FT_Vector vec1, vec2; + + if (point + 1 > limit || + PVG_FT_CURVE_TAG(tags[1]) != PVG_FT_CURVE_TAG_CUBIC) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1 = point[-2]; + vec2 = point[-1]; + + if (point <= limit) { + PVG_FT_Vector vec; + + vec = point[0]; + + error = PVG_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &vec); + if (error) goto Exit; + continue; + } + + error = PVG_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &v_start); + goto Close; + } + } + } + + Close: + if (error) goto Exit; + + if (stroker->first_point) { + stroker->subpath_open = TRUE; + error = ft_stroker_subpath_start(stroker, 0, 0); + if (error) goto Exit; + } + + error = PVG_FT_Stroker_EndSubPath(stroker); + if (error) goto Exit; + + first = last + 1; + } + + return 0; + +Exit: + return error; + +Invalid_Outline: + return -2; // PVG_FT_THROW( Invalid_Outline ); +} + +/* END */ diff --git a/vendor/lunasvg/plutovg/source/plutovg-ft-stroker.h b/vendor/lunasvg/plutovg/source/plutovg-ft-stroker.h new file mode 100644 index 0000000..530a6f2 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-ft-stroker.h @@ -0,0 +1,320 @@ +/***************************************************************************/ +/* */ +/* ftstroke.h */ +/* */ +/* FreeType path stroker (specification). */ +/* */ +/* Copyright 2002-2006, 2008, 2009, 2011-2012 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, FTL.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#ifndef PLUTOVG_FT_STROKER_H +#define PLUTOVG_FT_STROKER_H + +#include "plutovg-ft-raster.h" + +/************************************************************** + * + * @type: + * PVG_FT_Stroker + * + * @description: + * Opaque handler to a path stroker object. + */ +typedef struct PVG_FT_StrokerRec_* PVG_FT_Stroker; + + +/************************************************************** + * + * @enum: + * PVG_FT_Stroker_LineJoin + * + * @description: + * These values determine how two joining lines are rendered + * in a stroker. + * + * @values: + * PVG_FT_STROKER_LINEJOIN_ROUND :: + * Used to render rounded line joins. Circular arcs are used + * to join two lines smoothly. + * + * PVG_FT_STROKER_LINEJOIN_BEVEL :: + * Used to render beveled line joins. The outer corner of + * the joined lines is filled by enclosing the triangular + * region of the corner with a straight line between the + * outer corners of each stroke. + * + * PVG_FT_STROKER_LINEJOIN_MITER_FIXED :: + * Used to render mitered line joins, with fixed bevels if the + * miter limit is exceeded. The outer edges of the strokes + * for the two segments are extended until they meet at an + * angle. If the segments meet at too sharp an angle (such + * that the miter would extend from the intersection of the + * segments a distance greater than the product of the miter + * limit value and the border radius), then a bevel join (see + * above) is used instead. This prevents long spikes being + * created. PVG_FT_STROKER_LINEJOIN_MITER_FIXED generates a miter + * line join as used in PostScript and PDF. + * + * PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE :: + * PVG_FT_STROKER_LINEJOIN_MITER :: + * Used to render mitered line joins, with variable bevels if + * the miter limit is exceeded. The intersection of the + * strokes is clipped at a line perpendicular to the bisector + * of the angle between the strokes, at the distance from the + * intersection of the segments equal to the product of the + * miter limit value and the border radius. This prevents + * long spikes being created. + * PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE generates a mitered line + * join as used in XPS. PVG_FT_STROKER_LINEJOIN_MITER is an alias + * for PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE, retained for + * backwards compatibility. + */ +typedef enum PVG_FT_Stroker_LineJoin_ +{ + PVG_FT_STROKER_LINEJOIN_ROUND = 0, + PVG_FT_STROKER_LINEJOIN_BEVEL = 1, + PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE = 2, + PVG_FT_STROKER_LINEJOIN_MITER = PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE, + PVG_FT_STROKER_LINEJOIN_MITER_FIXED = 3 + +} PVG_FT_Stroker_LineJoin; + + +/************************************************************** + * + * @enum: + * PVG_FT_Stroker_LineCap + * + * @description: + * These values determine how the end of opened sub-paths are + * rendered in a stroke. + * + * @values: + * PVG_FT_STROKER_LINECAP_BUTT :: + * The end of lines is rendered as a full stop on the last + * point itself. + * + * PVG_FT_STROKER_LINECAP_ROUND :: + * The end of lines is rendered as a half-circle around the + * last point. + * + * PVG_FT_STROKER_LINECAP_SQUARE :: + * The end of lines is rendered as a square around the + * last point. + */ +typedef enum PVG_FT_Stroker_LineCap_ +{ + PVG_FT_STROKER_LINECAP_BUTT = 0, + PVG_FT_STROKER_LINECAP_ROUND, + PVG_FT_STROKER_LINECAP_SQUARE + +} PVG_FT_Stroker_LineCap; + + +/************************************************************** + * + * @enum: + * PVG_FT_StrokerBorder + * + * @description: + * These values are used to select a given stroke border + * in @PVG_FT_Stroker_GetBorderCounts and @PVG_FT_Stroker_ExportBorder. + * + * @values: + * PVG_FT_STROKER_BORDER_LEFT :: + * Select the left border, relative to the drawing direction. + * + * PVG_FT_STROKER_BORDER_RIGHT :: + * Select the right border, relative to the drawing direction. + * + * @note: + * Applications are generally interested in the `inside' and `outside' + * borders. However, there is no direct mapping between these and the + * `left' and `right' ones, since this really depends on the glyph's + * drawing orientation, which varies between font formats. + * + * You can however use @PVG_FT_Outline_GetInsideBorder and + * @PVG_FT_Outline_GetOutsideBorder to get these. + */ +typedef enum PVG_FT_StrokerBorder_ +{ + PVG_FT_STROKER_BORDER_LEFT = 0, + PVG_FT_STROKER_BORDER_RIGHT + +} PVG_FT_StrokerBorder; + + +/************************************************************** + * + * @function: + * PVG_FT_Stroker_New + * + * @description: + * Create a new stroker object. + * + * @input: + * library :: + * FreeType library handle. + * + * @output: + * astroker :: + * A new stroker object handle. NULL in case of error. + * + * @return: + * FreeType error code. 0~means success. + */ +PVG_FT_Error +PVG_FT_Stroker_New( PVG_FT_Stroker *astroker ); + + +/************************************************************** + * + * @function: + * PVG_FT_Stroker_Set + * + * @description: + * Reset a stroker object's attributes. + * + * @input: + * stroker :: + * The target stroker handle. + * + * radius :: + * The border radius. + * + * line_cap :: + * The line cap style. + * + * line_join :: + * The line join style. + * + * miter_limit :: + * The miter limit for the PVG_FT_STROKER_LINEJOIN_MITER_FIXED and + * PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE line join styles, + * expressed as 16.16 fixed-point value. + * + * @note: + * The radius is expressed in the same units as the outline + * coordinates. + */ +void +PVG_FT_Stroker_Set( PVG_FT_Stroker stroker, + PVG_FT_Fixed radius, + PVG_FT_Stroker_LineCap line_cap, + PVG_FT_Stroker_LineJoin line_join, + PVG_FT_Fixed miter_limit ); + +/************************************************************** + * + * @function: + * PVG_FT_Stroker_ParseOutline + * + * @description: + * A convenience function used to parse a whole outline with + * the stroker. The resulting outline(s) can be retrieved + * later by functions like @PVG_FT_Stroker_GetCounts and @PVG_FT_Stroker_Export. + * + * @input: + * stroker :: + * The target stroker handle. + * + * outline :: + * The source outline. + * + * + * @return: + * FreeType error code. 0~means success. + * + * @note: + * If `opened' is~0 (the default), the outline is treated as a closed + * path, and the stroker generates two distinct `border' outlines. + * + * + * This function calls @PVG_FT_Stroker_Rewind automatically. + */ +PVG_FT_Error +PVG_FT_Stroker_ParseOutline( PVG_FT_Stroker stroker, + const PVG_FT_Outline* outline); + + +/************************************************************** + * + * @function: + * PVG_FT_Stroker_GetCounts + * + * @description: + * Call this function once you have finished parsing your paths + * with the stroker. It returns the number of points and + * contours necessary to export all points/borders from the stroked + * outline/path. + * + * @input: + * stroker :: + * The target stroker handle. + * + * @output: + * anum_points :: + * The number of points. + * + * anum_contours :: + * The number of contours. + * + * @return: + * FreeType error code. 0~means success. + */ +PVG_FT_Error +PVG_FT_Stroker_GetCounts( PVG_FT_Stroker stroker, + PVG_FT_UInt *anum_points, + PVG_FT_UInt *anum_contours ); + + +/************************************************************** + * + * @function: + * PVG_FT_Stroker_Export + * + * @description: + * Call this function after @PVG_FT_Stroker_GetBorderCounts to + * export all borders to your own @PVG_FT_Outline structure. + * + * Note that this function appends the border points and + * contours to your outline, but does not try to resize its + * arrays. + * + * @input: + * stroker :: + * The target stroker handle. + * + * outline :: + * The target outline handle. + */ +void +PVG_FT_Stroker_Export( PVG_FT_Stroker stroker, + PVG_FT_Outline* outline ); + + +/************************************************************** + * + * @function: + * PVG_FT_Stroker_Done + * + * @description: + * Destroy a stroker object. + * + * @input: + * stroker :: + * A stroker handle. Can be NULL. + */ +void +PVG_FT_Stroker_Done( PVG_FT_Stroker stroker ); + + +#endif // PLUTOVG_FT_STROKER_H diff --git a/vendor/lunasvg/plutovg/source/plutovg-ft-types.h b/vendor/lunasvg/plutovg/source/plutovg-ft-types.h new file mode 100644 index 0000000..4b3db90 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-ft-types.h @@ -0,0 +1,176 @@ +/**************************************************************************** + * + * fttypes.h + * + * FreeType simple types definitions (specification only). + * + * Copyright (C) 1996-2020 by + * David Turner, Robert Wilhelm, and Werner Lemberg. + * + * This file is part of the FreeType project, and may only be used, + * modified, and distributed under the terms of the FreeType project + * license, FTL.TXT. By continuing to use, modify, or distribute + * this file you indicate that you have read the license and + * understand and accept it fully. + * + */ + +#ifndef PLUTOVG_FT_TYPES_H +#define PLUTOVG_FT_TYPES_H + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Fixed */ +/* */ +/* */ +/* This type is used to store 16.16 fixed-point values, like scaling */ +/* values or matrix coefficients. */ +/* */ +typedef signed long PVG_FT_Fixed; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Int */ +/* */ +/* */ +/* A typedef for the int type. */ +/* */ +typedef signed int PVG_FT_Int; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_UInt */ +/* */ +/* */ +/* A typedef for the unsigned int type. */ +/* */ +typedef unsigned int PVG_FT_UInt; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Long */ +/* */ +/* */ +/* A typedef for signed long. */ +/* */ +typedef signed long PVG_FT_Long; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_ULong */ +/* */ +/* */ +/* A typedef for unsigned long. */ +/* */ +typedef unsigned long PVG_FT_ULong; + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Short */ +/* */ +/* */ +/* A typedef for signed short. */ +/* */ +typedef signed short PVG_FT_Short; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Byte */ +/* */ +/* */ +/* A simple typedef for the _unsigned_ char type. */ +/* */ +typedef unsigned char PVG_FT_Byte; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Bool */ +/* */ +/* */ +/* A typedef of unsigned char, used for simple booleans. As usual, */ +/* values 1 and~0 represent true and false, respectively. */ +/* */ +typedef unsigned char PVG_FT_Bool; + + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Error */ +/* */ +/* */ +/* The FreeType error code type. A value of~0 is always interpreted */ +/* as a successful operation. */ +/* */ +typedef int PVG_FT_Error; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Pos */ +/* */ +/* */ +/* The type PVG_FT_Pos is used to store vectorial coordinates. Depending */ +/* on the context, these can represent distances in integer font */ +/* units, or 16.16, or 26.6 fixed-point pixel coordinates. */ +/* */ +typedef signed long PVG_FT_Pos; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Vector */ +/* */ +/* */ +/* A simple structure used to store a 2D vector; coordinates are of */ +/* the PVG_FT_Pos type. */ +/* */ +/* */ +/* x :: The horizontal coordinate. */ +/* y :: The vertical coordinate. */ +/* */ +typedef struct PVG_FT_Vector_ +{ + PVG_FT_Pos x; + PVG_FT_Pos y; + +} PVG_FT_Vector; + + +typedef long long int PVG_FT_Int64; +typedef unsigned long long int PVG_FT_UInt64; + +typedef signed int PVG_FT_Int32; +typedef unsigned int PVG_FT_UInt32; + +#define PVG_FT_BOOL( x ) ( (PVG_FT_Bool)( x ) ) + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#define PVG_FT_BEGIN_STMNT do { +#define PVG_FT_END_STMNT } while (0) + +#endif // PLUTOVG_FT_TYPES_H diff --git a/vendor/lunasvg/plutovg/source/plutovg-matrix.c b/vendor/lunasvg/plutovg/source/plutovg-matrix.c new file mode 100644 index 0000000..d784d7a --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-matrix.c @@ -0,0 +1,229 @@ +#include "plutovg.h" +#include "plutovg-utils.h" + +void plutovg_matrix_init(plutovg_matrix_t* matrix, float a, float b, float c, float d, float e, float f) +{ + matrix->a = a; matrix->b = b; + matrix->c = c; matrix->d = d; + matrix->e = e; matrix->f = f; +} + +void plutovg_matrix_init_identity(plutovg_matrix_t* matrix) +{ + plutovg_matrix_init(matrix, 1, 0, 0, 1, 0, 0); +} + +void plutovg_matrix_init_translate(plutovg_matrix_t* matrix, float tx, float ty) +{ + plutovg_matrix_init(matrix, 1, 0, 0, 1, tx, ty); +} + +void plutovg_matrix_init_scale(plutovg_matrix_t* matrix, float sx, float sy) +{ + plutovg_matrix_init(matrix, sx, 0, 0, sy, 0, 0); +} + +void plutovg_matrix_init_rotate(plutovg_matrix_t* matrix, float angle) +{ + float c = cosf(angle); + float s = sinf(angle); + plutovg_matrix_init(matrix, c, s, -s, c, 0, 0); +} + +void plutovg_matrix_init_shear(plutovg_matrix_t* matrix, float shx, float shy) +{ + plutovg_matrix_init(matrix, 1, tanf(shy), tanf(shx), 1, 0, 0); +} + +void plutovg_matrix_translate(plutovg_matrix_t* matrix, float tx, float ty) +{ + plutovg_matrix_t m; + plutovg_matrix_init_translate(&m, tx, ty); + plutovg_matrix_multiply(matrix, &m, matrix); +} + +void plutovg_matrix_scale(plutovg_matrix_t* matrix, float sx, float sy) +{ + plutovg_matrix_t m; + plutovg_matrix_init_scale(&m, sx, sy); + plutovg_matrix_multiply(matrix, &m, matrix); +} + +void plutovg_matrix_rotate(plutovg_matrix_t* matrix, float angle) +{ + plutovg_matrix_t m; + plutovg_matrix_init_rotate(&m, angle); + plutovg_matrix_multiply(matrix, &m, matrix); +} + +void plutovg_matrix_shear(plutovg_matrix_t* matrix, float shx, float shy) +{ + plutovg_matrix_t m; + plutovg_matrix_init_shear(&m, shx, shy); + plutovg_matrix_multiply(matrix, &m, matrix); +} + +void plutovg_matrix_multiply(plutovg_matrix_t* matrix, const plutovg_matrix_t* left, const plutovg_matrix_t* right) +{ + float a = left->a * right->a + left->b * right->c; + float b = left->a * right->b + left->b * right->d; + float c = left->c * right->a + left->d * right->c; + float d = left->c * right->b + left->d * right->d; + float e = left->e * right->a + left->f * right->c + right->e; + float f = left->e * right->b + left->f * right->d + right->f; + plutovg_matrix_init(matrix, a, b, c, d, e, f); +} + +bool plutovg_matrix_invert(const plutovg_matrix_t* matrix, plutovg_matrix_t* inverse) +{ + float det = (matrix->a * matrix->d - matrix->b * matrix->c); + if(det == 0.f) + return false; + if(inverse) { + float inv_det = 1.f / det; + float a = matrix->a * inv_det; + float b = matrix->b * inv_det; + float c = matrix->c * inv_det; + float d = matrix->d * inv_det; + float e = (matrix->c * matrix->f - matrix->d * matrix->e) * inv_det; + float f = (matrix->b * matrix->e - matrix->a * matrix->f) * inv_det; + plutovg_matrix_init(inverse, d, -b, -c, a, e, f); + } + + return true; +} + +void plutovg_matrix_map(const plutovg_matrix_t* matrix, float x, float y, float* xx, float* yy) +{ + *xx = x * matrix->a + y * matrix->c + matrix->e; + *yy = x * matrix->b + y * matrix->d + matrix->f; +} + +void plutovg_matrix_map_point(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst) +{ + plutovg_matrix_map(matrix, src->x, src->y, &dst->x, &dst->y); +} + +void plutovg_matrix_map_points(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst, int count) +{ + for(int i = 0; i < count; ++i) { + plutovg_matrix_map_point(matrix, &src[i], &dst[i]); + } +} + +void plutovg_matrix_map_rect(const plutovg_matrix_t* matrix, const plutovg_rect_t* src, plutovg_rect_t* dst) +{ + plutovg_point_t p[4]; + p[0].x = src->x; + p[0].y = src->y; + p[1].x = src->x + src->w; + p[1].y = src->y; + p[2].x = src->x + src->w; + p[2].y = src->y + src->h; + p[3].x = src->x; + p[3].y = src->y + src->h; + plutovg_matrix_map_points(matrix, p, p, 4); + + float l = p[0].x; + float t = p[0].y; + float r = p[0].x; + float b = p[0].y; + + for(int i = 1; i < 4; i++) { + if(p[i].x < l) l = p[i].x; + if(p[i].x > r) r = p[i].x; + if(p[i].y < t) t = p[i].y; + if(p[i].y > b) b = p[i].y; + } + + dst->x = l; + dst->y = t; + dst->w = r - l; + dst->h = b - t; +} + +static int parse_matrix_parameters(const char** begin, const char* end, float values[6], int required, int optional) +{ + if(!plutovg_skip_ws_and_delim(begin, end, '(')) + return 0; + int count = 0; + int max_count = required + optional; + bool has_trailing_comma = false; + for(; count < max_count; ++count) { + if(!plutovg_parse_number(begin, end, values + count)) + break; + plutovg_skip_ws_or_comma(begin, end, &has_trailing_comma); + } + + if(!has_trailing_comma && (count == required || count == max_count) + && plutovg_skip_delim(begin, end, ')')) { + return count; + } + + return 0; +} + +bool plutovg_matrix_parse(plutovg_matrix_t* matrix, const char* data, int length) +{ + float values[6]; + plutovg_matrix_init_identity(matrix); + if(length == -1) + length = strlen(data); + const char* it = data; + const char* end = it + length; + bool has_trailing_comma = false; + plutovg_skip_ws(&it, end); + while(it < end) { + if(plutovg_skip_string(&it, end, "matrix")) { + int count = parse_matrix_parameters(&it, end, values, 6, 0); + if(count == 0) + return false; + plutovg_matrix_t m = { values[0], values[1], values[2], values[3], values[4], values[5] }; + plutovg_matrix_multiply(matrix, &m, matrix); + } else if(plutovg_skip_string(&it, end, "translate")) { + int count = parse_matrix_parameters(&it, end, values, 1, 1); + if(count == 0) + return false; + if(count == 1) { + plutovg_matrix_translate(matrix, values[0], 0); + } else { + plutovg_matrix_translate(matrix, values[0], values[1]); + } + } else if(plutovg_skip_string(&it, end, "scale")) { + int count = parse_matrix_parameters(&it, end, values, 1, 1); + if(count == 0) + return false; + if(count == 1) { + plutovg_matrix_scale(matrix, values[0], values[0]); + } else { + plutovg_matrix_scale(matrix, values[0], values[1]); + } + } else if(plutovg_skip_string(&it, end, "rotate")) { + int count = parse_matrix_parameters(&it, end, values, 1, 2); + if(count == 0) + return false; + if(count == 3) + plutovg_matrix_translate(matrix, values[1], values[2]); + plutovg_matrix_rotate(matrix, PLUTOVG_DEG2RAD(values[0])); + if(count == 3) { + plutovg_matrix_translate(matrix, -values[1], -values[2]); + } + } else if(plutovg_skip_string(&it, end, "skewX")) { + int count = parse_matrix_parameters(&it, end, values, 1, 0); + if(count == 0) + return false; + plutovg_matrix_shear(matrix, PLUTOVG_DEG2RAD(values[0]), 0); + } else if(plutovg_skip_string(&it, end, "skewY")) { + int count = parse_matrix_parameters(&it, end, values, 1, 0); + if(count == 0) + return false; + plutovg_matrix_shear(matrix, 0, PLUTOVG_DEG2RAD(values[0])); + } else { + return false; + } + + plutovg_skip_ws_or_comma(&it, end, &has_trailing_comma); + } + + return !has_trailing_comma; +} diff --git a/vendor/lunasvg/plutovg/source/plutovg-paint.c b/vendor/lunasvg/plutovg/source/plutovg-paint.c new file mode 100644 index 0000000..43672ff --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-paint.c @@ -0,0 +1,489 @@ +#include "plutovg-private.h" +#include "plutovg-utils.h" + +#include + +void plutovg_color_init_rgb(plutovg_color_t* color, float r, float g, float b) +{ + plutovg_color_init_rgba(color, r, g, b, 1.f); +} + +void plutovg_color_init_rgba(plutovg_color_t* color, float r, float g, float b, float a) +{ + color->r = plutovg_clamp(r, 0.f, 1.f); + color->g = plutovg_clamp(g, 0.f, 1.f); + color->b = plutovg_clamp(b, 0.f, 1.f); + color->a = plutovg_clamp(a, 0.f, 1.f); +} + +void plutovg_color_init_rgb8(plutovg_color_t* color, int r, int g, int b) +{ + plutovg_color_init_rgba8(color, r, g, b, 255); +} + +void plutovg_color_init_rgba8(plutovg_color_t* color, int r, int g, int b, int a) +{ + plutovg_color_init_rgba(color, r / 255.f, g / 255.f, b / 255.f, a / 255.f); +} + +void plutovg_color_init_rgba32(plutovg_color_t* color, unsigned int value) +{ + uint8_t r = (value >> 24) & 0xFF; + uint8_t g = (value >> 16) & 0xFF; + uint8_t b = (value >> 8) & 0xFF; + uint8_t a = (value >> 0) & 0xFF; + plutovg_color_init_rgba8(color, r, g, b, a); +} + +void plutovg_color_init_argb32(plutovg_color_t* color, unsigned int value) +{ + uint8_t a = (value >> 24) & 0xFF; + uint8_t r = (value >> 16) & 0xFF; + uint8_t g = (value >> 8) & 0xFF; + uint8_t b = (value >> 0) & 0xFF; + plutovg_color_init_rgba8(color, r, g, b, a); +} + +void plutovg_color_init_hsl(plutovg_color_t* color, float h, float s, float l) +{ + plutovg_color_init_hsla(color, h, s, l, 1.f); +} + +static inline float hsl_component(float h, float s, float l, float n) +{ + const float k = fmodf(n + h / 30.f, 12.f); + const float a = s * plutovg_min(l, 1.f - l); + return l - a * plutovg_max(-1.f, plutovg_min(1.f, plutovg_min(k - 3.f, 9.f - k))); +} + +void plutovg_color_init_hsla(plutovg_color_t* color, float h, float s, float l, float a) +{ + h = fmodf(h, 360.f); + if(h < 0.f) { h += 360.f; } + + float r = hsl_component(h, s, l, 0); + float g = hsl_component(h, s, l, 8); + float b = hsl_component(h, s, l, 4); + plutovg_color_init_rgba(color, r, g, b, a); +} + +unsigned int plutovg_color_to_rgba32(const plutovg_color_t* color) +{ + uint32_t r = lroundf(color->r * 255); + uint32_t g = lroundf(color->g * 255); + uint32_t b = lroundf(color->b * 255); + uint32_t a = lroundf(color->a * 255); + return (r << 24) | (g << 16) | (b << 8) | (a); +} + +unsigned int plutovg_color_to_argb32(const plutovg_color_t* color) +{ + uint32_t a = lroundf(color->a * 255); + uint32_t r = lroundf(color->r * 255); + uint32_t g = lroundf(color->g * 255); + uint32_t b = lroundf(color->b * 255); + return (a << 24) | (r << 16) | (g << 8) | (b); +} + +static inline uint8_t hex_digit(uint8_t c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + if(c >= 'a' && c <= 'f') + return 10 + c - 'a'; + return 10 + c - 'A'; +} + +static inline uint8_t hex_byte(uint8_t c1, uint8_t c2) +{ + uint8_t h1 = hex_digit(c1); + uint8_t h2 = hex_digit(c2); + return (h1 << 4) | h2; +} + +#define MAX_NAME 20 +typedef struct { + const char* name; + uint32_t value; +} color_entry_t; + +static int color_entry_compare(const void* a, const void* b) +{ + const char* name = a; + const color_entry_t* entry = b; + return strcmp(name, entry->name); +} + +static bool parse_rgb_component(const char** begin, const char* end, float* component) +{ + float value = 0; + if(!plutovg_parse_number(begin, end, &value)) + return false; + if(plutovg_skip_delim(begin, end, '%')) + value *= 2.55f; + *component = plutovg_clamp(value, 0.f, 255.f) / 255.f; + return true; +} + +static bool parse_alpha_component(const char** begin, const char* end, float* component) +{ + float value = 0; + if(!plutovg_parse_number(begin, end, &value)) + return false; + if(plutovg_skip_delim(begin, end, '%')) + value /= 100.f; + *component = plutovg_clamp(value, 0.f, 1.f); + return true; +} + +int plutovg_color_parse(plutovg_color_t* color, const char* data, int length) +{ + if(length == -1) + length = strlen(data); + const char* it = data; + const char* end = it + length; + plutovg_skip_ws(&it, end); + if(plutovg_skip_delim(&it, end, '#')) { + int r, g, b, a = 255; + const char* begin = it; + while(it < end && isxdigit(*it)) + ++it; + int count = it - begin; + if(count == 3 || count == 4) { + r = hex_byte(begin[0], begin[0]); + g = hex_byte(begin[1], begin[1]); + b = hex_byte(begin[2], begin[2]); + if(count == 4) { + a = hex_byte(begin[3], begin[3]); + } + } else if(count == 6 || count == 8) { + r = hex_byte(begin[0], begin[1]); + g = hex_byte(begin[2], begin[3]); + b = hex_byte(begin[4], begin[5]); + if(count == 8) { + a = hex_byte(begin[6], begin[7]); + } + } else { + return 0; + } + + plutovg_color_init_rgba8(color, r, g, b, a); + } else { + int name_length = 0; + char name[MAX_NAME + 1]; + while(it < end && name_length < MAX_NAME && isalpha(*it)) + name[name_length++] = tolower(*it++); + name[name_length] = '\0'; + + if(strcmp(name, "transparent") == 0) { + plutovg_color_init_rgba(color, 0, 0, 0, 0); + } else if(strcmp(name, "rgb") == 0 || strcmp(name, "rgba") == 0) { + if(!plutovg_skip_ws_and_delim(&it, end, '(')) + return 0; + float r, g, b, a = 1.f; + if(!parse_rgb_component(&it, end, &r) + || !plutovg_skip_ws_and_comma(&it, end) + || !parse_rgb_component(&it, end, &g) + || !plutovg_skip_ws_and_comma(&it, end) + || !parse_rgb_component(&it, end, &b)) { + return 0; + } + + if(plutovg_skip_ws_and_comma(&it, end) + && !parse_alpha_component(&it, end, &a)) { + return 0; + } + + plutovg_skip_ws(&it, end); + if(!plutovg_skip_delim(&it, end, ')')) + return 0; + plutovg_color_init_rgba(color, r, g, b, a); + } else if(strcmp(name, "hsl") == 0 || strcmp(name, "hsla") == 0) { + if(!plutovg_skip_ws_and_delim(&it, end, '(')) + return 0; + float h, s, l, a = 1.f; + if(!plutovg_parse_number(&it, end, &h) + || !plutovg_skip_ws_and_comma(&it, end) + || !parse_alpha_component(&it, end, &s) + || !plutovg_skip_ws_and_comma(&it, end) + || !parse_alpha_component(&it, end, &l)) { + return 0; + } + + if(plutovg_skip_ws_and_comma(&it, end) + && !parse_alpha_component(&it, end, &a)) { + return 0; + } + + plutovg_skip_ws(&it, end); + if(!plutovg_skip_delim(&it, end, ')')) + return 0; + plutovg_color_init_hsla(color, h, s, l, a); + } else { + static const color_entry_t colormap[] = { + {"aliceblue", 0xF0F8FF}, + {"antiquewhite", 0xFAEBD7}, + {"aqua", 0x00FFFF}, + {"aquamarine", 0x7FFFD4}, + {"azure", 0xF0FFFF}, + {"beige", 0xF5F5DC}, + {"bisque", 0xFFE4C4}, + {"black", 0x000000}, + {"blanchedalmond", 0xFFEBCD}, + {"blue", 0x0000FF}, + {"blueviolet", 0x8A2BE2}, + {"brown", 0xA52A2A}, + {"burlywood", 0xDEB887}, + {"cadetblue", 0x5F9EA0}, + {"chartreuse", 0x7FFF00}, + {"chocolate", 0xD2691E}, + {"coral", 0xFF7F50}, + {"cornflowerblue", 0x6495ED}, + {"cornsilk", 0xFFF8DC}, + {"crimson", 0xDC143C}, + {"cyan", 0x00FFFF}, + {"darkblue", 0x00008B}, + {"darkcyan", 0x008B8B}, + {"darkgoldenrod", 0xB8860B}, + {"darkgray", 0xA9A9A9}, + {"darkgreen", 0x006400}, + {"darkgrey", 0xA9A9A9}, + {"darkkhaki", 0xBDB76B}, + {"darkmagenta", 0x8B008B}, + {"darkolivegreen", 0x556B2F}, + {"darkorange", 0xFF8C00}, + {"darkorchid", 0x9932CC}, + {"darkred", 0x8B0000}, + {"darksalmon", 0xE9967A}, + {"darkseagreen", 0x8FBC8F}, + {"darkslateblue", 0x483D8B}, + {"darkslategray", 0x2F4F4F}, + {"darkslategrey", 0x2F4F4F}, + {"darkturquoise", 0x00CED1}, + {"darkviolet", 0x9400D3}, + {"deeppink", 0xFF1493}, + {"deepskyblue", 0x00BFFF}, + {"dimgray", 0x696969}, + {"dimgrey", 0x696969}, + {"dodgerblue", 0x1E90FF}, + {"firebrick", 0xB22222}, + {"floralwhite", 0xFFFAF0}, + {"forestgreen", 0x228B22}, + {"fuchsia", 0xFF00FF}, + {"gainsboro", 0xDCDCDC}, + {"ghostwhite", 0xF8F8FF}, + {"gold", 0xFFD700}, + {"goldenrod", 0xDAA520}, + {"gray", 0x808080}, + {"green", 0x008000}, + {"greenyellow", 0xADFF2F}, + {"grey", 0x808080}, + {"honeydew", 0xF0FFF0}, + {"hotpink", 0xFF69B4}, + {"indianred", 0xCD5C5C}, + {"indigo", 0x4B0082}, + {"ivory", 0xFFFFF0}, + {"khaki", 0xF0E68C}, + {"lavender", 0xE6E6FA}, + {"lavenderblush", 0xFFF0F5}, + {"lawngreen", 0x7CFC00}, + {"lemonchiffon", 0xFFFACD}, + {"lightblue", 0xADD8E6}, + {"lightcoral", 0xF08080}, + {"lightcyan", 0xE0FFFF}, + {"lightgoldenrodyellow", 0xFAFAD2}, + {"lightgray", 0xD3D3D3}, + {"lightgreen", 0x90EE90}, + {"lightgrey", 0xD3D3D3}, + {"lightpink", 0xFFB6C1}, + {"lightsalmon", 0xFFA07A}, + {"lightseagreen", 0x20B2AA}, + {"lightskyblue", 0x87CEFA}, + {"lightslategray", 0x778899}, + {"lightslategrey", 0x778899}, + {"lightsteelblue", 0xB0C4DE}, + {"lightyellow", 0xFFFFE0}, + {"lime", 0x00FF00}, + {"limegreen", 0x32CD32}, + {"linen", 0xFAF0E6}, + {"magenta", 0xFF00FF}, + {"maroon", 0x800000}, + {"mediumaquamarine", 0x66CDAA}, + {"mediumblue", 0x0000CD}, + {"mediumorchid", 0xBA55D3}, + {"mediumpurple", 0x9370DB}, + {"mediumseagreen", 0x3CB371}, + {"mediumslateblue", 0x7B68EE}, + {"mediumspringgreen", 0x00FA9A}, + {"mediumturquoise", 0x48D1CC}, + {"mediumvioletred", 0xC71585}, + {"midnightblue", 0x191970}, + {"mintcream", 0xF5FFFA}, + {"mistyrose", 0xFFE4E1}, + {"moccasin", 0xFFE4B5}, + {"navajowhite", 0xFFDEAD}, + {"navy", 0x000080}, + {"oldlace", 0xFDF5E6}, + {"olive", 0x808000}, + {"olivedrab", 0x6B8E23}, + {"orange", 0xFFA500}, + {"orangered", 0xFF4500}, + {"orchid", 0xDA70D6}, + {"palegoldenrod", 0xEEE8AA}, + {"palegreen", 0x98FB98}, + {"paleturquoise", 0xAFEEEE}, + {"palevioletred", 0xDB7093}, + {"papayawhip", 0xFFEFD5}, + {"peachpuff", 0xFFDAB9}, + {"peru", 0xCD853F}, + {"pink", 0xFFC0CB}, + {"plum", 0xDDA0DD}, + {"powderblue", 0xB0E0E6}, + {"purple", 0x800080}, + {"rebeccapurple", 0x663399}, + {"red", 0xFF0000}, + {"rosybrown", 0xBC8F8F}, + {"royalblue", 0x4169E1}, + {"saddlebrown", 0x8B4513}, + {"salmon", 0xFA8072}, + {"sandybrown", 0xF4A460}, + {"seagreen", 0x2E8B57}, + {"seashell", 0xFFF5EE}, + {"sienna", 0xA0522D}, + {"silver", 0xC0C0C0}, + {"skyblue", 0x87CEEB}, + {"slateblue", 0x6A5ACD}, + {"slategray", 0x708090}, + {"slategrey", 0x708090}, + {"snow", 0xFFFAFA}, + {"springgreen", 0x00FF7F}, + {"steelblue", 0x4682B4}, + {"tan", 0xD2B48C}, + {"teal", 0x008080}, + {"thistle", 0xD8BFD8}, + {"tomato", 0xFF6347}, + {"turquoise", 0x40E0D0}, + {"violet", 0xEE82EE}, + {"wheat", 0xF5DEB3}, + {"white", 0xFFFFFF}, + {"whitesmoke", 0xF5F5F5}, + {"yellow", 0xFFFF00}, + {"yellowgreen", 0x9ACD32} + }; + + const color_entry_t* entry = bsearch(name, colormap, sizeof(colormap) / sizeof(color_entry_t), sizeof(color_entry_t), color_entry_compare); + if(entry == NULL) + return 0; + plutovg_color_init_argb32(color, 0xFF000000 | entry->value); + } + } + + plutovg_skip_ws(&it, end); + return it - data; +} + +static void* plutovg_paint_create(plutovg_paint_type_t type, size_t size) +{ + plutovg_paint_t* paint = malloc(size); + plutovg_init_reference(paint); + paint->type = type; + return paint; +} + +plutovg_paint_t* plutovg_paint_create_rgb(float r, float g, float b) +{ + return plutovg_paint_create_rgba(r, g, b, 1.f); +} + +plutovg_paint_t* plutovg_paint_create_rgba(float r, float g, float b, float a) +{ + plutovg_solid_paint_t* solid = plutovg_paint_create(PLUTOVG_PAINT_TYPE_COLOR, sizeof(plutovg_solid_paint_t)); + solid->color.r = plutovg_clamp(r, 0.f, 1.f); + solid->color.g = plutovg_clamp(g, 0.f, 1.f); + solid->color.b = plutovg_clamp(b, 0.f, 1.f); + solid->color.a = plutovg_clamp(a, 0.f, 1.f); + return &solid->base; +} + +plutovg_paint_t* plutovg_paint_create_color(const plutovg_color_t* color) +{ + return plutovg_paint_create_rgba(color->r, color->g, color->b, color->a); +} + +static plutovg_gradient_paint_t* plutovg_gradient_create(plutovg_gradient_type_t type, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix) +{ + plutovg_gradient_paint_t* gradient = plutovg_paint_create(PLUTOVG_PAINT_TYPE_GRADIENT, sizeof(plutovg_gradient_paint_t) + nstops * sizeof(plutovg_gradient_stop_t)); + gradient->type = type; + gradient->spread = spread; + gradient->matrix = matrix ? *matrix : PLUTOVG_IDENTITY_MATRIX; + gradient->stops = (plutovg_gradient_stop_t*)(gradient + 1); + gradient->nstops = nstops; + + float prev_offset = 0.f; + for(int i = 0; i < nstops; ++i) { + const plutovg_gradient_stop_t* stop = stops + i; + gradient->stops[i].offset = plutovg_max(prev_offset, plutovg_clamp(stop->offset, 0.f, 1.f)); + gradient->stops[i].color.r = plutovg_clamp(stop->color.r, 0.f, 1.f); + gradient->stops[i].color.g = plutovg_clamp(stop->color.g, 0.f, 1.f); + gradient->stops[i].color.b = plutovg_clamp(stop->color.b, 0.f, 1.f); + gradient->stops[i].color.a = plutovg_clamp(stop->color.a, 0.f, 1.f); + prev_offset = gradient->stops[i].offset; + } + + return gradient; +} + +plutovg_paint_t* plutovg_paint_create_linear_gradient(float x1, float y1, float x2, float y2, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix) +{ + plutovg_gradient_paint_t* gradient = plutovg_gradient_create(PLUTOVG_GRADIENT_TYPE_LINEAR, spread, stops, nstops, matrix); + gradient->values[0] = x1; + gradient->values[1] = y1; + gradient->values[2] = x2; + gradient->values[3] = y2; + return &gradient->base; +} + +plutovg_paint_t* plutovg_paint_create_radial_gradient(float cx, float cy, float cr, float fx, float fy, float fr, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix) +{ + plutovg_gradient_paint_t* gradient = plutovg_gradient_create(PLUTOVG_GRADIENT_TYPE_RADIAL, spread, stops, nstops, matrix); + gradient->values[0] = cx; + gradient->values[1] = cy; + gradient->values[2] = cr; + gradient->values[3] = fx; + gradient->values[4] = fy; + gradient->values[5] = fr; + return &gradient->base; +} + +plutovg_paint_t* plutovg_paint_create_texture(plutovg_surface_t* surface, plutovg_texture_type_t type, float opacity, const plutovg_matrix_t* matrix) +{ + plutovg_texture_paint_t* texture = plutovg_paint_create(PLUTOVG_PAINT_TYPE_TEXTURE, sizeof(plutovg_texture_paint_t)); + texture->type = type; + texture->opacity = plutovg_clamp(opacity, 0.f, 1.f); + texture->matrix = matrix ? *matrix : PLUTOVG_IDENTITY_MATRIX; + texture->surface = plutovg_surface_reference(surface); + return &texture->base; +} + +plutovg_paint_t* plutovg_paint_reference(plutovg_paint_t* paint) +{ + plutovg_increment_reference(paint); + return paint; +} + +void plutovg_paint_destroy(plutovg_paint_t* paint) +{ + if(plutovg_destroy_reference(paint)) { + if(paint->type == PLUTOVG_PAINT_TYPE_TEXTURE) { + plutovg_texture_paint_t* texture = (plutovg_texture_paint_t*)(paint); + plutovg_surface_destroy(texture->surface); + } + + free(paint); + } +} + +int plutovg_paint_get_reference_count(const plutovg_paint_t* paint) +{ + return plutovg_get_reference_count(paint); +} diff --git a/vendor/lunasvg/plutovg/source/plutovg-path.c b/vendor/lunasvg/plutovg/source/plutovg-path.c new file mode 100644 index 0000000..2efaa63 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-path.c @@ -0,0 +1,918 @@ +#include "plutovg-private.h" +#include "plutovg-utils.h" + +#include + +void plutovg_path_iterator_init(plutovg_path_iterator_t* it, const plutovg_path_t* path) +{ + it->elements = path->elements.data; + it->size = path->elements.size; + it->index = 0; +} + +bool plutovg_path_iterator_has_next(const plutovg_path_iterator_t* it) +{ + return it->index < it->size; +} + +plutovg_path_command_t plutovg_path_iterator_next(plutovg_path_iterator_t* it, plutovg_point_t points[3]) +{ + const plutovg_path_element_t* elements = it->elements + it->index; + switch(elements[0].header.command) { + case PLUTOVG_PATH_COMMAND_MOVE_TO: + case PLUTOVG_PATH_COMMAND_LINE_TO: + case PLUTOVG_PATH_COMMAND_CLOSE: + points[0] = elements[1].point; + break; + case PLUTOVG_PATH_COMMAND_CUBIC_TO: + points[0] = elements[1].point; + points[1] = elements[2].point; + points[2] = elements[3].point; + break; + } + + it->index += elements[0].header.length; + return elements[0].header.command; +} + +plutovg_path_t* plutovg_path_create(void) +{ + plutovg_path_t* path = malloc(sizeof(plutovg_path_t)); + plutovg_init_reference(path); + path->num_points = 0; + path->num_contours = 0; + path->num_curves = 0; + path->start_point = PLUTOVG_EMPTY_POINT; + plutovg_array_init(path->elements); + return path; +} + +plutovg_path_t* plutovg_path_reference(plutovg_path_t* path) +{ + plutovg_increment_reference(path); + return path; +} + +void plutovg_path_destroy(plutovg_path_t* path) +{ + if(plutovg_destroy_reference(path)) { + plutovg_array_destroy(path->elements); + free(path); + } +} + +int plutovg_path_get_reference_count(const plutovg_path_t* path) +{ + return plutovg_get_reference_count(path); +} + +int plutovg_path_get_elements(const plutovg_path_t* path, const plutovg_path_element_t** elements) +{ + if(elements) + *elements = path->elements.data; + return path->elements.size; +} + +static plutovg_path_element_t* plutovg_path_add_command(plutovg_path_t* path, plutovg_path_command_t command, int npoints) +{ + const int length = npoints + 1; + plutovg_array_ensure(path->elements, length); + plutovg_path_element_t* elements = path->elements.data + path->elements.size; + elements->header.command = command; + elements->header.length = length; + path->elements.size += length; + path->num_points += npoints; + return elements + 1; +} + +void plutovg_path_move_to(plutovg_path_t* path, float x, float y) +{ + plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_MOVE_TO, 1); + elements[0].point = PLUTOVG_MAKE_POINT(x, y); + path->start_point = PLUTOVG_MAKE_POINT(x, y); + path->num_contours += 1; +} + +void plutovg_path_line_to(plutovg_path_t* path, float x, float y) +{ + if(path->elements.size == 0) + plutovg_path_move_to(path, 0, 0); + plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_LINE_TO, 1); + elements[0].point = PLUTOVG_MAKE_POINT(x, y); +} + +void plutovg_path_quad_to(plutovg_path_t* path, float x1, float y1, float x2, float y2) +{ + float current_x, current_y; + plutovg_path_get_current_point(path, ¤t_x, ¤t_y); + float cp1x = 2.f / 3.f * x1 + 1.f / 3.f * current_x; + float cp1y = 2.f / 3.f * y1 + 1.f / 3.f * current_y; + float cp2x = 2.f / 3.f * x1 + 1.f / 3.f * x2; + float cp2y = 2.f / 3.f * y1 + 1.f / 3.f * y2; + plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, x2, y2); +} + +void plutovg_path_cubic_to(plutovg_path_t* path, float x1, float y1, float x2, float y2, float x3, float y3) +{ + if(path->elements.size == 0) + plutovg_path_move_to(path, 0, 0); + plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_CUBIC_TO, 3); + elements[0].point = PLUTOVG_MAKE_POINT(x1, y1); + elements[1].point = PLUTOVG_MAKE_POINT(x2, y2); + elements[2].point = PLUTOVG_MAKE_POINT(x3, y3); + path->num_curves += 1; +} + +void plutovg_path_arc_to(plutovg_path_t* path, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y) +{ + float current_x, current_y; + plutovg_path_get_current_point(path, ¤t_x, ¤t_y); + if(rx == 0.f || ry == 0.f || (current_x == x && current_y == y)) { + plutovg_path_line_to(path, x, y); + return; + } + + if(rx < 0.f) rx = -rx; + if(ry < 0.f) ry = -ry; + + float dx = (current_x - x) * 0.5f; + float dy = (current_y - y) * 0.5f; + + plutovg_matrix_t matrix; + plutovg_matrix_init_rotate(&matrix, -angle); + plutovg_matrix_map(&matrix, dx, dy, &dx, &dy); + + float rxrx = rx * rx; + float ryry = ry * ry; + float dxdx = dx * dx; + float dydy = dy * dy; + float radius = dxdx / rxrx + dydy / ryry; + if(radius > 1.f) { + rx *= sqrtf(radius); + ry *= sqrtf(radius); + } + + plutovg_matrix_init_scale(&matrix, 1.f / rx, 1.f / ry); + plutovg_matrix_rotate(&matrix, -angle); + + float x1, y1; + float x2, y2; + plutovg_matrix_map(&matrix, current_x, current_y, &x1, &y1); + plutovg_matrix_map(&matrix, x, y, &x2, &y2); + + float dx1 = x2 - x1; + float dy1 = y2 - y1; + float d = dx1 * dx1 + dy1 * dy1; + float scale_sq = 1.f / d - 0.25f; + if(scale_sq < 0.f) scale_sq = 0.f; + float scale = sqrtf(scale_sq); + if(sweep_flag == large_arc_flag) + scale = -scale; + dx1 *= scale; + dy1 *= scale; + + float cx1 = 0.5f * (x1 + x2) - dy1; + float cy1 = 0.5f * (y1 + y2) + dx1; + + float th1 = atan2f(y1 - cy1, x1 - cx1); + float th2 = atan2f(y2 - cy1, x2 - cx1); + float th_arc = th2 - th1; + if(th_arc < 0.f && sweep_flag) + th_arc += PLUTOVG_TWO_PI; + else if(th_arc > 0.f && !sweep_flag) + th_arc -= PLUTOVG_TWO_PI; + plutovg_matrix_init_rotate(&matrix, angle); + plutovg_matrix_scale(&matrix, rx, ry); + int segments = (int)(ceilf(fabsf(th_arc / (PLUTOVG_HALF_PI + 0.001f)))); + for(int i = 0; i < segments; i++) { + float th_start = th1 + i * th_arc / segments; + float th_end = th1 + (i + 1) * th_arc / segments; + float t = (8.f / 6.f) * tanf(0.25f * (th_end - th_start)); + + float x3 = cosf(th_end) + cx1; + float y3 = sinf(th_end) + cy1; + + float cp2x = x3 + t * sinf(th_end); + float cp2y = y3 - t * cosf(th_end); + + float cp1x = cosf(th_start) - t * sinf(th_start); + float cp1y = sinf(th_start) + t * cosf(th_start); + + cp1x += cx1; + cp1y += cy1; + + plutovg_matrix_map(&matrix, cp1x, cp1y, &cp1x, &cp1y); + plutovg_matrix_map(&matrix, cp2x, cp2y, &cp2x, &cp2y); + plutovg_matrix_map(&matrix, x3, y3, &x3, &y3); + + plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, x3, y3); + } +} + +void plutovg_path_close(plutovg_path_t* path) +{ + if(path->elements.size == 0) + return; + plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_CLOSE, 1); + elements[0].point = path->start_point; +} + +void plutovg_path_get_current_point(const plutovg_path_t* path, float* x, float* y) +{ + float xx = 0.f; + float yy = 0.f; + if(path->num_points > 0) { + xx = path->elements.data[path->elements.size - 1].point.x; + yy = path->elements.data[path->elements.size - 1].point.y; + } + + if(x) *x = xx; + if(y) *y = yy; +} + +void plutovg_path_reserve(plutovg_path_t* path, int count) +{ + plutovg_array_ensure(path->elements, count); +} + +void plutovg_path_reset(plutovg_path_t* path) +{ + plutovg_array_clear(path->elements); + path->start_point = PLUTOVG_EMPTY_POINT; + path->num_points = 0; + path->num_contours = 0; + path->num_curves = 0; +} + +void plutovg_path_add_rect(plutovg_path_t* path, float x, float y, float w, float h) +{ + plutovg_path_reserve(path, 6 * 2); + plutovg_path_move_to(path, x, y); + plutovg_path_line_to(path, x + w, y); + plutovg_path_line_to(path, x + w, y + h); + plutovg_path_line_to(path, x, y + h); + plutovg_path_line_to(path, x, y); + plutovg_path_close(path); +} + +void plutovg_path_add_round_rect(plutovg_path_t* path, float x, float y, float w, float h, float rx, float ry) +{ + rx = plutovg_min(rx, w * 0.5f); + ry = plutovg_min(ry, h * 0.5f); + if(rx == 0.f && ry == 0.f) { + plutovg_path_add_rect(path, x, y, w, h); + return; + } + + float right = x + w; + float bottom = y + h; + + float cpx = rx * PLUTOVG_KAPPA; + float cpy = ry * PLUTOVG_KAPPA; + + plutovg_path_reserve(path, 6 * 2 + 4 * 4); + plutovg_path_move_to(path, x, y+ry); + plutovg_path_cubic_to(path, x, y+ry-cpy, x+rx-cpx, y, x+rx, y); + plutovg_path_line_to(path, right-rx, y); + plutovg_path_cubic_to(path, right-rx+cpx, y, right, y+ry-cpy, right, y+ry); + plutovg_path_line_to(path, right, bottom-ry); + plutovg_path_cubic_to(path, right, bottom-ry+cpy, right-rx+cpx, bottom, right-rx, bottom); + plutovg_path_line_to(path, x+rx, bottom); + plutovg_path_cubic_to(path, x+rx-cpx, bottom, x, bottom-ry+cpy, x, bottom-ry); + plutovg_path_line_to(path, x, y+ry); + plutovg_path_close(path); +} + +void plutovg_path_add_ellipse(plutovg_path_t* path, float cx, float cy, float rx, float ry) +{ + float left = cx - rx; + float top = cy - ry; + float right = cx + rx; + float bottom = cy + ry; + + float cpx = rx * PLUTOVG_KAPPA; + float cpy = ry * PLUTOVG_KAPPA; + + plutovg_path_reserve(path, 2 * 2 + 4 * 4); + plutovg_path_move_to(path, cx, top); + plutovg_path_cubic_to(path, cx+cpx, top, right, cy-cpy, right, cy); + plutovg_path_cubic_to(path, right, cy+cpy, cx+cpx, bottom, cx, bottom); + plutovg_path_cubic_to(path, cx-cpx, bottom, left, cy+cpy, left, cy); + plutovg_path_cubic_to(path, left, cy-cpy, cx-cpx, top, cx, top); + plutovg_path_close(path); +} + +void plutovg_path_add_circle(plutovg_path_t* path, float cx, float cy, float r) +{ + plutovg_path_add_ellipse(path, cx, cy, r, r); +} + +void plutovg_path_add_arc(plutovg_path_t* path, float cx, float cy, float r, float a0, float a1, bool ccw) +{ + float da = a1 - a0; + if(fabsf(da) > PLUTOVG_TWO_PI) { + da = PLUTOVG_TWO_PI; + } else if(da != 0.f && ccw != (da < 0.f)) { + da += PLUTOVG_TWO_PI * (ccw ? -1 : 1); + } + + int seg_n = (int)(ceilf(fabsf(da) / PLUTOVG_HALF_PI)); + if(seg_n == 0) + return; + float a = a0; + float ax = cx + cosf(a) * r; + float ay = cy + sinf(a) * r; + + float seg_a = da / seg_n; + float d = (seg_a / PLUTOVG_HALF_PI) * PLUTOVG_KAPPA * r; + float dx = -sinf(a) * d; + float dy = cosf(a) * d; + + plutovg_path_reserve(path, 2 + 4 * seg_n); + if(path->elements.size == 0) { + plutovg_path_move_to(path, ax, ay); + } else { + plutovg_path_line_to(path, ax, ay); + } + + for(int i = 0; i < seg_n; i++) { + float cp1x = ax + dx; + float cp1y = ay + dy; + + a += seg_a; + ax = cx + cosf(a) * r; + ay = cy + sinf(a) * r; + + dx = -sinf(a) * d; + dy = cosf(a) * d; + + float cp2x = ax - dx; + float cp2y = ay - dy; + + plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, ax, ay); + } +} + +void plutovg_path_transform(plutovg_path_t* path, const plutovg_matrix_t* matrix) +{ + plutovg_path_element_t* elements = path->elements.data; + for(int i = 0; i < path->elements.size; i += elements[i].header.length) { + switch(elements[i].header.command) { + case PLUTOVG_PATH_COMMAND_MOVE_TO: + case PLUTOVG_PATH_COMMAND_LINE_TO: + case PLUTOVG_PATH_COMMAND_CLOSE: + plutovg_matrix_map_point(matrix, &elements[i + 1].point, &elements[i + 1].point); + break; + case PLUTOVG_PATH_COMMAND_CUBIC_TO: + plutovg_matrix_map_point(matrix, &elements[i + 1].point, &elements[i + 1].point); + plutovg_matrix_map_point(matrix, &elements[i + 2].point, &elements[i + 2].point); + plutovg_matrix_map_point(matrix, &elements[i + 3].point, &elements[i + 3].point); + break; + } + } +} + +void plutovg_path_add_path(plutovg_path_t* path, const plutovg_path_t* source, const plutovg_matrix_t* matrix) +{ + if(matrix == NULL) { + plutovg_array_append(path->elements, source->elements); + path->start_point = source->start_point; + path->num_points += source->num_points; + path->num_contours += source->num_contours; + path->num_curves += source->num_curves; + return; + } + + plutovg_path_iterator_t it; + plutovg_path_iterator_init(&it, source); + + plutovg_point_t points[3]; + plutovg_array_ensure(path->elements, source->elements.size); + while(plutovg_path_iterator_has_next(&it)) { + switch(plutovg_path_iterator_next(&it, points)) { + case PLUTOVG_PATH_COMMAND_MOVE_TO: + plutovg_matrix_map_points(matrix, points, points, 1); + plutovg_path_move_to(path, points[0].x, points[0].y); + break; + case PLUTOVG_PATH_COMMAND_LINE_TO: + plutovg_matrix_map_points(matrix, points, points, 1); + plutovg_path_line_to(path, points[0].x, points[0].y); + break; + case PLUTOVG_PATH_COMMAND_CUBIC_TO: + plutovg_matrix_map_points(matrix, points, points, 3); + plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); + break; + case PLUTOVG_PATH_COMMAND_CLOSE: + plutovg_path_close(path); + break; + } + } +} + +void plutovg_path_traverse(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure) +{ + plutovg_path_iterator_t it; + plutovg_path_iterator_init(&it, path); + + plutovg_point_t points[3]; + while(plutovg_path_iterator_has_next(&it)) { + switch(plutovg_path_iterator_next(&it, points)) { + case PLUTOVG_PATH_COMMAND_MOVE_TO: + traverse_func(closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, 1); + break; + case PLUTOVG_PATH_COMMAND_LINE_TO: + traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, points, 1); + break; + case PLUTOVG_PATH_COMMAND_CUBIC_TO: + traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); + break; + case PLUTOVG_PATH_COMMAND_CLOSE: + traverse_func(closure, PLUTOVG_PATH_COMMAND_CLOSE, points, 1); + break; + } + } +} + +typedef struct { + float x1; float y1; + float x2; float y2; + float x3; float y3; + float x4; float y4; +} bezier_t; + +static inline void split_bezier(const bezier_t* b, bezier_t* first, bezier_t* second) +{ + float c = (b->x2 + b->x3) * 0.5f; + first->x2 = (b->x1 + b->x2) * 0.5f; + second->x3 = (b->x3 + b->x4) * 0.5f; + first->x1 = b->x1; + second->x4 = b->x4; + first->x3 = (first->x2 + c) * 0.5f; + second->x2 = (second->x3 + c) * 0.5f; + first->x4 = second->x1 = (first->x3 + second->x2) * 0.5f; + + c = (b->y2 + b->y3) * 0.5f; + first->y2 = (b->y1 + b->y2) * 0.5f; + second->y3 = (b->y3 + b->y4) * 0.5f; + first->y1 = b->y1; + second->y4 = b->y4; + first->y3 = (first->y2 + c) * 0.5f; + second->y2 = (second->y3 + c) * 0.5f; + first->y4 = second->y1 = (first->y3 + second->y2) * 0.5f; +} + +void plutovg_path_traverse_flatten(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure) +{ + if(path->num_curves == 0) { + plutovg_path_traverse(path, traverse_func, closure); + return; + } + + const float threshold = 0.25f; + + plutovg_path_iterator_t it; + plutovg_path_iterator_init(&it, path); + + bezier_t beziers[32]; + plutovg_point_t points[3]; + plutovg_point_t current_point = {0, 0}; + while(plutovg_path_iterator_has_next(&it)) { + plutovg_path_command_t command = plutovg_path_iterator_next(&it, points); + switch(command) { + case PLUTOVG_PATH_COMMAND_MOVE_TO: + case PLUTOVG_PATH_COMMAND_LINE_TO: + case PLUTOVG_PATH_COMMAND_CLOSE: + traverse_func(closure, command, points, 1); + current_point = points[0]; + break; + case PLUTOVG_PATH_COMMAND_CUBIC_TO: + beziers[0].x1 = current_point.x; + beziers[0].y1 = current_point.y; + beziers[0].x2 = points[0].x; + beziers[0].y2 = points[0].y; + beziers[0].x3 = points[1].x; + beziers[0].y3 = points[1].y; + beziers[0].x4 = points[2].x; + beziers[0].y4 = points[2].y; + bezier_t* b = beziers; + while(b >= beziers) { + float y4y1 = b->y4 - b->y1; + float x4x1 = b->x4 - b->x1; + float l = fabsf(x4x1) + fabsf(y4y1); + float d; + if(l > 1.f) { + d = fabsf((x4x1)*(b->y1 - b->y2) - (y4y1)*(b->x1 - b->x2)) + fabsf((x4x1)*(b->y1 - b->y3) - (y4y1)*(b->x1 - b->x3)); + } else { + d = fabsf(b->x1 - b->x2) + fabsf(b->y1 - b->y2) + fabsf(b->x1 - b->x3) + fabsf(b->y1 - b->y3); + l = 1.f; + } + + if(d < threshold*l || b == beziers + 31) { + plutovg_point_t p = { b->x4, b->y4 }; + traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p, 1); + --b; + } else { + split_bezier(b, b + 1, b); + ++b; + } + } + + current_point = points[2]; + break; + } + } +} + +typedef struct { + const float* dashes; int ndashes; + float start_phase; float phase; + int start_index; int index; + bool start_toggle; bool toggle; + plutovg_point_t current_point; + plutovg_path_traverse_func_t traverse_func; + void* closure; +} dasher_t; + +static void dash_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) +{ + dasher_t* dasher = (dasher_t*)(closure); + if(command == PLUTOVG_PATH_COMMAND_MOVE_TO) { + if(dasher->start_toggle) + dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, npoints); + dasher->current_point = points[0]; + dasher->phase = dasher->start_phase; + dasher->index = dasher->start_index; + dasher->toggle = dasher->start_toggle; + return; + } + + assert(command == PLUTOVG_PATH_COMMAND_LINE_TO || command == PLUTOVG_PATH_COMMAND_CLOSE); + plutovg_point_t p0 = dasher->current_point; + plutovg_point_t p1 = points[0]; + float dx = p1.x - p0.x; + float dy = p1.y - p0.y; + float dist0 = sqrtf(dx*dx + dy*dy); + float dist1 = 0.f; + while(dist0 - dist1 > dasher->dashes[dasher->index % dasher->ndashes] - dasher->phase) { + dist1 += dasher->dashes[dasher->index % dasher->ndashes] - dasher->phase; + float a = dist1 / dist0; + plutovg_point_t p = { p0.x + a * dx, p0.y + a * dy }; + if(dasher->toggle) { + dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p, 1); + } else { + dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_MOVE_TO, &p, 1); + } + + dasher->phase = 0.f; + dasher->toggle = !dasher->toggle; + dasher->index++; + } + + if(dasher->toggle) { + dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p1, 1); + } + + dasher->phase += dist0 - dist1; + dasher->current_point = p1; +} + +void plutovg_path_traverse_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes, plutovg_path_traverse_func_t traverse_func, void* closure) +{ + float dash_sum = 0.f; + for(int i = 0; i < ndashes; ++i) + dash_sum += dashes[i]; + if(ndashes % 2 == 1) + dash_sum *= 2.f; + if(dash_sum <= 0.f) { + plutovg_path_traverse(path, traverse_func, closure); + return; + } + + dasher_t dasher; + dasher.dashes = dashes; + dasher.ndashes = ndashes; + dasher.start_phase = fmodf(offset, dash_sum); + if(dasher.start_phase < 0.f) + dasher.start_phase += dash_sum; + dasher.start_index = 0; + dasher.start_toggle = true; + while(dasher.start_phase > 0.f && dasher.start_phase >= dasher.dashes[dasher.start_index % dasher.ndashes]) { + dasher.start_phase -= dashes[dasher.start_index % dasher.ndashes]; + dasher.start_toggle = !dasher.start_toggle; + dasher.start_index++; + } + + dasher.phase = dasher.start_phase; + dasher.index = dasher.start_index; + dasher.toggle = dasher.start_toggle; + dasher.current_point = PLUTOVG_EMPTY_POINT; + dasher.traverse_func = traverse_func; + dasher.closure = closure; + plutovg_path_traverse_flatten(path, dash_traverse_func, &dasher); +} + +plutovg_path_t* plutovg_path_clone(const plutovg_path_t* path) +{ + plutovg_path_t* clone = plutovg_path_create(); + plutovg_array_append(clone->elements, path->elements); + clone->start_point = path->start_point; + clone->num_points = path->num_points; + clone->num_contours = path->num_contours; + clone->num_curves = path->num_curves; + return clone; +} + +static void clone_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) +{ + plutovg_path_t* path = (plutovg_path_t*)(closure); + switch(command) { + case PLUTOVG_PATH_COMMAND_MOVE_TO: + plutovg_path_move_to(path, points[0].x, points[0].y); + break; + case PLUTOVG_PATH_COMMAND_LINE_TO: + plutovg_path_line_to(path, points[0].x, points[0].y); + break; + case PLUTOVG_PATH_COMMAND_CUBIC_TO: + plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); + break; + case PLUTOVG_PATH_COMMAND_CLOSE: + plutovg_path_close(path); + break; + } +} + +plutovg_path_t* plutovg_path_clone_flatten(const plutovg_path_t* path) +{ + plutovg_path_t* clone = plutovg_path_create(); + plutovg_path_reserve(clone, path->elements.size + path->num_curves * 32); + plutovg_path_traverse_flatten(path, clone_traverse_func, clone); + return clone; +} + +plutovg_path_t* plutovg_path_clone_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes) +{ + plutovg_path_t* clone = plutovg_path_create(); + plutovg_path_reserve(clone, path->elements.size + path->num_curves * 32); + plutovg_path_traverse_dashed(path, offset, dashes, ndashes, clone_traverse_func, clone); + return clone; +} + +typedef struct { + plutovg_point_t current_point; + bool is_first_point; + float length; + float x1; + float y1; + float x2; + float y2; +} extents_calculator_t; + +static void extents_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) +{ + extents_calculator_t* calculator = (extents_calculator_t*)(closure); + if(calculator->is_first_point) { + assert(command == PLUTOVG_PATH_COMMAND_MOVE_TO); + calculator->is_first_point = false; + calculator->current_point = points[0]; + calculator->x1 = points[0].x; + calculator->y1 = points[0].y; + calculator->x2 = points[0].x; + calculator->y2 = points[0].y; + calculator->length = 0; + return; + } + + for(int i = 0; i < npoints; ++i) { + calculator->x1 = plutovg_min(calculator->x1, points[i].x); + calculator->y1 = plutovg_min(calculator->y1, points[i].y); + calculator->x2 = plutovg_max(calculator->x2, points[i].x); + calculator->y2 = plutovg_max(calculator->y2, points[i].y); + if(command != PLUTOVG_PATH_COMMAND_MOVE_TO) + calculator->length += hypotf(points[i].x - calculator->current_point.x, points[i].y - calculator->current_point.y); + calculator->current_point = points[i]; + } +} + +float plutovg_path_extents(const plutovg_path_t* path, plutovg_rect_t* extents, bool tight) +{ + extents_calculator_t calculator = {{0, 0}, true, 0, 0, 0, 0, 0}; + if(tight) { + plutovg_path_traverse_flatten(path, extents_traverse_func, &calculator); + } else { + plutovg_path_traverse(path, extents_traverse_func, &calculator); + } + + if(extents) { + extents->x = calculator.x1; + extents->y = calculator.y1; + extents->w = calculator.x2 - calculator.x1; + extents->h = calculator.y2 - calculator.y1; + } + + return calculator.length; +} + +float plutovg_path_length(const plutovg_path_t* path) +{ + return plutovg_path_extents(path, NULL, true); +} + +static inline bool parse_arc_flag(const char** begin, const char* end, bool* flag) +{ + if(plutovg_skip_delim(begin, end, '0')) + *flag = 0; + else if(plutovg_skip_delim(begin, end, '1')) + *flag = 1; + else + return false; + plutovg_skip_ws_or_comma(begin, end, NULL); + return true; +} + +static inline bool parse_path_coordinates(const char** begin, const char* end, float values[6], int offset, int count) +{ + for(int i = 0; i < count; i++) { + if(!plutovg_parse_number(begin, end, values + offset + i)) + return false; + plutovg_skip_ws_or_comma(begin, end, NULL); + } + + return true; +} + +bool plutovg_path_parse(plutovg_path_t* path, const char* data, int length) +{ + if(length == -1) + length = strlen(data); + const char* it = data; + const char* end = it + length; + + float values[6]; + bool flags[2]; + + float start_x = 0; + float start_y = 0; + float current_x = 0; + float current_y = 0; + float last_control_x = 0; + float last_control_y = 0; + + char command = 0; + char last_command = 0; + plutovg_skip_ws(&it, end); + while(it < end) { + if(PLUTOVG_IS_ALPHA(*it)) { + command = *it++; + plutovg_skip_ws(&it, end); + } + + if(!last_command && !(command == 'M' || command == 'm')) + return false; + if(command == 'M' || command == 'm') { + if(!parse_path_coordinates(&it, end, values, 0, 2)) + return false; + if(command == 'm') { + values[0] += current_x; + values[1] += current_y; + } + + plutovg_path_move_to(path, values[0], values[1]); + current_x = start_x = values[0]; + current_y = start_y = values[1]; + command = command == 'm' ? 'l' : 'L'; + } else if(command == 'L' || command == 'l') { + if(!parse_path_coordinates(&it, end, values, 0, 2)) + return false; + if(command == 'l') { + values[0] += current_x; + values[1] += current_y; + } + + plutovg_path_line_to(path, values[0], values[1]); + current_x = values[0]; + current_y = values[1]; + } else if(command == 'H' || command == 'h') { + if(!parse_path_coordinates(&it, end, values, 0, 1)) + return false; + if(command == 'h') { + values[0] += current_x; + } + + plutovg_path_line_to(path, values[0], current_y); + current_x = values[0]; + } else if(command == 'V' || command == 'v') { + if(!parse_path_coordinates(&it, end, values, 1, 1)) + return false; + if(command == 'v') { + values[1] += current_y; + } + + plutovg_path_line_to(path, current_x, values[1]); + current_y = values[1]; + } else if(command == 'Q' || command == 'q') { + if(!parse_path_coordinates(&it, end, values, 0, 4)) + return false; + if(command == 'q') { + values[0] += current_x; + values[1] += current_y; + values[2] += current_x; + values[3] += current_y; + } + + plutovg_path_quad_to(path, values[0], values[1], values[2], values[3]); + last_control_x = values[0]; + last_control_y = values[1]; + current_x = values[2]; + current_y = values[3]; + } else if(command == 'C' || command == 'c') { + if(!parse_path_coordinates(&it, end, values, 0, 6)) + return false; + if(command == 'c') { + values[0] += current_x; + values[1] += current_y; + values[2] += current_x; + values[3] += current_y; + values[4] += current_x; + values[5] += current_y; + } + + plutovg_path_cubic_to(path, values[0], values[1], values[2], values[3], values[4], values[5]); + last_control_x = values[2]; + last_control_y = values[3]; + current_x = values[4]; + current_y = values[5]; + } else if(command == 'T' || command == 't') { + if(last_command != 'Q' && last_command != 'q' && last_command != 'T' && last_command != 't') { + values[0] = current_x; + values[1] = current_y; + } else { + values[0] = 2 * current_x - last_control_x; + values[1] = 2 * current_y - last_control_y; + } + + if(!parse_path_coordinates(&it, end, values, 2, 2)) + return false; + if(command == 't') { + values[2] += current_x; + values[3] += current_y; + } + + plutovg_path_quad_to(path, values[0], values[1], values[2], values[3]); + last_control_x = values[0]; + last_control_y = values[1]; + current_x = values[2]; + current_y = values[3]; + } else if(command == 'S' || command == 's') { + if(last_command != 'C' && last_command != 'c' && last_command != 'S' && last_command != 's') { + values[0] = current_x; + values[1] = current_y; + } else { + values[0] = 2 * current_x - last_control_x; + values[1] = 2 * current_y - last_control_y; + } + + if(!parse_path_coordinates(&it, end, values, 2, 4)) + return false; + if(command == 's') { + values[2] += current_x; + values[3] += current_y; + values[4] += current_x; + values[5] += current_y; + } + + plutovg_path_cubic_to(path, values[0], values[1], values[2], values[3], values[4], values[5]); + last_control_x = values[2]; + last_control_y = values[3]; + current_x = values[4]; + current_y = values[5]; + } else if(command == 'A' || command == 'a') { + if(!parse_path_coordinates(&it, end, values, 0, 3) + || !parse_arc_flag(&it, end, &flags[0]) + || !parse_arc_flag(&it, end, &flags[1]) + || !parse_path_coordinates(&it, end, values, 3, 2)) { + return false; + } + + if(command == 'a') { + values[3] += current_x; + values[4] += current_y; + } + + plutovg_path_arc_to(path, values[0], values[1], PLUTOVG_DEG2RAD(values[2]), flags[0], flags[1], values[3], values[4]); + current_x = values[3]; + current_y = values[4]; + } else if(command == 'Z' || command == 'z') { + if(last_command == 'Z' || last_command == 'z') + return false; + plutovg_path_close(path); + current_x = start_x; + current_y = start_y; + } else { + return false; + } + + last_command = command; + } + + return true; +} diff --git a/vendor/lunasvg/plutovg/source/plutovg-private.h b/vendor/lunasvg/plutovg/source/plutovg-private.h new file mode 100644 index 0000000..838cd43 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-private.h @@ -0,0 +1,180 @@ +#ifndef PLUTOVG_PRIVATE_H +#define PLUTOVG_PRIVATE_H + +#include "plutovg.h" + +#if defined(_WIN32) + +#include + +typedef LONG plutovg_ref_count_t; + +#define plutovg_init_reference(ob) ((ob)->ref_count = 1) +#define plutovg_increment_reference(ob) (void)(ob && InterlockedIncrement(&(ob)->ref_count)) +#define plutovg_destroy_reference(ob) (ob && InterlockedDecrement(&(ob)->ref_count) == 0) +#define plutovg_get_reference_count(ob) ((ob) ? InterlockedCompareExchange((LONG*)&(ob)->ref_count, 0, 0) : 0) + +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) + +#include + +typedef atomic_int plutovg_ref_count_t; + +#define plutovg_init_reference(ob) atomic_init(&(ob)->ref_count, 1) +#define plutovg_increment_reference(ob) (void)(ob && atomic_fetch_add(&(ob)->ref_count, 1)) +#define plutovg_destroy_reference(ob) (ob && atomic_fetch_sub(&(ob)->ref_count, 1) == 1) +#define plutovg_get_reference_count(ob) ((ob) ? atomic_load(&(ob)->ref_count) : 0) + +#else + +typedef int plutovg_ref_count_t; + +#define plutovg_init_reference(ob) ((ob)->ref_count = 1) +#define plutovg_increment_reference(ob) (void)(ob && ++(ob)->ref_count) +#define plutovg_destroy_reference(ob) (ob && --(ob)->ref_count == 0) +#define plutovg_get_reference_count(ob) ((ob) ? (ob)->ref_count : 0) + +#endif + +struct plutovg_surface { + plutovg_ref_count_t ref_count; + int width; + int height; + int stride; + unsigned char* data; +}; + +struct plutovg_path { + plutovg_ref_count_t ref_count; + int num_points; + int num_contours; + int num_curves; + plutovg_point_t start_point; + struct { + plutovg_path_element_t* data; + int size; + int capacity; + } elements; +}; + +typedef enum { + PLUTOVG_PAINT_TYPE_COLOR, + PLUTOVG_PAINT_TYPE_GRADIENT, + PLUTOVG_PAINT_TYPE_TEXTURE +} plutovg_paint_type_t; + +struct plutovg_paint { + plutovg_ref_count_t ref_count; + plutovg_paint_type_t type; +}; + +typedef struct { + plutovg_paint_t base; + plutovg_color_t color; +} plutovg_solid_paint_t; + +typedef enum { + PLUTOVG_GRADIENT_TYPE_LINEAR, + PLUTOVG_GRADIENT_TYPE_RADIAL +} plutovg_gradient_type_t; + +typedef struct { + plutovg_paint_t base; + plutovg_gradient_type_t type; + plutovg_spread_method_t spread; + plutovg_matrix_t matrix; + plutovg_gradient_stop_t* stops; + int nstops; + float values[6]; +} plutovg_gradient_paint_t; + +typedef struct { + plutovg_paint_t base; + plutovg_texture_type_t type; + float opacity; + plutovg_matrix_t matrix; + plutovg_surface_t* surface; +} plutovg_texture_paint_t; + +typedef struct { + int x; + int len; + int y; + unsigned char coverage; +} plutovg_span_t; + +typedef struct { + struct { + plutovg_span_t* data; + int size; + int capacity; + } spans; + + int x; + int y; + int w; + int h; +} plutovg_span_buffer_t; + +typedef struct { + float offset; + struct { + float* data; + int size; + int capacity; + } array; +} plutovg_stroke_dash_t; + +typedef struct { + float width; + plutovg_line_cap_t cap; + plutovg_line_join_t join; + float miter_limit; +} plutovg_stroke_style_t; + +typedef struct { + plutovg_stroke_style_t style; + plutovg_stroke_dash_t dash; +} plutovg_stroke_data_t; + +typedef struct plutovg_state { + plutovg_paint_t* paint; + plutovg_font_face_t* font_face; + plutovg_color_t color; + plutovg_matrix_t matrix; + plutovg_stroke_data_t stroke; + plutovg_span_buffer_t clip_spans; + plutovg_fill_rule_t winding; + plutovg_operator_t op; + float font_size; + float opacity; + bool clipping; + struct plutovg_state* next; +} plutovg_state_t; + +struct plutovg_canvas { + plutovg_ref_count_t ref_count; + plutovg_surface_t* surface; + plutovg_path_t* path; + plutovg_state_t* state; + plutovg_state_t* freed_state; + plutovg_font_face_cache_t* face_cache; + plutovg_rect_t clip_rect; + plutovg_span_buffer_t clip_spans; + plutovg_span_buffer_t fill_spans; +}; + +void plutovg_span_buffer_init(plutovg_span_buffer_t* span_buffer); +void plutovg_span_buffer_init_rect(plutovg_span_buffer_t* span_buffer, int x, int y, int width, int height); +void plutovg_span_buffer_reset(plutovg_span_buffer_t* span_buffer); +void plutovg_span_buffer_destroy(plutovg_span_buffer_t* span_buffer); +void plutovg_span_buffer_copy(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* source); +bool plutovg_span_buffer_contains(const plutovg_span_buffer_t* span_buffer, float x, float y); +void plutovg_span_buffer_extents(plutovg_span_buffer_t* span_buffer, plutovg_rect_t* extents); +void plutovg_span_buffer_intersect(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* a, const plutovg_span_buffer_t* b); + +void plutovg_rasterize(plutovg_span_buffer_t* span_buffer, const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_rect_t* clip_rect, const plutovg_stroke_data_t* stroke_data, plutovg_fill_rule_t winding); +void plutovg_blend(plutovg_canvas_t* canvas, const plutovg_span_buffer_t* span_buffer); +void plutovg_memfill32(unsigned int* dest, int length, unsigned int value); + +#endif // PLUTOVG_PRIVATE_H diff --git a/vendor/lunasvg/plutovg/source/plutovg-rasterize.c b/vendor/lunasvg/plutovg/source/plutovg-rasterize.c new file mode 100644 index 0000000..559f0ee --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-rasterize.c @@ -0,0 +1,394 @@ +#include "plutovg-private.h" +#include "plutovg-utils.h" + +#include "plutovg-ft-raster.h" +#include "plutovg-ft-stroker.h" + +#include + +void plutovg_span_buffer_init(plutovg_span_buffer_t* span_buffer) +{ + plutovg_array_init(span_buffer->spans); + plutovg_span_buffer_reset(span_buffer); +} + +void plutovg_span_buffer_init_rect(plutovg_span_buffer_t* span_buffer, int x, int y, int width, int height) +{ + plutovg_array_clear(span_buffer->spans); + plutovg_array_ensure(span_buffer->spans, height); + plutovg_span_t* spans = span_buffer->spans.data; + for(int i = 0; i < height; i++) { + spans[i].x = x; + spans[i].y = y + i; + spans[i].len = width; + spans[i].coverage = 255; + } + + span_buffer->x = x; + span_buffer->y = y; + span_buffer->w = width; + span_buffer->h = height; + span_buffer->spans.size = height; +} + +void plutovg_span_buffer_reset(plutovg_span_buffer_t* span_buffer) +{ + plutovg_array_clear(span_buffer->spans); + span_buffer->x = 0; + span_buffer->y = 0; + span_buffer->w = -1; + span_buffer->h = -1; +} + +void plutovg_span_buffer_destroy(plutovg_span_buffer_t* span_buffer) +{ + plutovg_array_destroy(span_buffer->spans); +} + +void plutovg_span_buffer_copy(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* source) +{ + plutovg_array_clear(span_buffer->spans); + plutovg_array_append(span_buffer->spans, source->spans); + span_buffer->x = source->x; + span_buffer->y = source->y; + span_buffer->w = source->w; + span_buffer->h = source->h; +} + +bool plutovg_span_buffer_contains(const plutovg_span_buffer_t* span_buffer, float x, float y) +{ + const int ix = (int)floorf(x); + const int iy = (int)floorf(y); + + for(int i = 0; i < span_buffer->spans.size; i++) { + plutovg_span_t* span = &span_buffer->spans.data[i]; + if(span->y != iy) + continue; + if(ix >= span->x && ix < (span->x + span->len)) { + return true; + } + } + + return false; +} + +static void plutovg_span_buffer_update_extents(plutovg_span_buffer_t* span_buffer) +{ + if(span_buffer->w != -1 && span_buffer->h != -1) + return; + if(span_buffer->spans.size == 0) { + span_buffer->x = 0; + span_buffer->y = 0; + span_buffer->w = 0; + span_buffer->h = 0; + return; + } + + plutovg_span_t* spans = span_buffer->spans.data; + int x1 = INT_MAX; + int y1 = spans[0].y; + int x2 = 0; + int y2 = spans[span_buffer->spans.size - 1].y; + for(int i = 0; i < span_buffer->spans.size; i++) { + if(spans[i].x < x1) x1 = spans[i].x; + if(spans[i].x + spans[i].len > x2) x2 = spans[i].x + spans[i].len; + } + + span_buffer->x = x1; + span_buffer->y = y1; + span_buffer->w = x2 - x1; + span_buffer->h = y2 - y1 + 1; +} + +void plutovg_span_buffer_extents(plutovg_span_buffer_t* span_buffer, plutovg_rect_t* extents) +{ + plutovg_span_buffer_update_extents(span_buffer); + extents->x = span_buffer->x; + extents->y = span_buffer->y; + extents->w = span_buffer->w; + extents->h = span_buffer->h; +} + +void plutovg_span_buffer_intersect(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* a, const plutovg_span_buffer_t* b) +{ + plutovg_span_buffer_reset(span_buffer); + plutovg_array_ensure(span_buffer->spans, plutovg_max(a->spans.size, b->spans.size)); + + plutovg_span_t* a_spans = a->spans.data; + plutovg_span_t* a_end = a_spans + a->spans.size; + + plutovg_span_t* b_spans = b->spans.data; + plutovg_span_t* b_end = b_spans + b->spans.size; + while(a_spans < a_end && b_spans < b_end) { + if(b_spans->y > a_spans->y) { + ++a_spans; + continue; + } + + if(a_spans->y != b_spans->y) { + ++b_spans; + continue; + } + + int ax1 = a_spans->x; + int ax2 = ax1 + a_spans->len; + int bx1 = b_spans->x; + int bx2 = bx1 + b_spans->len; + if(bx1 < ax1 && bx2 < ax1) { + ++b_spans; + continue; + } + + if(ax1 < bx1 && ax2 < bx1) { + ++a_spans; + continue; + } + + int x = plutovg_max(ax1, bx1); + int len = plutovg_min(ax2, bx2) - x; + if(len) { + plutovg_array_ensure(span_buffer->spans, 1); + plutovg_span_t* span = span_buffer->spans.data + span_buffer->spans.size; + span->x = x; + span->len = len; + span->y = a_spans->y; + span->coverage = (a_spans->coverage * b_spans->coverage) / 255; + span_buffer->spans.size += 1; + } + + if(ax2 < bx2) { + ++a_spans; + } else { + ++b_spans; + } + } +} + +#define ALIGN_SIZE(size) (((size) + 7ul) & ~7ul) +static PVG_FT_Outline* ft_outline_create(int points, int contours) +{ + size_t points_size = ALIGN_SIZE((points + contours) * sizeof(PVG_FT_Vector)); + size_t tags_size = ALIGN_SIZE((points + contours) * sizeof(char)); + size_t contours_size = ALIGN_SIZE(contours * sizeof(int)); + size_t contours_flag_size = ALIGN_SIZE(contours * sizeof(char)); + PVG_FT_Outline* outline = malloc(points_size + tags_size + contours_size + contours_flag_size + sizeof(PVG_FT_Outline)); + + PVG_FT_Byte* outline_data = (PVG_FT_Byte*)(outline + 1); + outline->points = (PVG_FT_Vector*)(outline_data); + outline->tags = (char*)(outline_data + points_size); + outline->contours = (int*)(outline_data + points_size + tags_size); + outline->contours_flag = (char*)(outline_data + points_size + tags_size + contours_size); + outline->n_points = 0; + outline->n_contours = 0; + outline->flags = 0x0; + return outline; +} + +static void ft_outline_destroy(PVG_FT_Outline* outline) +{ + free(outline); +} + +#define FT_COORD(x) (PVG_FT_Pos)(roundf(x * 64)) +static void ft_outline_move_to(PVG_FT_Outline* ft, float x, float y) +{ + ft->points[ft->n_points].x = FT_COORD(x); + ft->points[ft->n_points].y = FT_COORD(y); + ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON; + if(ft->n_points) { + ft->contours[ft->n_contours] = ft->n_points - 1; + ft->n_contours++; + } + + ft->contours_flag[ft->n_contours] = 1; + ft->n_points++; +} + +static void ft_outline_line_to(PVG_FT_Outline* ft, float x, float y) +{ + ft->points[ft->n_points].x = FT_COORD(x); + ft->points[ft->n_points].y = FT_COORD(y); + ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON; + ft->n_points++; +} + +static void ft_outline_cubic_to(PVG_FT_Outline* ft, float x1, float y1, float x2, float y2, float x3, float y3) +{ + ft->points[ft->n_points].x = FT_COORD(x1); + ft->points[ft->n_points].y = FT_COORD(y1); + ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_CUBIC; + ft->n_points++; + + ft->points[ft->n_points].x = FT_COORD(x2); + ft->points[ft->n_points].y = FT_COORD(y2); + ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_CUBIC; + ft->n_points++; + + ft->points[ft->n_points].x = FT_COORD(x3); + ft->points[ft->n_points].y = FT_COORD(y3); + ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON; + ft->n_points++; +} + +static void ft_outline_close(PVG_FT_Outline* ft) +{ + ft->contours_flag[ft->n_contours] = 0; + int index = ft->n_contours ? ft->contours[ft->n_contours - 1] + 1 : 0; + if(index == ft->n_points) + return; + ft->points[ft->n_points].x = ft->points[index].x; + ft->points[ft->n_points].y = ft->points[index].y; + ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON; + ft->n_points++; +} + +static void ft_outline_end(PVG_FT_Outline* ft) +{ + if(ft->n_points) { + ft->contours[ft->n_contours] = ft->n_points - 1; + ft->n_contours++; + } +} + +static PVG_FT_Outline* ft_outline_convert_stroke(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_data_t* stroke_data); + +static PVG_FT_Outline* ft_outline_convert(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_data_t* stroke_data) +{ + if(stroke_data) { + return ft_outline_convert_stroke(path, matrix, stroke_data); + } + + plutovg_path_iterator_t it; + plutovg_path_iterator_init(&it, path); + + plutovg_point_t points[3]; + PVG_FT_Outline* outline = ft_outline_create(path->num_points, path->num_contours); + while(plutovg_path_iterator_has_next(&it)) { + switch(plutovg_path_iterator_next(&it, points)) { + case PLUTOVG_PATH_COMMAND_MOVE_TO: + plutovg_matrix_map_points(matrix, points, points, 1); + ft_outline_move_to(outline, points[0].x, points[0].y); + break; + case PLUTOVG_PATH_COMMAND_LINE_TO: + plutovg_matrix_map_points(matrix, points, points, 1); + ft_outline_line_to(outline, points[0].x, points[0].y); + break; + case PLUTOVG_PATH_COMMAND_CUBIC_TO: + plutovg_matrix_map_points(matrix, points, points, 3); + ft_outline_cubic_to(outline, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); + break; + case PLUTOVG_PATH_COMMAND_CLOSE: + ft_outline_close(outline); + break; + } + } + + ft_outline_end(outline); + return outline; +} + +static PVG_FT_Outline* ft_outline_convert_dash(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_dash_t* stroke_dash) +{ + if(stroke_dash->array.size == 0) + return ft_outline_convert(path, matrix, NULL); + plutovg_path_t* dashed = plutovg_path_clone_dashed(path, stroke_dash->offset, stroke_dash->array.data, stroke_dash->array.size); + PVG_FT_Outline* outline = ft_outline_convert(dashed, matrix, NULL); + plutovg_path_destroy(dashed); + return outline; +} + +static PVG_FT_Outline* ft_outline_convert_stroke(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_data_t* stroke_data) +{ + double scale_x = sqrt(matrix->a * matrix->a + matrix->b * matrix->b); + double scale_y = sqrt(matrix->c * matrix->c + matrix->d * matrix->d); + + double scale = hypot(scale_x, scale_y) / PLUTOVG_SQRT2; + double width = stroke_data->style.width * scale; + + PVG_FT_Fixed ftWidth = (PVG_FT_Fixed)(width * 0.5 * (1 << 6)); + PVG_FT_Fixed ftMiterLimit = (PVG_FT_Fixed)(stroke_data->style.miter_limit * (1 << 16)); + + PVG_FT_Stroker_LineCap ftCap; + switch(stroke_data->style.cap) { + case PLUTOVG_LINE_CAP_SQUARE: + ftCap = PVG_FT_STROKER_LINECAP_SQUARE; + break; + case PLUTOVG_LINE_CAP_ROUND: + ftCap = PVG_FT_STROKER_LINECAP_ROUND; + break; + default: + ftCap = PVG_FT_STROKER_LINECAP_BUTT; + break; + } + + PVG_FT_Stroker_LineJoin ftJoin; + switch(stroke_data->style.join) { + case PLUTOVG_LINE_JOIN_BEVEL: + ftJoin = PVG_FT_STROKER_LINEJOIN_BEVEL; + break; + case PLUTOVG_LINE_JOIN_ROUND: + ftJoin = PVG_FT_STROKER_LINEJOIN_ROUND; + break; + default: + ftJoin = PVG_FT_STROKER_LINEJOIN_MITER_FIXED; + break; + } + + PVG_FT_Stroker stroker; + PVG_FT_Stroker_New(&stroker); + PVG_FT_Stroker_Set(stroker, ftWidth, ftCap, ftJoin, ftMiterLimit); + + PVG_FT_Outline* outline = ft_outline_convert_dash(path, matrix, &stroke_data->dash); + PVG_FT_Stroker_ParseOutline(stroker, outline); + + PVG_FT_UInt points; + PVG_FT_UInt contours; + PVG_FT_Stroker_GetCounts(stroker, &points, &contours); + + PVG_FT_Outline* stroke_outline = ft_outline_create(points, contours); + PVG_FT_Stroker_Export(stroker, stroke_outline); + + PVG_FT_Stroker_Done(stroker); + ft_outline_destroy(outline); + return stroke_outline; +} + +static void spans_generation_callback(int count, const PVG_FT_Span* spans, void* user) +{ + plutovg_span_buffer_t* span_buffer = (plutovg_span_buffer_t*)(user); + plutovg_array_append_data(span_buffer->spans, spans, count); +} + +void plutovg_rasterize(plutovg_span_buffer_t* span_buffer, const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_rect_t* clip_rect, const plutovg_stroke_data_t* stroke_data, plutovg_fill_rule_t winding) +{ + PVG_FT_Outline* outline = ft_outline_convert(path, matrix, stroke_data); + if(stroke_data) { + outline->flags = PVG_FT_OUTLINE_NONE; + } else { + switch(winding) { + case PLUTOVG_FILL_RULE_EVEN_ODD: + outline->flags = PVG_FT_OUTLINE_EVEN_ODD_FILL; + break; + default: + outline->flags = PVG_FT_OUTLINE_NONE; + break; + } + } + + PVG_FT_Raster_Params params; + params.flags = PVG_FT_RASTER_FLAG_DIRECT | PVG_FT_RASTER_FLAG_AA; + params.gray_spans = spans_generation_callback; + params.user = span_buffer; + params.source = outline; + if(clip_rect) { + params.flags |= PVG_FT_RASTER_FLAG_CLIP; + params.clip_box.xMin = (PVG_FT_Pos)clip_rect->x; + params.clip_box.yMin = (PVG_FT_Pos)clip_rect->y; + params.clip_box.xMax = (PVG_FT_Pos)(clip_rect->x + clip_rect->w); + params.clip_box.yMax = (PVG_FT_Pos)(clip_rect->y + clip_rect->h); + } + + plutovg_span_buffer_reset(span_buffer); + PVG_FT_Raster_Render(¶ms); + ft_outline_destroy(outline); +} diff --git a/vendor/lunasvg/plutovg/source/plutovg-stb-image-write.h b/vendor/lunasvg/plutovg/source/plutovg-stb-image-write.h new file mode 100644 index 0000000..dba9089 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-stb-image-write.h @@ -0,0 +1,1724 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + 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. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five 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); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + 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 format expands Y to RGB in the file format and 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.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef PLUTOVG_STB_IMAGE_WRITE_H +#define PLUTOVG_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//PLUTOVG_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + 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; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + 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; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(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 + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__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 stbiw__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 stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + 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 = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__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] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__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 = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__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 = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + 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; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__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 STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + 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 j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF 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((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 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 +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/vendor/lunasvg/plutovg/source/plutovg-stb-image.h b/vendor/lunasvg/plutovg/source/plutovg-stb-image.h new file mode 100644 index 0000000..3938baf --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-stb-image.h @@ -0,0 +1,7988 @@ +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef PLUTOVG_STB_IMAGE_H +#define PLUTOVG_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data); +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // PLUTOVG_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#if defined(_MSC_VER) || defined(__SYMBIAN32__) +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { + h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + j->marker = stbi__skip_jpeg_junk_at_end(j); + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); + } else { + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); + } + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + int hit_zeof_once; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); + } + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } + return 1; + } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (len > a->zout_end - zout) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + a->hit_zeof_once = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub +}; + +static int stbi__paeth(int a, int b, int c) +{ + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16 ? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + + for (j=0; j < y; ++j) { + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; + int filter = *raw++; + + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; + } + + raw += nk; + + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; + + // expand bits to bytes first + if (depth == 4) { + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; + } + } else if (depth == 2) { + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; + } + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; + } + } + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); + if (img_n == 1) { + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; + } + } else { + STBI_ASSERT(img_n == 3); + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; + } + } + } + } + } + + STBI_FREE(filter_buf); + if (!all_ok) return 0; + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + } + // even with SCAN_header, have to scan to see if we have a tRNS + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } + if (z->depth == 16) { + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } + + if (req_comp && req_comp != s->img_n) { + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/vendor/lunasvg/plutovg/source/plutovg-stb-truetype.h b/vendor/lunasvg/plutovg/source/plutovg-stb-truetype.h new file mode 100644 index 0000000..a66c5b4 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-stb-truetype.h @@ -0,0 +1,5077 @@ +// stb_truetype.h - v1.26 - public domain +// authored from 2009-2021 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// 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 +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.26 (2021-08-28) fix broken rasterizer +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need 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. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts 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_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// 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 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. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); 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=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + 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, e.g. if you don't +//// link with the C runtime library. + +#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]; + + // e.g. #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 + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(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 PLUTOVG_STB_TRUETYPE_H +#define PLUTOVG_STB_TRUETYPE_H + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// 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; + +STBTT_DEF 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; + +STBTT_DEF void stbtt_GetBakedQuad(const 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. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *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 align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF 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. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +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,gpos,svg; // 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__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF 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 + +STBTT_DEF 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. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF 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. + +STBTT_DEF 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. + +STBTT_DEF 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 + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF 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 + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF 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 + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF 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 + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// 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, + STBTT_vcubic + }; +#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,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF 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 +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF 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 + +STBTT_DEF 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 + +STBTT_DEF 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. + +STBTT_DEF 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 + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(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 oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF 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.) + +STBTT_DEF 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) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF 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); +STBTT_DEF 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_DEF 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); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(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 oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF 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; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// 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. + + +STBTT_DEF 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 + +STBTT_DEF 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 + +STBTT_DEF 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 // PLUTOVG_STB_TRUETYPE_H + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// 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) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#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(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 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + 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; +} + +static int stbtt_GetFontOffsetForIndex_internal(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*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 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) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + 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 + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // 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; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF 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; + + // 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 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + 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; +} + +STBTT_DEF 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; + + STBTT_assert(!info->cff.size); + + 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 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + 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; +} + +STBTT_DEF 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); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + 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; +} + +static int stbtt__GetGlyphShapeTT(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 < 0) { + // 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) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_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 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_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 { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // FALLTHROUGH + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF 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)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // 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; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // 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; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(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; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: return -1; // unsupported + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i, sti; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i= pairSetCount) return 0; + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + stbtt_uint8 *class1Records, *class2Records; + stbtt_int16 xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // 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)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF 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); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF 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); +} + +STBTT_DEF 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; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF 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=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // 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_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF 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); +} + +STBTT_DEF 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); +} + +STBTT_DEF 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); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// 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->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_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 >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_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__hheap hh = { 0, 0, 0 }; + 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->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } 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 = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // 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; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) +{ + STBTT_assert(top_width >= 0); + STBTT_assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; +} + +static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) +{ + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); +} + +static float stbtt__sized_triangle_area(float height, float width) +{ + return height * width / 2; +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); + scanline_fill[x] += height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, y_final, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + STBTT_assert(dy >= 0); + STBTT_assert(dx >= 0); + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1+1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | xxxxx..|............|............|............| + // | | /- xx*xxxx........|............|............| + // | | dy < | xxxxxx..|............|............| + // y_final | | \- | xx*xxx.........|............| + // sy1 | | | | xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) + y_crossing = y_bottom; + + sign = e->direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + y_final = y_bottom; + dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; this + // is exactly what the variable 'area' stores. it also gets a contribution + // from the line segment within it. the THIRD pixel will get the first + // pixel's rectangle contribution, the second pixel's rectangle contribution, + // and its own contribution. the 'own contribution' is the same in every pixel except + // the leftmost and rightmost, a trapezoid that slides down in each pixel. + // the second pixel's contribution to the third pixel will be the + // rectangle 1 wide times the height change in the second pixel, which is dy. + + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes for each step in x + // so the area advances by 'step' every time + + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); + + // the rest of the line is filled based on the total height of the line segment in this pixel + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +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__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +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; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // 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 + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++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); + stbtt__sort_edges(e, n); + + // 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; +} + +// tessellate until threshold 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; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static 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; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + 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; +} + +STBTT_DEF 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 = 0; + int *winding_lengths = NULL; + 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); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF 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) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_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; +} + +STBTT_DEF 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); +} + +STBTT_DEF 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); +} + +STBTT_DEF 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); +} + +STBTT_DEF 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); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(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 oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF 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)); +} + +STBTT_DEF 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); +} + +STBTT_DEF 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 + +static int stbtt_BakeFontBitmap_internal(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; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + 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 + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const 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; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + 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; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(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 prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3] = {0.f,0.f,0.f}; + float px,py,t,it,dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// 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(stbtt_uint8 *s1, stbtt_int32 len1, 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; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (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 +STBTT_DEF 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); + stbtt_int32 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) { + 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_internal((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; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, 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; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 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 +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/vendor/lunasvg/plutovg/source/plutovg-surface.c b/vendor/lunasvg/plutovg/source/plutovg-surface.c new file mode 100644 index 0000000..f12cd38 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-surface.c @@ -0,0 +1,295 @@ +#include "plutovg-private.h" +#include "plutovg-utils.h" + +#define STB_IMAGE_WRITE_STATIC +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "plutovg-stb-image-write.h" + +#define STB_IMAGE_STATIC +#define STB_IMAGE_IMPLEMENTATION +#include "plutovg-stb-image.h" + +static plutovg_surface_t* plutovg_surface_create_uninitialized(int width, int height) +{ + static const int kMaxSize = 1 << 15; + if(width <= 0 || height <= 0 || width >= kMaxSize || height >= kMaxSize) + return NULL; + const size_t size = width * height * 4; + plutovg_surface_t* surface = malloc(size + sizeof(plutovg_surface_t)); + if(surface == NULL) + return NULL; + plutovg_init_reference(surface); + surface->width = width; + surface->height = height; + surface->stride = width * 4; + surface->data = (uint8_t*)(surface + 1); + return surface; +} + +plutovg_surface_t* plutovg_surface_create(int width, int height) +{ + plutovg_surface_t* surface = plutovg_surface_create_uninitialized(width, height); + if(surface) + memset(surface->data, 0, surface->height * surface->stride); + return surface; +} + +plutovg_surface_t* plutovg_surface_create_for_data(unsigned char* data, int width, int height, int stride) +{ + plutovg_surface_t* surface = malloc(sizeof(plutovg_surface_t)); + plutovg_init_reference(surface); + surface->width = width; + surface->height = height; + surface->stride = stride; + surface->data = data; + return surface; +} + +static plutovg_surface_t* plutovg_surface_load_from_image(stbi_uc* image, int width, int height) +{ + plutovg_surface_t* surface = plutovg_surface_create_uninitialized(width, height); + if(surface) + plutovg_convert_rgba_to_argb(surface->data, image, surface->width, surface->height, surface->stride); + stbi_image_free(image); + return surface; +} + +plutovg_surface_t* plutovg_surface_load_from_image_file(const char* filename) +{ + int width, height, channels; + stbi_uc* image = stbi_load(filename, &width, &height, &channels, STBI_rgb_alpha); + if(image == NULL) + return NULL; + return plutovg_surface_load_from_image(image, width, height); +} + +plutovg_surface_t* plutovg_surface_load_from_image_data(const void* data, int length) +{ + int width, height, channels; + stbi_uc* image = stbi_load_from_memory(data, length, &width, &height, &channels, STBI_rgb_alpha); + if(image == NULL) + return NULL; + return plutovg_surface_load_from_image(image, width, height); +} + +static const uint8_t base64_table[128] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3F, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, + 0x3C, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, + 0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +plutovg_surface_t* plutovg_surface_load_from_image_base64(const char* data, int length) +{ + plutovg_surface_t* surface = NULL; + uint8_t* output_data = NULL; + size_t output_length = 0; + + size_t equals_sign_count = 0; + size_t sidx = 0; + size_t didx = 0; + + if(length == -1) + length = strlen(data); + output_data = malloc(length); + if(output_data == NULL) + return NULL; + for(int i = 0; i < length; ++i) { + uint8_t cc = data[i]; + if(cc == '=') { + ++equals_sign_count; + } else if(cc == '+' || cc == '/' || PLUTOVG_IS_ALNUM(cc)) { + if(equals_sign_count > 0) + goto cleanup; + output_data[output_length++] = base64_table[cc]; + } else if(!PLUTOVG_IS_WS(cc)) { + goto cleanup; + } + } + + if(output_length == 0 || equals_sign_count > 2 || (output_length % 4) == 1) + goto cleanup; + output_length -= (output_length + 3) / 4; + if(output_length == 0) { + goto cleanup; + } + + if(output_length > 1) { + while(didx < output_length - 2) { + output_data[didx + 0] = (((output_data[sidx + 0] << 2) & 255) | ((output_data[sidx + 1] >> 4) & 003)); + output_data[didx + 1] = (((output_data[sidx + 1] << 4) & 255) | ((output_data[sidx + 2] >> 2) & 017)); + output_data[didx + 2] = (((output_data[sidx + 2] << 6) & 255) | ((output_data[sidx + 3] >> 0) & 077)); + sidx += 4; + didx += 3; + } + } + + if(didx < output_length) + output_data[didx] = (((output_data[sidx + 0] << 2) & 255) | ((output_data[sidx + 1] >> 4) & 003)); + if(++didx < output_length) { + output_data[didx] = (((output_data[sidx + 1] << 4) & 255) | ((output_data[sidx + 2] >> 2) & 017)); + } + + surface = plutovg_surface_load_from_image_data(output_data, output_length); +cleanup: + free(output_data); + return surface; +} + +plutovg_surface_t* plutovg_surface_reference(plutovg_surface_t* surface) +{ + plutovg_increment_reference(surface); + return surface; +} + +void plutovg_surface_destroy(plutovg_surface_t* surface) +{ + if(plutovg_destroy_reference(surface)) { + free(surface); + } +} + +int plutovg_surface_get_reference_count(const plutovg_surface_t* surface) +{ + return plutovg_get_reference_count(surface); +} + +unsigned char* plutovg_surface_get_data(const plutovg_surface_t* surface) +{ + return surface->data; +} + +int plutovg_surface_get_width(const plutovg_surface_t* surface) +{ + return surface->width; +} + +int plutovg_surface_get_height(const plutovg_surface_t* surface) +{ + return surface->height; +} + +int plutovg_surface_get_stride(const plutovg_surface_t* surface) +{ + return surface->stride; +} + +void plutovg_surface_clear(plutovg_surface_t* surface, const plutovg_color_t* color) +{ + uint32_t pixel = plutovg_premultiply_argb(plutovg_color_to_argb32(color)); + for(int y = 0; y < surface->height; y++) { + uint32_t* pixels = (uint32_t*)(surface->data + surface->stride * y); + plutovg_memfill32(pixels, surface->width, pixel); + } +} + +static void plutovg_surface_write_begin(const plutovg_surface_t* surface) +{ + plutovg_convert_argb_to_rgba(surface->data, surface->data, surface->width, surface->height, surface->stride); +} + +static void plutovg_surface_write_end(const plutovg_surface_t* surface) +{ + plutovg_convert_rgba_to_argb(surface->data, surface->data, surface->width, surface->height, surface->stride); +} + +bool plutovg_surface_write_to_png(const plutovg_surface_t* surface, const char* filename) +{ + plutovg_surface_write_begin(surface); + int success = stbi_write_png(filename, surface->width, surface->height, 4, surface->data, surface->stride); + plutovg_surface_write_end(surface); + return success; +} + +bool plutovg_surface_write_to_jpg(const plutovg_surface_t* surface, const char* filename, int quality) +{ + plutovg_surface_write_begin(surface); + int success = stbi_write_jpg(filename, surface->width, surface->height, 4, surface->data, quality); + plutovg_surface_write_end(surface); + return success; +} + +bool plutovg_surface_write_to_png_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure) +{ + plutovg_surface_write_begin(surface); + int success = stbi_write_png_to_func(write_func, closure, surface->width, surface->height, 4, surface->data, surface->stride); + plutovg_surface_write_end(surface); + return success; +} + +bool plutovg_surface_write_to_jpg_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure, int quality) +{ + plutovg_surface_write_begin(surface); + int success = stbi_write_jpg_to_func(write_func, closure, surface->width, surface->height, 4, surface->data, quality); + plutovg_surface_write_end(surface); + return success; +} + +void plutovg_convert_argb_to_rgba(unsigned char* dst, const unsigned char* src, int width, int height, int stride) +{ + for(int y = 0; y < height; y++) { + const uint32_t* src_row = (const uint32_t*)(src + stride * y); + unsigned char* dst_row = dst + stride * y; + for(int x = 0; x < width; x++) { + uint32_t pixel = src_row[x]; + uint32_t a = (pixel >> 24) & 0xFF; + if(a == 0) { + *dst_row++ = 0; + *dst_row++ = 0; + *dst_row++ = 0; + *dst_row++ = 0; + } else { + uint32_t r = (pixel >> 16) & 0xFF; + uint32_t g = (pixel >> 8) & 0xFF; + uint32_t b = (pixel >> 0) & 0xFF; + if(a != 255) { + r = (r * 255) / a; + g = (g * 255) / a; + b = (b * 255) / a; + } + + *dst_row++ = r; + *dst_row++ = g; + *dst_row++ = b; + *dst_row++ = a; + } + } + } +} + +void plutovg_convert_rgba_to_argb(unsigned char* dst, const unsigned char* src, int width, int height, int stride) +{ + for(int y = 0; y < height; y++) { + const unsigned char* src_row = src + stride * y; + uint32_t* dst_row = (uint32_t*)(dst + stride * y); + for(int x = 0; x < width; x++) { + uint32_t a = src_row[4 * x + 3]; + if(a == 0) { + dst_row[x] = 0x00000000; + } else { + uint32_t r = src_row[4 * x + 0]; + uint32_t g = src_row[4 * x + 1]; + uint32_t b = src_row[4 * x + 2]; + if(a != 255) { + r = (r * a) / 255; + g = (g * a) / 255; + b = (b * a) / 255; + } + + dst_row[x] = (a << 24) | (r << 16) | (g << 8) | b; + } + } + } +} diff --git a/vendor/lunasvg/plutovg/source/plutovg-utils.h b/vendor/lunasvg/plutovg/source/plutovg-utils.h new file mode 100644 index 0000000..68c86bd --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-utils.h @@ -0,0 +1,211 @@ +#ifndef PLUTOVG_UTILS_H +#define PLUTOVG_UTILS_H + +#include +#include +#include +#include +#include +#include +#include + +#define PLUTOVG_IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define PLUTOVG_IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) +#define PLUTOVG_IS_ALNUM(c) (PLUTOVG_IS_ALPHA(c) || PLUTOVG_IS_NUM(c)) +#define PLUTOVG_IS_WS(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r') + +#define plutovg_min(a, b) ((a) < (b) ? (a) : (b)) +#define plutovg_max(a, b) ((a) > (b) ? (a) : (b)) +#define plutovg_clamp(v, lo, hi) ((v) < (lo) ? (lo) : ((v) > (hi) ? (hi) : (v))) + +#define plutovg_alpha(c) (((c) >> 24) & 0xff) +#define plutovg_red(c) (((c) >> 16) & 0xff) +#define plutovg_green(c) (((c) >> 8) & 0xff) +#define plutovg_blue(c) (((c) >> 0) & 0xff) + +#define plutovg_array_init(array) \ + do { \ + (array).data = NULL; \ + (array).size = 0; \ + (array).capacity = 0; \ + } while(0) + +#define plutovg_array_ensure(array, count) \ + do { \ + if((array).size + (count) > (array).capacity) { \ + int capacity = (array).size + (count); \ + int newcapacity = (array).capacity == 0 ? 8 : (array).capacity; \ + while(newcapacity < capacity) { newcapacity *= 2; } \ + (array).data = realloc((array).data, newcapacity * sizeof((array).data[0])); \ + (array).capacity = newcapacity; \ + } \ + } while(0) + +#define plutovg_array_append_data(array, newdata, count) \ + do { \ + if(newdata && count > 0) { \ + plutovg_array_ensure(array, count); \ + memcpy((array).data + (array).size, newdata, (count) * sizeof((newdata)[0])); \ + (array).size += count; \ + } \ + } while(0) + +#define plutovg_array_append(array, other) plutovg_array_append_data(array, (other).data, (other).size) +#define plutovg_array_clear(array) ((array).size = 0) +#define plutovg_array_destroy(array) free((array).data) + +static inline uint32_t plutovg_premultiply_argb(uint32_t color) +{ + uint32_t a = plutovg_alpha(color); + uint32_t r = plutovg_red(color); + uint32_t g = plutovg_green(color); + uint32_t b = plutovg_blue(color); + if(a != 255) { + r = (r * a) / 255; + g = (g * a) / 255; + b = (b * a) / 255; + } + + return (a << 24) | (r << 16) | (g << 8) | (b); +} + +static inline bool plutovg_parse_number(const char** begin, const char* end, float* number) +{ + const char* it = *begin; + float integer = 0; + float fraction = 0; + float exponent = 0; + int sign = 1; + int expsign = 1; + + if(it < end && *it == '+') { + ++it; + } else if(it < end && *it == '-') { + ++it; + sign = -1; + } + + if(it >= end || (*it != '.' && !PLUTOVG_IS_NUM(*it))) + return false; + if(PLUTOVG_IS_NUM(*it)) { + do { + integer = 10.f * integer + (*it++ - '0'); + } while(it < end && PLUTOVG_IS_NUM(*it)); + } + + if(it < end && *it == '.') { + ++it; + if(it >= end || !PLUTOVG_IS_NUM(*it)) + return false; + float divisor = 1.f; + do { + fraction = 10.f * fraction + (*it++ - '0'); + divisor *= 10.f; + } while(it < end && PLUTOVG_IS_NUM(*it)); + fraction /= divisor; + } + + if(it < end && (*it == 'e' || *it == 'E')) { + ++it; + if(it < end && *it == '+') { + ++it; + } else if(it < end && *it == '-') { + ++it; + expsign = -1; + } + + if(it >= end || !PLUTOVG_IS_NUM(*it)) + return false; + do { + exponent = 10 * exponent + (*it++ - '0'); + } while(it < end && PLUTOVG_IS_NUM(*it)); + } + + *begin = it; + *number = sign * (integer + fraction); + if(exponent) + *number *= powf(10.f, expsign * exponent); + return *number >= -FLT_MAX && *number <= FLT_MAX; +} + +static inline bool plutovg_skip_delim(const char** begin, const char* end, const char delim) +{ + const char* it = *begin; + if(it < end && *it == delim) { + *begin = it + 1; + return true; + } + + return false; +} + +static inline bool plutovg_skip_string(const char** begin, const char* end, const char* data) +{ + const char* it = *begin; + while(it < end && *data && *it == *data) { + ++data; + ++it; + } + + if(*data == '\0') { + *begin = it; + return true; + } + + return false; +} + +static inline bool plutovg_skip_ws(const char** begin, const char* end) +{ + const char* it = *begin; + while(it < end && PLUTOVG_IS_WS(*it)) + ++it; + *begin = it; + return it < end; +} + +static inline bool plutovg_skip_ws_and_delim(const char** begin, const char* end, char delim) +{ + const char* it = *begin; + if(plutovg_skip_ws(&it, end)) { + if(!plutovg_skip_delim(&it, end, delim)) + return false; + plutovg_skip_ws(&it, end); + } else { + return false; + } + + *begin = it; + return it < end; +} + +static inline bool plutovg_skip_ws_and_comma(const char** begin, const char* end) +{ + return plutovg_skip_ws_and_delim(begin, end, ','); +} + +static inline bool plutovg_skip_ws_or_delim(const char** begin, const char* end, char delim, bool* has_delim) +{ + const char* it = *begin; + if(has_delim) + *has_delim = false; + if(plutovg_skip_ws(&it, end)) { + if(plutovg_skip_delim(&it, end, delim)) { + if(has_delim) + *has_delim = true; + plutovg_skip_ws(&it, end); + } + } + + if(it == *begin) + return false; + *begin = it; + return it < end; +} + +static inline bool plutovg_skip_ws_or_comma(const char** begin, const char* end, bool* has_comma) +{ + return plutovg_skip_ws_or_delim(begin, end, ',', has_comma); +} + +#endif // PLUTOVG_UTILS_H diff --git a/vendor/lunasvg/source/graphics.cpp b/vendor/lunasvg/source/graphics.cpp new file mode 100644 index 0000000..248c087 --- /dev/null +++ b/vendor/lunasvg/source/graphics.cpp @@ -0,0 +1,693 @@ +#include "graphics.h" +#include "lunasvg.h" + +#include +#include + +namespace lunasvg { + +const Color Color::Black(0xFF000000); +const Color Color::White(0xFFFFFFFF); +const Color Color::Transparent(0x00000000); + +const Rect Rect::Empty(0, 0, 0, 0); +const Rect Rect::Invalid(0, 0, -1, -1); +const Rect Rect::Infinite(-FLT_MAX / 2.f, -FLT_MAX / 2.f, FLT_MAX, FLT_MAX); + +Rect::Rect(const Box& box) + : x(box.x), y(box.y), w(box.w), h(box.h) +{ +} + +const Transform Transform::Identity(1, 0, 0, 1, 0, 0); + +Transform::Transform() +{ + plutovg_matrix_init_identity(&m_matrix); +} + +Transform::Transform(float a, float b, float c, float d, float e, float f) +{ + plutovg_matrix_init(&m_matrix, a, b, c, d, e, f); +} + +Transform::Transform(const Matrix& matrix) + : Transform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f) +{ +} + +Transform Transform::operator*(const Transform& transform) const +{ + plutovg_matrix_t result; + plutovg_matrix_multiply(&result, &transform.m_matrix, &m_matrix); + return result; +} + +Transform& Transform::operator*=(const Transform& transform) +{ + return (*this = *this * transform); +} + +Transform& Transform::multiply(const Transform& transform) +{ + return (*this *= transform); +} + +Transform& Transform::translate(float tx, float ty) +{ + return multiply(translated(tx, ty)); +} + +Transform& Transform::scale(float sx, float sy) +{ + return multiply(scaled(sx, sy)); +} + +Transform& Transform::rotate(float angle, float cx, float cy) +{ + return multiply(rotated(angle, cx, cy)); +} + +Transform& Transform::shear(float shx, float shy) +{ + return multiply(sheared(shx, shy)); +} + +Transform& Transform::postMultiply(const Transform& transform) +{ + return (*this = transform * *this); +} + +Transform& Transform::postTranslate(float tx, float ty) +{ + return postMultiply(translated(tx, ty)); +} + +Transform& Transform::postScale(float sx, float sy) +{ + return postMultiply(scaled(sx, sy)); +} + +Transform& Transform::postRotate(float angle, float cx, float cy) +{ + return postMultiply(rotated(angle, cx, cy)); +} + +Transform& Transform::postShear(float shx, float shy) +{ + return postMultiply(sheared(shx, shy)); +} + +Transform Transform::inverse() const +{ + plutovg_matrix_t inverse; + plutovg_matrix_invert(&m_matrix, &inverse); + return inverse; +} + +Transform& Transform::invert() +{ + plutovg_matrix_invert(&m_matrix, &m_matrix); + return *this; +} + +void Transform::reset() +{ + plutovg_matrix_init_identity(&m_matrix); +} + +Point Transform::mapPoint(float x, float y) const +{ + plutovg_matrix_map(&m_matrix, x, y, &x, &y); + return Point(x, y); +} + +Point Transform::mapPoint(const Point& point) const +{ + return mapPoint(point.x, point.y); +} + +Rect Transform::mapRect(const Rect& rect) const +{ + if(!rect.isValid()) { + return Rect::Invalid; + } + + plutovg_rect_t result = {rect.x, rect.y, rect.w, rect.h}; + plutovg_matrix_map_rect(&m_matrix, &result, &result); + return result; +} + +float Transform::xScale() const +{ + return std::sqrt(m_matrix.a * m_matrix.a + m_matrix.b * m_matrix.b); +} + +float Transform::yScale() const +{ + return std::sqrt(m_matrix.c * m_matrix.c + m_matrix.d * m_matrix.d); +} + +bool Transform::parse(const char* data, size_t length) +{ + return plutovg_matrix_parse(&m_matrix, data, length); +} + +Transform Transform::rotated(float angle, float cx, float cy) +{ + plutovg_matrix_t matrix; + if(cx == 0.f && cy == 0.f) { + plutovg_matrix_init_rotate(&matrix, PLUTOVG_DEG2RAD(angle)); + } else { + plutovg_matrix_init_translate(&matrix, cx, cy); + plutovg_matrix_rotate(&matrix, PLUTOVG_DEG2RAD(angle)); + plutovg_matrix_translate(&matrix, -cx, -cy); + } + + return matrix; +} + +Transform Transform::scaled(float sx, float sy) +{ + plutovg_matrix_t matrix; + plutovg_matrix_init_scale(&matrix, sx, sy); + return matrix; +} + +Transform Transform::sheared(float shx, float shy) +{ + plutovg_matrix_t matrix; + plutovg_matrix_init_shear(&matrix, PLUTOVG_DEG2RAD(shx), PLUTOVG_DEG2RAD(shy)); + return matrix; +} + +Transform Transform::translated(float tx, float ty) +{ + plutovg_matrix_t matrix; + plutovg_matrix_init_translate(&matrix, tx, ty); + return matrix; +} + +Path::Path(const Path& path) + : m_data(plutovg_path_reference(path.data())) +{ +} + +Path::Path(Path&& path) + : m_data(path.release()) +{ +} + +Path::~Path() +{ + plutovg_path_destroy(m_data); +} + +Path& Path::operator=(const Path& path) +{ + Path(path).swap(*this); + return *this; +} + +Path& Path::operator=(Path&& path) +{ + Path(std::move(path)).swap(*this); + return *this; +} + +void Path::moveTo(float x, float y) +{ + plutovg_path_move_to(ensure(), x, y); +} + +void Path::lineTo(float x, float y) +{ + plutovg_path_line_to(ensure(), x, y); +} + +void Path::quadTo(float x1, float y1, float x2, float y2) +{ + plutovg_path_quad_to(ensure(), x1, y1, x2, y2); +} + +void Path::cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) +{ + plutovg_path_cubic_to(ensure(), x1, y1, x2, y2, x3, y3); +} + +void Path::arcTo(float rx, float ry, float xAxisRotation, bool largeArcFlag, bool sweepFlag, float x, float y) +{ + plutovg_path_arc_to(ensure(), rx, ry, PLUTOVG_DEG2RAD(xAxisRotation), largeArcFlag, sweepFlag, x, y); +} + +void Path::close() +{ + plutovg_path_close(ensure()); +} + +void Path::addEllipse(float cx, float cy, float rx, float ry) +{ + plutovg_path_add_ellipse(ensure(), cx, cy, rx, ry); +} + +void Path::addRoundRect(float x, float y, float w, float h, float rx, float ry) +{ + plutovg_path_add_round_rect(ensure(), x, y, w, h, rx, ry); +} + +void Path::addRect(float x, float y, float w, float h) +{ + plutovg_path_add_rect(ensure(), x, y, w, h); +} + +void Path::addEllipse(const Point& center, const Size& radii) +{ + addEllipse(center.x, center.y, radii.w, radii.h); +} + +void Path::addRoundRect(const Rect& rect, const Size& radii) +{ + addRoundRect(rect.x, rect.y, rect.w, rect.h, radii.w, radii.h); +} + +void Path::addRect(const Rect& rect) +{ + addRect(rect.x, rect.y, rect.w, rect.h); +} + +void Path::reset() +{ + if(m_data == nullptr) + return; + if(isUnique()) { + plutovg_path_reset(m_data); + } else { + plutovg_path_destroy(m_data); + m_data = nullptr; + } +} + +Rect Path::boundingRect() const +{ + if(m_data == nullptr) + return Rect::Empty; + plutovg_rect_t extents; + plutovg_path_extents(m_data, &extents, false); + return extents; +} + +bool Path::isEmpty() const +{ + if(m_data) + return plutovg_path_get_elements(m_data, nullptr) == 0; + return true; +} + +bool Path::isUnique() const +{ + return plutovg_path_get_reference_count(m_data) == 1; +} + +bool Path::parse(const char* data, size_t length) +{ + plutovg_path_reset(ensure()); + return plutovg_path_parse(m_data, data, length); +} + +plutovg_path_t* Path::ensure() +{ + if(isNull()) { + m_data = plutovg_path_create(); + } else if(!isUnique()) { + plutovg_path_destroy(m_data); + m_data = plutovg_path_clone(m_data); + } + + return m_data; +} + +PathIterator::PathIterator(const Path& path) + : m_size(plutovg_path_get_elements(path.data(), &m_elements)) + , m_index(0) +{ +} + +PathCommand PathIterator::currentSegment(std::array& points) const +{ + auto command = m_elements[m_index].header.command; + switch(command) { + case PLUTOVG_PATH_COMMAND_MOVE_TO: + points[0] = m_elements[m_index + 1].point; + break; + case PLUTOVG_PATH_COMMAND_LINE_TO: + points[0] = m_elements[m_index + 1].point; + break; + case PLUTOVG_PATH_COMMAND_CUBIC_TO: + points[0] = m_elements[m_index + 1].point; + points[1] = m_elements[m_index + 2].point; + points[2] = m_elements[m_index + 3].point; + break; + case PLUTOVG_PATH_COMMAND_CLOSE: + points[0] = m_elements[m_index + 1].point; + break; + } + + return PathCommand(command); +} + +void PathIterator::next() +{ + m_index += m_elements[m_index].header.length; +} + +FontFace::FontFace(plutovg_font_face_t* face) + : m_face(plutovg_font_face_reference(face)) +{ +} + +FontFace::FontFace(const void* data, size_t length, plutovg_destroy_func_t destroy_func, void* closure) + : m_face(plutovg_font_face_load_from_data(data, length, 0, destroy_func, closure)) +{ +} + +FontFace::FontFace(const char* filename) + : m_face(plutovg_font_face_load_from_file(filename, 0)) +{ +} + +FontFace::FontFace(const FontFace& face) + : m_face(plutovg_font_face_reference(face.get())) +{ +} + +FontFace::FontFace(FontFace&& face) + : m_face(face.release()) +{ +} + +FontFace::~FontFace() +{ + plutovg_font_face_destroy(m_face); +} + +FontFace& FontFace::operator=(const FontFace& face) +{ + FontFace(face).swap(*this); + return *this; +} + +FontFace& FontFace::operator=(FontFace&& face) +{ + FontFace(std::move(face)).swap(*this); + return *this; +} + +void FontFace::swap(FontFace& face) +{ + std::swap(m_face, face.m_face); +} + +plutovg_font_face_t* FontFace::release() +{ + return std::exchange(m_face, nullptr); +} + +bool FontFaceCache::addFontFace(const std::string& family, bool bold, bool italic, const FontFace& face) +{ + if(!face.isNull()) + plutovg_font_face_cache_add(m_cache, family.data(), bold, italic, face.get()); + return !face.isNull(); +} + +FontFace FontFaceCache::getFontFace(const std::string& family, bool bold, bool italic) const +{ + if(auto face = plutovg_font_face_cache_get(m_cache, family.data(), bold, italic)) { + return FontFace(face); + } + + static const struct { + const char* generic; + const char* fallback; + } generic_fallbacks[] = { +#if defined(__linux__) + {"sans-serif", "DejaVu Sans"}, + {"serif", "DejaVu Serif"}, + {"monospace", "DejaVu Sans Mono"}, +#else + {"sans-serif", "Arial"}, + {"serif", "Times New Roman"}, + {"monospace", "Courier New"}, +#endif + {"cursive", "Comic Sans MS"}, + {"fantasy", "Impact"} + }; + + for(auto value : generic_fallbacks) { + if(value.generic == family || family.empty()) { + return FontFace(plutovg_font_face_cache_get(m_cache, value.fallback, bold, italic)); + } + } + + return FontFace(); +} + +FontFaceCache::FontFaceCache() + : m_cache(plutovg_font_face_cache_create()) +{ +#ifndef LUNASVG_DISABLE_LOAD_SYSTEM_FONTS + plutovg_font_face_cache_load_sys(m_cache); +#endif +} + +FontFaceCache* fontFaceCache() +{ + static FontFaceCache cache; + return &cache; +} + +Font::Font(const FontFace& face, float size) + : m_face(face), m_size(size) +{ + if(m_size > 0.f && !m_face.isNull()) { + plutovg_font_face_get_metrics(m_face.get(), m_size, &m_ascent, &m_descent, &m_lineGap, nullptr); + } +} + +float Font::xHeight() const +{ + plutovg_rect_t extents = {0}; + if(m_size > 0.f && !m_face.isNull()) + plutovg_font_face_get_glyph_metrics(m_face.get(), m_size, 'x', nullptr, nullptr, &extents); + return extents.h; +} + +float Font::measureText(const std::u32string_view& text) const +{ + if(m_size > 0.f && !m_face.isNull()) + return plutovg_font_face_text_extents(m_face.get(), m_size, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF32, nullptr); + return 0; +} + +std::shared_ptr Canvas::create(const Bitmap& bitmap) +{ + return std::shared_ptr(new Canvas(bitmap)); +} + +std::shared_ptr Canvas::create(float x, float y, float width, float height) +{ + constexpr int kMaxSize = 1 << 15; + if(width <= 0 || height <= 0 || width >= kMaxSize || height >= kMaxSize) + return std::shared_ptr(new Canvas(0, 0, 1, 1)); + auto l = static_cast(std::floor(x)); + auto t = static_cast(std::floor(y)); + auto r = static_cast(std::ceil(x + width)); + auto b = static_cast(std::ceil(y + height)); + return std::shared_ptr(new Canvas(l, t, r - l, b - t)); +} + +std::shared_ptr Canvas::create(const Rect& extents) +{ + return create(extents.x, extents.y, extents.w, extents.h); +} + +void Canvas::setColor(const Color& color) +{ + setColor(color.redF(), color.greenF(), color.blueF(), color.alphaF()); +} + +void Canvas::setColor(float r, float g, float b, float a) +{ + plutovg_canvas_set_rgba(m_canvas, r, g, b, a); +} + +void Canvas::setLinearGradient(float x1, float y1, float x2, float y2, SpreadMethod spread, const GradientStops& stops, const Transform& transform) +{ + plutovg_canvas_set_linear_gradient(m_canvas, x1, y1, x2, y2, static_cast(spread), stops.data(), stops.size(), &transform.matrix()); +} + +void Canvas::setRadialGradient(float cx, float cy, float r, float fx, float fy, SpreadMethod spread, const GradientStops& stops, const Transform& transform) +{ + plutovg_canvas_set_radial_gradient(m_canvas, cx, cy, r, fx, fy, 0.f, static_cast(spread), stops.data(), stops.size(), &transform.matrix()); +} + +void Canvas::setTexture(const Canvas& source, TextureType type, float opacity, const Transform& transform) +{ + plutovg_canvas_set_texture(m_canvas, source.surface(), static_cast(type), opacity, &transform.matrix()); +} + +void Canvas::fillPath(const Path& path, FillRule fillRule, const Transform& transform) +{ + plutovg_canvas_set_matrix(m_canvas, &m_translation); + plutovg_canvas_transform(m_canvas, &transform.matrix()); + plutovg_canvas_set_fill_rule(m_canvas, static_cast(fillRule)); + plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER); + plutovg_canvas_fill_path(m_canvas, path.data()); +} + +void Canvas::strokePath(const Path& path, const StrokeData& strokeData, const Transform& transform) +{ + plutovg_canvas_set_matrix(m_canvas, &m_translation); + plutovg_canvas_transform(m_canvas, &transform.matrix()); + plutovg_canvas_set_line_width(m_canvas, strokeData.lineWidth()); + plutovg_canvas_set_miter_limit(m_canvas, strokeData.miterLimit()); + plutovg_canvas_set_line_cap(m_canvas, static_cast(strokeData.lineCap())); + plutovg_canvas_set_line_join(m_canvas, static_cast(strokeData.lineJoin())); + plutovg_canvas_set_dash_offset(m_canvas, strokeData.dashOffset()); + plutovg_canvas_set_dash_array(m_canvas, strokeData.dashArray().data(), strokeData.dashArray().size()); + plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER); + plutovg_canvas_stroke_path(m_canvas, path.data()); +} + +void Canvas::fillText(const std::u32string_view& text, const Font& font, const Point& origin, const Transform& transform) +{ + plutovg_canvas_set_matrix(m_canvas, &m_translation); + plutovg_canvas_transform(m_canvas, &transform.matrix()); + plutovg_canvas_set_fill_rule(m_canvas, PLUTOVG_FILL_RULE_NON_ZERO); + plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER); + plutovg_canvas_set_font(m_canvas, font.face().get(), font.size()); + plutovg_canvas_fill_text(m_canvas, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF32, origin.x, origin.y); +} + +void Canvas::strokeText(const std::u32string_view& text, float strokeWidth, const Font& font, const Point& origin, const Transform& transform) +{ + plutovg_canvas_set_matrix(m_canvas, &m_translation); + plutovg_canvas_transform(m_canvas, &transform.matrix()); + plutovg_canvas_set_line_width(m_canvas, strokeWidth); + plutovg_canvas_set_miter_limit(m_canvas, 4.f); + plutovg_canvas_set_line_cap(m_canvas, PLUTOVG_LINE_CAP_BUTT); + plutovg_canvas_set_line_join(m_canvas, PLUTOVG_LINE_JOIN_MITER); + plutovg_canvas_set_dash_offset(m_canvas, 0.f); + plutovg_canvas_set_dash_array(m_canvas, nullptr, 0); + plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER); + plutovg_canvas_set_font(m_canvas, font.face().get(), font.size()); + plutovg_canvas_stroke_text(m_canvas, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF32, origin.x, origin.y); +} + +void Canvas::clipPath(const Path& path, FillRule clipRule, const Transform& transform) +{ + plutovg_canvas_set_matrix(m_canvas, &m_translation); + plutovg_canvas_transform(m_canvas, &transform.matrix()); + plutovg_canvas_set_fill_rule(m_canvas, static_cast(clipRule)); + plutovg_canvas_clip_path(m_canvas, path.data()); +} + +void Canvas::clipRect(const Rect& rect, FillRule clipRule, const Transform& transform) +{ + plutovg_canvas_set_matrix(m_canvas, &m_translation); + plutovg_canvas_transform(m_canvas, &transform.matrix()); + plutovg_canvas_set_fill_rule(m_canvas, static_cast(clipRule)); + plutovg_canvas_clip_rect(m_canvas, rect.x, rect.y, rect.w, rect.h); +} + +void Canvas::drawImage(const Bitmap& image, const Rect& dstRect, const Rect& srcRect, const Transform& transform) +{ + auto xScale = dstRect.w / srcRect.w; + auto yScale = dstRect.h / srcRect.h; + plutovg_matrix_t matrix = { xScale, 0, 0, yScale, -srcRect.x * xScale, -srcRect.y * yScale }; + plutovg_canvas_set_matrix(m_canvas, &m_translation); + plutovg_canvas_transform(m_canvas, &transform.matrix()); + plutovg_canvas_translate(m_canvas, dstRect.x, dstRect.y); + plutovg_canvas_set_fill_rule(m_canvas, PLUTOVG_FILL_RULE_NON_ZERO); + plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER); + plutovg_canvas_set_texture(m_canvas, image.surface(), PLUTOVG_TEXTURE_TYPE_PLAIN, 1.f, &matrix); + plutovg_canvas_fill_rect(m_canvas, 0, 0, dstRect.w, dstRect.h); +} + +void Canvas::blendCanvas(const Canvas& canvas, BlendMode blendMode, float opacity) +{ + plutovg_matrix_t matrix = { 1, 0, 0, 1, static_cast(canvas.x()), static_cast(canvas.y()) }; + plutovg_canvas_set_matrix(m_canvas, &m_translation); + plutovg_canvas_set_operator(m_canvas, static_cast(blendMode)); + plutovg_canvas_set_texture(m_canvas, canvas.surface(), PLUTOVG_TEXTURE_TYPE_PLAIN, opacity, &matrix); + plutovg_canvas_paint(m_canvas); +} + +void Canvas::save() +{ + plutovg_canvas_save(m_canvas); +} + +void Canvas::restore() +{ + plutovg_canvas_restore(m_canvas); +} + +int Canvas::width() const +{ + return plutovg_surface_get_width(m_surface); +} + +int Canvas::height() const +{ + return plutovg_surface_get_height(m_surface); +} + +void Canvas::convertToLuminanceMask() +{ + auto width = plutovg_surface_get_width(m_surface); + auto height = plutovg_surface_get_height(m_surface); + auto stride = plutovg_surface_get_stride(m_surface); + auto data = plutovg_surface_get_data(m_surface); + for(int y = 0; y < height; y++) { + auto pixels = reinterpret_cast(data + stride * y); + for(int x = 0; x < width; x++) { + auto pixel = pixels[x]; + auto a = (pixel >> 24) & 0xFF; + auto r = (pixel >> 16) & 0xFF; + auto g = (pixel >> 8) & 0xFF; + auto b = (pixel >> 0) & 0xFF; + if(a) { + r = (r * 255) / a; + g = (g * 255) / a; + b = (b * 255) / a; + } + + auto l = (r * 0.2125 + g * 0.7154 + b * 0.0721); + pixels[x] = static_cast(l * (a / 255.0)) << 24; + } + } +} + +Canvas::~Canvas() +{ + plutovg_canvas_destroy(m_canvas); + plutovg_surface_destroy(m_surface); +} + +Canvas::Canvas(const Bitmap& bitmap) + : m_surface(plutovg_surface_reference(bitmap.surface())) + , m_canvas(plutovg_canvas_create(m_surface)) + , m_translation({1, 0, 0, 1, 0, 0}) + , m_x(0), m_y(0) +{ +} + +Canvas::Canvas(int x, int y, int width, int height) + : m_surface(plutovg_surface_create(width, height)) + , m_canvas(plutovg_canvas_create(m_surface)) + , m_translation({1, 0, 0, 1, -static_cast(x), -static_cast(y)}) + , m_x(x), m_y(y) +{ +} + +} // namespace lunasvg diff --git a/vendor/lunasvg/source/graphics.h b/vendor/lunasvg/source/graphics.h new file mode 100644 index 0000000..7e6bc12 --- /dev/null +++ b/vendor/lunasvg/source/graphics.h @@ -0,0 +1,562 @@ +#ifndef LUNASVG_GRAPHICS_H +#define LUNASVG_GRAPHICS_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace lunasvg { + +enum class LineCap : uint8_t { + Butt = PLUTOVG_LINE_CAP_BUTT, + Round = PLUTOVG_LINE_CAP_ROUND, + Square = PLUTOVG_LINE_CAP_SQUARE +}; + +enum class LineJoin : uint8_t { + Miter = PLUTOVG_LINE_JOIN_MITER, + Round = PLUTOVG_LINE_JOIN_ROUND, + Bevel = PLUTOVG_LINE_JOIN_BEVEL +}; + +enum class FillRule : uint8_t { + NonZero = PLUTOVG_FILL_RULE_NON_ZERO, + EvenOdd = PLUTOVG_FILL_RULE_EVEN_ODD +}; + +enum class SpreadMethod : uint8_t { + Pad = PLUTOVG_SPREAD_METHOD_PAD, + Reflect = PLUTOVG_SPREAD_METHOD_REFLECT, + Repeat = PLUTOVG_SPREAD_METHOD_REPEAT +}; + +class Color { +public: + constexpr Color() = default; + constexpr explicit Color(uint32_t value) : m_value(value) {} + constexpr Color(int r, int g, int b, int a = 255) : m_value(a << 24 | r << 16 | g << 8 | b) {} + + constexpr uint8_t alpha() const { return (m_value >> 24) & 0xff; } + constexpr uint8_t red() const { return (m_value >> 16) & 0xff; } + constexpr uint8_t green() const { return (m_value >> 8) & 0xff; } + constexpr uint8_t blue() const { return (m_value >> 0) & 0xff; } + + constexpr float alphaF() const { return alpha() / 255.f; } + constexpr float redF() const { return red() / 255.f; } + constexpr float greenF() const { return green() / 255.f; } + constexpr float blueF() const { return blue() / 255.f; } + + constexpr uint32_t value() const { return m_value; } + + constexpr bool isOpaque() const { return alpha() == 255; } + constexpr bool isVisible() const { return alpha() > 0; } + + constexpr Color opaqueColor() const { return Color(m_value | 0xFF000000); } + constexpr Color colorWithAlpha(float opacity) const; + + static const Color Transparent; + static const Color Black; + static const Color White; + +private: + uint32_t m_value = 0; +}; + +constexpr Color Color::colorWithAlpha(float opacity) const +{ + auto rgb = m_value & 0x00FFFFFF; + auto a = static_cast(alpha() * std::clamp(opacity, 0.f, 1.f)); + return Color(rgb | a << 24); +} + +class Point { +public: + constexpr Point() = default; + constexpr Point(const plutovg_point_t& point) : Point(point.x, point.y) {} + constexpr Point(float x, float y) : x(x), y(y) {} + + constexpr void move(float dx, float dy) { x += dx; y += dy; } + constexpr void move(float d) { move(d, d); } + constexpr void move(const Point& p) { move(p.x, p.y); } + + constexpr void scale(float sx, float sy) { x *= sx; y *= sy; } + constexpr void scale(float s) { scale(s, s); } + + constexpr float dot(const Point& p) const { return x * p.x + y * p.y; } + +public: + float x{0}; + float y{0}; +}; + +constexpr Point operator+(const Point& a, const Point& b) +{ + return Point(a.x + b.x, a.y + b.y); +} + +constexpr Point operator-(const Point& a, const Point& b) +{ + return Point(a.x - b.x, a.y - b.y); +} + +constexpr Point operator-(const Point& a) +{ + return Point(-a.x, -a.y); +} + +constexpr Point& operator+=(Point& a, const Point& b) +{ + a.move(b); + return a; +} + +constexpr Point& operator-=(Point& a, const Point& b) +{ + a.move(-b); + return a; +} + +constexpr float operator*(const Point& a, const Point& b) +{ + return a.dot(b); +} + +class Size { +public: + constexpr Size() = default; + constexpr Size(float w, float h) : w(w), h(h) {} + + constexpr void expand(float dw, float dh) { w += dw; h += dh; } + constexpr void expand(float d) { expand(d, d); } + constexpr void expand(const Size& s) { expand(s.w, s.h); } + + constexpr void scale(float sw, float sh) { w *= sw; h *= sh; } + constexpr void scale(float s) { scale(s, s); } + + constexpr bool isEmpty() const { return w <= 0.f || h <= 0.f; } + constexpr bool isZero() const { return w <= 0.f && h <= 0.f; } + constexpr bool isValid() const { return w >= 0.f && h >= 0.f; } + +public: + float w{0}; + float h{0}; +}; + +constexpr Size operator+(const Size& a, const Size& b) +{ + return Size(a.w + b.w, a.h + b.h); +} + +constexpr Size operator-(const Size& a, const Size& b) +{ + return Size(a.w - b.w, a.h - b.h); +} + +constexpr Size operator-(const Size& a) +{ + return Size(-a.w, -a.h); +} + +constexpr Size& operator+=(Size& a, const Size& b) +{ + a.expand(b); + return a; +} + +constexpr Size& operator-=(Size& a, const Size& b) +{ + a.expand(-b); + return a; +} + +class Box; + +class Rect { +public: + constexpr Rect() = default; + constexpr explicit Rect(const Size& size) : Rect(size.w, size.h) {} + constexpr Rect(float width, float height) : Rect(0, 0, width, height) {} + constexpr Rect(const Point& origin, const Size& size) : Rect(origin.x, origin.y, size.w, size.h) {} + constexpr Rect(const plutovg_rect_t& rect) : Rect(rect.x, rect.y, rect.w, rect.h) {} + constexpr Rect(float x, float y, float w, float h) : x(x), y(y), w(w), h(h) {} + + Rect(const Box& box); + + constexpr void move(float dx, float dy) { x += dx; y += dy; } + constexpr void move(float d) { move(d, d); } + constexpr void move(const Point& p) { move(p.x, p.y); } + + constexpr void scale(float sx, float sy) { x *= sx; y *= sy; w *= sx; h *= sy; } + constexpr void scale(float s) { scale(s, s); } + + constexpr void inflate(float dx, float dy) { x -= dx; y -= dy; w += dx * 2.f; h += dy * 2.f; } + constexpr void inflate(float d) { inflate(d, d); } + + constexpr bool contains(float px, float py) const { return px >= x && px <= x + w && py >= y && py <= y + h; } + constexpr bool contains(const Point& p) const { return contains(p.x, p.y); } + + constexpr Rect intersected(const Rect& rect) const; + constexpr Rect united(const Rect& rect) const; + + constexpr Rect& intersect(const Rect& o); + constexpr Rect& unite(const Rect& o); + + constexpr Point origin() const { return Point(x, y); } + constexpr Size size() const { return Size(w, h); } + + constexpr float right() const { return x + w; } + constexpr float bottom() const { return y + h; } + + constexpr bool isEmpty() const { return w <= 0.f || h <= 0.f; } + constexpr bool isZero() const { return w <= 0.f && h <= 0.f; } + constexpr bool isValid() const { return w >= 0.f && h >= 0.f; } + + static const Rect Empty; + static const Rect Invalid; + static const Rect Infinite; + +public: + float x{0}; + float y{0}; + float w{0}; + float h{0}; +}; + +constexpr Rect Rect::intersected(const Rect& rect) const +{ + if(!rect.isValid()) + return *this; + if(!isValid()) + return rect; + auto l = std::max(x, rect.x); + auto t = std::max(y, rect.y); + auto r = std::min(x + w, rect.x + rect.w); + auto b = std::min(y + h, rect.y + rect.h); + if(l >= r || t >= b) + return Rect::Empty; + return Rect(l, t, r - l, b - t); +} + +constexpr Rect Rect::united(const Rect& rect) const +{ + if(!rect.isValid()) + return *this; + if(!isValid()) + return rect; + auto l = std::min(x, rect.x); + auto t = std::min(y, rect.y); + auto r = std::max(x + w, rect.x + rect.w); + auto b = std::max(y + h, rect.y + rect.h); + return Rect(l, t, r - l, b - t); +} + +constexpr Rect& Rect::intersect(const Rect& o) +{ + *this = intersected(o); + return *this; +} + +constexpr Rect& Rect::unite(const Rect& o) +{ + *this = united(o); + return *this; +} + +class Matrix; + +class Transform { +public: + Transform(); + Transform(const Matrix& matrix); + Transform(float a, float b, float c, float d, float e, float f); + Transform(const plutovg_matrix_t& matrix) : m_matrix(matrix) {} + + Transform operator*(const Transform& transform) const; + Transform& operator*=(const Transform& transform); + + Transform& multiply(const Transform& transform); + Transform& translate(float tx, float ty); + Transform& scale(float sx, float sy); + Transform& rotate(float angle, float cx = 0.f, float cy = 0.f); + Transform& shear(float shx, float shy); + + Transform& postMultiply(const Transform& transform); + Transform& postTranslate(float tx, float ty); + Transform& postScale(float sx, float sy); + Transform& postRotate(float angle, float cx = 0.f, float cy = 0.f); + Transform& postShear(float shx, float shy); + + Transform inverse() const; + Transform& invert(); + + void reset(); + + Point mapPoint(float x, float y) const; + Point mapPoint(const Point& point) const; + Rect mapRect(const Rect& rect) const; + + float xScale() const; + float yScale() const; + + const plutovg_matrix_t& matrix() const { return m_matrix; } + plutovg_matrix_t& matrix() { return m_matrix; } + + bool parse(const char* data, size_t length); + + static Transform translated(float tx, float ty); + static Transform scaled(float sx, float sy); + static Transform rotated(float angle, float cx, float cy); + static Transform sheared(float shx, float shy); + + static const Transform Identity; + +private: + plutovg_matrix_t m_matrix; +}; + +enum class PathCommand { + MoveTo = PLUTOVG_PATH_COMMAND_MOVE_TO, + LineTo = PLUTOVG_PATH_COMMAND_LINE_TO, + CubicTo = PLUTOVG_PATH_COMMAND_CUBIC_TO, + Close = PLUTOVG_PATH_COMMAND_CLOSE +}; + +class Path { +public: + Path() = default; + Path(const Path& path); + Path(Path&& path); + ~Path(); + + Path& operator=(const Path& path); + Path& operator=(Path&& path); + + void swap(Path& path); + + void moveTo(float x, float y); + void lineTo(float x, float y); + void quadTo(float x1, float y1, float x2, float y2); + void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3); + void arcTo(float rx, float ry, float xAxisRotation, bool largeArcFlag, bool sweepFlag, float x, float y); + void close(); + + void addEllipse(float cx, float cy, float rx, float ry); + void addRoundRect(float x, float y, float w, float h, float rx, float ry); + void addRect(float x, float y, float w, float h); + + void addEllipse(const Point& center, const Size& radii); + void addRoundRect(const Rect& rect, const Size& radii); + void addRect(const Rect& rect); + + void reset(); + + Rect boundingRect() const; + bool isEmpty() const; + bool isUnique() const; + bool isNull() const { return m_data == nullptr; } + plutovg_path_t* data() const { return m_data; } + + bool parse(const char* data, size_t length); + +private: + plutovg_path_t* release(); + plutovg_path_t* ensure(); + plutovg_path_t* m_data = nullptr; +}; + +inline void Path::swap(Path& path) +{ + std::swap(m_data, path.m_data); +} + +inline plutovg_path_t* Path::release() +{ + return std::exchange(m_data, nullptr); +} + +class PathIterator { +public: + PathIterator(const Path& path); + + PathCommand currentSegment(std::array& points) const; + bool isDone() const { return m_index >= m_size; } + void next(); + +private: + const plutovg_path_element_t* m_elements; + const int m_size; + int m_index; +}; + +class FontFace { +public: + FontFace() = default; + explicit FontFace(plutovg_font_face_t* face); + FontFace(const void* data, size_t length, plutovg_destroy_func_t destroy_func, void* closure); + FontFace(const char* filename); + FontFace(const FontFace& face); + FontFace(FontFace&& face); + ~FontFace(); + + FontFace& operator=(const FontFace& face); + FontFace& operator=(FontFace&& face); + + void swap(FontFace& face); + + bool isNull() const { return m_face == nullptr; } + plutovg_font_face_t* get() const { return m_face; } + +private: + plutovg_font_face_t* release(); + plutovg_font_face_t* m_face = nullptr; +}; + +class FontFaceCache { +public: + bool addFontFace(const std::string& family, bool bold, bool italic, const FontFace& face); + FontFace getFontFace(const std::string& family, bool bold, bool italic) const; + +private: + FontFaceCache(); + plutovg_font_face_cache_t* m_cache; + friend FontFaceCache* fontFaceCache(); +}; + +FontFaceCache* fontFaceCache(); + +class Font { +public: + Font() = default; + Font(const FontFace& face, float size); + + float ascent() const { return m_ascent; } + float descent() const { return m_descent; } + float height() const { return m_ascent - m_descent; } + float lineGap() const { return m_lineGap; } + float xHeight() const; + + float measureText(const std::u32string_view& text) const; + + const FontFace& face() const { return m_face; } + float size() const { return m_size; } + + bool isNull() const { return m_size <= 0.f || m_face.isNull(); } + +private: + FontFace m_face; + float m_size = 0.f; + float m_ascent = 0.f; + float m_descent = 0.f; + float m_lineGap = 0.f; +}; + +enum class TextureType { + Plain = PLUTOVG_TEXTURE_TYPE_PLAIN, + Tiled = PLUTOVG_TEXTURE_TYPE_TILED +}; + +enum class BlendMode { + Src = PLUTOVG_OPERATOR_SRC, + Src_Over = PLUTOVG_OPERATOR_SRC_OVER, + Dst_In = PLUTOVG_OPERATOR_DST_IN, + Dst_Out = PLUTOVG_OPERATOR_DST_OUT +}; + +using DashArray = std::vector; + +class StrokeData { +public: + explicit StrokeData(float lineWidth = 1.f) : m_lineWidth(lineWidth) {} + + void setLineWidth(float lineWidth) { m_lineWidth = lineWidth; } + float lineWidth() const { return m_lineWidth; } + + void setMiterLimit(float miterLimit) { m_miterLimit = miterLimit; } + float miterLimit() const { return m_miterLimit; } + + void setDashOffset(float dashOffset) { m_dashOffset = dashOffset; } + float dashOffset() const { return m_dashOffset; } + + void setDashArray(DashArray dashArray) { m_dashArray = std::move(dashArray); } + const DashArray& dashArray() const { return m_dashArray; } + + void setLineCap(LineCap lineCap) { m_lineCap = lineCap; } + LineCap lineCap() const { return m_lineCap; } + + void setLineJoin(LineJoin lineJoin) { m_lineJoin = lineJoin; } + LineJoin lineJoin() const { return m_lineJoin; } + +private: + float m_lineWidth; + float m_miterLimit{4.f}; + float m_dashOffset{0.f}; + LineCap m_lineCap{LineCap::Butt}; + LineJoin m_lineJoin{LineJoin::Miter}; + DashArray m_dashArray; +}; + +using GradientStop = plutovg_gradient_stop_t; +using GradientStops = std::vector; + +class Bitmap; + +class Canvas { +public: + static std::shared_ptr create(const Bitmap& bitmap); + static std::shared_ptr create(float x, float y, float width, float height); + static std::shared_ptr create(const Rect& extents); + + void setColor(const Color& color); + void setColor(float r, float g, float b, float a); + void setLinearGradient(float x1, float y1, float x2, float y2, SpreadMethod spread, const GradientStops& stops, const Transform& transform); + void setRadialGradient(float cx, float cy, float r, float fx, float fy, SpreadMethod spread, const GradientStops& stops, const Transform& transform); + void setTexture(const Canvas& source, TextureType type, float opacity, const Transform& transform); + + void fillPath(const Path& path, FillRule fillRule, const Transform& transform); + void strokePath(const Path& path, const StrokeData& strokeData, const Transform& transform); + + void fillText(const std::u32string_view& text, const Font& font, const Point& origin, const Transform& transform); + void strokeText(const std::u32string_view& text, float strokeWidth, const Font& font, const Point& origin, const Transform& transform); + + void clipPath(const Path& path, FillRule clipRule, const Transform& transform); + void clipRect(const Rect& rect, FillRule clipRule, const Transform& transform); + + void drawImage(const Bitmap& image, const Rect& dstRect, const Rect& srcRect, const Transform& transform); + void blendCanvas(const Canvas& canvas, BlendMode blendMode, float opacity); + + void save(); + void restore(); + + void convertToLuminanceMask(); + + int x() const { return m_x; } + int y() const { return m_y; } + int width() const; + int height() const; + + Rect extents() const { return Rect(m_x, m_y, width(), height()); } + + plutovg_surface_t* surface() const { return m_surface; } + plutovg_canvas_t* canvas() const { return m_canvas; } + + ~Canvas(); + +private: + Canvas(const Bitmap& bitmap); + Canvas(int x, int y, int width, int height); + plutovg_surface_t* m_surface; + plutovg_canvas_t* m_canvas; + plutovg_matrix_t m_translation; + const int m_x; + const int m_y; +}; + +} // namespace lunasvg + +#endif // LUNASVG_GRAPHICS_H diff --git a/vendor/lunasvg/source/lunasvg.cpp b/vendor/lunasvg/source/lunasvg.cpp new file mode 100644 index 0000000..0a4399f --- /dev/null +++ b/vendor/lunasvg/source/lunasvg.cpp @@ -0,0 +1,536 @@ +#include "lunasvg.h" +#include "svgelement.h" +#include "svgrenderstate.h" + +#include +#include +#include + +int lunasvg_version() +{ + return LUNASVG_VERSION; +} + +const char* lunasvg_version_string() +{ + return LUNASVG_VERSION_STRING; +} + +bool lunasvg_add_font_face_from_file(const char* family, bool bold, bool italic, const char* filename) +{ + return lunasvg::fontFaceCache()->addFontFace(family, bold, italic, lunasvg::FontFace(filename)); +} + +bool lunasvg_add_font_face_from_data(const char* family, bool bold, bool italic, const void* data, size_t length, lunasvg_destroy_func_t destroy_func, void* closure) +{ + return lunasvg::fontFaceCache()->addFontFace(family, bold, italic, lunasvg::FontFace(data, length, destroy_func, closure)); +} + +namespace lunasvg { + +Bitmap::Bitmap(int width, int height) + : m_surface(plutovg_surface_create(width, height)) +{ +} + +Bitmap::Bitmap(uint8_t* data, int width, int height, int stride) + : m_surface(plutovg_surface_create_for_data(data, width, height, stride)) +{ +} + +Bitmap::Bitmap(const Bitmap& bitmap) + : m_surface(plutovg_surface_reference(bitmap.surface())) +{ +} + +Bitmap::Bitmap(Bitmap&& bitmap) + : m_surface(bitmap.release()) +{ +} + +Bitmap::~Bitmap() +{ + plutovg_surface_destroy(m_surface); +} + +Bitmap& Bitmap::operator=(const Bitmap& bitmap) +{ + Bitmap(bitmap).swap(*this); + return *this; +} + +void Bitmap::swap(Bitmap& bitmap) +{ + std::swap(m_surface, bitmap.m_surface); +} + +uint8_t* Bitmap::data() const +{ + if(m_surface) + return plutovg_surface_get_data(m_surface); + return nullptr; +} + +int Bitmap::width() const +{ + if(m_surface) + return plutovg_surface_get_width(m_surface); + return 0; +} + +int Bitmap::height() const +{ + if(m_surface) + return plutovg_surface_get_height(m_surface); + return 0; +} + +int Bitmap::stride() const +{ + if(m_surface) + return plutovg_surface_get_stride(m_surface); + return 0; +} + +void Bitmap::clear(uint32_t value) +{ + if(m_surface == nullptr) + return; + plutovg_color_t color; + plutovg_color_init_rgba32(&color, value); + plutovg_surface_clear(m_surface, &color); +} + +void Bitmap::convertToRGBA() +{ + if(m_surface == nullptr) + return; + auto data = plutovg_surface_get_data(m_surface); + auto width = plutovg_surface_get_width(m_surface); + auto height = plutovg_surface_get_height(m_surface); + auto stride = plutovg_surface_get_stride(m_surface); + plutovg_convert_argb_to_rgba(data, data, width, height, stride); +} + +Bitmap& Bitmap::operator=(Bitmap&& bitmap) +{ + Bitmap(std::move(bitmap)).swap(*this); + return *this; +} + +bool Bitmap::writeToPng(const std::string& filename) const +{ + if(m_surface) + return plutovg_surface_write_to_png(m_surface, filename.data()); + return false; +} + +bool Bitmap::writeToPng(lunasvg_write_func_t callback, void* closure) const +{ + if(m_surface) + return plutovg_surface_write_to_png_stream(m_surface, callback, closure); + return false; +} + +plutovg_surface_t* Bitmap::release() +{ + return std::exchange(m_surface, nullptr); +} + +Box::Box(float x, float y, float w, float h) + : x(x), y(y), w(w), h(h) +{ +} + +Box::Box(const Rect& rect) + : x(rect.x), y(rect.y), w(rect.w), h(rect.h) +{ +} + +Box& Box::transform(const Matrix &matrix) +{ + *this = transformed(matrix); + return *this; +} + +Box Box::transformed(const Matrix& matrix) const +{ + return Transform(matrix).mapRect(*this); +} + +Matrix::Matrix(float a, float b, float c, float d, float e, float f) + : a(a), b(b), c(c), d(d), e(e), f(f) +{ +} + +Matrix::Matrix(const plutovg_matrix_t& matrix) + : a(matrix.a), b(matrix.b), c(matrix.c), d(matrix.d), e(matrix.e), f(matrix.f) +{ +} + +Matrix::Matrix(const Transform& transform) + : Matrix(transform.matrix()) +{ +} + +Matrix Matrix::operator*(const Matrix& matrix) const +{ + return Transform(*this) * Transform(matrix); +} + +Matrix& Matrix::operator*=(const Matrix &matrix) +{ + return (*this = *this * matrix); +} + +Matrix& Matrix::multiply(const Matrix& matrix) +{ + return (*this *= matrix); +} + +Matrix& Matrix::scale(float sx, float sy) +{ + return multiply(scaled(sx, sy)); +} + +Matrix& Matrix::translate(float tx, float ty) +{ + return multiply(translated(tx, ty)); +} + +Matrix& Matrix::rotate(float angle, float cx, float cy) +{ + return multiply(rotated(angle, cx, cy)); +} + +Matrix& Matrix::shear(float shx, float shy) +{ + return multiply(sheared(shx, shy)); +} + +Matrix Matrix::inverse() const +{ + return Transform(*this).inverse(); +} + +Matrix& Matrix::invert() +{ + return (*this = inverse()); +} + +void Matrix::reset() +{ + *this = Matrix(1, 0, 0, 1, 0, 0); +} + +Matrix Matrix::translated(float tx, float ty) +{ + return Transform::translated(tx, ty); +} + +Matrix Matrix::scaled(float sx, float sy) +{ + return Transform::scaled(sx, sy); +} + +Matrix Matrix::rotated(float angle, float cx, float cy) +{ + return Transform::rotated(angle, cx, cy); +} + +Matrix Matrix::sheared(float shx, float shy) +{ + return Transform::sheared(shx, shy); +} + +Node::Node(SVGNode* node) + : m_node(node) +{ +} + +bool Node::isTextNode() const +{ + return m_node && m_node->isTextNode(); +} + +bool Node::isElement() const +{ + return m_node && m_node->isElement(); +} + +TextNode Node::toTextNode() const +{ + if(m_node && m_node->isTextNode()) + return static_cast(m_node); + return TextNode(); +} + +Element Node::toElement() const +{ + if(m_node && m_node->isElement()) + return static_cast(m_node); + return Element(); +} + +Element Node::parentElement() const +{ + if(m_node) + return m_node->parentElement(); + return Element(); +} + +TextNode::TextNode(SVGTextNode* text) + : Node(text) +{ +} + +const std::string& TextNode::data() const +{ + if(m_node) + return text()->data(); + return emptyString; +} + +void TextNode::setData(const std::string& data) +{ + if(m_node) { + text()->setData(data); + } +} + +SVGTextNode* TextNode::text() const +{ + return static_cast(m_node); +} + +Element::Element(SVGElement* element) + : Node(element) +{ +} + +bool Element::hasAttribute(const std::string& name) const +{ + if(m_node) + return element()->hasAttribute(name); + return false; +} + +const std::string& Element::getAttribute(const std::string& name) const +{ + if(m_node) + return element()->getAttribute(name); + return emptyString; +} + +void Element::setAttribute(const std::string& name, const std::string& value) +{ + if(m_node) { + element()->setAttribute(name, value); + } +} + +void Element::render(Bitmap& bitmap, const Matrix& matrix) const +{ + if(m_node == nullptr || bitmap.isNull()) + return; + auto canvas = Canvas::create(bitmap); + SVGRenderState state(nullptr, nullptr, matrix, SVGRenderMode::Painting, canvas); + element(true)->render(state); +} + +Bitmap Element::renderToBitmap(int width, int height, uint32_t backgroundColor) const +{ + if(m_node == nullptr) + return Bitmap(); + auto elementBounds = element(true)->localTransform().mapRect(element()->paintBoundingBox()); + if(elementBounds.isEmpty()) + return Bitmap(); + if(width <= 0 && height <= 0) { + width = static_cast(std::ceil(elementBounds.w)); + height = static_cast(std::ceil(elementBounds.h)); + } else if(width > 0 && height <= 0) { + height = static_cast(std::ceil(width * elementBounds.h / elementBounds.w)); + } else if(height > 0 && width <= 0) { + width = static_cast(std::ceil(height * elementBounds.w / elementBounds.h)); + } + + auto xScale = width / elementBounds.w; + auto yScale = height / elementBounds.h; + + Matrix matrix(xScale, 0, 0, yScale, -elementBounds.x * xScale, -elementBounds.y * yScale); + Bitmap bitmap(width, height); + if(backgroundColor) bitmap.clear(backgroundColor); + render(bitmap, matrix); + return bitmap; +} + +Matrix Element::getLocalMatrix() const +{ + if(m_node) + return element(true)->localTransform(); + return Matrix(); +} + +Matrix Element::getGlobalMatrix() const +{ + if(m_node == nullptr) + return Matrix(); + auto transform = element(true)->localTransform(); + for(auto parent = element()->parentElement(); parent; parent = parent->parentElement()) + transform.postMultiply(parent->localTransform()); + return transform; +} + +Box Element::getLocalBoundingBox() const +{ + return getBoundingBox().transformed(getLocalMatrix()); +} + +Box Element::getGlobalBoundingBox() const +{ + return getBoundingBox().transformed(getGlobalMatrix()); +} + +Box Element::getBoundingBox() const +{ + if(m_node) + return element(true)->paintBoundingBox(); + return Box(); +} + +NodeList Element::children() const +{ + if(m_node == nullptr) + return NodeList(); + NodeList children; + for(const auto& child : element()->children()) + children.push_back(child.get()); + return children; +} + +SVGElement* Element::element(bool layoutIfNeeded) const +{ + auto element = static_cast(m_node); + if(element && layoutIfNeeded) + element->rootElement()->layoutIfNeeded(); + return element; +} + +std::unique_ptr Document::loadFromFile(const std::string& filename) +{ + std::ifstream fs; + fs.open(filename); + if(!fs.is_open()) + return nullptr; + std::string content; + std::getline(fs, content, '\0'); + fs.close(); + return loadFromData(content); +} + +std::unique_ptr Document::loadFromData(const std::string& string) +{ + return loadFromData(string.data(), string.size()); +} + +std::unique_ptr Document::loadFromData(const char* data) +{ + return loadFromData(data, std::strlen(data)); +} + +std::unique_ptr Document::loadFromData(const char* data, size_t length) +{ + std::unique_ptr document(new Document); + if(!document->parse(data, length)) + return nullptr; + return document; +} + +float Document::width() const +{ + return rootElement(true)->intrinsicWidth(); +} + +float Document::height() const +{ + return rootElement(true)->intrinsicHeight(); +} + +Box Document::boundingBox() const +{ + return rootElement(true)->localTransform().mapRect(rootElement()->paintBoundingBox()); +} + +void Document::updateLayout() +{ + m_rootElement->layoutIfNeeded(); +} + +void Document::forceLayout() +{ + m_rootElement->forceLayout(); +} + +void Document::render(Bitmap& bitmap, const Matrix& matrix) const +{ + if(bitmap.isNull()) + return; + auto canvas = Canvas::create(bitmap); + SVGRenderState state(nullptr, nullptr, matrix, SVGRenderMode::Painting, canvas); + rootElement(true)->render(state); +} + +Bitmap Document::renderToBitmap(int width, int height, uint32_t backgroundColor) const +{ + auto intrinsicWidth = rootElement(true)->intrinsicWidth(); + auto intrinsicHeight = rootElement()->intrinsicHeight(); + if(intrinsicWidth == 0.f || intrinsicHeight == 0.f) + return Bitmap(); + if(width <= 0 && height <= 0) { + width = static_cast(std::ceil(intrinsicWidth)); + height = static_cast(std::ceil(intrinsicHeight)); + } else if(width > 0 && height <= 0) { + height = static_cast(std::ceil(width * intrinsicHeight / intrinsicWidth)); + } else if(height > 0 && width <= 0) { + width = static_cast(std::ceil(height * intrinsicWidth / intrinsicHeight)); + } + + auto xScale = width / intrinsicWidth; + auto yScale = height / intrinsicHeight; + + Matrix matrix(xScale, 0, 0, yScale, 0, 0); + Bitmap bitmap(width, height); + if(backgroundColor) bitmap.clear(backgroundColor); + render(bitmap, matrix); + return bitmap; +} + +Element Document::elementFromPoint(float x, float y) const +{ + return rootElement(true)->elementFromPoint(x, y); +} + +Element Document::getElementById(const std::string& id) const +{ + return m_rootElement->getElementById(id); +} + +Element Document::documentElement() const +{ + return m_rootElement.get(); +} + +SVGRootElement* Document::rootElement(bool layoutIfNeeded) const +{ + if(layoutIfNeeded) + m_rootElement->layoutIfNeeded(); + return m_rootElement.get(); +} + +Document::Document(Document&&) = default; +Document& Document::operator=(Document&&) = default; + +Document::Document() = default; +Document::~Document() = default; + +} // namespace lunasvg diff --git a/vendor/lunasvg/source/svgelement.cpp b/vendor/lunasvg/source/svgelement.cpp new file mode 100644 index 0000000..7d62cc6 --- /dev/null +++ b/vendor/lunasvg/source/svgelement.cpp @@ -0,0 +1,1225 @@ +#include "svgelement.h" +#include "svgpaintelement.h" +#include "svggeometryelement.h" +#include "svgtextelement.h" +#include "svgproperty.h" +#include "svglayoutstate.h" +#include "svgrenderstate.h" + +#include + +namespace lunasvg { + +ElementID elementid(std::string_view name) +{ + static const struct { + std::string_view name; + ElementID value; + } table[] = { + {"a", ElementID::G}, + {"circle", ElementID::Circle}, + {"clipPath", ElementID::ClipPath}, + {"defs", ElementID::Defs}, + {"ellipse", ElementID::Ellipse}, + {"g", ElementID::G}, + {"image", ElementID::Image}, + {"line", ElementID::Line}, + {"linearGradient", ElementID::LinearGradient}, + {"marker", ElementID::Marker}, + {"mask", ElementID::Mask}, + {"path", ElementID::Path}, + {"pattern", ElementID::Pattern}, + {"polygon", ElementID::Polygon}, + {"polyline", ElementID::Polyline}, + {"radialGradient", ElementID::RadialGradient}, + {"rect", ElementID::Rect}, + {"stop", ElementID::Stop}, + {"style", ElementID::Style}, + {"svg", ElementID::Svg}, + {"symbol", ElementID::Symbol}, + {"text", ElementID::Text}, + {"tspan", ElementID::Tspan}, + {"use", ElementID::Use} + }; + + auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; }); + if(it == std::end(table) || it->name != name) + return ElementID::Unknown; + return it->value; +} + +SVGTextNode::SVGTextNode(Document* document) + : SVGNode(document) +{ +} + +void SVGTextNode::setData(const std::string& data) +{ + rootElement()->setNeedsLayout(); + m_data.assign(data); +} + +std::unique_ptr SVGTextNode::clone(bool deep) const +{ + auto node = std::make_unique(document()); + node->setData(m_data); + return node; +} + +const std::string emptyString; + +std::unique_ptr SVGElement::create(Document* document, ElementID id) +{ + switch(id) { + case ElementID::Svg: + return std::make_unique(document); + case ElementID::Path: + return std::make_unique(document); + case ElementID::G: + return std::make_unique(document); + case ElementID::Rect: + return std::make_unique(document); + case ElementID::Circle: + return std::make_unique(document); + case ElementID::Ellipse: + return std::make_unique(document); + case ElementID::Line: + return std::make_unique(document); + case ElementID::Defs: + return std::make_unique(document); + case ElementID::Polygon: + case ElementID::Polyline: + return std::make_unique(document, id); + case ElementID::Stop: + return std::make_unique(document); + case ElementID::LinearGradient: + return std::make_unique(document); + case ElementID::RadialGradient: + return std::make_unique(document); + case ElementID::Symbol: + return std::make_unique(document); + case ElementID::Use: + return std::make_unique(document); + case ElementID::Pattern: + return std::make_unique(document); + case ElementID::Mask: + return std::make_unique(document); + case ElementID::ClipPath: + return std::make_unique(document); + case ElementID::Marker: + return std::make_unique(document); + case ElementID::Image: + return std::make_unique(document); + case ElementID::Style: + return std::make_unique(document); + case ElementID::Text: + return std::make_unique(document); + case ElementID::Tspan: + return std::make_unique(document); + default: + assert(false); + } + + return nullptr; +} + +SVGElement::SVGElement(Document* document, ElementID id) + : SVGNode(document) + , m_id(id) +{ +} + +bool SVGElement::hasAttribute(std::string_view name) const +{ + auto id = propertyid(name); + if(id == PropertyID::Unknown) + return false; + return hasAttribute(id); +} + +const std::string& SVGElement::getAttribute(std::string_view name) const +{ + auto id = propertyid(name); + if(id == PropertyID::Unknown) + return emptyString; + return getAttribute(id); +} + +bool SVGElement::setAttribute(std::string_view name, const std::string& value) +{ + auto id = propertyid(name); + if(id == PropertyID::Unknown) + return false; + return setAttribute(0x1000, id, value); +} + +const Attribute* SVGElement::findAttribute(PropertyID id) const +{ + for(const auto& attribute : m_attributes) { + if(id == attribute.id()) { + return &attribute; + } + } + + return nullptr; +} + +bool SVGElement::hasAttribute(PropertyID id) const +{ + for(const auto& attribute : m_attributes) { + if(id == attribute.id()) { + return true; + } + } + + return false; +} + +const std::string& SVGElement::getAttribute(PropertyID id) const +{ + for(const auto& attribute : m_attributes) { + if(id == attribute.id()) { + return attribute.value(); + } + } + + return emptyString; +} + +bool SVGElement::setAttribute(int specificity, PropertyID id, const std::string& value) +{ + for(auto& attribute : m_attributes) { + if(id == attribute.id()) { + if(specificity < attribute.specificity()) + return false; + parseAttribute(id, value); + attribute = Attribute(specificity, id, value); + return true; + } + } + + parseAttribute(id, value); + m_attributes.emplace_front(specificity, id, value); + return true; +} + +void SVGElement::setAttributes(const AttributeList& attributes) +{ + for(const auto& attribute : attributes) { + setAttribute(attribute); + } +} + +bool SVGElement::setAttribute(const Attribute& attribute) +{ + return setAttribute(attribute.specificity(), attribute.id(), attribute.value()); +} + +void SVGElement::parseAttribute(PropertyID id, const std::string& value) +{ + rootElement()->setNeedsLayout(); + if(auto property = getProperty(id)) { + property->parse(value); + } +} + +SVGElement* SVGElement::previousElement() const +{ + auto parent = parentElement(); + if(parent == nullptr) + return nullptr; + const auto& children = parent->children(); + auto it = children.begin(); + auto end = children.end(); + SVGElement* element = nullptr; + for(; it != end; ++it) { + SVGNode* node = &**it; + if(node->isTextNode()) + continue; + if(node == this) + return element; + element = static_cast(node); + } + + return nullptr; +} + +SVGElement* SVGElement::nextElement() const +{ + auto parent = parentElement(); + if(parent == nullptr) + return nullptr; + const auto& children = parent->children(); + auto it = children.rbegin(); + auto end = children.rend(); + SVGElement* element = nullptr; + for(; it != end; ++it) { + SVGNode* node = &**it; + if(node->isTextNode()) + continue; + if(node == this) + return element; + element = static_cast(node); + } + + return nullptr; +} + +SVGNode* SVGElement::addChild(std::unique_ptr child) +{ + child->setParentElement(this); + m_children.push_back(std::move(child)); + return &*m_children.back(); +} + +SVGNode* SVGElement::firstChild() const +{ + if(m_children.empty()) + return nullptr; + return &*m_children.front(); +} + +SVGNode* SVGElement::lastChild() const +{ + if(m_children.empty()) + return nullptr; + return &*m_children.back(); +} + +Rect SVGElement::fillBoundingBox() const +{ + auto fillBoundingBox = Rect::Invalid; + for(const auto& child : m_children) { + if(auto element = toSVGElement(child); element && !element->isHiddenElement()) { + fillBoundingBox.unite(element->localTransform().mapRect(element->fillBoundingBox())); + } + } + + if(!fillBoundingBox.isValid()) + fillBoundingBox = Rect::Empty; + return fillBoundingBox; +} + +Rect SVGElement::strokeBoundingBox() const +{ + auto strokeBoundingBox = Rect::Invalid; + for(const auto& child : m_children) { + if(auto element = toSVGElement(child); element && !element->isHiddenElement()) { + strokeBoundingBox.unite(element->localTransform().mapRect(element->strokeBoundingBox())); + } + } + + if(!strokeBoundingBox.isValid()) + strokeBoundingBox = Rect::Empty; + return strokeBoundingBox; +} + +Rect SVGElement::paintBoundingBox() const +{ + if(m_paintBoundingBox.isValid()) + return m_paintBoundingBox; + m_paintBoundingBox = Rect::Empty; + m_paintBoundingBox = strokeBoundingBox(); + assert(m_paintBoundingBox.isValid()); + if(m_clipper) m_paintBoundingBox.intersect(m_clipper->clipBoundingBox(this)); + if(m_masker) m_paintBoundingBox.intersect(m_masker->maskBoundingBox(this)); + return m_paintBoundingBox; +} + +SVGMarkerElement* SVGElement::getMarker(std::string_view id) const +{ + auto element = rootElement()->getElementById(id); + if(element && element->id() == ElementID::Marker) + return static_cast(element); + return nullptr; +} + +SVGClipPathElement* SVGElement::getClipper(std::string_view id) const +{ + auto element = rootElement()->getElementById(id); + if(element && element->id() == ElementID::ClipPath) + return static_cast(element); + return nullptr; +} + +SVGMaskElement* SVGElement::getMasker(std::string_view id) const +{ + auto element = rootElement()->getElementById(id); + if(element && element->id() == ElementID::Mask) + return static_cast(element); + return nullptr; +} + +SVGPaintElement* SVGElement::getPainter(std::string_view id) const +{ + auto element = rootElement()->getElementById(id); + if(element && element->isPaintElement()) + return static_cast(element); + return nullptr; +} + +SVGElement* SVGElement::elementFromPoint(float x, float y) +{ + auto it = m_children.rbegin(); + auto end = m_children.rend(); + for(; it != end; ++it) { + auto child = toSVGElement(*it); + if(child && !child->isHiddenElement()) { + if(auto element = child->elementFromPoint(x, y)) { + return element; + } + } + } + + if(isPointableElement()) { + auto transform = localTransform(); + for(auto parent = parentElement(); parent; parent = parent->parentElement()) + transform.postMultiply(parent->localTransform()); + auto bbox = transform.mapRect(paintBoundingBox()); + if(bbox.contains(x, y)) { + return this; + } + } + + return nullptr; +} + +void SVGElement::addProperty(SVGProperty& value) +{ + m_properties.push_front(&value); +} + +SVGProperty* SVGElement::getProperty(PropertyID id) const +{ + for(auto property : m_properties) { + if(id == property->id()) { + return property; + } + } + + return nullptr; +} + +Size SVGElement::currentViewportSize() const +{ + auto parent = parentElement(); + if(parent == nullptr) { + auto element = static_cast(this); + const auto& viewBox = element->viewBox(); + if(viewBox.value().isValid()) + return viewBox.value().size(); + return Size(300, 150); + } + + if(parent->id() == ElementID::Svg) { + auto element = static_cast(parent); + const auto& viewBox = element->viewBox(); + if(viewBox.value().isValid()) + return viewBox.value().size(); + LengthContext lengthContext(element); + auto width = lengthContext.valueForLength(element->width()); + auto height = lengthContext.valueForLength(element->height()); + return Size(width, height); + } + + return parent->currentViewportSize(); +} + +void SVGElement::cloneChildren(SVGElement* parentElement) const +{ + for(const auto& child : m_children) { + parentElement->addChild(child->clone(true)); + } +} + +std::unique_ptr SVGElement::clone(bool deep) const +{ + auto element = SVGElement::create(document(), m_id); + element->setAttributes(m_attributes); + if(deep) { cloneChildren(element.get()); } + return element; +} + +void SVGElement::build() +{ + for(const auto& child : m_children) { + if(auto element = toSVGElement(child)) { + element->build(); + } + } +} + +void SVGElement::layoutElement(const SVGLayoutState& state) +{ + m_paintBoundingBox = Rect::Invalid; + m_clipper = getClipper(state.clip_path()); + m_masker = getMasker(state.mask()); + m_opacity = state.opacity(); + + m_font_size = state.font_size(); + m_display = state.display(); + m_overflow = state.overflow(); + m_visibility = state.visibility(); + m_pointer_events = state.pointer_events(); +} + +void SVGElement::layoutChildren(SVGLayoutState& state) +{ + for(const auto& child : m_children) { + if(auto element = toSVGElement(child)) { + element->layout(state); + } + } +} + +void SVGElement::layout(SVGLayoutState& state) +{ + SVGLayoutState newState(state, this); + layoutElement(newState); + layoutChildren(newState); +} + +void SVGElement::renderChildren(SVGRenderState& state) const +{ + for(const auto& child : m_children) { + if(auto element = toSVGElement(child)) { + element->render(state); + } + } +} + +void SVGElement::render(SVGRenderState& state) const +{ +} + +bool SVGElement::isHiddenElement() const +{ + if(isDisplayNone()) + return true; + switch(m_id) { + case ElementID::Defs: + case ElementID::Symbol: + case ElementID::Marker: + case ElementID::ClipPath: + case ElementID::Mask: + case ElementID::LinearGradient: + case ElementID::RadialGradient: + case ElementID::Pattern: + case ElementID::Stop: + return true; + default: + return false; + } +} + +bool SVGElement::isPointableElement() const +{ + if(m_pointer_events != PointerEvents::None + && m_visibility != Visibility::Hidden + && m_display != Display::None + && m_opacity != 0.f) { + switch(m_id) { + case ElementID::Line: + case ElementID::Rect: + case ElementID::Ellipse: + case ElementID::Circle: + case ElementID::Polyline: + case ElementID::Polygon: + case ElementID::Path: + case ElementID::Text: + case ElementID::Image: + return true; + default: + break; + } + } + + return false; +} + +SVGStyleElement::SVGStyleElement(Document* document) + : SVGElement(document, ElementID::Style) +{ +} + +SVGFitToViewBox::SVGFitToViewBox(SVGElement* element) + : m_viewBox(PropertyID::ViewBox) + , m_preserveAspectRatio(PropertyID::PreserveAspectRatio) +{ + element->addProperty(m_viewBox); + element->addProperty(m_preserveAspectRatio); +} + +Transform SVGFitToViewBox::viewBoxToViewTransform(const Size& viewportSize) const +{ + const auto& viewBoxRect = m_viewBox.value(); + if(viewBoxRect.isEmpty() || viewportSize.isEmpty()) + return Transform::Identity; + return m_preserveAspectRatio.getTransform(viewBoxRect, viewportSize); +} + +Rect SVGFitToViewBox::getClipRect(const Size& viewportSize) const +{ + const auto& viewBoxRect = m_viewBox.value(); + if(viewBoxRect.isEmpty() || viewportSize.isEmpty()) + return Rect(0, 0, viewportSize.w, viewportSize.h); + return m_preserveAspectRatio.getClipRect(viewBoxRect, viewportSize); +} + +SVGURIReference::SVGURIReference(SVGElement* element) + : m_href(PropertyID::Href) +{ + element->addProperty(m_href); +} + +SVGElement* SVGURIReference::getTargetElement(const Document* document) const +{ + std::string_view value(m_href.value()); + if(value.empty() || value.front() != '#') + return nullptr; + return document->rootElement()->getElementById(value.substr(1)); +} + +bool SVGPaintServer::applyPaint(SVGRenderState& state) const +{ + if(!isRenderable()) + return false; + if(m_element) return m_element->applyPaint(state, m_opacity); + state->setColor(m_color.colorWithAlpha(m_opacity)); + return true; +} + +SVGGraphicsElement::SVGGraphicsElement(Document* document, ElementID id) + : SVGElement(document, id) + , m_transform(PropertyID::Transform) +{ + addProperty(m_transform); +} + +SVGPaintServer SVGGraphicsElement::getPaintServer(const Paint& paint, float opacity) const +{ + if(paint.isNone()) + return SVGPaintServer(); + if(auto element = getPainter(paint.id())) + return SVGPaintServer(element, paint.color(), opacity); + return SVGPaintServer(nullptr, paint.color(), opacity); +} + +StrokeData SVGGraphicsElement::getStrokeData(const SVGLayoutState& state) const +{ + LengthContext lengthContext(this); + StrokeData strokeData(lengthContext.valueForLength(state.stroke_width(), LengthDirection::Diagonal)); + strokeData.setMiterLimit(state.stroke_miterlimit()); + strokeData.setLineCap(state.stroke_linecap()); + strokeData.setLineJoin(state.stroke_linejoin()); + strokeData.setDashOffset(lengthContext.valueForLength(state.stroke_dashoffset(), LengthDirection::Diagonal)); + + DashArray dashArray; + for(const auto& dash : state.stroke_dasharray()) + dashArray.push_back(lengthContext.valueForLength(dash, LengthDirection::Diagonal)); + strokeData.setDashArray(std::move(dashArray)); + return strokeData; +} + +SVGSVGElement::SVGSVGElement(Document* document) + : SVGGraphicsElement(document, ElementID::Svg) + , SVGFitToViewBox(this) + , m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None) + , m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None) + , m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent) + , m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent) +{ + addProperty(m_x); + addProperty(m_y); + addProperty(m_width); + addProperty(m_height); +} + +Transform SVGSVGElement::localTransform() const +{ + LengthContext lengthContext(this); + const Rect viewportRect = { + lengthContext.valueForLength(m_x), + lengthContext.valueForLength(m_y), + lengthContext.valueForLength(m_width), + lengthContext.valueForLength(m_height) + }; + + if(isRootElement()) + return viewBoxToViewTransform(viewportRect.size()); + return SVGGraphicsElement::localTransform() * Transform::translated(viewportRect.x, viewportRect.y) * viewBoxToViewTransform(viewportRect.size()); +} + +void SVGSVGElement::render(SVGRenderState& state) const +{ + if(isDisplayNone()) + return; + LengthContext lengthContext(this); + const Size viewportSize = { + lengthContext.valueForLength(m_width), + lengthContext.valueForLength(m_height) + }; + + if(viewportSize.isEmpty()) + return; + SVGBlendInfo blendInfo(this); + SVGRenderState newState(this, state, localTransform()); + newState.beginGroup(blendInfo); + if(isOverflowHidden()) + newState->clipRect(getClipRect(viewportSize), FillRule::NonZero, newState.currentTransform()); + renderChildren(newState); + newState.endGroup(blendInfo); +} + +SVGRootElement::SVGRootElement(Document* document) + : SVGSVGElement(document) +{ +} + +SVGRootElement* SVGRootElement::layoutIfNeeded() +{ + if(needsLayout()) + forceLayout(); + return this; +} + +SVGElement* SVGRootElement::getElementById(std::string_view id) const +{ + auto it = m_idCache.find(id); + if(it == m_idCache.end()) + return nullptr; + return it->second; +} + +void SVGRootElement::addElementById(const std::string& id, SVGElement* element) +{ + m_idCache.emplace(id, element); +} + +void SVGRootElement::layout(SVGLayoutState& state) +{ + SVGSVGElement::layout(state); + + LengthContext lengthContext(this); + if(!width().isPercent()) { + m_intrinsicWidth = lengthContext.valueForLength(width()); + } else { + m_intrinsicWidth = 0.f; + } + + if(!height().isPercent()) { + m_intrinsicHeight = lengthContext.valueForLength(height()); + } else { + m_intrinsicHeight = 0.f; + } + + const auto& viewBoxRect = viewBox().value(); + if(!viewBoxRect.isEmpty() && (!m_intrinsicWidth || !m_intrinsicHeight)) { + auto intrinsicRatio = viewBoxRect.w / viewBoxRect.h; + if(!m_intrinsicWidth && m_intrinsicHeight) + m_intrinsicWidth = m_intrinsicHeight * intrinsicRatio; + else if(m_intrinsicWidth && !m_intrinsicHeight) { + m_intrinsicHeight = m_intrinsicWidth / intrinsicRatio; + } + } + + if(viewBoxRect.isValid() && (!m_intrinsicWidth || !m_intrinsicHeight)) { + m_intrinsicWidth = viewBoxRect.w; + m_intrinsicHeight = viewBoxRect.h; + } + + if(!m_intrinsicWidth || !m_intrinsicHeight) { + auto boundingBox = paintBoundingBox(); + if(!m_intrinsicWidth) + m_intrinsicWidth = boundingBox.right(); + if(!m_intrinsicHeight) { + m_intrinsicHeight = boundingBox.bottom(); + } + } +} + +void SVGRootElement::forceLayout() +{ + SVGLayoutState state; + layout(state); +} + +SVGUseElement::SVGUseElement(Document* document) + : SVGGraphicsElement(document, ElementID::Use) + , SVGURIReference(this) + , m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None) + , m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None) + , m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent) + , m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent) +{ + addProperty(m_x); + addProperty(m_y); + addProperty(m_width); + addProperty(m_height); +} + +Transform SVGUseElement::localTransform() const +{ + LengthContext lengthContext(this); + const Point translation = { + lengthContext.valueForLength(m_x), + lengthContext.valueForLength(m_y) + }; + + return SVGGraphicsElement::localTransform() * Transform::translated(translation.x, translation.y); +} + +void SVGUseElement::render(SVGRenderState& state) const +{ + if(isDisplayNone()) + return; + SVGBlendInfo blendInfo(this); + SVGRenderState newState(this, state, localTransform()); + newState.beginGroup(blendInfo); + renderChildren(newState); + newState.endGroup(blendInfo); +} + +void SVGUseElement::build() +{ + if(auto targetElement = getTargetElement(document())) { + if(auto newElement = cloneTargetElement(targetElement)) { + addChild(std::move(newElement)); + } + } + + SVGGraphicsElement::build(); +} + +inline bool isDisallowedElement(const SVGElement* element) +{ + switch(element->id()) { + case ElementID::Circle: + case ElementID::Ellipse: + case ElementID::G: + case ElementID::Image: + case ElementID::Line: + case ElementID::Path: + case ElementID::Polygon: + case ElementID::Polyline: + case ElementID::Rect: + case ElementID::Svg: + case ElementID::Symbol: + case ElementID::Text: + case ElementID::Tspan: + case ElementID::Use: + return false; + default: + return true; + } +} + +std::unique_ptr SVGUseElement::cloneTargetElement(SVGElement* targetElement) +{ + if(targetElement == this || isDisallowedElement(targetElement)) + return nullptr; + const auto& idAttr = targetElement->getAttribute(PropertyID::Id); + auto parent = parentElement(); + while(parent) { + auto attribute = parent->findAttribute(PropertyID::Id); + if(attribute && idAttr == attribute->value()) + return nullptr; + parent = parent->parentElement(); + } + + auto tagId = targetElement->id(); + if(tagId == ElementID::Symbol) { + tagId = ElementID::Svg; + } + + auto newElement = SVGElement::create(document(), tagId); + newElement->setAttributes(targetElement->attributes()); + if(newElement->id() == ElementID::Svg) { + for(const auto& attribute : attributes()) { + if(attribute.id() == PropertyID::Width || attribute.id() == PropertyID::Height) { + newElement->setAttribute(attribute); + } + } + } + + if(newElement->id() != ElementID::Use) + targetElement->cloneChildren(newElement.get()); + return newElement; +} + +SVGImageElement::SVGImageElement(Document* document) + : SVGGraphicsElement(document, ElementID::Image) + , m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None) + , m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None) + , m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent) + , m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent) + , m_preserveAspectRatio(PropertyID::PreserveAspectRatio) +{ + addProperty(m_x); + addProperty(m_y); + addProperty(m_width); + addProperty(m_height); + addProperty(m_preserveAspectRatio); +} + +Rect SVGImageElement::fillBoundingBox() const +{ + LengthContext lengthContext(this); + const Rect viewportRect = { + lengthContext.valueForLength(m_x), + lengthContext.valueForLength(m_y), + lengthContext.valueForLength(m_width), + lengthContext.valueForLength(m_height) + }; + + return viewportRect; +} + +Rect SVGImageElement::strokeBoundingBox() const +{ + return fillBoundingBox(); +} + +void SVGImageElement::render(SVGRenderState& state) const +{ + if(m_image.isNull() || isDisplayNone() || isVisibilityHidden()) + return; + Rect dstRect(fillBoundingBox()); + Rect srcRect(0, 0, m_image.width(), m_image.height()); + if(dstRect.isEmpty() || srcRect.isEmpty()) + return; + m_preserveAspectRatio.transformRect(dstRect, srcRect); + + SVGBlendInfo blendInfo(this); + SVGRenderState newState(this, state, localTransform()); + newState.beginGroup(blendInfo); + newState->drawImage(m_image, dstRect, srcRect, newState.currentTransform()); + newState.endGroup(blendInfo); +} + +static Bitmap loadImageResource(const std::string& href) +{ + if(href.compare(0, 5, "data:") == 0) { + std::string_view input(href); + auto index = input.find(',', 5); + if(index == std::string_view::npos) + return Bitmap(); + input.remove_prefix(index + 1); + return plutovg_surface_load_from_image_base64(input.data(), input.length()); + } + + return plutovg_surface_load_from_image_file(href.data()); +} + +void SVGImageElement::parseAttribute(PropertyID id, const std::string& value) +{ + if(id == PropertyID::Href) { + m_image = loadImageResource(value); + } else { + SVGGraphicsElement::parseAttribute(id, value); + } +} + +SVGSymbolElement::SVGSymbolElement(Document* document) + : SVGGraphicsElement(document, ElementID::Symbol) + , SVGFitToViewBox(this) +{ +} + +SVGGElement::SVGGElement(Document* document) + : SVGGraphicsElement(document, ElementID::G) +{ +} + +void SVGGElement::render(SVGRenderState& state) const +{ + if(isDisplayNone()) + return; + SVGBlendInfo blendInfo(this); + SVGRenderState newState(this, state, localTransform()); + newState.beginGroup(blendInfo); + renderChildren(newState); + newState.endGroup(blendInfo); +} + +SVGDefsElement::SVGDefsElement(Document* document) + : SVGGraphicsElement(document, ElementID::Defs) +{ +} + +SVGMarkerElement::SVGMarkerElement(Document* document) + : SVGElement(document, ElementID::Marker) + , SVGFitToViewBox(this) + , m_refX(PropertyID::RefX, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None) + , m_refY(PropertyID::RefY, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None) + , m_markerWidth(PropertyID::MarkerWidth, LengthDirection::Horizontal, LengthNegativeMode::Forbid, 3.f, LengthUnits::None) + , m_markerHeight(PropertyID::MarkerHeight, LengthDirection::Vertical, LengthNegativeMode::Forbid, 3.f, LengthUnits::None) + , m_markerUnits(PropertyID::MarkerUnits, MarkerUnits::StrokeWidth) + , m_orient(PropertyID::Orient) +{ + addProperty(m_refX); + addProperty(m_refY); + addProperty(m_markerWidth); + addProperty(m_markerHeight); + addProperty(m_markerUnits); + addProperty(m_orient); +} + +Point SVGMarkerElement::refPoint() const +{ + LengthContext lengthContext(this); + const Point refPoint = { + lengthContext.valueForLength(m_refX), + lengthContext.valueForLength(m_refY) + }; + + return refPoint; +} + +Size SVGMarkerElement::markerSize() const +{ + LengthContext lengthContext(this); + const Size markerSize = { + lengthContext.valueForLength(m_markerWidth), + lengthContext.valueForLength(m_markerHeight) + }; + + return markerSize; +} + +Transform SVGMarkerElement::markerTransform(const Point& origin, float angle, float strokeWidth) const +{ + auto transform = Transform::translated(origin.x, origin.y); + if(m_orient.orientType() == SVGAngle::OrientType::Angle) { + transform.rotate(m_orient.value()); + } else { + transform.rotate(angle); + } + + auto viewTransform = viewBoxToViewTransform(markerSize()); + auto refOrigin = viewTransform.mapPoint(refPoint()); + if(m_markerUnits.value() == MarkerUnits::StrokeWidth) + transform.scale(strokeWidth, strokeWidth); + transform.translate(-refOrigin.x, -refOrigin.y); + return transform * viewTransform; +} + +Rect SVGMarkerElement::markerBoundingBox(const Point& origin, float angle, float strokeWidth) const +{ + return markerTransform(origin, angle, strokeWidth).mapRect(paintBoundingBox()); +} + +void SVGMarkerElement::renderMarker(SVGRenderState& state, const Point& origin, float angle, float strokeWidth) const +{ + if(state.hasCycleReference(this)) + return; + SVGBlendInfo blendInfo(this); + SVGRenderState newState(this, state, markerTransform(origin, angle, strokeWidth)); + newState.beginGroup(blendInfo); + if(isOverflowHidden()) + newState->clipRect(getClipRect(markerSize()), FillRule::NonZero, newState.currentTransform()); + renderChildren(newState); + newState.endGroup(blendInfo); +} + +Transform SVGMarkerElement::localTransform() const +{ + return viewBoxToViewTransform(markerSize()); +} + +SVGClipPathElement::SVGClipPathElement(Document* document) + : SVGGraphicsElement(document, ElementID::ClipPath) + , m_clipPathUnits(PropertyID::ClipPathUnits, Units::UserSpaceOnUse) +{ + addProperty(m_clipPathUnits); +} + +Rect SVGClipPathElement::clipBoundingBox(const SVGElement* element) const +{ + auto clipBoundingBox = paintBoundingBox(); + if(m_clipPathUnits.value() == Units::ObjectBoundingBox) { + auto bbox = element->fillBoundingBox(); + clipBoundingBox.x = clipBoundingBox.x * bbox.w + bbox.x; + clipBoundingBox.y = clipBoundingBox.y * bbox.h + bbox.y; + clipBoundingBox.w = clipBoundingBox.w * bbox.w; + clipBoundingBox.h = clipBoundingBox.h * bbox.h; + } + + return localTransform().mapRect(clipBoundingBox); +} + +void SVGClipPathElement::applyClipMask(SVGRenderState& state) const +{ + if(state.hasCycleReference(this)) + return; + auto maskImage = Canvas::create(state.currentTransform().mapRect(state.paintBoundingBox())); + auto currentTransform = state.currentTransform() * localTransform(); + if(m_clipPathUnits.value() == Units::ObjectBoundingBox) { + auto bbox = state.fillBoundingBox(); + currentTransform.translate(bbox.x, bbox.y); + currentTransform.scale(bbox.w, bbox.h); + } + + SVGRenderState newState(this, &state, currentTransform, SVGRenderMode::Clipping, maskImage); + renderChildren(newState); + if(clipper()) { + clipper()->applyClipMask(newState); + } + + state->blendCanvas(*maskImage, BlendMode::Dst_In, 1.f); +} + +inline const SVGGeometryElement* toSVGGeometryElement(const SVGNode* node) +{ + if(node && node->isGeometryElement()) + return static_cast(node); + return nullptr; +} + +void SVGClipPathElement::applyClipPath(SVGRenderState& state) const +{ + auto currentTransform = state.currentTransform() * localTransform(); + if(m_clipPathUnits.value() == Units::ObjectBoundingBox) { + auto bbox = state.fillBoundingBox(); + currentTransform.translate(bbox.x, bbox.y); + currentTransform.scale(bbox.w, bbox.h); + } + + for(const auto& child : children()) { + auto element = toSVGElement(child); + if(element == nullptr || element->isDisplayNone()) + continue; + Transform clipTransform(currentTransform); + auto shapeElement = toSVGGeometryElement(element); + if(shapeElement == nullptr) { + if(element->id() != ElementID::Use) + continue; + clipTransform.multiply(element->localTransform()); + shapeElement = toSVGGeometryElement(element->firstChild()); + } + + if(shapeElement == nullptr || !shapeElement->isRenderable()) + continue; + state->clipPath(shapeElement->path(), shapeElement->clip_rule(), clipTransform * shapeElement->localTransform()); + return; + } + + state->clipRect(Rect::Empty, FillRule::NonZero, Transform::Identity); +} + +bool SVGClipPathElement::requiresMasking() const +{ + if(clipper()) + return true; + const SVGGeometryElement* prevShapeElement = nullptr; + for(const auto& child : children()) { + auto element = toSVGElement(child); + if(element == nullptr || element->isDisplayNone()) + continue; + auto shapeElement = toSVGGeometryElement(element); + if(shapeElement == nullptr) { + if(element->isTextPositioningElement()) + return true; + if(element->id() != ElementID::Use) + continue; + if(element->clipper()) + return true; + shapeElement = toSVGGeometryElement(element->firstChild()); + } + + if(shapeElement == nullptr || !shapeElement->isRenderable()) + continue; + if(prevShapeElement || shapeElement->clipper()) + return true; + prevShapeElement = shapeElement; + } + + return false; +} + +SVGMaskElement::SVGMaskElement(Document* document) + : SVGElement(document, ElementID::Mask) + , m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow, -10.f, LengthUnits::Percent) + , m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow, -10.f, LengthUnits::Percent) + , m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid, 120.f, LengthUnits::Percent) + , m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid, 120.f, LengthUnits::Percent) + , m_maskUnits(PropertyID::MaskUnits, Units::ObjectBoundingBox) + , m_maskContentUnits(PropertyID::MaskContentUnits, Units::UserSpaceOnUse) +{ + addProperty(m_x); + addProperty(m_y); + addProperty(m_width); + addProperty(m_height); + addProperty(m_maskUnits); + addProperty(m_maskContentUnits); +} + +Rect SVGMaskElement::maskRect(const SVGElement* element) const +{ + LengthContext lengthContext(this, m_maskUnits.value()); + Rect maskRect = { + lengthContext.valueForLength(m_x), + lengthContext.valueForLength(m_y), + lengthContext.valueForLength(m_width), + lengthContext.valueForLength(m_height) + }; + + if(m_maskUnits.value() == Units::ObjectBoundingBox) { + auto bbox = element->fillBoundingBox(); + maskRect.x = maskRect.x * bbox.w + bbox.x; + maskRect.y = maskRect.y * bbox.h + bbox.y; + maskRect.w = maskRect.w * bbox.w; + maskRect.h = maskRect.h * bbox.h; + } + + return maskRect; +} + +Rect SVGMaskElement::maskBoundingBox(const SVGElement* element) const +{ + auto maskBoundingBox = paintBoundingBox(); + if(m_maskContentUnits.value() == Units::ObjectBoundingBox) { + auto bbox = element->fillBoundingBox(); + maskBoundingBox.x = maskBoundingBox.x * bbox.w + bbox.x; + maskBoundingBox.y = maskBoundingBox.y * bbox.h + bbox.y; + maskBoundingBox.w = maskBoundingBox.w * bbox.w; + maskBoundingBox.h = maskBoundingBox.h * bbox.h; + } + + return maskBoundingBox.intersected(maskRect(element)); +} + +void SVGMaskElement::applyMask(SVGRenderState& state) const +{ + if(state.hasCycleReference(this)) + return; + auto maskImage = Canvas::create(state.currentTransform().mapRect(state.paintBoundingBox())); + maskImage->clipRect(maskRect(state.element()), FillRule::NonZero, state.currentTransform()); + + auto currentTransform = state.currentTransform(); + if(m_maskContentUnits.value() == Units::ObjectBoundingBox) { + auto bbox = state.fillBoundingBox(); + currentTransform.translate(bbox.x, bbox.y); + currentTransform.scale(bbox.w, bbox.h); + } + + SVGRenderState newState(this, &state, currentTransform, SVGRenderMode::Painting, maskImage); + renderChildren(newState); + if(clipper()) + clipper()->applyClipMask(newState); + if(masker()) { + masker()->applyMask(newState); + } + + if(m_mask_type == MaskType::Luminance) + maskImage->convertToLuminanceMask(); + state->blendCanvas(*maskImage, BlendMode::Dst_In, 1.f); +} + +void SVGMaskElement::layoutElement(const SVGLayoutState& state) +{ + m_mask_type = state.mask_type(); + SVGElement::layoutElement(state); +} + +} // namespace lunasvg diff --git a/vendor/lunasvg/source/svgelement.h b/vendor/lunasvg/source/svgelement.h new file mode 100644 index 0000000..ff5d008 --- /dev/null +++ b/vendor/lunasvg/source/svgelement.h @@ -0,0 +1,498 @@ +#ifndef LUNASVG_SVGELEMENT_H +#define LUNASVG_SVGELEMENT_H + +#include "lunasvg.h" +#include "svgproperty.h" + +#include +#include +#include +#include + +namespace lunasvg { + +class Document; +class SVGElement; +class SVGRootElement; + +class SVGNode { +public: + SVGNode(Document* document) + : m_document(document) + {} + + virtual ~SVGNode() = default; + virtual bool isTextNode() const { return false; } + virtual bool isElement() const { return false; } + virtual bool isPaintElement() const { return false; } + virtual bool isGraphicsElement() const { return false; } + virtual bool isGeometryElement() const { return false; } + virtual bool isTextPositioningElement() const { return false; } + + Document* document() const { return m_document; } + SVGRootElement* rootElement() const { return m_document->rootElement(); } + + SVGElement* parentElement() const { return m_parentElement; } + void setParentElement(SVGElement* parent) { m_parentElement = parent; } + + bool isRootElement() const { return m_parentElement == nullptr; } + + virtual std::unique_ptr clone(bool deep) const = 0; + +private: + SVGNode(const SVGNode&) = delete; + SVGNode& operator=(const SVGNode&) = delete; + Document* m_document; + SVGElement* m_parentElement = nullptr; +}; + +class SVGTextNode final : public SVGNode { +public: + SVGTextNode(Document* document); + + bool isTextNode() const final { return true; } + + const std::string& data() const { return m_data; } + void setData(const std::string& data); + + std::unique_ptr clone(bool deep) const final; + +private: + std::string m_data; +}; + +class Attribute { +public: + Attribute() = default; + Attribute(int specificity, PropertyID id, std::string value) + : m_specificity(specificity), m_id(id), m_value(std::move(value)) + {} + + int specificity() const { return m_specificity; } + PropertyID id() const { return m_id; } + const std::string& value() const { return m_value; } + +private: + int m_specificity; + PropertyID m_id; + std::string m_value; +}; + +using AttributeList = std::forward_list; + +enum class ElementID : uint8_t { + Unknown = 0, + Star, + Circle, + ClipPath, + Defs, + Ellipse, + G, + Image, + Line, + LinearGradient, + Marker, + Mask, + Path, + Pattern, + Polygon, + Polyline, + RadialGradient, + Rect, + Stop, + Style, + Svg, + Symbol, + Text, + Tspan, + Use +}; + +ElementID elementid(std::string_view name); + +using SVGNodeList = std::list>; +using SVGPropertyList = std::forward_list; + +class SVGMarkerElement; +class SVGClipPathElement; +class SVGMaskElement; +class SVGPaintElement; +class SVGLayoutState; +class SVGRenderState; + +extern const std::string emptyString; + +class SVGElement : public SVGNode { +public: + static std::unique_ptr create(Document* document, ElementID id); + + SVGElement(Document* document, ElementID id); + virtual ~SVGElement() = default; + + bool hasAttribute(std::string_view name) const; + const std::string& getAttribute(std::string_view name) const; + bool setAttribute(std::string_view name, const std::string& value); + + const Attribute* findAttribute(PropertyID id) const; + bool hasAttribute(PropertyID id) const; + const std::string& getAttribute(PropertyID id) const; + bool setAttribute(int specificity, PropertyID id, const std::string& value); + void setAttributes(const AttributeList& attributes); + bool setAttribute(const Attribute& attribute); + + virtual void parseAttribute(PropertyID id, const std::string& value); + + SVGElement* previousElement() const; + SVGElement* nextElement() const; + + SVGNode* addChild(std::unique_ptr child); + SVGNode* firstChild() const; + SVGNode* lastChild() const; + + ElementID id() const { return m_id; } + const AttributeList& attributes() const { return m_attributes; } + const SVGPropertyList& properties() const { return m_properties; } + const SVGNodeList& children() const { return m_children; } + + virtual Transform localTransform() const { return Transform::Identity; } + virtual Rect fillBoundingBox() const; + virtual Rect strokeBoundingBox() const; + virtual Rect paintBoundingBox() const; + + SVGMarkerElement* getMarker(std::string_view id) const; + SVGClipPathElement* getClipper(std::string_view id) const; + SVGMaskElement* getMasker(std::string_view id) const; + SVGPaintElement* getPainter(std::string_view id) const; + + SVGElement* elementFromPoint(float x, float y); + + template + void transverse(T callback); + + void addProperty(SVGProperty& value); + SVGProperty* getProperty(PropertyID id) const; + Size currentViewportSize() const; + float font_size() const { return m_font_size; } + + void cloneChildren(SVGElement* parentElement) const; + std::unique_ptr clone(bool deep) const final; + + virtual void build(); + + virtual void layoutElement(const SVGLayoutState& state); + void layoutChildren(SVGLayoutState& state); + virtual void layout(SVGLayoutState& state); + + void renderChildren(SVGRenderState& state) const; + virtual void render(SVGRenderState& state) const; + + bool isDisplayNone() const { return m_display == Display::None; } + bool isOverflowHidden() const { return m_overflow == Overflow::Hidden; } + bool isVisibilityHidden() const { return m_visibility != Visibility::Visible; } + + bool isHiddenElement() const; + bool isPointableElement() const; + + const SVGClipPathElement* clipper() const { return m_clipper; } + const SVGMaskElement* masker() const { return m_masker; } + float opacity() const { return m_opacity; } + + bool isElement() const final { return true; } + +private: + mutable Rect m_paintBoundingBox = Rect::Invalid; + const SVGClipPathElement* m_clipper = nullptr; + const SVGMaskElement* m_masker = nullptr; + float m_opacity = 1.f; + + float m_font_size = 12.f; + Display m_display = Display::Inline; + Overflow m_overflow = Overflow::Visible; + Visibility m_visibility = Visibility::Visible; + PointerEvents m_pointer_events = PointerEvents::Auto; + + ElementID m_id; + AttributeList m_attributes; + SVGPropertyList m_properties; + SVGNodeList m_children; +}; + +inline const SVGElement* toSVGElement(const SVGNode* node) +{ + if(node && node->isElement()) + return static_cast(node); + return nullptr; +} + +inline SVGElement* toSVGElement(SVGNode* node) +{ + if(node && node->isElement()) + return static_cast(node); + return nullptr; +} + +inline SVGElement* toSVGElement(const std::unique_ptr& node) +{ + return toSVGElement(node.get()); +} + +template +inline void SVGElement::transverse(T callback) +{ + callback(this); + for(const auto& child : m_children) { + if(auto element = toSVGElement(child)) { + element->transverse(callback); + } + } +} + +class SVGStyleElement final : public SVGElement { +public: + SVGStyleElement(Document* document); +}; + +class SVGFitToViewBox { +public: + SVGFitToViewBox(SVGElement* element); + + const SVGRect& viewBox() const { return m_viewBox; } + const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; } + Transform viewBoxToViewTransform(const Size& viewportSize) const; + Rect getClipRect(const Size& viewportSize) const; + +private: + SVGRect m_viewBox; + SVGPreserveAspectRatio m_preserveAspectRatio; +}; + +class SVGURIReference { +public: + SVGURIReference(SVGElement* element); + + const SVGString& href() const { return m_href; } + const std::string& hrefString() const { return m_href.value(); } + SVGElement* getTargetElement(const Document* document) const; + +private: + SVGString m_href; +}; + +class SVGPaintServer { +public: + SVGPaintServer() = default; + SVGPaintServer(const SVGPaintElement* element, const Color& color, float opacity) + : m_element(element), m_color(color), m_opacity(opacity) + {} + + bool isRenderable() const { return m_opacity > 0.f && (m_element || m_color.alpha() > 0); } + + const SVGPaintElement* element() const { return m_element; } + const Color& color() const { return m_color; } + float opacity() const { return m_opacity; } + + bool applyPaint(SVGRenderState& state) const; + +private: + const SVGPaintElement* m_element = nullptr; + Color m_color = Color::Transparent; + float m_opacity = 0.f; +}; + +class SVGGraphicsElement : public SVGElement { +public: + SVGGraphicsElement(Document* document, ElementID id); + + bool isGraphicsElement() const final { return true; } + + const SVGTransform& transform() const { return m_transform; } + Transform localTransform() const override { return m_transform.value(); } + + SVGPaintServer getPaintServer(const Paint& paint, float opacity) const; + StrokeData getStrokeData(const SVGLayoutState& state) const; + +private: + SVGTransform m_transform; +}; + +class SVGSVGElement : public SVGGraphicsElement, public SVGFitToViewBox { +public: + SVGSVGElement(Document* document); + + const SVGLength& x() const { return m_x; } + const SVGLength& y() const { return m_y; } + const SVGLength& width() const { return m_width; } + const SVGLength& height() const { return m_height; } + + Transform localTransform() const override; + void render(SVGRenderState& state) const override; + +private: + SVGLength m_x; + SVGLength m_y; + SVGLength m_width; + SVGLength m_height; +}; + +class SVGRootElement final : public SVGSVGElement { +public: + SVGRootElement(Document* document); + + float intrinsicWidth() const { return m_intrinsicWidth; } + float intrinsicHeight() const { return m_intrinsicHeight; } + + void setNeedsLayout() { m_intrinsicWidth = -1.f; } + bool needsLayout() const { return m_intrinsicWidth == -1.f; } + + SVGRootElement* layoutIfNeeded(); + + SVGElement* getElementById(std::string_view id) const; + void addElementById(const std::string& id, SVGElement* element); + void layout(SVGLayoutState& state) final; + + void forceLayout(); + +private: + std::map> m_idCache; + float m_intrinsicWidth{-1.f}; + float m_intrinsicHeight{-1.f}; +}; + +class SVGUseElement final : public SVGGraphicsElement, public SVGURIReference { +public: + SVGUseElement(Document* document); + + const SVGLength& x() const { return m_x; } + const SVGLength& y() const { return m_y; } + const SVGLength& width() const { return m_width; } + const SVGLength& height() const { return m_height; } + + Transform localTransform() const final; + void render(SVGRenderState& state) const final; + void build() final; + +private: + std::unique_ptr cloneTargetElement(SVGElement* targetElement); + SVGLength m_x; + SVGLength m_y; + SVGLength m_width; + SVGLength m_height; +}; + +class SVGImageElement final : public SVGGraphicsElement { +public: + SVGImageElement(Document* document); + + const SVGLength& x() const { return m_x; } + const SVGLength& y() const { return m_y; } + const SVGLength& width() const { return m_width; } + const SVGLength& height() const { return m_height; } + const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; } + const Bitmap& image() const { return m_image; } + + Rect fillBoundingBox() const final; + Rect strokeBoundingBox() const final; + void render(SVGRenderState& state) const final; + void parseAttribute(PropertyID id, const std::string& value) final; + +private: + SVGLength m_x; + SVGLength m_y; + SVGLength m_width; + SVGLength m_height; + SVGPreserveAspectRatio m_preserveAspectRatio; + Bitmap m_image; +}; + +class SVGSymbolElement final : public SVGGraphicsElement, public SVGFitToViewBox { +public: + SVGSymbolElement(Document* document); +}; + +class SVGGElement final : public SVGGraphicsElement { +public: + SVGGElement(Document* document); + + void render(SVGRenderState& state) const final; +}; + +class SVGDefsElement final : public SVGGraphicsElement { +public: + SVGDefsElement(Document* document); +}; + +class SVGMarkerElement final : public SVGElement, public SVGFitToViewBox { +public: + SVGMarkerElement(Document* document); + + const SVGLength& refX() const { return m_refX; } + const SVGLength& refY() const { return m_refY; } + const SVGLength& markerWidth() const { return m_markerWidth; } + const SVGLength& markerHeight() const { return m_markerHeight; } + const SVGEnumeration& markerUnits() const { return m_markerUnits; } + const SVGAngle& orient() const { return m_orient; } + + Point refPoint() const; + Size markerSize() const; + + Transform markerTransform(const Point& origin, float angle, float strokeWidth) const; + Rect markerBoundingBox(const Point& origin, float angle, float strokeWidth) const; + void renderMarker(SVGRenderState& state, const Point& origin, float angle, float strokeWidth) const; + + Transform localTransform() const final; + +private: + SVGLength m_refX; + SVGLength m_refY; + SVGLength m_markerWidth; + SVGLength m_markerHeight; + SVGEnumeration m_markerUnits; + SVGAngle m_orient; +}; + +class SVGClipPathElement final : public SVGGraphicsElement { +public: + SVGClipPathElement(Document* document); + + const SVGEnumeration& clipPathUnits() const { return m_clipPathUnits; } + Rect clipBoundingBox(const SVGElement* element) const; + + void applyClipMask(SVGRenderState& state) const; + void applyClipPath(SVGRenderState& state) const; + + bool requiresMasking() const; + +private: + SVGEnumeration m_clipPathUnits; +}; + +class SVGMaskElement final : public SVGElement { +public: + SVGMaskElement(Document* document); + + const SVGLength& x() const { return m_x; } + const SVGLength& y() const { return m_y; } + const SVGLength& width() const { return m_width; } + const SVGLength& height() const { return m_height; } + const SVGEnumeration& maskUnits() const { return m_maskUnits; } + const SVGEnumeration& maskContentUnits() const { return m_maskContentUnits; } + + Rect maskRect(const SVGElement* element) const; + Rect maskBoundingBox(const SVGElement* element) const; + void applyMask(SVGRenderState& state) const; + + void layoutElement(const SVGLayoutState& state) final; + +private: + SVGLength m_x; + SVGLength m_y; + SVGLength m_width; + SVGLength m_height; + SVGEnumeration m_maskUnits; + SVGEnumeration m_maskContentUnits; + MaskType m_mask_type = MaskType::Luminance; +}; + +} // namespace lunasvg + +#endif // LUNASVG_SVGELEMENT_H diff --git a/vendor/lunasvg/source/svggeometryelement.cpp b/vendor/lunasvg/source/svggeometryelement.cpp new file mode 100644 index 0000000..e2c9708 --- /dev/null +++ b/vendor/lunasvg/source/svggeometryelement.cpp @@ -0,0 +1,324 @@ +#include "svggeometryelement.h" +#include "svglayoutstate.h" +#include "svgrenderstate.h" + +#include + +namespace lunasvg { + +Rect SVGMarkerPosition::markerBoundingBox(float strokeWidth) const +{ + return m_element->markerBoundingBox(m_origin, m_angle, strokeWidth); +} + +void SVGMarkerPosition::renderMarker(SVGRenderState& state, float strokeWidth) const +{ + m_element->renderMarker(state, m_origin, m_angle, strokeWidth); +} + +SVGGeometryElement::SVGGeometryElement(Document* document, ElementID id) + : SVGGraphicsElement(document, id) +{ +} + +Rect SVGGeometryElement::strokeBoundingBox() const +{ + auto strokeBoundingBox = fillBoundingBox(); + if(m_stroke.isRenderable()) { + float capLimit = m_strokeData.lineWidth() / 2.f; + if(m_strokeData.lineCap() == LineCap::Square) + capLimit *= PLUTOVG_SQRT2; + float joinLimit = m_strokeData.lineWidth() / 2.f; + if(m_strokeData.lineJoin() == LineJoin::Miter) { + joinLimit *= m_strokeData.miterLimit(); + } + + strokeBoundingBox.inflate(std::max(capLimit, joinLimit)); + } + + for(const auto& markerPosition : m_markerPositions) + strokeBoundingBox.unite(markerPosition.markerBoundingBox(m_strokeData.lineWidth())); + return strokeBoundingBox; +} + +void SVGGeometryElement::layoutElement(const SVGLayoutState& state) +{ + m_fill_rule = state.fill_rule(); + m_clip_rule = state.clip_rule(); + m_fill = getPaintServer(state.fill(), state.fill_opacity()); + m_stroke = getPaintServer(state.stroke(), state.stroke_opacity()); + m_strokeData = getStrokeData(state); + SVGGraphicsElement::layoutElement(state); + + m_path.reset(); + m_markerPositions.clear(); + m_fillBoundingBox = updateShape(m_path); + updateMarkerPositions(m_markerPositions, state); +} + +void SVGGeometryElement::updateMarkerPositions(SVGMarkerPositionList& positions, const SVGLayoutState& state) +{ + if(m_path.isEmpty()) + return; + auto markerStart = getMarker(state.marker_start()); + auto markerMid = getMarker(state.marker_mid()); + auto markerEnd = getMarker(state.marker_end()); + if(markerStart == nullptr && markerMid == nullptr && markerEnd == nullptr) { + return; + } + + Point origin; + Point startPoint; + Point inslopePoints[2]; + Point outslopePoints[2]; + + int index = 0; + std::array points; + PathIterator it(m_path); + while(!it.isDone()) { + switch(it.currentSegment(points)) { + case PathCommand::MoveTo: + startPoint = points[0]; + inslopePoints[0] = origin; + inslopePoints[1] = points[0]; + origin = points[0]; + break; + case PathCommand::LineTo: + inslopePoints[0] = origin; + inslopePoints[1] = points[0]; + origin = points[0]; + break; + case PathCommand::CubicTo: + inslopePoints[0] = points[1]; + inslopePoints[1] = points[2]; + origin = points[2]; + break; + case PathCommand::Close: + inslopePoints[0] = origin; + inslopePoints[1] = points[0]; + origin = startPoint; + startPoint = Point(); + break; + } + + it.next(); + + if(!it.isDone() && (markerStart || markerMid)) { + it.currentSegment(points); + outslopePoints[0] = origin; + outslopePoints[1] = points[0]; + if(index == 0 && markerStart) { + auto slope = outslopePoints[1] - outslopePoints[0]; + auto angle = 180.f * std::atan2(slope.y, slope.x) / PLUTOVG_PI; + const auto& orient = markerStart->orient(); + if(orient.orientType() == SVGAngle::OrientType::AutoStartReverse) + angle -= 180.f; + positions.emplace_back(markerStart, origin, angle); + } + + if(index > 0 && markerMid) { + auto inslope = inslopePoints[1] - inslopePoints[0]; + auto outslope = outslopePoints[1] - outslopePoints[0]; + auto inangle = 180.f * std::atan2(inslope.y, inslope.x) / PLUTOVG_PI; + auto outangle = 180.f * std::atan2(outslope.y, outslope.x) / PLUTOVG_PI; + if(std::abs(inangle - outangle) > 180.f) + inangle += 360.f; + auto angle = (inangle + outangle) * 0.5f; + positions.emplace_back(markerMid, origin, angle); + } + } + + if(markerEnd && it.isDone()) { + auto slope = inslopePoints[1] - inslopePoints[0]; + auto angle = 180.f * std::atan2(slope.y, slope.x) / PLUTOVG_PI; + positions.emplace_back(markerEnd, origin, angle); + } + + index += 1; + } +} + +void SVGGeometryElement::render(SVGRenderState& state) const +{ + if(!isRenderable()) + return; + SVGBlendInfo blendInfo(this); + SVGRenderState newState(this, state, localTransform()); + newState.beginGroup(blendInfo); + if(newState.mode() == SVGRenderMode::Clipping) { + newState->setColor(Color::White); + newState->fillPath(m_path, m_clip_rule, newState.currentTransform()); + } else { + if(m_fill.applyPaint(newState)) + newState->fillPath(m_path, m_fill_rule, newState.currentTransform()); + if(m_stroke.applyPaint(newState)) { + newState->strokePath(m_path, m_strokeData, newState.currentTransform()); + } + + for(const auto& markerPosition : m_markerPositions) { + markerPosition.renderMarker(newState, m_strokeData.lineWidth()); + } + } + + newState.endGroup(blendInfo); +} + +SVGLineElement::SVGLineElement(Document* document) + : SVGGeometryElement(document, ElementID::Line) + , m_x1(PropertyID::X1, LengthDirection::Horizontal, LengthNegativeMode::Allow) + , m_y1(PropertyID::Y1, LengthDirection::Vertical, LengthNegativeMode::Allow) + , m_x2(PropertyID::X2, LengthDirection::Horizontal, LengthNegativeMode::Allow) + , m_y2(PropertyID::Y2, LengthDirection::Vertical, LengthNegativeMode::Allow) +{ + addProperty(m_x1); + addProperty(m_y1); + addProperty(m_x2); + addProperty(m_y2); +} + +Rect SVGLineElement::updateShape(Path& path) +{ + LengthContext lengthContext(this); + auto x1 = lengthContext.valueForLength(m_x1); + auto y1 = lengthContext.valueForLength(m_y1); + auto x2 = lengthContext.valueForLength(m_x2); + auto y2 = lengthContext.valueForLength(m_y2); + + path.moveTo(x1, y1); + path.lineTo(x2, y2); + return Rect(x1, y1, x2 - x1, y2 - y1); +} + +SVGRectElement::SVGRectElement(Document* document) + : SVGGeometryElement(document, ElementID::Rect) + , m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow) + , m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow) + , m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid) + , m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid) + , m_rx(PropertyID::Rx, LengthDirection::Horizontal, LengthNegativeMode::Forbid) + , m_ry(PropertyID::Ry, LengthDirection::Vertical, LengthNegativeMode::Forbid) +{ + addProperty(m_x); + addProperty(m_y); + addProperty(m_width); + addProperty(m_height); + addProperty(m_rx); + addProperty(m_ry); +} + +Rect SVGRectElement::updateShape(Path& path) +{ + LengthContext lengthContext(this); + auto width = lengthContext.valueForLength(m_width); + auto height = lengthContext.valueForLength(m_height); + if(width <= 0.f || height <= 0.f) { + return Rect::Empty; + } + + auto x = lengthContext.valueForLength(m_x); + auto y = lengthContext.valueForLength(m_y); + + auto rx = lengthContext.valueForLength(m_rx); + auto ry = lengthContext.valueForLength(m_ry); + + if(rx <= 0.f) rx = ry; + if(ry <= 0.f) ry = rx; + + rx = std::min(rx, width / 2.f); + ry = std::min(ry, height / 2.f); + + path.addRoundRect(x, y, width, height, rx, ry); + return Rect(x, y, width, height); +} + +SVGEllipseElement::SVGEllipseElement(Document* document) + : SVGGeometryElement(document, ElementID::Ellipse) + , m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow) + , m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow) + , m_rx(PropertyID::Rx, LengthDirection::Diagonal, LengthNegativeMode::Forbid) + , m_ry(PropertyID::Ry, LengthDirection::Diagonal, LengthNegativeMode::Forbid) +{ + addProperty(m_cx); + addProperty(m_cy); + addProperty(m_rx); + addProperty(m_ry); +} + +Rect SVGEllipseElement::updateShape(Path& path) +{ + LengthContext lengthContext(this); + auto rx = lengthContext.valueForLength(m_rx); + auto ry = lengthContext.valueForLength(m_ry); + if(rx <= 0.f || ry <= 0.f) { + return Rect::Empty; + } + + auto cx = lengthContext.valueForLength(m_cx); + auto cy = lengthContext.valueForLength(m_cy); + path.addEllipse(cx, cy, rx, ry); + return Rect(cx - rx, cy - ry, rx + rx, ry + ry); +} + +SVGCircleElement::SVGCircleElement(Document* document) + : SVGGeometryElement(document, ElementID::Circle) + , m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow) + , m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow) + , m_r(PropertyID::R, LengthDirection::Diagonal, LengthNegativeMode::Forbid) +{ + addProperty(m_cx); + addProperty(m_cy); + addProperty(m_r); +} + +Rect SVGCircleElement::updateShape(Path& path) +{ + LengthContext lengthContext(this); + auto r = lengthContext.valueForLength(m_r); + if(r <= 0.f) { + return Rect::Empty; + } + + auto cx = lengthContext.valueForLength(m_cx); + auto cy = lengthContext.valueForLength(m_cy); + path.addEllipse(cx, cy, r, r); + return Rect(cx - r, cy - r, r + r, r + r); +} + +SVGPolyElement::SVGPolyElement(Document* document, ElementID id) + : SVGGeometryElement(document, id) + , m_points(PropertyID::Points) +{ + addProperty(m_points); +} + +Rect SVGPolyElement::updateShape(Path& path) +{ + const auto& points = m_points.values(); + if(points.empty()) { + return Rect::Empty; + } + + path.moveTo(points[0].x, points[0].y); + for(size_t i = 1; i < points.size(); i++) { + path.lineTo(points[i].x, points[i].y); + } + + if(id() == ElementID::Polygon) + path.close(); + return path.boundingRect(); +} + +SVGPathElement::SVGPathElement(Document* document) + : SVGGeometryElement(document, ElementID::Path) + , m_d(PropertyID::D) +{ + addProperty(m_d); +} + +Rect SVGPathElement::updateShape(Path& path) +{ + path = m_d.value(); + return path.boundingRect(); +} + +} // namespace lunasvg diff --git a/vendor/lunasvg/source/svggeometryelement.h b/vendor/lunasvg/source/svggeometryelement.h new file mode 100644 index 0000000..b02d8b3 --- /dev/null +++ b/vendor/lunasvg/source/svggeometryelement.h @@ -0,0 +1,139 @@ +#ifndef LUNASVG_SVGGEOMETRYELEMENT_H +#define LUNASVG_SVGGEOMETRYELEMENT_H + +#include "svgelement.h" + +namespace lunasvg { + +class SVGMarkerPosition { +public: + SVGMarkerPosition(const SVGMarkerElement* element, const Point& origin, float angle) + : m_element(element), m_origin(origin), m_angle(angle) + {} + + const SVGMarkerElement* element() const { return m_element; } + const Point& origin() const { return m_origin; } + float angle() const { return m_angle; } + + Rect markerBoundingBox(float strokeWidth) const; + void renderMarker(SVGRenderState& state, float strokeWidth) const; + +private: + const SVGMarkerElement* m_element; + Point m_origin; + float m_angle; +}; + +using SVGMarkerPositionList = std::vector; + +class SVGGeometryElement : public SVGGraphicsElement { +public: + SVGGeometryElement(Document* document, ElementID id); + + bool isGeometryElement() const final { return true; } + + Rect fillBoundingBox() const override { return m_fillBoundingBox; } + Rect strokeBoundingBox() const override; + void layoutElement(const SVGLayoutState& state) override; + + bool isRenderable() const { return !m_path.isNull() && !isDisplayNone() && !isVisibilityHidden(); } + + FillRule fill_rule() const { return m_fill_rule; } + FillRule clip_rule() const { return m_clip_rule; } + + virtual Rect updateShape(Path& path) = 0; + + void updateMarkerPositions(SVGMarkerPositionList& positions, const SVGLayoutState& state); + void render(SVGRenderState& state) const override; + + const Path& path() const { return m_path; } + +private: + Path m_path; + Rect m_fillBoundingBox; + StrokeData m_strokeData; + + SVGPaintServer m_fill; + SVGPaintServer m_stroke; + SVGMarkerPositionList m_markerPositions; + + FillRule m_fill_rule = FillRule::NonZero; + FillRule m_clip_rule = FillRule::NonZero; +}; + +class SVGLineElement final : public SVGGeometryElement { +public: + SVGLineElement(Document* document); + + Rect updateShape(Path& path) final; + +private: + SVGLength m_x1; + SVGLength m_y1; + SVGLength m_x2; + SVGLength m_y2; +}; + +class SVGRectElement final : public SVGGeometryElement { +public: + SVGRectElement(Document* document); + + Rect updateShape(Path& path) final; + +private: + SVGLength m_x; + SVGLength m_y; + SVGLength m_width; + SVGLength m_height; + SVGLength m_rx; + SVGLength m_ry; +}; + +class SVGEllipseElement final : public SVGGeometryElement { +public: + SVGEllipseElement(Document* document); + + Rect updateShape(Path& path) final; + +private: + SVGLength m_cx; + SVGLength m_cy; + SVGLength m_rx; + SVGLength m_ry; +}; + +class SVGCircleElement final : public SVGGeometryElement { +public: + SVGCircleElement(Document* document); + + Rect updateShape(Path& path) final; + +private: + SVGLength m_cx; + SVGLength m_cy; + SVGLength m_r; +}; + +class SVGPolyElement final : public SVGGeometryElement { +public: + SVGPolyElement(Document* document, ElementID id); + + Rect updateShape(Path& path) final; + +private: + SVGPointList m_points; +}; + +class SVGPathElement final : public SVGGeometryElement { +public: + SVGPathElement(Document* document); + + Rect updateShape(Path& path) final; + +private: + SVGPath m_d; +}; + +} // namespace lunasvg + +#endif // LUNASVG_SVGGEOMETRYELEMENT_H diff --git a/vendor/lunasvg/source/svglayoutstate.cpp b/vendor/lunasvg/source/svglayoutstate.cpp new file mode 100644 index 0000000..bf5b92d --- /dev/null +++ b/vendor/lunasvg/source/svglayoutstate.cpp @@ -0,0 +1,607 @@ +#include "svglayoutstate.h" +#include "svgelement.h" +#include "svgparserutils.h" + +#include + +namespace lunasvg { + +static std::optional parseColorValue(std::string_view& input, const SVGLayoutState* state) +{ + if(skipString(input, "currentColor")) { + return state->color(); + } + + plutovg_color_t color; + int length = plutovg_color_parse(&color, input.data(), input.length()); + if(length == 0) + return std::nullopt; + input.remove_prefix(length); + return Color(plutovg_color_to_argb32(&color)); +} + +static Color parseColor(std::string_view input, const SVGLayoutState* state, const Color& defaultValue) +{ + auto color = parseColorValue(input, state); + if(!color || !input.empty()) + color = defaultValue; + return color.value(); +} + +static Color parseColorOrNone(std::string_view input, const SVGLayoutState* state, const Color& defaultValue) +{ + if(input.compare("none") == 0) + return Color::Transparent; + return parseColor(input, state, defaultValue); +} + +static bool parseUrlValue(std::string_view& input, std::string& value) +{ + if(!skipString(input, "url") + || !skipOptionalSpaces(input) + || !skipDelimiter(input, '(') + || !skipOptionalSpaces(input)) { + return false; + } + + switch(input.front()) { + case '\'': + case '\"': { + auto delim = input.front(); + input.remove_prefix(1); + skipOptionalSpaces(input); + if(!skipDelimiter(input, '#')) + return false; + while(!input.empty() && input.front() != delim) { + value += input.front(); + input.remove_prefix(1); + } + + skipOptionalSpaces(input); + if(!skipDelimiter(input, delim)) + return false; + break; + } case '#': { + input.remove_prefix(1); + while(!input.empty() && input.front() != ')') { + value += input.front(); + input.remove_prefix(1); + } + + break; + } default: + return false; + } + + return skipOptionalSpaces(input) && skipDelimiter(input, ')'); +} + +static std::string parseUrl(std::string_view input) +{ + std::string value; + if(!parseUrlValue(input, value) || !input.empty()) + value.clear(); + return value; +} + +static Paint parsePaint(std::string_view input, const SVGLayoutState* state, const Color& defaultValue) +{ + std::string id; + if(!parseUrlValue(input, id)) + return Paint(parseColorOrNone(input, state, defaultValue)); + if(skipOptionalSpaces(input)) + return Paint(id, parseColorOrNone(input, state, defaultValue)); + return Paint(id, Color::Transparent); +} + +static float parseNumberOrPercentage(std::string_view input, bool allowPercentage, float defaultValue) +{ + float value; + if(!parseNumber(input, value)) + return defaultValue; + if(allowPercentage) { + if(skipDelimiter(input, '%')) + value /= 100.f; + value = std::clamp(value, 0.f, 1.f); + } + + if(!input.empty()) + return defaultValue; + return value; +} + +static Length parseLength(std::string_view input, LengthNegativeMode mode, const Length& defaultValue) +{ + Length value; + if(!value.parse(input, mode)) + value = defaultValue; + return value; +} + +static BaselineShift parseBaselineShift(std::string_view input) +{ + if(input.compare("baseline") == 0) + return BaselineShift::Type::Baseline; + if(input.compare("sub") == 0) + return BaselineShift::Type::Sub; + if(input.compare("super") == 0) + return BaselineShift::Type::Super; + return parseLength(input, LengthNegativeMode::Allow, Length(0.f, LengthUnits::None)); +} + +static LengthList parseDashArray(std::string_view input) +{ + if(input.compare("none") == 0) + return LengthList(); + LengthList values; + do { + size_t count = 0; + while(count < input.length() && input[count] != ',' && !IS_WS(input[count])) + ++count; + Length value(0, LengthUnits::None); + if(!value.parse(input.substr(0, count), LengthNegativeMode::Forbid)) + return LengthList(); + input.remove_prefix(count); + values.push_back(std::move(value)); + } while(skipOptionalSpacesOrComma(input)); + return values; +} + +static Length parseLengthOrNormal(std::string_view input) +{ + if(input.compare("normal") == 0) + return Length(0, LengthUnits::None); + return parseLength(input, LengthNegativeMode::Allow, Length(0, LengthUnits::None)); +} + +static float parseFontSize(std::string_view input, const SVGLayoutState* state) +{ + auto length = parseLength(input, LengthNegativeMode::Forbid, Length(12, LengthUnits::None)); + if(length.units() == LengthUnits::Percent) + return length.value() * state->font_size() / 100.f; + if(length.units() == LengthUnits::Ex) + return length.value() * state->font_size() / 2.f; + if(length.units() == LengthUnits::Em) + return length.value() * state->font_size(); + return length.value(); +} + +template +static Enum parseEnumValue(std::string_view input, const SVGEnumerationEntry(&entries)[N], Enum defaultValue) +{ + for(const auto& entry : entries) { + if(input == entry.second) { + return entry.first; + } + } + + return defaultValue; +} + +static Display parseDisplay(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {Display::Inline, "inline"}, + {Display::None, "none"} + }; + + return parseEnumValue(input, entries, Display::Inline); +} + +static Visibility parseVisibility(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {Visibility::Visible, "visible"}, + {Visibility::Hidden, "hidden"}, + {Visibility::Collapse, "collapse"} + }; + + return parseEnumValue(input, entries, Visibility::Visible); +} + +static Overflow parseOverflow(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {Overflow::Visible, "visible"}, + {Overflow::Hidden, "hidden"} + }; + + return parseEnumValue(input, entries, Overflow::Visible); +} + +static PointerEvents parsePointerEvents(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {PointerEvents::None,"none"}, + {PointerEvents::Auto,"auto"}, + {PointerEvents::Stroke,"stroke"}, + {PointerEvents::Fill,"fill"}, + {PointerEvents::Painted,"painted"}, + {PointerEvents::Visible,"visible"}, + {PointerEvents::VisibleStroke,"visibleStroke"}, + {PointerEvents::VisibleFill,"visibleFill"}, + {PointerEvents::VisiblePainted,"visiblePainted"}, + {PointerEvents::BoundingBox,"bounding-box"}, + {PointerEvents::All,"all"}, + }; + + return parseEnumValue(input, entries, PointerEvents::Auto); +} + +static FontWeight parseFontWeight(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {FontWeight::Normal, "normal"}, + {FontWeight::Bold, "bold"}, + {FontWeight::Bold, "bolder"}, + {FontWeight::Normal, "lighter"}, + {FontWeight::Normal, "100"}, + {FontWeight::Normal, "200"}, + {FontWeight::Normal, "300"}, + {FontWeight::Normal, "400"}, + {FontWeight::Normal, "500"}, + {FontWeight::Bold, "600"}, + {FontWeight::Bold, "700"}, + {FontWeight::Bold, "800"}, + {FontWeight::Bold, "900"} + }; + + return parseEnumValue(input, entries, FontWeight::Normal); +} + +static FontStyle parseFontStyle(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {FontStyle::Normal, "normal"}, + {FontStyle::Italic, "italic"}, + {FontStyle::Italic, "oblique"} + }; + + return parseEnumValue(input, entries, FontStyle::Normal); +} + +static AlignmentBaseline parseAlignmentBaseline(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {AlignmentBaseline::Auto, "auto"}, + {AlignmentBaseline::Baseline, "baseline"}, + {AlignmentBaseline::BeforeEdge, "before-edge"}, + {AlignmentBaseline::TextBeforeEdge, "text-before-edge"}, + {AlignmentBaseline::Middle, "middle"}, + {AlignmentBaseline::Central, "central"}, + {AlignmentBaseline::AfterEdge, "after-edge"}, + {AlignmentBaseline::TextAfterEdge, "text-after-edge"}, + {AlignmentBaseline::Ideographic, "ideographic"}, + {AlignmentBaseline::Alphabetic, "alphabetic"}, + {AlignmentBaseline::Hanging, "hanging"}, + {AlignmentBaseline::Mathematical, "mathematical"} + }; + + return parseEnumValue(input, entries, AlignmentBaseline::Auto); +} + +static DominantBaseline parseDominantBaseline(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {DominantBaseline::Auto, "auto"}, + {DominantBaseline::UseScript, "use-script"}, + {DominantBaseline::NoChange, "no-change"}, + {DominantBaseline::ResetSize, "reset-size"}, + {DominantBaseline::Ideographic, "ideographic"}, + {DominantBaseline::Alphabetic, "alphabetic"}, + {DominantBaseline::Hanging, "hanging"}, + {DominantBaseline::Mathematical, "mathematical"}, + {DominantBaseline::Central, "central"}, + {DominantBaseline::Middle, "middle"}, + {DominantBaseline::TextAfterEdge, "text-after-edge"}, + {DominantBaseline::TextBeforeEdge, "text-before-edge"} + }; + + return parseEnumValue(input, entries, DominantBaseline::Auto); +} + +static Direction parseDirection(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {Direction::Ltr, "ltr"}, + {Direction::Rtl, "rtl"} + }; + + return parseEnumValue(input, entries, Direction::Ltr); +} + +static WritingMode parseWritingMode(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {WritingMode::Horizontal, "horizontal-tb"}, + {WritingMode::Vertical, "vertical-rl"}, + {WritingMode::Vertical, "vertical-lr"}, + {WritingMode::Horizontal, "lr-tb"}, + {WritingMode::Horizontal, "lr"}, + {WritingMode::Horizontal, "rl-tb"}, + {WritingMode::Horizontal, "rl"}, + {WritingMode::Vertical, "tb-rl"}, + {WritingMode::Vertical, "tb"} + }; + + return parseEnumValue(input, entries, WritingMode::Horizontal); +} + +static TextOrientation parseTextOrientation(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {TextOrientation::Mixed, "mixed"}, + {TextOrientation::Upright, "upright"} + }; + + return parseEnumValue(input, entries, TextOrientation::Mixed); +} + +static TextAnchor parseTextAnchor(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {TextAnchor::Start, "start"}, + {TextAnchor::Middle, "middle"}, + {TextAnchor::End, "end"} + }; + + return parseEnumValue(input, entries, TextAnchor::Start); +} + +static WhiteSpace parseWhiteSpace(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {WhiteSpace::Default, "default"}, + {WhiteSpace::Preserve, "preserve"}, + {WhiteSpace::Default, "normal"}, + {WhiteSpace::Default, "nowrap"}, + {WhiteSpace::Default, "pre-line"}, + {WhiteSpace::Preserve, "pre-wrap"}, + {WhiteSpace::Preserve, "pre"} + }; + + return parseEnumValue(input, entries, WhiteSpace::Default); +} + +static MaskType parseMaskType(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {MaskType::Luminance, "luminance"}, + {MaskType::Alpha, "alpha"} + }; + + return parseEnumValue(input, entries, MaskType::Luminance); +} + +static FillRule parseFillRule(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {FillRule::NonZero, "nonzero"}, + {FillRule::EvenOdd, "evenodd"} + }; + + return parseEnumValue(input, entries, FillRule::NonZero); +} + +static LineCap parseLineCap(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {LineCap::Butt, "butt"}, + {LineCap::Round, "round"}, + {LineCap::Square, "square"} + }; + + return parseEnumValue(input, entries, LineCap::Butt); +} + +static LineJoin parseLineJoin(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {LineJoin::Miter, "miter"}, + {LineJoin::Round, "round"}, + {LineJoin::Bevel, "bevel"} + }; + + return parseEnumValue(input, entries, LineJoin::Miter); +} + +SVGLayoutState::SVGLayoutState(const SVGLayoutState& parent, const SVGElement* element) + : m_parent(&parent) + , m_element(element) + , m_fill(parent.fill()) + , m_stroke(parent.stroke()) + , m_color(parent.color()) + , m_fill_opacity(parent.fill_opacity()) + , m_stroke_opacity(parent.stroke_opacity()) + , m_stroke_miterlimit(parent.stroke_miterlimit()) + , m_font_size(parent.font_size()) + , m_letter_spacing(parent.letter_spacing()) + , m_word_spacing(parent.word_spacing()) + , m_stroke_width(parent.stroke_width()) + , m_stroke_dashoffset(parent.stroke_dashoffset()) + , m_stroke_dasharray(parent.stroke_dasharray()) + , m_stroke_linecap(parent.stroke_linecap()) + , m_stroke_linejoin(parent.stroke_linejoin()) + , m_fill_rule(parent.fill_rule()) + , m_clip_rule(parent.clip_rule()) + , m_font_weight(parent.font_weight()) + , m_font_style(parent.font_style()) + , m_dominant_baseline(parent.dominant_baseline()) + , m_text_anchor(parent.text_anchor()) + , m_white_space(parent.white_space()) + , m_writing_mode(parent.writing_mode()) + , m_text_orientation(parent.text_orientation()) + , m_direction(parent.direction()) + , m_visibility(parent.visibility()) + , m_overflow(element->isRootElement() ? Overflow::Visible : Overflow::Hidden) + , m_pointer_events(parent.pointer_events()) + , m_marker_start(parent.marker_start()) + , m_marker_mid(parent.marker_mid()) + , m_marker_end(parent.marker_end()) + , m_font_family(parent.font_family()) +{ + for(const auto& attribute : element->attributes()) { + std::string_view input(attribute.value()); + stripLeadingAndTrailingSpaces(input); + if(input.empty() || input.compare("inherit") == 0) + continue; + switch(attribute.id()) { + case PropertyID::Fill: + m_fill = parsePaint(input, this, Color::Black); + break; + case PropertyID::Stroke: + m_stroke = parsePaint(input, this, Color::Transparent); + break; + case PropertyID::Color: + m_color = parseColor(input, this, Color::Black); + break; + case PropertyID::Stop_Color: + m_stop_color = parseColor(input, this, Color::Black); + break; + case PropertyID::Opacity: + m_opacity = parseNumberOrPercentage(input, true, 1.f); + break; + case PropertyID::Fill_Opacity: + m_fill_opacity = parseNumberOrPercentage(input, true, 1.f); + break; + case PropertyID::Stroke_Opacity: + m_stroke_opacity = parseNumberOrPercentage(input, true, 1.f); + break; + case PropertyID::Stop_Opacity: + m_stop_opacity = parseNumberOrPercentage(input, true, 1.f); + break; + case PropertyID::Stroke_Miterlimit: + m_stroke_miterlimit = parseNumberOrPercentage(input, false, 4.f); + break; + case PropertyID::Font_Size: + m_font_size = parseFontSize(input, this); + break; + case PropertyID::Letter_Spacing: + m_letter_spacing = parseLengthOrNormal(input); + break; + case PropertyID::Word_Spacing: + m_word_spacing = parseLengthOrNormal(input); + break; + case PropertyID::Baseline_Shift: + m_baseline_shift = parseBaselineShift(input); + break; + case PropertyID::Stroke_Width: + m_stroke_width = parseLength(input, LengthNegativeMode::Forbid, Length(1.f, LengthUnits::None)); + break; + case PropertyID::Stroke_Dashoffset: + m_stroke_dashoffset = parseLength(input, LengthNegativeMode::Allow, Length(0.f, LengthUnits::None)); + break; + case PropertyID::Stroke_Dasharray: + m_stroke_dasharray = parseDashArray(input); + break; + case PropertyID::Stroke_Linecap: + m_stroke_linecap = parseLineCap(input); + break; + case PropertyID::Stroke_Linejoin: + m_stroke_linejoin = parseLineJoin(input); + break; + case PropertyID::Fill_Rule: + m_fill_rule = parseFillRule(input); + break; + case PropertyID::Clip_Rule: + m_clip_rule = parseFillRule(input); + break; + case PropertyID::Font_Weight: + m_font_weight = parseFontWeight(input); + break; + case PropertyID::Font_Style: + m_font_style = parseFontStyle(input); + break; + case PropertyID::Alignment_Baseline: + m_alignment_baseline = parseAlignmentBaseline(input); + break; + case PropertyID::Dominant_Baseline: + m_dominant_baseline = parseDominantBaseline(input); + break; + case PropertyID::Direction: + m_direction = parseDirection(input); + break; + case PropertyID::Text_Anchor: + m_text_anchor = parseTextAnchor(input); + break; + case PropertyID::White_Space: + m_white_space = parseWhiteSpace(input); + break; + case PropertyID::Writing_Mode: + m_writing_mode = parseWritingMode(input); + break; + case PropertyID::Text_Orientation: + m_text_orientation = parseTextOrientation(input); + break; + case PropertyID::Display: + m_display = parseDisplay(input); + break; + case PropertyID::Visibility: + m_visibility = parseVisibility(input); + break; + case PropertyID::Overflow: + m_overflow = parseOverflow(input); + break; + case PropertyID::Pointer_Events: + m_pointer_events = parsePointerEvents(input); + break; + case PropertyID::Mask_Type: + m_mask_type = parseMaskType(input); + break; + case PropertyID::Mask: + m_mask = parseUrl(input); + break; + case PropertyID::Clip_Path: + m_clip_path = parseUrl(input); + break; + case PropertyID::Marker_Start: + m_marker_start = parseUrl(input); + break; + case PropertyID::Marker_Mid: + m_marker_mid = parseUrl(input); + break; + case PropertyID::Marker_End: + m_marker_end = parseUrl(input); + break; + case PropertyID::Font_Family: + m_font_family.assign(input); + break; + default: + break; + } + } +} + +Font SVGLayoutState::font() const +{ + auto bold = m_font_weight == FontWeight::Bold; + auto italic = m_font_style == FontStyle::Italic; + + FontFace face; + std::string_view input(m_font_family); + while(!input.empty() && face.isNull()) { + auto family = input.substr(0, input.find(',')); + input.remove_prefix(family.length()); + if(!input.empty() && input.front() == ',') + input.remove_prefix(1); + stripLeadingAndTrailingSpaces(family); + if(!family.empty() && (family.front() == '\'' || family.front() == '"')) { + auto quote = family.front(); + family.remove_prefix(1); + if(!family.empty() && family.back() == quote) + family.remove_suffix(1); + stripLeadingAndTrailingSpaces(family); + } + + std::string font_family(family); + if(!font_family.empty()) { + face = fontFaceCache()->getFontFace(font_family, bold, italic); + } + } + + if(face.isNull()) + face = fontFaceCache()->getFontFace(emptyString, bold, italic); + return Font(face, m_font_size); +} + +} // namespace lunasvg diff --git a/vendor/lunasvg/source/svglayoutstate.h b/vendor/lunasvg/source/svglayoutstate.h new file mode 100644 index 0000000..4d08ad9 --- /dev/null +++ b/vendor/lunasvg/source/svglayoutstate.h @@ -0,0 +1,129 @@ +#ifndef LUNASVG_SVGLAYOUTSTATE_H +#define LUNASVG_SVGLAYOUTSTATE_H + +#include "svgproperty.h" + +namespace lunasvg { + +class SVGLayoutState { +public: + SVGLayoutState() = default; + SVGLayoutState(const SVGLayoutState& parent, const SVGElement* element); + + const SVGLayoutState* parent() const { return m_parent; } + const SVGElement* element() const { return m_element; } + + const Paint& fill() const { return m_fill; } + const Paint& stroke() const { return m_stroke; } + + const Color& color() const { return m_color; } + const Color& stop_color() const { return m_stop_color; } + + float opacity() const { return m_opacity; } + float stop_opacity() const { return m_stop_opacity; } + float fill_opacity() const { return m_fill_opacity; } + float stroke_opacity() const { return m_stroke_opacity; } + float stroke_miterlimit() const { return m_stroke_miterlimit; } + float font_size() const { return m_font_size; } + + const Length& letter_spacing() const { return m_letter_spacing; } + const Length& word_spacing() const { return m_word_spacing; } + + const BaselineShift& baseline_shift() const { return m_baseline_shift; } + const Length& stroke_width() const { return m_stroke_width; } + const Length& stroke_dashoffset() const { return m_stroke_dashoffset; } + const LengthList& stroke_dasharray() const { return m_stroke_dasharray; } + + LineCap stroke_linecap() const { return m_stroke_linecap; } + LineJoin stroke_linejoin() const { return m_stroke_linejoin; } + + FillRule fill_rule() const { return m_fill_rule; } + FillRule clip_rule() const { return m_clip_rule; } + + FontWeight font_weight() const { return m_font_weight; } + FontStyle font_style() const { return m_font_style; } + + AlignmentBaseline alignment_baseline() const { return m_alignment_baseline; } + DominantBaseline dominant_baseline() const { return m_dominant_baseline; } + + TextAnchor text_anchor() const { return m_text_anchor; } + WhiteSpace white_space() const { return m_white_space; } + WritingMode writing_mode() const { return m_writing_mode; } + TextOrientation text_orientation() const { return m_text_orientation; } + Direction direction() const { return m_direction; } + + Display display() const { return m_display; } + Visibility visibility() const { return m_visibility; } + Overflow overflow() const { return m_overflow; } + PointerEvents pointer_events() const { return m_pointer_events; } + MaskType mask_type() const { return m_mask_type; } + + const std::string& mask() const { return m_mask; } + const std::string& clip_path() const { return m_clip_path; } + const std::string& marker_start() const { return m_marker_start; } + const std::string& marker_mid() const { return m_marker_mid; } + const std::string& marker_end() const { return m_marker_end; } + const std::string& font_family() const { return m_font_family; } + + Font font() const; + +private: + const SVGLayoutState* m_parent = nullptr; + const SVGElement* m_element = nullptr; + + Paint m_fill{Color::Black}; + Paint m_stroke{Color::Transparent}; + + Color m_color = Color::Black; + Color m_stop_color = Color::Black; + + float m_opacity = 1.f; + float m_fill_opacity = 1.f; + float m_stroke_opacity = 1.f; + float m_stop_opacity = 1.f; + float m_stroke_miterlimit = 4.f; + float m_font_size = 12.f; + + Length m_letter_spacing{0.f, LengthUnits::None}; + Length m_word_spacing{0.f, LengthUnits::None}; + + BaselineShift m_baseline_shift; + Length m_stroke_width{1.f, LengthUnits::None}; + Length m_stroke_dashoffset{0.f, LengthUnits::None}; + LengthList m_stroke_dasharray; + + LineCap m_stroke_linecap = LineCap::Butt; + LineJoin m_stroke_linejoin = LineJoin::Miter; + + FillRule m_fill_rule = FillRule::NonZero; + FillRule m_clip_rule = FillRule::NonZero; + + FontWeight m_font_weight = FontWeight::Normal; + FontStyle m_font_style = FontStyle::Normal; + + AlignmentBaseline m_alignment_baseline = AlignmentBaseline::Auto; + DominantBaseline m_dominant_baseline = DominantBaseline::Auto; + + TextAnchor m_text_anchor = TextAnchor::Start; + WhiteSpace m_white_space = WhiteSpace::Default; + WritingMode m_writing_mode = WritingMode::Horizontal; + TextOrientation m_text_orientation = TextOrientation::Mixed; + Direction m_direction = Direction::Ltr; + + Display m_display = Display::Inline; + Visibility m_visibility = Visibility::Visible; + Overflow m_overflow = Overflow::Visible; + PointerEvents m_pointer_events = PointerEvents::Auto; + MaskType m_mask_type = MaskType::Luminance; + + std::string m_mask; + std::string m_clip_path; + std::string m_marker_start; + std::string m_marker_mid; + std::string m_marker_end; + std::string m_font_family; +}; + +} // namespace lunasvg + +#endif // LUNASVG_SVGLAYOUTSTATE_H diff --git a/vendor/lunasvg/source/svgpaintelement.cpp b/vendor/lunasvg/source/svgpaintelement.cpp new file mode 100644 index 0000000..9432d6e --- /dev/null +++ b/vendor/lunasvg/source/svgpaintelement.cpp @@ -0,0 +1,364 @@ +#include "svgpaintelement.h" +#include "svglayoutstate.h" +#include "svgrenderstate.h" + +#include +#include + +namespace lunasvg { + +SVGPaintElement::SVGPaintElement(Document* document, ElementID id) + : SVGElement(document, id) +{ +} + +SVGStopElement::SVGStopElement(Document* document) + : SVGElement(document, ElementID::Stop) + , m_offset(PropertyID::Offset, 0.f) +{ + addProperty(m_offset); +} + +void SVGStopElement::layoutElement(const SVGLayoutState& state) +{ + m_stop_color = state.stop_color(); + m_stop_opacity = state.stop_opacity(); + SVGElement::layoutElement(state); +} + +GradientStop SVGStopElement::gradientStop(float opacity) const +{ + Color stopColor = m_stop_color.colorWithAlpha(m_stop_opacity * opacity); + GradientStop gradientStop = { + m_offset.value(), { stopColor.redF(), stopColor.greenF(), stopColor.blueF(), stopColor.alphaF() } + }; + + return gradientStop; +} + +SVGGradientElement::SVGGradientElement(Document* document, ElementID id) + : SVGPaintElement(document, id) + , SVGURIReference(this) + , m_gradientTransform(PropertyID::GradientTransform) + , m_gradientUnits(PropertyID::GradientUnits, Units::ObjectBoundingBox) + , m_spreadMethod(PropertyID::SpreadMethod, SpreadMethod::Pad) +{ + addProperty(m_gradientTransform); + addProperty(m_gradientUnits); + addProperty(m_spreadMethod); +} + +void SVGGradientElement::collectGradientAttributes(SVGGradientAttributes& attributes) const +{ + if(!attributes.hasGradientTransform() && hasAttribute(PropertyID::GradientTransform)) + attributes.setGradientTransform(this); + if(!attributes.hasSpreadMethod() && hasAttribute(PropertyID::SpreadMethod)) + attributes.setSpreadMethod(this); + if(!attributes.hasGradientUnits() && hasAttribute(PropertyID::GradientUnits)) + attributes.setGradientUnits(this); + if(!attributes.hasGradientContentElement()) { + for(const auto& child : children()) { + if(auto element = toSVGElement(child); element && element->id() == ElementID::Stop) { + attributes.setGradientContentElement(this); + break; + } + } + } +} + +SVGLinearGradientElement::SVGLinearGradientElement(Document* document) + : SVGGradientElement(document, ElementID::LinearGradient) + , m_x1(PropertyID::X1, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent) + , m_y1(PropertyID::Y1, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent) + , m_x2(PropertyID::X2, LengthDirection::Horizontal, LengthNegativeMode::Allow, 100.f, LengthUnits::Percent) + , m_y2(PropertyID::Y2, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent) +{ + addProperty(m_x1); + addProperty(m_y1); + addProperty(m_x2); + addProperty(m_y2); +} + +SVGLinearGradientAttributes SVGLinearGradientElement::collectGradientAttributes() const +{ + SVGLinearGradientAttributes attributes; + std::set processedGradients; + const SVGGradientElement* current = this; + while(true) { + current->collectGradientAttributes(attributes); + if(current->id() == ElementID::LinearGradient) { + auto element = static_cast(current); + if(!attributes.hasX1() && element->hasAttribute(PropertyID::X1)) + attributes.setX1(element); + if(!attributes.hasY1() && element->hasAttribute(PropertyID::Y1)) + attributes.setY1(element); + if(!attributes.hasX2() && element->hasAttribute(PropertyID::X2)) + attributes.setX2(element); + if(!attributes.hasY2() && element->hasAttribute(PropertyID::Y2)) { + attributes.setY2(element); + } + } + + auto targetElement = current->getTargetElement(document()); + if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient)) + break; + processedGradients.insert(current); + current = static_cast(targetElement); + if(processedGradients.count(current) > 0) { + break; + } + } + + attributes.setDefaultValues(this); + return attributes; +} + +static GradientStops buildGradientStops(const SVGGradientElement* element, float opacity) +{ + GradientStops gradientStops; + + const auto& children = element->children(); + gradientStops.reserve(children.size()); + for(const auto& child : children) { + auto childElement = toSVGElement(child); + if(childElement && childElement->id() == ElementID::Stop) { + auto stopElement = static_cast(childElement); + gradientStops.push_back(stopElement->gradientStop(opacity)); + } + } + + return gradientStops; +} + +bool SVGLinearGradientElement::applyPaint(SVGRenderState& state, float opacity) const +{ + auto attributes = collectGradientAttributes(); + auto gradientContentElement = attributes.gradientContentElement(); + auto gradientStops = buildGradientStops(gradientContentElement, opacity); + if(gradientStops.empty()) + return false; + LengthContext lengthContext(this, attributes.gradientUnits()); + auto x1 = lengthContext.valueForLength(attributes.x1()); + auto y1 = lengthContext.valueForLength(attributes.y1()); + auto x2 = lengthContext.valueForLength(attributes.x2()); + auto y2 = lengthContext.valueForLength(attributes.y2()); + if(gradientStops.size() == 1 || (x1 == x2 && y1 == y2)) { + const auto& lastStop = gradientStops.back(); + state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a); + return true; + } + + auto spreadMethod = attributes.spreadMethod(); + auto gradientUnits = attributes.gradientUnits(); + auto gradientTransform = attributes.gradientTransform(); + if(gradientUnits == Units::ObjectBoundingBox) { + auto bbox = state.fillBoundingBox(); + gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y)); + } + + state->setLinearGradient(x1, y1, x2, y2, spreadMethod, gradientStops, gradientTransform); + return true; +} + +SVGRadialGradientElement::SVGRadialGradientElement(Document* document) + : SVGGradientElement(document, ElementID::RadialGradient) + , m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow, 50.f, LengthUnits::Percent) + , m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow, 50.f, LengthUnits::Percent) + , m_r(PropertyID::R, LengthDirection::Diagonal, LengthNegativeMode::Forbid, 50.f, LengthUnits::Percent) + , m_fx(PropertyID::Fx, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None) + , m_fy(PropertyID::Fy, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None) +{ + addProperty(m_cx); + addProperty(m_cy); + addProperty(m_r); + addProperty(m_fx); + addProperty(m_fy); +} + +bool SVGRadialGradientElement::applyPaint(SVGRenderState& state, float opacity) const +{ + auto attributes = collectGradientAttributes(); + auto gradientContentElement = attributes.gradientContentElement(); + auto gradientStops = buildGradientStops(gradientContentElement, opacity); + if(gradientStops.empty()) + return false; + LengthContext lengthContext(this, attributes.gradientUnits()); + auto r = lengthContext.valueForLength(attributes.r()); + if(r == 0.f || gradientStops.size() == 1) { + const auto& lastStop = gradientStops.back(); + state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a); + return true; + } + + auto fx = lengthContext.valueForLength(attributes.fx()); + auto fy = lengthContext.valueForLength(attributes.fy()); + auto cx = lengthContext.valueForLength(attributes.cx()); + auto cy = lengthContext.valueForLength(attributes.cy()); + + auto spreadMethod = attributes.spreadMethod(); + auto gradientUnits = attributes.gradientUnits(); + auto gradientTransform = attributes.gradientTransform(); + if(gradientUnits == Units::ObjectBoundingBox) { + auto bbox = state.fillBoundingBox(); + gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y)); + } + + state->setRadialGradient(cx, cy, r, fx, fy, spreadMethod, gradientStops, gradientTransform); + return true; +} + +SVGRadialGradientAttributes SVGRadialGradientElement::collectGradientAttributes() const +{ + SVGRadialGradientAttributes attributes; + std::set processedGradients; + const SVGGradientElement* current = this; + while(true) { + current->collectGradientAttributes(attributes); + if(current->id() == ElementID::RadialGradient) { + auto element = static_cast(current); + if(!attributes.hasCx() && element->hasAttribute(PropertyID::Cx)) + attributes.setCx(element); + if(!attributes.hasCy() && element->hasAttribute(PropertyID::Cy)) + attributes.setCy(element); + if(!attributes.hasR() && element->hasAttribute(PropertyID::R)) + attributes.setR(element); + if(!attributes.hasFx() && element->hasAttribute(PropertyID::Fx)) + attributes.setFx(element); + if(!attributes.hasFy() && element->hasAttribute(PropertyID::Fy)) { + attributes.setFy(element); + } + } + + auto targetElement = current->getTargetElement(document()); + if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient)) + break; + processedGradients.insert(current); + current = static_cast(targetElement); + if(processedGradients.count(current) > 0) { + break; + } + } + + attributes.setDefaultValues(this); + return attributes; +} + +SVGPatternElement::SVGPatternElement(Document* document) + : SVGPaintElement(document, ElementID::Pattern) + , SVGURIReference(this) + , SVGFitToViewBox(this) + , m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow) + , m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow) + , m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid) + , m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid) + , m_patternTransform(PropertyID::PatternTransform) + , m_patternUnits(PropertyID::PatternUnits, Units::ObjectBoundingBox) + , m_patternContentUnits(PropertyID::PatternContentUnits, Units::UserSpaceOnUse) +{ + addProperty(m_x); + addProperty(m_y); + addProperty(m_width); + addProperty(m_height); + addProperty(m_patternTransform); + addProperty(m_patternUnits); + addProperty(m_patternContentUnits); +} + +bool SVGPatternElement::applyPaint(SVGRenderState& state, float opacity) const +{ + if(state.hasCycleReference(this)) + return false; + auto attributes = collectPatternAttributes(); + auto patternContentElement = attributes.patternContentElement(); + if(patternContentElement == nullptr) + return false; + LengthContext lengthContext(this, attributes.patternUnits()); + Rect patternRect = { + lengthContext.valueForLength(attributes.x()), + lengthContext.valueForLength(attributes.y()), + lengthContext.valueForLength(attributes.width()), + lengthContext.valueForLength(attributes.height()) + }; + + if(attributes.patternUnits() == Units::ObjectBoundingBox) { + auto bbox = state.fillBoundingBox(); + patternRect.x = patternRect.x * bbox.w + bbox.x; + patternRect.y = patternRect.y * bbox.h + bbox.y; + patternRect.w = patternRect.w * bbox.w; + patternRect.h = patternRect.h * bbox.h; + } + + auto currentTransform = attributes.patternTransform() * state.currentTransform(); + auto xScale = currentTransform.xScale(); + auto yScale = currentTransform.yScale(); + + auto patternImage = Canvas::create(0, 0, patternRect.w * xScale, patternRect.h * yScale); + auto patternImageTransform = Transform::scaled(xScale, yScale); + + const auto& viewBoxRect = attributes.viewBox(); + if(viewBoxRect.isValid()) { + const auto& preserveAspectRatio = attributes.preserveAspectRatio(); + patternImageTransform.multiply(preserveAspectRatio.getTransform(viewBoxRect, patternRect.size())); + } else if(attributes.patternContentUnits() == Units::ObjectBoundingBox) { + auto bbox = state.fillBoundingBox(); + patternImageTransform.scale(bbox.w, bbox.h); + } + + SVGRenderState newState(this, &state, patternImageTransform, SVGRenderMode::Painting, patternImage); + patternContentElement->renderChildren(newState); + + auto patternTransform = attributes.patternTransform(); + patternTransform.translate(patternRect.x, patternRect.y); + patternTransform.scale(patternRect.w / patternImage->width(), patternRect.h / patternImage->height()); + state->setTexture(*patternImage, TextureType::Tiled, opacity, patternTransform); + return true; +} + +SVGPatternAttributes SVGPatternElement::collectPatternAttributes() const +{ + SVGPatternAttributes attributes; + std::set processedPatterns; + const SVGPatternElement* current = this; + while(true) { + if(!attributes.hasX() && current->hasAttribute(PropertyID::X)) + attributes.setX(current); + if(!attributes.hasY() && current->hasAttribute(PropertyID::Y)) + attributes.setY(current); + if(!attributes.hasWidth() && current->hasAttribute(PropertyID::Width)) + attributes.setWidth(current); + if(!attributes.hasHeight() && current->hasAttribute(PropertyID::Height)) + attributes.setHeight(current); + if(!attributes.hasPatternTransform() && current->hasAttribute(PropertyID::PatternTransform)) + attributes.setPatternTransform(current); + if(!attributes.hasPatternUnits() && current->hasAttribute(PropertyID::PatternUnits)) + attributes.setPatternUnits(current); + if(!attributes.hasPatternContentUnits() && current->hasAttribute(PropertyID::PatternContentUnits)) + attributes.setPatternContentUnits(current); + if(!attributes.hasViewBox() && current->hasAttribute(PropertyID::ViewBox)) + attributes.setViewBox(current); + if(!attributes.hasPreserveAspectRatio() && current->hasAttribute(PropertyID::PreserveAspectRatio)) + attributes.setPreserveAspectRatio(current); + if(!attributes.hasPatternContentElement()) { + for(const auto& child : current->children()) { + if(child->isElement()) { + attributes.setPatternContentElement(current); + break; + } + } + } + + auto targetElement = current->getTargetElement(document()); + if(!targetElement || targetElement->id() != ElementID::Pattern) + break; + processedPatterns.insert(current); + current = static_cast(targetElement); + if(processedPatterns.count(current) > 0) { + break; + } + } + + attributes.setDefaultValues(this); + return attributes; +} + +} // namespace lunasvg diff --git a/vendor/lunasvg/source/svgpaintelement.h b/vendor/lunasvg/source/svgpaintelement.h new file mode 100644 index 0000000..19f605a --- /dev/null +++ b/vendor/lunasvg/source/svgpaintelement.h @@ -0,0 +1,288 @@ +#ifndef LUNASVG_SVGPAINTELEMENT_H +#define LUNASVG_SVGPAINTELEMENT_H + +#include "svgelement.h" + +namespace lunasvg { + +class SVGPaintElement : public SVGElement { +public: + SVGPaintElement(Document* document, ElementID id); + + bool isPaintElement() const final { return true; } + + virtual bool applyPaint(SVGRenderState& state, float opacity) const = 0; +}; + +class SVGStopElement final : public SVGElement { +public: + SVGStopElement(Document* document); + + void layoutElement(const SVGLayoutState& state) final; + const SVGNumberPercentage& offset() const { return m_offset; } + GradientStop gradientStop(float opacity) const; + +private: + SVGNumberPercentage m_offset; + Color m_stop_color = Color::Black; + float m_stop_opacity = 1.f; +}; + +class SVGGradientAttributes; + +class SVGGradientElement : public SVGPaintElement, public SVGURIReference { +public: + SVGGradientElement(Document* document, ElementID id); + + const SVGTransform& gradientTransform() const { return m_gradientTransform; } + const SVGEnumeration& gradientUnits() const { return m_gradientUnits; } + const SVGEnumeration& spreadMethod() const { return m_spreadMethod; } + void collectGradientAttributes(SVGGradientAttributes& attributes) const; + +private: + SVGTransform m_gradientTransform; + SVGEnumeration m_gradientUnits; + SVGEnumeration m_spreadMethod; +}; + +class SVGGradientAttributes { +public: + SVGGradientAttributes() = default; + + const Transform& gradientTransform() const { return m_gradientTransform->gradientTransform().value(); } + SpreadMethod spreadMethod() const { return m_spreadMethod->spreadMethod().value(); } + Units gradientUnits() const { return m_gradientUnits->gradientUnits().value(); } + const SVGGradientElement* gradientContentElement() const { return m_gradientContentElement; } + + bool hasGradientTransform() const { return m_gradientTransform; } + bool hasSpreadMethod() const { return m_spreadMethod; } + bool hasGradientUnits() const { return m_gradientUnits; } + bool hasGradientContentElement() const { return m_gradientContentElement; } + + void setGradientTransform(const SVGGradientElement* value) { m_gradientTransform = value; } + void setSpreadMethod(const SVGGradientElement* value) { m_spreadMethod = value; } + void setGradientUnits(const SVGGradientElement* value) { m_gradientUnits = value; } + void setGradientContentElement(const SVGGradientElement* value) { m_gradientContentElement = value; } + + void setDefaultValues(const SVGGradientElement* element) { + if(!m_gradientTransform) { m_gradientTransform = element; } + if(!m_spreadMethod) { m_spreadMethod = element; } + if(!m_gradientUnits) { m_gradientUnits = element; } + if(!m_gradientContentElement) { m_gradientContentElement = element; } + } + +private: + const SVGGradientElement* m_gradientTransform{nullptr}; + const SVGGradientElement* m_spreadMethod{nullptr}; + const SVGGradientElement* m_gradientUnits{nullptr}; + const SVGGradientElement* m_gradientContentElement{nullptr}; +}; + +class SVGLinearGradientAttributes; + +class SVGLinearGradientElement final : public SVGGradientElement { +public: + SVGLinearGradientElement(Document* document); + + const SVGLength& x1() const { return m_x1; } + const SVGLength& y1() const { return m_y1; } + const SVGLength& x2() const { return m_x2; } + const SVGLength& y2() const { return m_y2; } + + bool applyPaint(SVGRenderState& state, float opacity) const final; + +private: + SVGLinearGradientAttributes collectGradientAttributes() const; + SVGLength m_x1; + SVGLength m_y1; + SVGLength m_x2; + SVGLength m_y2; +}; + +class SVGLinearGradientAttributes : public SVGGradientAttributes { +public: + SVGLinearGradientAttributes() = default; + + const SVGLength& x1() const { return m_x1->x1(); } + const SVGLength& y1() const { return m_y1->y1(); } + const SVGLength& x2() const { return m_x2->x2(); } + const SVGLength& y2() const { return m_y2->y2(); } + + bool hasX1() const { return m_x1; } + bool hasY1() const { return m_y1; } + bool hasX2() const { return m_x2; } + bool hasY2() const { return m_y2; } + + void setX1(const SVGLinearGradientElement* value) { m_x1 = value; } + void setY1(const SVGLinearGradientElement* value) { m_y1 = value; } + void setX2(const SVGLinearGradientElement* value) { m_x2 = value; } + void setY2(const SVGLinearGradientElement* value) { m_y2 = value; } + + void setDefaultValues(const SVGLinearGradientElement* element) { + SVGGradientAttributes::setDefaultValues(element); + if(!m_x1) { m_x1 = element; } + if(!m_y1) { m_y1 = element; } + if(!m_x2) { m_x2 = element; } + if(!m_y2) { m_y2 = element; } + } + +private: + const SVGLinearGradientElement* m_x1{nullptr}; + const SVGLinearGradientElement* m_y1{nullptr}; + const SVGLinearGradientElement* m_x2{nullptr}; + const SVGLinearGradientElement* m_y2{nullptr}; +}; + +class SVGRadialGradientAttributes; + +class SVGRadialGradientElement final : public SVGGradientElement { +public: + SVGRadialGradientElement(Document* document); + + const SVGLength& cx() const { return m_cx; } + const SVGLength& cy() const { return m_cy; } + const SVGLength& r() const { return m_r; } + const SVGLength& fx() const { return m_fx; } + const SVGLength& fy() const { return m_fy; } + + bool applyPaint(SVGRenderState& state, float opacity) const final; + +private: + SVGRadialGradientAttributes collectGradientAttributes() const; + SVGLength m_cx; + SVGLength m_cy; + SVGLength m_r; + SVGLength m_fx; + SVGLength m_fy; +}; + +class SVGRadialGradientAttributes : public SVGGradientAttributes { +public: + SVGRadialGradientAttributes() = default; + + const SVGLength& cx() const { return m_cx->cx(); } + const SVGLength& cy() const { return m_cy->cy(); } + const SVGLength& r() const { return m_r->r(); } + const SVGLength& fx() const { return m_fx ? m_fx->fx() : m_cx->cx(); } + const SVGLength& fy() const { return m_fy ? m_fy->fy() : m_cy->cy(); } + + bool hasCx() const { return m_cx; } + bool hasCy() const { return m_cy; } + bool hasR() const { return m_r; } + bool hasFx() const { return m_fx; } + bool hasFy() const { return m_fy; } + + void setCx(const SVGRadialGradientElement* value) { m_cx = value; } + void setCy(const SVGRadialGradientElement* value) { m_cy = value; } + void setR(const SVGRadialGradientElement* value) { m_r = value; } + void setFx(const SVGRadialGradientElement* value) { m_fx = value; } + void setFy(const SVGRadialGradientElement* value) { m_fy = value; } + + void setDefaultValues(const SVGRadialGradientElement* element) { + SVGGradientAttributes::setDefaultValues(element); + if(!m_cx) { m_cx = element; } + if(!m_cy) { m_cy = element; } + if(!m_r) { m_r = element; } + } + +private: + const SVGRadialGradientElement* m_cx{nullptr}; + const SVGRadialGradientElement* m_cy{nullptr}; + const SVGRadialGradientElement* m_r{nullptr}; + const SVGRadialGradientElement* m_fx{nullptr}; + const SVGRadialGradientElement* m_fy{nullptr}; +}; + +class SVGPatternAttributes; + +class SVGPatternElement final : public SVGPaintElement, public SVGURIReference, public SVGFitToViewBox { +public: + SVGPatternElement(Document* document); + + const SVGLength& x() const { return m_x; } + const SVGLength& y() const { return m_y; } + const SVGLength& width() const { return m_width; } + const SVGLength& height() const { return m_height; } + const SVGTransform& patternTransform() const { return m_patternTransform; } + const SVGEnumeration& patternUnits() const { return m_patternUnits; } + const SVGEnumeration& patternContentUnits() const { return m_patternContentUnits; } + + bool applyPaint(SVGRenderState& state, float opacity) const final; + +private: + SVGPatternAttributes collectPatternAttributes() const; + SVGLength m_x; + SVGLength m_y; + SVGLength m_width; + SVGLength m_height; + SVGTransform m_patternTransform; + SVGEnumeration m_patternUnits; + SVGEnumeration m_patternContentUnits; +}; + +class SVGPatternAttributes { +public: + SVGPatternAttributes() = default; + + const SVGLength& x() const { return m_x->x(); } + const SVGLength& y() const { return m_y->y(); } + const SVGLength& width() const { return m_width->width(); } + const SVGLength& height() const { return m_height->height(); } + const Transform& patternTransform() const { return m_patternTransform->patternTransform().value(); } + Units patternUnits() const { return m_patternUnits->patternUnits().value(); } + Units patternContentUnits() const { return m_patternContentUnits->patternContentUnits().value(); } + const Rect& viewBox() const { return m_viewBox->viewBox().value(); } + const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio->preserveAspectRatio(); } + const SVGPatternElement* patternContentElement() const { return m_patternContentElement; } + + bool hasX() const { return m_x; } + bool hasY() const { return m_y; } + bool hasWidth() const { return m_width; } + bool hasHeight() const { return m_height; } + bool hasPatternTransform() const { return m_patternTransform; } + bool hasPatternUnits() const { return m_patternUnits; } + bool hasPatternContentUnits() const { return m_patternContentUnits; } + bool hasViewBox() const { return m_viewBox; } + bool hasPreserveAspectRatio() const { return m_preserveAspectRatio; } + bool hasPatternContentElement() const { return m_patternContentElement; } + + void setX(const SVGPatternElement* value) { m_x = value; } + void setY(const SVGPatternElement* value) { m_y = value; } + void setWidth(const SVGPatternElement* value) { m_width = value; } + void setHeight(const SVGPatternElement* value) { m_height = value; } + void setPatternTransform(const SVGPatternElement* value) { m_patternTransform = value; } + void setPatternUnits(const SVGPatternElement* value) { m_patternUnits = value; } + void setPatternContentUnits(const SVGPatternElement* value) { m_patternContentUnits = value; } + void setViewBox(const SVGPatternElement* value) { m_viewBox = value; } + void setPreserveAspectRatio(const SVGPatternElement* value) { m_preserveAspectRatio = value; } + void setPatternContentElement(const SVGPatternElement* value) { m_patternContentElement = value; } + + void setDefaultValues(const SVGPatternElement* element) { + if(!m_x) { m_x = element; } + if(!m_y) { m_y = element; } + if(!m_width) { m_width = element; } + if(!m_height) { m_height = element; } + if(!m_patternTransform) { m_patternTransform = element; } + if(!m_patternUnits) { m_patternUnits = element; } + if(!m_patternContentUnits) { m_patternContentUnits = element; } + if(!m_viewBox) { m_viewBox = element; } + if(!m_preserveAspectRatio) { m_preserveAspectRatio = element; } + if(!m_patternContentElement) { m_patternContentElement = element; } + } + +private: + const SVGPatternElement* m_x{nullptr}; + const SVGPatternElement* m_y{nullptr}; + const SVGPatternElement* m_width{nullptr}; + const SVGPatternElement* m_height{nullptr}; + const SVGPatternElement* m_patternTransform{nullptr}; + const SVGPatternElement* m_patternUnits{nullptr}; + const SVGPatternElement* m_patternContentUnits{nullptr}; + const SVGPatternElement* m_viewBox{nullptr}; + const SVGPatternElement* m_preserveAspectRatio{nullptr}; + const SVGPatternElement* m_patternContentElement{nullptr}; +}; + +} // namespace lunasvg + +#endif // LUNASVG_SVGPAINTELEMENT_H diff --git a/vendor/lunasvg/source/svgparser.cpp b/vendor/lunasvg/source/svgparser.cpp new file mode 100644 index 0000000..183937c --- /dev/null +++ b/vendor/lunasvg/source/svgparser.cpp @@ -0,0 +1,956 @@ +#include "lunasvg.h" +#include "svgelement.h" +#include "svgparserutils.h" + +#include + +namespace lunasvg { + +struct SimpleSelector; + +using Selector = std::vector; +using SelectorList = std::vector; + +struct AttributeSelector { + enum class MatchType { + None, + Equals, + Contains, + Includes, + StartsWith, + EndsWith, + DashEquals + }; + + MatchType matchType{MatchType::None}; + PropertyID id{PropertyID::Unknown}; + std::string value; +}; + +struct PseudoClassSelector { + enum class Type { + Unknown, + Empty, + Root, + Is, + Not, + FirstChild, + LastChild, + OnlyChild, + FirstOfType, + LastOfType, + OnlyOfType + }; + + Type type{Type::Unknown}; + SelectorList subSelectors; +}; + +struct SimpleSelector { + enum class Combinator { + None, + Descendant, + Child, + DirectAdjacent, + InDirectAdjacent + }; + + explicit SimpleSelector(Combinator combinator) : combinator(combinator) {} + + Combinator combinator{Combinator::Descendant}; + ElementID id{ElementID::Star}; + std::vector attributeSelectors; + std::vector pseudoClassSelectors; +}; + +struct Declaration { + int specificity; + PropertyID id; + std::string value; +}; + +using DeclarationList = std::vector; + +struct Rule { + SelectorList selectors; + DeclarationList declarations; +}; + +class RuleData { +public: + RuleData(const Selector& selector, const DeclarationList& declarations, size_t specificity, size_t position) + : m_selector(selector), m_declarations(declarations), m_specificity(specificity), m_position(position) + {} + + bool isLessThan(const RuleData& rule) const { return std::tie(m_specificity, m_position) < std::tie(rule.m_specificity, rule.m_position); } + + const Selector& selector() const { return m_selector; } + const DeclarationList& declarations() const { return m_declarations; } + size_t specificity() const { return m_specificity; } + size_t position() const { return m_position; } + + bool match(const SVGElement* element) const; + +private: + Selector m_selector; + DeclarationList m_declarations; + size_t m_specificity; + size_t m_position; +}; + +inline bool operator<(const RuleData& a, const RuleData& b) { return a.isLessThan(b); } + +using RuleDataList = std::vector; + +constexpr bool equals(std::string_view value, std::string_view subvalue) +{ + return value.compare(subvalue) == 0; +} + +constexpr bool contains(std::string_view value, std::string_view subvalue) +{ + return value.find(subvalue) != std::string_view::npos; +} + +constexpr bool includes(std::string_view value, std::string_view subvalue) +{ + if(subvalue.empty() || subvalue.length() > value.length()) + return false; + std::string_view input(value); + while(!input.empty()) { + skipOptionalSpaces(input); + std::string_view start(input); + while(!input.empty() && !IS_WS(input.front())) + input.remove_prefix(1); + if(subvalue == start.substr(0, start.length() - input.length())) { + return true; + } + } + + return false; +} + +constexpr bool startswith(std::string_view value, std::string_view subvalue) +{ + if(subvalue.empty() || subvalue.length() > value.length()) + return false; + return subvalue == value.substr(0, subvalue.size()); +} + +constexpr bool endswith(std::string_view value, std::string_view subvalue) +{ + if(subvalue.empty() || subvalue.length() > value.length()) + return false; + return subvalue == value.substr(value.size() - subvalue.size(), subvalue.size()); +} + +constexpr bool dashequals(std::string_view value, std::string_view subvalue) +{ + if(startswith(value, subvalue)) + return (value.length() == subvalue.length() || value.at(subvalue.length()) == '-'); + return false; +} + +static bool matchAttributeSelector(const AttributeSelector& selector, const SVGElement* element) +{ + const auto& value = element->getAttribute(selector.id); + if(selector.matchType == AttributeSelector::MatchType::None) + return !value.empty(); + if(selector.matchType == AttributeSelector::MatchType::Equals) + return equals(value, selector.value); + if(selector.matchType == AttributeSelector::MatchType::Contains) + return contains(value, selector.value); + if(selector.matchType == AttributeSelector::MatchType::Includes) + return includes(value, selector.value); + if(selector.matchType == AttributeSelector::MatchType::StartsWith) + return startswith(value, selector.value); + if(selector.matchType == AttributeSelector::MatchType::EndsWith) + return endswith(value, selector.value); + if(selector.matchType == AttributeSelector::MatchType::DashEquals) + return dashequals(value, selector.value); + return false; +} + +static bool matchSimpleSelector(const SimpleSelector& selector, const SVGElement* element); + +static bool matchPseudoClassSelector(const PseudoClassSelector& selector, const SVGElement* element) +{ + if(selector.type == PseudoClassSelector::Type::Empty) + return element->children().empty(); + if(selector.type == PseudoClassSelector::Type::Root) + return element->isRootElement(); + if(selector.type == PseudoClassSelector::Type::Is) { + for(const auto& subSelector : selector.subSelectors) { + for(const auto& simpleSelector : subSelector) { + if(!matchSimpleSelector(simpleSelector, element)) { + return false; + } + } + } + + return true; + } + + if(selector.type == PseudoClassSelector::Type::Not) { + for(const auto& subSelector : selector.subSelectors) { + for(const auto& simpleSelector : subSelector) { + if(matchSimpleSelector(simpleSelector, element)) { + return false; + } + } + } + + return true; + } + + if(selector.type == PseudoClassSelector::Type::FirstChild) + return !element->previousElement(); + if(selector.type == PseudoClassSelector::Type::LastChild) + return !element->nextElement(); + if(selector.type == PseudoClassSelector::Type::OnlyChild) + return !(element->previousElement() || element->nextElement()); + if(selector.type == PseudoClassSelector::Type::FirstOfType) { + auto sibling = element->previousElement(); + while(sibling) { + if(sibling->id() == element->id()) + return false; + sibling = sibling->previousElement(); + } + + return true; + } + + if(selector.type == PseudoClassSelector::Type::LastOfType) { + auto sibling = element->nextElement(); + while(sibling) { + if(sibling->id() == element->id()) + return false; + sibling = sibling->nextElement(); + } + + return true; + } + + return false; +} + +static bool matchSimpleSelector(const SimpleSelector& selector, const SVGElement* element) +{ + if(selector.id != ElementID::Star && selector.id != element->id()) + return false; + for(const auto& sel : selector.attributeSelectors) { + if(!matchAttributeSelector(sel, element)) { + return false; + } + } + + for(const auto& sel : selector.pseudoClassSelectors) { + if(!matchPseudoClassSelector(sel, element)) { + return false; + } + } + + return true; +} + +static bool matchSelector(const Selector& selector, const SVGElement* element) +{ + if(selector.empty()) + return false; + auto it = selector.rbegin(); + auto end = selector.rend(); + if(!matchSimpleSelector(*it, element)) { + return false; + } + + auto combinator = it->combinator; + ++it; + + while(it != end) { + switch(combinator) { + case SimpleSelector::Combinator::Child: + case SimpleSelector::Combinator::Descendant: + element = element->parentElement(); + break; + case SimpleSelector::Combinator::DirectAdjacent: + case SimpleSelector::Combinator::InDirectAdjacent: + element = element->previousElement(); + break; + case SimpleSelector::Combinator::None: + assert(false); + } + + if(element == nullptr) + return false; + if(matchSimpleSelector(*it, element)) { + combinator = it->combinator; + ++it; + } else if(combinator != SimpleSelector::Combinator::Descendant + && combinator != SimpleSelector::Combinator::InDirectAdjacent) { + return false; + } + } + + return true; +} + +bool RuleData::match(const SVGElement* element) const +{ + return matchSelector(m_selector, element); +} + +constexpr bool IS_CSS_STARTNAMECHAR(int c) { return IS_ALPHA(c) || c == '_' || c == '-'; } +constexpr bool IS_CSS_NAMECHAR(int c) { return IS_CSS_STARTNAMECHAR(c) || IS_NUM(c); } + +inline bool readCSSIdentifier(std::string_view& input, std::string& output) +{ + if(input.empty() || !IS_CSS_STARTNAMECHAR(input.front())) + return false; + output.clear(); + do { + output.push_back(input.front()); + input.remove_prefix(1); + } while(!input.empty() && IS_CSS_NAMECHAR(input.front())); + return true; +} + +static bool parseTagSelector(std::string_view& input, SimpleSelector& simpleSelector) +{ + std::string name; + if(skipDelimiter(input, '*')) + simpleSelector.id = ElementID::Star; + else if(readCSSIdentifier(input, name)) + simpleSelector.id = elementid(name); + else + return false; + return true; +} + +static bool parseIdSelector(std::string_view& input, SimpleSelector& simpleSelector) +{ + AttributeSelector a; + a.id = PropertyID::Id; + a.matchType = AttributeSelector::MatchType::Equals; + if(!readCSSIdentifier(input, a.value)) + return false; + simpleSelector.attributeSelectors.push_back(std::move(a)); + return true; +} + +static bool parseClassSelector(std::string_view& input, SimpleSelector& simpleSelector) +{ + AttributeSelector a; + a.id = PropertyID::Class; + a.matchType = AttributeSelector::MatchType::Includes; + if(!readCSSIdentifier(input, a.value)) + return false; + simpleSelector.attributeSelectors.push_back(std::move(a)); + return true; +} + +static bool parseAttributeSelector(std::string_view& input, SimpleSelector& simpleSelector) +{ + std::string name; + skipOptionalSpaces(input); + if(!readCSSIdentifier(input, name)) + return false; + AttributeSelector a; + a.id = propertyid(name); + a.matchType = AttributeSelector::MatchType::None; + if(skipDelimiter(input, '=')) + a.matchType = AttributeSelector::MatchType::Equals; + else if(skipString(input, "*=")) + a.matchType = AttributeSelector::MatchType::Contains; + else if(skipString(input, "~=")) + a.matchType = AttributeSelector::MatchType::Includes; + else if(skipString(input, "^=")) + a.matchType = AttributeSelector::MatchType::StartsWith; + else if(skipString(input, "$=")) + a.matchType = AttributeSelector::MatchType::EndsWith; + else if(skipString(input, "|=")) + a.matchType = AttributeSelector::MatchType::DashEquals; + if(a.matchType != AttributeSelector::MatchType::None) { + skipOptionalSpaces(input); + if(!readCSSIdentifier(input, a.value)) { + if(input.empty() || !(input.front() == '\"' || input.front() == '\'')) + return false; + auto quote = input.front(); + input.remove_prefix(1); + auto n = input.find(quote); + if(n == std::string_view::npos) + return false; + a.value.assign(input.substr(0, n)); + input.remove_prefix(n + 1); + } + } + + skipOptionalSpaces(input); + if(!skipDelimiter(input, ']')) + return false; + simpleSelector.attributeSelectors.push_back(std::move(a)); + return true; +} + +static bool parseSelectors(std::string_view& input, SelectorList& selectors); + +static bool parsePseudoClassSelector(std::string_view& input, SimpleSelector& simpleSelector) +{ + std::string name; + if(!readCSSIdentifier(input, name)) + return false; + PseudoClassSelector selector; + if(name.compare("empty") == 0) + selector.type = PseudoClassSelector::Type::Empty; + else if(name.compare("root") == 0) + selector.type = PseudoClassSelector::Type::Root; + else if(name.compare("not") == 0) + selector.type = PseudoClassSelector::Type::Not; + else if(name.compare("first-child") == 0) + selector.type = PseudoClassSelector::Type::FirstChild; + else if(name.compare("last-child") == 0) + selector.type = PseudoClassSelector::Type::LastChild; + else if(name.compare("only-child") == 0) + selector.type = PseudoClassSelector::Type::OnlyChild; + else if(name.compare("first-of-type") == 0) + selector.type = PseudoClassSelector::Type::FirstOfType; + else if(name.compare("last-of-type") == 0) + selector.type = PseudoClassSelector::Type::LastOfType; + else if(name.compare("only-of-type") == 0) + selector.type = PseudoClassSelector::Type::OnlyOfType; + if(selector.type == PseudoClassSelector::Type::Is || selector.type == PseudoClassSelector::Type::Not) { + skipOptionalSpaces(input); + if(!skipDelimiter(input, '(')) + return false; + skipOptionalSpaces(input); + if(!parseSelectors(input, selector.subSelectors)) + return false; + skipOptionalSpaces(input); + if(!skipDelimiter(input, ')')) { + return false; + } + } + + simpleSelector.pseudoClassSelectors.push_back(std::move(selector)); + return true; +} + +static bool parseSimpleSelector(std::string_view& input, SimpleSelector& simpleSelector, bool& failed) +{ + auto consumed = parseTagSelector(input, simpleSelector); + do { + if(skipDelimiter(input, '#')) + failed = !parseIdSelector(input, simpleSelector); + else if(skipDelimiter(input, '.')) + failed = !parseClassSelector(input, simpleSelector); + else if(skipDelimiter(input, '[')) + failed = !parseAttributeSelector(input, simpleSelector); + else if(skipDelimiter(input, ':')) + failed = !parsePseudoClassSelector(input, simpleSelector); + else + break; + consumed = true; + } while(!failed); + return consumed && !failed; +} + +static bool parseCombinator(std::string_view& input, SimpleSelector::Combinator& combinator) +{ + combinator = SimpleSelector::Combinator::None; + while(!input.empty() && IS_WS(input.front())) { + combinator = SimpleSelector::Combinator::Descendant; + input.remove_prefix(1); + } + + if(skipDelimiterAndOptionalSpaces(input, '>')) + combinator = SimpleSelector::Combinator::Child; + else if(skipDelimiterAndOptionalSpaces(input, '+')) + combinator = SimpleSelector::Combinator::DirectAdjacent; + else if(skipDelimiterAndOptionalSpaces(input, '~')) + combinator = SimpleSelector::Combinator::InDirectAdjacent; + return combinator != SimpleSelector::Combinator::None; +} + +static bool parseSelector(std::string_view& input, Selector& selector) +{ + auto combinator = SimpleSelector::Combinator::None; + do { + bool failed = false; + SimpleSelector simpleSelector(combinator); + if(!parseSimpleSelector(input, simpleSelector, failed)) + return !failed && (combinator == SimpleSelector::Combinator::Descendant); + selector.push_back(std::move(simpleSelector)); + } while(parseCombinator(input, combinator)); + return true; +} + +static bool parseSelectors(std::string_view& input, SelectorList& selectors) +{ + do { + Selector selector; + if(!parseSelector(input, selector)) + return false; + selectors.push_back(std::move(selector)); + } while(skipDelimiterAndOptionalSpaces(input, ',')); + return true; +} + +static bool parseDeclarations(std::string_view& input, DeclarationList& declarations) +{ + if(!skipDelimiter(input, '{')) + return false; + skipOptionalSpaces(input); + do { + std::string name; + if(!readCSSIdentifier(input, name)) + return false; + skipOptionalSpaces(input); + if(!skipDelimiter(input, ':')) + return false; + skipOptionalSpaces(input); + std::string_view value(input); + while(!input.empty() && !(input.front() == '!' || input.front() == ';' || input.front() == '}')) + input.remove_prefix(1); + value.remove_suffix(input.length()); + stripTrailingSpaces(value); + + Declaration declaration; + declaration.specificity = 0x10; + declaration.id = csspropertyid(name); + declaration.value.assign(value); + if(skipDelimiter(input, '!')) { + skipOptionalSpaces(input); + if(!skipString(input, "important")) + return false; + declaration.specificity = 0x1000; + } + + if(declaration.id != PropertyID::Unknown) + declarations.push_back(std::move(declaration)); + skipOptionalSpacesOrDelimiter(input, ';'); + } while(!input.empty() && input.front() != '}'); + return skipDelimiter(input, '}'); +} + +static bool parseRule(std::string_view& input, Rule& rule) +{ + if(!parseSelectors(input, rule.selectors)) + return false; + return parseDeclarations(input, rule.declarations); +} + +static RuleDataList parseStyleSheet(std::string_view input) +{ + RuleDataList rules; + while(!input.empty()) { + skipOptionalSpaces(input); + if(skipDelimiter(input, '@')) { + int depth = 0; + while(!input.empty()) { + auto ch = input.front(); + input.remove_prefix(1); + if(ch == ';' && depth == 0) + break; + if(ch == '{') ++depth; + else if(ch == '}' && depth > 0) { + if(depth == 1) + break; + --depth; + } + } + + continue; + } + + Rule rule; + if(!parseRule(input, rule)) + break; + for(const auto& selector : rule.selectors) { + size_t specificity = 0; + for(const auto& simpleSelector : selector) { + specificity += (simpleSelector.id == ElementID::Star) ? 0x0 : 0x1; + for(const auto& attributeSelector : simpleSelector.attributeSelectors) { + specificity += (attributeSelector.id == PropertyID::Id) ? 0x10000 : 0x100; + } + for(const auto& pseudoClassSelector : simpleSelector.pseudoClassSelectors) { + specificity += 0x100; + } + } + + rules.emplace_back(selector, rule.declarations, specificity, rules.size()); + } + } + + return rules; +} + +static SelectorList parseQuerySelectors(std::string_view input) +{ + SelectorList selectors; + stripLeadingAndTrailingSpaces(input); + if(!parseSelectors(input, selectors) + || !input.empty()) { + return SelectorList(); + } + + return selectors; +} + +inline void parseInlineStyle(std::string_view input, SVGElement* element) +{ + std::string name; + skipOptionalSpaces(input); + while(readCSSIdentifier(input, name)) { + skipOptionalSpaces(input); + if(!skipDelimiter(input, ':')) + return; + std::string value; + while(!input.empty() && input.front() != ';') { + value.push_back(input.front()); + input.remove_prefix(1); + } + + auto id = csspropertyid(name); + if(id != PropertyID::Unknown) + element->setAttribute(0x100, id, value); + skipOptionalSpacesOrDelimiter(input, ';'); + } +} + +inline void removeStyleComments(std::string& value) +{ + auto start = value.find("/*"); + while(start != std::string::npos) { + auto end = value.find("*/", start + 2); + value.erase(start, end - start + 2); + start = value.find("/*"); + } +} + +inline bool decodeText(std::string_view input, std::string& output) +{ + output.clear(); + while(!input.empty()) { + auto ch = input.front(); + input.remove_prefix(1); + if(ch != '&') { + output.push_back(ch); + continue; + } + + if(skipDelimiter(input, '#')) { + int base = 10; + if(skipDelimiter(input, 'x')) + base = 16; + unsigned int cp; + if(!parseInteger(input, cp, base)) + return false; + char c[5] = {0, 0, 0, 0, 0}; + if(cp < 0x80) { + c[1] = 0; + c[0] = char(cp); + } else if(cp < 0x800) { + c[2] = 0; + c[1] = char((cp & 0x3F) | 0x80); + cp >>= 6; + c[0] = char(cp | 0xC0); + } else if(cp < 0x10000) { + c[3] = 0; + c[2] = char((cp & 0x3F) | 0x80); + cp >>= 6; + c[1] = char((cp & 0x3F) | 0x80); + cp >>= 6; + c[0] = char(cp | 0xE0); + } else if(cp < 0x200000) { + c[4] = 0; + c[3] = char((cp & 0x3F) | 0x80); + cp >>= 6; + c[2] = char((cp & 0x3F) | 0x80); + cp >>= 6; + c[1] = char((cp & 0x3F) | 0x80); + cp >>= 6; + c[0] = char(cp | 0xF0); + } + + output.append(c); + } else { + if(skipString(input, "amp")) { + output.push_back('&'); + } else if(skipString(input, "lt")) { + output.push_back('<'); + } else if(skipString(input, "gt")) { + output.push_back('>'); + } else if(skipString(input, "quot")) { + output.push_back('\"'); + } else if(skipString(input, "apos")) { + output.push_back('\''); + } else { + return false; + } + } + + if(!skipDelimiter(input, ';')) { + return false; + } + } + + return true; +} + +constexpr bool IS_STARTNAMECHAR(int c) { return IS_ALPHA(c) || c == '_' || c == ':'; } +constexpr bool IS_NAMECHAR(int c) { return IS_STARTNAMECHAR(c) || IS_NUM(c) || c == '-' || c == '.'; } + +inline bool readIdentifier(std::string_view& input, std::string& output) +{ + if(input.empty() || !IS_STARTNAMECHAR(input.front())) + return false; + output.clear(); + do { + output.push_back(input.front()); + input.remove_prefix(1); + } while(!input.empty() && IS_NAMECHAR(input.front())); + return true; +} + +bool Document::parse(const char* data, size_t length) +{ + std::string buffer; + std::string styleSheet; + SVGElement* currentElement = nullptr; + int ignoring = 0; + auto handleText = [&](std::string_view text, bool in_cdata) { + if(text.empty() || currentElement == nullptr || ignoring > 0) + return; + if(currentElement->id() != ElementID::Text && currentElement->id() != ElementID::Tspan && currentElement->id() != ElementID::Style) { + return; + } + + if(in_cdata) { + buffer.assign(text); + } else { + decodeText(text, buffer); + } + + if(currentElement->id() == ElementID::Style) { + removeStyleComments(buffer); + styleSheet.append(buffer); + } else { + auto node = std::make_unique(this); + node->setData(buffer); + currentElement->addChild(std::move(node)); + } + }; + + std::string_view input(data, length); + if(length >= 3) { + auto buffer = (const uint8_t*)(data); + + const auto c1 = buffer[0]; + const auto c2 = buffer[1]; + const auto c3 = buffer[2]; + if(c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) { + input.remove_prefix(3); + } + } + + while(!input.empty()) { + if(currentElement) { + auto text = input.substr(0, input.find('<')); + handleText(text, false); + input.remove_prefix(text.length()); + } else { + if(!skipOptionalSpaces(input)) { + break; + } + } + + if(!skipDelimiter(input, '<')) + return false; + if(skipDelimiter(input, '?')) { + if(!readIdentifier(input, buffer)) + return false; + auto n = input.find("?>"); + if(n == std::string_view::npos) + return false; + input.remove_prefix(n + 2); + continue; + } + + if(skipDelimiter(input, '!')) { + if(skipString(input, "--")) { + auto n = input.find("-->"); + if(n == std::string_view::npos) + return false; + handleText(input.substr(0, n), false); + input.remove_prefix(n + 3); + continue; + } + + if(skipString(input, "[CDATA[")) { + auto n = input.find("]]>"); + if(n == std::string_view::npos) + return false; + handleText(input.substr(0, n), true); + input.remove_prefix(n + 3); + continue; + } + + if(skipString(input, "DOCTYPE")) { + while(!input.empty() && input.front() != '>') { + if(input.front() == '[') { + int depth = 1; + input.remove_prefix(1); + while(!input.empty() && depth > 0) { + if(input.front() == '[') ++depth; + else if(input.front() == ']') --depth; + input.remove_prefix(1); + } + } else { + input.remove_prefix(1); + } + } + + if(!skipDelimiter(input, '>')) + return false; + continue; + } + + return false; + } + + if(skipDelimiter(input, '/')) { + if(currentElement == nullptr && ignoring == 0) + return false; + if(!readIdentifier(input, buffer)) + return false; + if(ignoring == 0) { + auto id = elementid(buffer); + if(id != currentElement->id()) + return false; + currentElement = currentElement->parentElement(); + } else { + --ignoring; + } + + skipOptionalSpaces(input); + if(!skipDelimiter(input, '>')) + return false; + continue; + } + + if(!readIdentifier(input, buffer)) + return false; + SVGElement* element = nullptr; + if(ignoring > 0) { + ++ignoring; + } else { + auto id = elementid(buffer); + if(id == ElementID::Unknown) { + ignoring = 1; + } else { + if(m_rootElement && currentElement == nullptr) + return false; + if(m_rootElement == nullptr) { + if(id != ElementID::Svg) + return false; + m_rootElement = std::make_unique(this); + element = m_rootElement.get(); + } else { + auto child = SVGElement::create(this, id); + element = child.get(); + currentElement->addChild(std::move(child)); + } + } + } + + skipOptionalSpaces(input); + while(readIdentifier(input, buffer)) { + skipOptionalSpaces(input); + if(!skipDelimiter(input, '=')) + return false; + skipOptionalSpaces(input); + if(input.empty() || !(input.front() == '\"' || input.front() == '\'')) + return false; + auto quote = input.front(); + input.remove_prefix(1); + auto n = input.find(quote); + if(n == std::string_view::npos) + return false; + auto id = PropertyID::Unknown; + if(element != nullptr) + id = propertyid(buffer); + if(id != PropertyID::Unknown) { + decodeText(input.substr(0, n), buffer); + if(id == PropertyID::Style) { + removeStyleComments(buffer); + parseInlineStyle(buffer, element); + } else { + if(id == PropertyID::Id) + m_rootElement->addElementById(buffer, element); + element->setAttribute(0x1, id, buffer); + } + } + + input.remove_prefix(n + 1); + skipOptionalSpaces(input); + } + + if(skipDelimiter(input, '>')) { + if(element != nullptr) + currentElement = element; + continue; + } + + if(skipDelimiter(input, '/')) { + if(!skipDelimiter(input, '>')) + return false; + if(ignoring > 0) + --ignoring; + continue; + } + + return false; + } + + if(m_rootElement == nullptr || ignoring > 0 || !input.empty()) + return false; + applyStyleSheet(styleSheet); + m_rootElement->build(); + return true; +} + +void Document::applyStyleSheet(const std::string& content) +{ + auto rules = parseStyleSheet(content); + if(!rules.empty()) { + std::sort(rules.begin(), rules.end()); + m_rootElement->transverse([&rules](SVGElement* element) { + for(const auto& rule : rules) { + if(rule.match(element)) { + for(const auto& declaration : rule.declarations()) { + element->setAttribute(declaration.specificity, declaration.id, declaration.value); + } + } + } + }); + } +} + +ElementList Document::querySelectorAll(const std::string& content) const +{ + auto selectors = parseQuerySelectors(content); + if(selectors.empty()) + return ElementList(); + ElementList elements; + m_rootElement->transverse([&](SVGElement* element) { + for(const auto& selector : selectors) { + if(matchSelector(selector, element)) { + elements.push_back(element); + break; + } + } + }); + + return elements; +} + +} // namespace lunasvg diff --git a/vendor/lunasvg/source/svgparserutils.h b/vendor/lunasvg/source/svgparserutils.h new file mode 100644 index 0000000..2cf2cf0 --- /dev/null +++ b/vendor/lunasvg/source/svgparserutils.h @@ -0,0 +1,210 @@ +#ifndef LUNASVG_SVGPARSERUTILS_H +#define LUNASVG_SVGPARSERUTILS_H + +#include +#include +#include + +namespace lunasvg { + +constexpr bool IS_NUM(int cc) { return cc >= '0' && cc <= '9'; } +constexpr bool IS_ALPHA(int cc) { return (cc >= 'a' && cc <= 'z') || (cc >= 'A' && cc <= 'Z'); } +constexpr bool IS_WS(int cc) { return cc == ' ' || cc == '\t' || cc == '\n' || cc == '\r'; } + +constexpr void stripLeadingSpaces(std::string_view& input) +{ + while(!input.empty() && IS_WS(input.front())) { + input.remove_prefix(1); + } +} + +constexpr void stripTrailingSpaces(std::string_view& input) +{ + while(!input.empty() && IS_WS(input.back())) { + input.remove_suffix(1); + } +} + +constexpr void stripLeadingAndTrailingSpaces(std::string_view& input) +{ + stripLeadingSpaces(input); + stripTrailingSpaces(input); +} + +constexpr bool skipOptionalSpaces(std::string_view& input) +{ + while(!input.empty() && IS_WS(input.front())) + input.remove_prefix(1); + return !input.empty(); +} + +constexpr bool skipOptionalSpacesOrDelimiter(std::string_view& input, char delimiter) +{ + if(!input.empty() && !IS_WS(input.front()) && delimiter != input.front()) + return false; + if(skipOptionalSpaces(input)) { + if(delimiter == input.front()) { + input.remove_prefix(1); + skipOptionalSpaces(input); + } + } + + return !input.empty(); +} + +constexpr bool skipOptionalSpacesOrComma(std::string_view& input) +{ + return skipOptionalSpacesOrDelimiter(input, ','); +} + +constexpr bool skipDelimiterAndOptionalSpaces(std::string_view& input, char delimiter) +{ + if(!input.empty() && input.front() == delimiter) { + input.remove_prefix(1); + skipOptionalSpaces(input); + return true; + } + + return false; +} + +constexpr bool skipDelimiter(std::string_view& input, char delimiter) +{ + if(!input.empty() && input.front() == delimiter) { + input.remove_prefix(1); + return true; + } + + return false; +} + +constexpr bool skipString(std::string_view& input, std::string_view value) +{ + if(input.size() >= value.size() && value == input.substr(0, value.size())) { + input.remove_prefix(value.size()); + return true; + } + + return false; +} + +constexpr bool isIntegralDigit(char ch, int base) +{ + if(IS_NUM(ch)) + return ch - '0' < base; + if(IS_ALPHA(ch)) + return (ch >= 'a' && ch < 'a' + base - 10) || (ch >= 'A' && ch < 'A' + base - 10); + return false; +} + +constexpr int toIntegralDigit(char ch) +{ + if(IS_NUM(ch)) + return ch - '0'; + if(ch >= 'a') + return ch - 'a' + 10; + return ch - 'A' + 10; +} + +template +inline bool parseInteger(std::string_view& input, T& integer, int base = 10) +{ + constexpr bool isSigned = std::numeric_limits::is_signed; + constexpr T intMax = std::numeric_limits::max(); + const T maxMultiplier = intMax / static_cast(base); + + T value = 0; + bool isNegative = false; + + if(!input.empty() && input.front() == '+') { + input.remove_prefix(1); + } else if(!input.empty() && isSigned && input.front() == '-') { + input.remove_prefix(1); + isNegative = true; + } + + if(input.empty() || !isIntegralDigit(input.front(), base)) + return false; + do { + const int digitValue = toIntegralDigit(input.front()); + if(value > maxMultiplier || (value == maxMultiplier && static_cast(digitValue) > (intMax % static_cast(base)) + isNegative)) + return false; + value = static_cast(base) * value + static_cast(digitValue); + input.remove_prefix(1); + } while(!input.empty() && isIntegralDigit(input.front(), base)); + + using SignedType = typename std::make_signed::type; + if(isNegative) + integer = -static_cast(value); + else + integer = value; + return true; +} + +template +inline bool parseNumber(std::string_view& input, T& number) +{ + constexpr T maxValue = std::numeric_limits::max(); + T integer = 0; + T fraction = 0; + int exponent = 0; + int sign = 1; + int expsign = 1; + + if(!input.empty() && input.front() == '+') { + input.remove_prefix(1); + } else if(!input.empty() && input.front() == '-') { + input.remove_prefix(1); + sign = -1; + } + + if(input.empty() || (!IS_NUM(input.front()) && input.front() != '.')) + return false; + if(IS_NUM(input.front())) { + do { + integer = static_cast(10) * integer + (input.front() - '0'); + input.remove_prefix(1); + } while(!input.empty() && IS_NUM(input.front())); + } + + if(!input.empty() && input.front() == '.') { + input.remove_prefix(1); + if(input.empty() || !IS_NUM(input.front())) + return false; + T divisor = static_cast(1); + do { + fraction = static_cast(10) * fraction + (input.front() - '0'); + divisor *= static_cast(10); + input.remove_prefix(1); + } while(!input.empty() && IS_NUM(input.front())); + fraction /= divisor; + } + + if(input.size() > 1 && (input[0] == 'e' || input[0] == 'E') + && (input[1] != 'x' && input[1] != 'm')) + { + input.remove_prefix(1); + if(!input.empty() && input.front() == '+') + input.remove_prefix(1); + else if(!input.empty() && input.front() == '-') { + input.remove_prefix(1); + expsign = -1; + } + + if(input.empty() || !IS_NUM(input.front())) + return false; + do { + exponent = 10 * exponent + (input.front() - '0'); + input.remove_prefix(1); + } while(!input.empty() && IS_NUM(input.front())); + } + + number = sign * (integer + fraction); + if(exponent) + number *= static_cast(std::pow(10.0, expsign * exponent)); + return number >= -maxValue && number <= maxValue; +} + +} // namespace lunasvg + +#endif // LUNASVG_SVGPARSERUTILS_H diff --git a/vendor/lunasvg/source/svgproperty.cpp b/vendor/lunasvg/source/svgproperty.cpp new file mode 100644 index 0000000..51c7ecc --- /dev/null +++ b/vendor/lunasvg/source/svgproperty.cpp @@ -0,0 +1,705 @@ +#include "svgproperty.h" +#include "svgelement.h" +#include "svgparserutils.h" + +#include + +namespace lunasvg { + +PropertyID propertyid(std::string_view name) +{ + static const struct { + std::string_view name; + PropertyID value; + } table[] = { + {"class", PropertyID::Class}, + {"clipPathUnits", PropertyID::ClipPathUnits}, + {"cx", PropertyID::Cx}, + {"cy", PropertyID::Cy}, + {"d", PropertyID::D}, + {"dx", PropertyID::Dx}, + {"dy", PropertyID::Dy}, + {"fx", PropertyID::Fx}, + {"fy", PropertyID::Fy}, + {"gradientTransform", PropertyID::GradientTransform}, + {"gradientUnits", PropertyID::GradientUnits}, + {"height", PropertyID::Height}, + {"href", PropertyID::Href}, + {"id", PropertyID::Id}, + {"lengthAdjust", PropertyID::LengthAdjust}, + {"markerHeight", PropertyID::MarkerHeight}, + {"markerUnits", PropertyID::MarkerUnits}, + {"markerWidth", PropertyID::MarkerWidth}, + {"maskContentUnits", PropertyID::MaskContentUnits}, + {"maskUnits", PropertyID::MaskUnits}, + {"offset", PropertyID::Offset}, + {"orient", PropertyID::Orient}, + {"patternContentUnits", PropertyID::PatternContentUnits}, + {"patternTransform", PropertyID::PatternTransform}, + {"patternUnits", PropertyID::PatternUnits}, + {"points", PropertyID::Points}, + {"preserveAspectRatio", PropertyID::PreserveAspectRatio}, + {"r", PropertyID::R}, + {"refX", PropertyID::RefX}, + {"refY", PropertyID::RefY}, + {"rotate", PropertyID::Rotate}, + {"rx", PropertyID::Rx}, + {"ry", PropertyID::Ry}, + {"spreadMethod", PropertyID::SpreadMethod}, + {"style", PropertyID::Style}, + {"textLength", PropertyID::TextLength}, + {"transform", PropertyID::Transform}, + {"viewBox", PropertyID::ViewBox}, + {"width", PropertyID::Width}, + {"x", PropertyID::X}, + {"x1", PropertyID::X1}, + {"x2", PropertyID::X2}, + {"xlink:href", PropertyID::Href}, + {"xml:space", PropertyID::White_Space}, + {"y", PropertyID::Y}, + {"y1", PropertyID::Y1}, + {"y2", PropertyID::Y2} + }; + + auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; }); + if(it == std::end(table) || it->name != name) + return csspropertyid(name); + return it->value; +} + +PropertyID csspropertyid(std::string_view name) +{ + static const struct { + std::string_view name; + PropertyID value; + } table[] = { + {"alignment-baseline", PropertyID::Alignment_Baseline}, + {"baseline-shift", PropertyID::Baseline_Shift}, + {"clip-path", PropertyID::Clip_Path}, + {"clip-rule", PropertyID::Clip_Rule}, + {"color", PropertyID::Color}, + {"direction", PropertyID::Direction}, + {"display", PropertyID::Display}, + {"dominant-baseline", PropertyID::Dominant_Baseline}, + {"fill", PropertyID::Fill}, + {"fill-opacity", PropertyID::Fill_Opacity}, + {"fill-rule", PropertyID::Fill_Rule}, + {"font-family", PropertyID::Font_Family}, + {"font-size", PropertyID::Font_Size}, + {"font-style", PropertyID::Font_Style}, + {"font-weight", PropertyID::Font_Weight}, + {"letter-spacing", PropertyID::Letter_Spacing}, + {"marker-end", PropertyID::Marker_End}, + {"marker-mid", PropertyID::Marker_Mid}, + {"marker-start", PropertyID::Marker_Start}, + {"mask", PropertyID::Mask}, + {"mask-type", PropertyID::Mask_Type}, + {"opacity", PropertyID::Opacity}, + {"overflow", PropertyID::Overflow}, + {"pointer-events", PropertyID::Pointer_Events}, + {"stop-color", PropertyID::Stop_Color}, + {"stop-opacity", PropertyID::Stop_Opacity}, + {"stroke", PropertyID::Stroke}, + {"stroke-dasharray", PropertyID::Stroke_Dasharray}, + {"stroke-dashoffset", PropertyID::Stroke_Dashoffset}, + {"stroke-linecap", PropertyID::Stroke_Linecap}, + {"stroke-linejoin", PropertyID::Stroke_Linejoin}, + {"stroke-miterlimit", PropertyID::Stroke_Miterlimit}, + {"stroke-opacity", PropertyID::Stroke_Opacity}, + {"stroke-width", PropertyID::Stroke_Width}, + {"text-anchor", PropertyID::Text_Anchor}, + {"text-orientation", PropertyID::Text_Orientation}, + {"visibility", PropertyID::Visibility}, + {"white-space", PropertyID::White_Space}, + {"word-spacing", PropertyID::Word_Spacing}, + {"writing-mode", PropertyID::Writing_Mode} + }; + + auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; }); + if(it == std::end(table) || it->name != name) + return PropertyID::Unknown; + return it->value; +} + +SVGProperty::SVGProperty(PropertyID id) + : m_id(id) +{ +} + +bool SVGString::parse(std::string_view input) +{ + stripLeadingAndTrailingSpaces(input); + m_value.assign(input); + return true; +} + +template<> +bool SVGEnumeration::parse(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {SpreadMethod::Pad, "pad"}, + {SpreadMethod::Reflect, "reflect"}, + {SpreadMethod::Repeat, "repeat"} + }; + + return parseEnum(input, entries); +} + +template<> +bool SVGEnumeration::parse(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {Units::UserSpaceOnUse, "userSpaceOnUse"}, + {Units::ObjectBoundingBox, "objectBoundingBox"} + }; + + return parseEnum(input, entries); +} + +template<> +bool SVGEnumeration::parse(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {MarkerUnits::StrokeWidth, "strokeWidth"}, + {MarkerUnits::UserSpaceOnUse, "userSpaceOnUse"} + }; + + return parseEnum(input, entries); +} + +template<> +bool SVGEnumeration::parse(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {LengthAdjust::Spacing, "spacing"}, + {LengthAdjust::SpacingAndGlyphs, "spacingAndGlyphs"} + }; + + return parseEnum(input, entries); +} + +template +template +bool SVGEnumeration::parseEnum(std::string_view input, const SVGEnumerationEntry(&entries)[N]) +{ + stripLeadingAndTrailingSpaces(input); + for(const auto& entry : entries) { + if(input == entry.second) { + m_value = entry.first; + return true; + } + } + + return false; +} + +bool SVGAngle::parse(std::string_view input) +{ + stripLeadingAndTrailingSpaces(input); + if(input == "auto") { + m_value = 0.f; + m_orientType = OrientType::Auto; + return true; + } + + if(input == "auto-start-reverse") { + m_value = 0.f; + m_orientType = OrientType::AutoStartReverse; + return true; + } + + float value = 0.f; + if(!parseNumber(input, value)) + return false; + if(!input.empty()) { + if(input == "rad") + value *= 180.f / PLUTOVG_PI; + else if(input == "grad") + value *= 360.f / 400.f; + else if(input == "turn") + value *= 360.f; + else if(input != "deg") { + return false; + } + } + + m_value = value; + m_orientType = OrientType::Angle; + return true; +} + +bool Length::parse(std::string_view input, LengthNegativeMode mode) +{ + float value = 0.f; + stripLeadingAndTrailingSpaces(input); + if(!parseNumber(input, value)) + return false; + if(value < 0.f && mode == LengthNegativeMode::Forbid) + return false; + if(input.empty()) { + m_value = value; + m_units = LengthUnits::None; + return true; + } + + constexpr auto dpi = 96.f; + switch(input.front()) { + case '%': + m_value = value; + m_units = LengthUnits::Percent; + input.remove_prefix(1); + break; + case 'p': + input.remove_prefix(1); + if(input.empty()) + return false; + else if(input.front() == 'x') + m_value = value; + else if(input.front() == 'c') + m_value = value * dpi / 6.f; + else if(input.front() == 't') + m_value = value * dpi / 72.f; + else + return false; + m_units = LengthUnits::Px; + input.remove_prefix(1); + break; + case 'i': + input.remove_prefix(1); + if(input.empty()) + return false; + else if(input.front() == 'n') + m_value = value * dpi; + else + return false; + m_units = LengthUnits::Px; + input.remove_prefix(1); + break; + case 'c': + input.remove_prefix(1); + if(input.empty()) + return false; + else if(input.front() == 'm') + m_value = value * dpi / 2.54f; + else + return false; + m_units = LengthUnits::Px; + input.remove_prefix(1); + break; + case 'm': + input.remove_prefix(1); + if(input.empty()) + return false; + else if(input.front() == 'm') + m_value = value * dpi / 25.4f; + else + return false; + m_units = LengthUnits::Px; + input.remove_prefix(1); + break; + case 'e': + input.remove_prefix(1); + if(input.empty()) + return false; + else if(input.front() == 'm') + m_units = LengthUnits::Em; + else if(input.front() == 'x') + m_units = LengthUnits::Ex; + else + return false; + m_value = value; + input.remove_prefix(1); + break; + default: + return false; + } + + return input.empty(); +} + +float LengthContext::valueForLength(const Length& length, LengthDirection direction) const +{ + if(length.units() == LengthUnits::Percent) { + if(m_units == Units::UserSpaceOnUse) + return length.value() * viewportDimension(direction) / 100.f; + return length.value() / 100.f; + } + + if(length.units() == LengthUnits::Ex) + return length.value() * m_element->font_size() / 2.f; + if(length.units() == LengthUnits::Em) + return length.value() * m_element->font_size(); + return length.value(); +} + +float LengthContext::viewportDimension(LengthDirection direction) const +{ + auto viewportSize = m_element->currentViewportSize(); + switch(direction) { + case LengthDirection::Horizontal: + return viewportSize.w; + case LengthDirection::Vertical: + return viewportSize.h; + default: + return std::sqrt(viewportSize.w * viewportSize.w + viewportSize.h * viewportSize.h) / PLUTOVG_SQRT2; + } +} + +bool SVGLength::parse(std::string_view input) +{ + return m_value.parse(input, m_negativeMode); +} + +bool SVGLengthList::parse(std::string_view input) +{ + m_values.clear(); + while(!input.empty()) { + size_t count = 0; + while(count < input.length() && input[count] != ',' && !IS_WS(input[count])) + ++count; + if(count == 0) + break; + Length value(0, LengthUnits::None); + if(!value.parse(input.substr(0, count), m_negativeMode)) + return false; + input.remove_prefix(count); + skipOptionalSpacesOrComma(input); + m_values.push_back(value); + } + + return true; +} + +bool SVGNumber::parse(std::string_view input) +{ + float value = 0.f; + stripLeadingAndTrailingSpaces(input); + if(!parseNumber(input, value)) + return false; + if(!input.empty()) + return false; + m_value = value; + return true; +} + +bool SVGNumberPercentage::parse(std::string_view input) +{ + float value = 0.f; + stripLeadingAndTrailingSpaces(input); + if(!parseNumber(input, value)) + return false; + if(!input.empty() && input.front() == '%') { + value /= 100.f; + input.remove_prefix(1); + } + + if(!input.empty()) + return false; + m_value = std::clamp(value, 0.f, 1.f); + return true; +} + +bool SVGNumberList::parse(std::string_view input) +{ + m_values.clear(); + stripLeadingSpaces(input); + while(!input.empty()) { + float value = 0.f; + if(!parseNumber(input, value)) + return false; + skipOptionalSpacesOrComma(input); + m_values.push_back(value); + } + + return true; +} + +bool SVGPath::parse(std::string_view input) +{ + return m_value.parse(input.data(), input.length()); +} + +bool SVGPoint::parse(std::string_view input) +{ + Point value; + stripLeadingAndTrailingSpaces(input); + if(!parseNumber(input, value.x) + || !skipOptionalSpaces(input) + || !parseNumber(input, value.y) + || !input.empty()) { + return false; + } + + m_value = value; + return true; +} + +bool SVGPointList::parse(std::string_view input) +{ + m_values.clear(); + stripLeadingSpaces(input); + while(!input.empty()) { + Point value; + if(!parseNumber(input, value.x) + || !skipOptionalSpacesOrComma(input) + || !parseNumber(input, value.y)) { + return false; + } + + m_values.push_back(value); + skipOptionalSpacesOrComma(input); + } + + return true; +} + +bool SVGRect::parse(std::string_view input) +{ + Rect value; + stripLeadingAndTrailingSpaces(input); + if(!parseNumber(input, value.x) + || !skipOptionalSpacesOrComma(input) + || !parseNumber(input, value.y) + || !skipOptionalSpacesOrComma(input) + || !parseNumber(input, value.w) + || !skipOptionalSpacesOrComma(input) + || !parseNumber(input, value.h) + || !input.empty()) { + return false; + } + + if(value.w < 0.f || value.h < 0.f) + return false; + m_value = value; + return true; +} + +bool SVGTransform::parse(std::string_view input) +{ + return m_value.parse(input.data(), input.length()); +} + +bool SVGPreserveAspectRatio::parse(std::string_view input) +{ + auto alignType = AlignType::xMidYMid; + stripLeadingSpaces(input); + if(skipString(input, "none")) + alignType = AlignType::None; + else if(skipString(input, "xMinYMin")) + alignType = AlignType::xMinYMin; + else if(skipString(input, "xMidYMin")) + alignType = AlignType::xMidYMin; + else if(skipString(input, "xMaxYMin")) + alignType = AlignType::xMaxYMin; + else if(skipString(input, "xMinYMid")) + alignType = AlignType::xMinYMid; + else if(skipString(input, "xMidYMid")) + alignType = AlignType::xMidYMid; + else if(skipString(input, "xMaxYMid")) + alignType = AlignType::xMaxYMid; + else if(skipString(input, "xMinYMax")) + alignType = AlignType::xMinYMax; + else if(skipString(input, "xMidYMax")) + alignType = AlignType::xMidYMax; + else if(skipString(input, "xMaxYMax")) + alignType = AlignType::xMaxYMax; + else { + return false; + } + + auto meetOrSlice = MeetOrSlice::Meet; + skipOptionalSpaces(input); + if(skipString(input, "meet")) { + meetOrSlice = MeetOrSlice::Meet; + } else if(skipString(input, "slice")) { + meetOrSlice = MeetOrSlice::Slice; + } + + if(alignType == AlignType::None) + meetOrSlice = MeetOrSlice::Meet; + skipOptionalSpaces(input); + if(!input.empty()) + return false; + m_alignType = alignType; + m_meetOrSlice = meetOrSlice; + return true; +} + +Rect SVGPreserveAspectRatio::getClipRect(const Rect& viewBoxRect, const Size& viewportSize) const +{ + assert(!viewBoxRect.isEmpty() && !viewportSize.isEmpty()); + auto xScale = viewportSize.w / viewBoxRect.w; + auto yScale = viewportSize.h / viewBoxRect.h; + if(m_alignType == AlignType::None) { + return Rect(viewBoxRect.x, viewBoxRect.y, viewportSize.w / xScale, viewportSize.h / yScale); + } + + auto scale = (m_meetOrSlice == MeetOrSlice::Meet) ? std::min(xScale, yScale) : std::max(xScale, yScale); + auto xOffset = -viewBoxRect.x * scale; + auto yOffset = -viewBoxRect.y * scale; + auto viewWidth = viewBoxRect.w * scale; + auto viewHeight = viewBoxRect.h * scale; + switch(m_alignType) { + case AlignType::xMidYMin: + case AlignType::xMidYMid: + case AlignType::xMidYMax: + xOffset += (viewportSize.w - viewWidth) * 0.5f; + break; + case AlignType::xMaxYMin: + case AlignType::xMaxYMid: + case AlignType::xMaxYMax: + xOffset += (viewportSize.w - viewWidth); + break; + default: + break; + } + + switch(m_alignType) { + case AlignType::xMinYMid: + case AlignType::xMidYMid: + case AlignType::xMaxYMid: + yOffset += (viewportSize.h - viewHeight) * 0.5f; + break; + case AlignType::xMinYMax: + case AlignType::xMidYMax: + case AlignType::xMaxYMax: + yOffset += (viewportSize.h - viewHeight); + break; + default: + break; + } + + return Rect(-xOffset / scale, -yOffset / scale, viewportSize.w / scale, viewportSize.h / scale); +} + +Transform SVGPreserveAspectRatio::getTransform(const Rect& viewBoxRect, const Size& viewportSize) const +{ + assert(!viewBoxRect.isEmpty() && !viewportSize.isEmpty()); + auto xScale = viewportSize.w / viewBoxRect.w; + auto yScale = viewportSize.h / viewBoxRect.h; + if(m_alignType == AlignType::None) { + return Transform(xScale, 0, 0, yScale, -viewBoxRect.x * xScale, -viewBoxRect.y * yScale); + } + + auto scale = (m_meetOrSlice == MeetOrSlice::Meet) ? std::min(xScale, yScale) : std::max(xScale, yScale); + auto xOffset = -viewBoxRect.x * scale; + auto yOffset = -viewBoxRect.y * scale; + auto viewWidth = viewBoxRect.w * scale; + auto viewHeight = viewBoxRect.h * scale; + switch(m_alignType) { + case AlignType::xMidYMin: + case AlignType::xMidYMid: + case AlignType::xMidYMax: + xOffset += (viewportSize.w - viewWidth) * 0.5f; + break; + case AlignType::xMaxYMin: + case AlignType::xMaxYMid: + case AlignType::xMaxYMax: + xOffset += (viewportSize.w - viewWidth); + break; + default: + break; + } + + switch(m_alignType) { + case AlignType::xMinYMid: + case AlignType::xMidYMid: + case AlignType::xMaxYMid: + yOffset += (viewportSize.h - viewHeight) * 0.5f; + break; + case AlignType::xMinYMax: + case AlignType::xMidYMax: + case AlignType::xMaxYMax: + yOffset += (viewportSize.h - viewHeight); + break; + default: + break; + } + + return Transform(scale, 0, 0, scale, xOffset, yOffset); +} + +void SVGPreserveAspectRatio::transformRect(Rect& dstRect, Rect& srcRect) const +{ + if(m_alignType == AlignType::None) + return; + auto viewSize = dstRect.size(); + auto imageSize = srcRect.size(); + if(m_meetOrSlice == MeetOrSlice::Meet) { + auto scale = imageSize.h / imageSize.w; + if(viewSize.h > viewSize.w * scale) { + dstRect.h = viewSize.w * scale; + switch(m_alignType) { + case AlignType::xMinYMid: + case AlignType::xMidYMid: + case AlignType::xMaxYMid: + dstRect.y += (viewSize.h - dstRect.h) * 0.5f; + break; + case AlignType::xMinYMax: + case AlignType::xMidYMax: + case AlignType::xMaxYMax: + dstRect.y += viewSize.h - dstRect.h; + break; + default: + break; + } + } + + if(viewSize.w > viewSize.h / scale) { + dstRect.w = viewSize.h / scale; + switch(m_alignType) { + case AlignType::xMidYMin: + case AlignType::xMidYMid: + case AlignType::xMidYMax: + dstRect.x += (viewSize.w - dstRect.w) * 0.5f; + break; + case AlignType::xMaxYMin: + case AlignType::xMaxYMid: + case AlignType::xMaxYMax: + dstRect.x += viewSize.w - dstRect.w; + break; + default: + break; + } + } + } else if(m_meetOrSlice == MeetOrSlice::Slice) { + auto scale = imageSize.h / imageSize.w; + if(viewSize.h < viewSize.w * scale) { + srcRect.h = viewSize.h * (imageSize.w / viewSize.w); + switch(m_alignType) { + case AlignType::xMinYMid: + case AlignType::xMidYMid: + case AlignType::xMaxYMid: + srcRect.y += (imageSize.h - srcRect.h) * 0.5f; + break; + case AlignType::xMinYMax: + case AlignType::xMidYMax: + case AlignType::xMaxYMax: + srcRect.y += imageSize.h - srcRect.h; + break; + default: + break; + } + } + + if(viewSize.w < viewSize.h / scale) { + srcRect.w = viewSize.w * (imageSize.h / viewSize.h); + switch(m_alignType) { + case AlignType::xMidYMin: + case AlignType::xMidYMid: + case AlignType::xMidYMax: + srcRect.x += (imageSize.w - srcRect.w) * 0.5f; + break; + case AlignType::xMaxYMin: + case AlignType::xMaxYMid: + case AlignType::xMaxYMax: + srcRect.x += imageSize.w - srcRect.w; + break; + default: + break; + } + } + } +} + +} // namespace lunasvg diff --git a/vendor/lunasvg/source/svgproperty.h b/vendor/lunasvg/source/svgproperty.h new file mode 100644 index 0000000..74a23a8 --- /dev/null +++ b/vendor/lunasvg/source/svgproperty.h @@ -0,0 +1,570 @@ +#ifndef LUNASVG_SVGPROPERTY_H +#define LUNASVG_SVGPROPERTY_H + +#include "graphics.h" + +#include + +namespace lunasvg { + +enum class PropertyID : uint8_t { + Unknown = 0, + Alignment_Baseline, + Baseline_Shift, + Class, + Clip_Path, + Clip_Rule, + ClipPathUnits, + Color, + Cx, + Cy, + D, + Direction, + Display, + Dominant_Baseline, + Dx, + Dy, + Fill, + Fill_Opacity, + Fill_Rule, + Font_Family, + Font_Size, + Font_Style, + Font_Weight, + Fx, + Fy, + GradientTransform, + GradientUnits, + Height, + Href, + Id, + LengthAdjust, + Letter_Spacing, + Marker_End, + Marker_Mid, + Marker_Start, + MarkerHeight, + MarkerUnits, + MarkerWidth, + Mask, + Mask_Type, + MaskContentUnits, + MaskUnits, + Offset, + Opacity, + Orient, + Overflow, + PatternContentUnits, + PatternTransform, + PatternUnits, + Pointer_Events, + Points, + PreserveAspectRatio, + R, + RefX, + RefY, + Rotate, + Rx, + Ry, + SpreadMethod, + Stop_Color, + Stop_Opacity, + Stroke, + Stroke_Dasharray, + Stroke_Dashoffset, + Stroke_Linecap, + Stroke_Linejoin, + Stroke_Miterlimit, + Stroke_Opacity, + Stroke_Width, + Style, + Text_Anchor, + Text_Orientation, + TextLength, + Transform, + ViewBox, + Visibility, + White_Space, + Width, + Word_Spacing, + Writing_Mode, + X, + X1, + X2, + Y, + Y1, + Y2 +}; + +PropertyID propertyid(std::string_view name); +PropertyID csspropertyid(std::string_view name); + +class SVGElement; + +class SVGProperty { +public: + SVGProperty(PropertyID id); + virtual ~SVGProperty() = default; + PropertyID id() const { return m_id; } + + virtual bool parse(std::string_view input) = 0; + +private: + SVGProperty(const SVGProperty&) = delete; + SVGProperty& operator=(const SVGProperty&) = delete; + PropertyID m_id; +}; + +class SVGString final : public SVGProperty { +public: + explicit SVGString(PropertyID id) + : SVGProperty(id) + {} + + const std::string& value() const { return m_value; } + bool parse(std::string_view input) final; + +private: + std::string m_value; +}; + +class Paint { +public: + Paint() = default; + explicit Paint(const Color& color) : m_color(color) {} + Paint(const std::string& id, const Color& color) + : m_id(id), m_color(color) + {} + + const Color& color() const { return m_color; } + const std::string& id() const { return m_id; } + bool isNone() const { return m_id.empty() && !m_color.isVisible(); } + +private: + std::string m_id; + Color m_color = Color::Transparent; +}; + +enum class Display : uint8_t { + Inline, + None +}; + +enum class Visibility : uint8_t { + Visible, + Hidden, + Collapse +}; + +enum class Overflow : uint8_t { + Visible, + Hidden +}; + +enum class PointerEvents : uint8_t { + None, + Auto, + Stroke, + Fill, + Painted, + Visible, + VisibleStroke, + VisibleFill, + VisiblePainted, + BoundingBox, + All +}; + +enum class FontStyle : uint8_t { + Normal, + Italic +}; + +enum class FontWeight : uint8_t { + Normal, + Bold +}; + +enum class AlignmentBaseline : uint8_t { + Auto, + Baseline, + BeforeEdge, + TextBeforeEdge, + Middle, + Central, + AfterEdge, + TextAfterEdge, + Ideographic, + Alphabetic, + Hanging, + Mathematical +}; + +enum class DominantBaseline : uint8_t { + Auto, + UseScript, + NoChange, + ResetSize, + Ideographic, + Alphabetic, + Hanging, + Mathematical, + Central, + Middle, + TextAfterEdge, + TextBeforeEdge +}; + +enum class TextAnchor : uint8_t { + Start, + Middle, + End +}; + +enum class WhiteSpace : uint8_t { + Default, + Preserve +}; + +enum class WritingMode : uint8_t { + Horizontal, + Vertical +}; + +enum class TextOrientation : uint8_t { + Mixed, + Upright +}; + +enum class Direction : uint8_t { + Ltr, + Rtl +}; + +enum class MaskType : uint8_t { + Luminance, + Alpha +}; + +enum class Units : uint8_t { + UserSpaceOnUse, + ObjectBoundingBox +}; + +enum class MarkerUnits : uint8_t { + StrokeWidth, + UserSpaceOnUse +}; + +enum class LengthAdjust : uint8_t { + Spacing, + SpacingAndGlyphs +}; + +template +using SVGEnumerationEntry = std::pair; + +template +class SVGEnumeration final : public SVGProperty { +public: + explicit SVGEnumeration(PropertyID id, Enum value) + : SVGProperty(id) + , m_value(value) + {} + + Enum value() const { return m_value; } + bool parse(std::string_view input) final; + +private: + template + bool parseEnum(std::string_view input, const SVGEnumerationEntry(&entries)[N]); + Enum m_value; +}; + +class SVGAngle final : public SVGProperty { +public: + enum class OrientType { + Auto, + AutoStartReverse, + Angle + }; + + explicit SVGAngle(PropertyID id) + : SVGProperty(id) + {} + + float value() const { return m_value; } + OrientType orientType() const { return m_orientType; } + bool parse(std::string_view input) final; + +private: + float m_value = 0; + OrientType m_orientType = OrientType::Angle; +}; + +enum class LengthUnits : uint8_t { + None, + Percent, + Px, + Em, + Ex +}; + +enum class LengthDirection : uint8_t { + Horizontal, + Vertical, + Diagonal +}; + +enum class LengthNegativeMode : uint8_t { + Allow, + Forbid +}; + +class Length { +public: + Length() = default; + Length(float value, LengthUnits units) + : m_value(value), m_units(units) + {} + + float value() const { return m_value; } + LengthUnits units() const { return m_units; } + + bool parse(std::string_view input, LengthNegativeMode mode); + +private: + float m_value = 0.f; + LengthUnits m_units = LengthUnits::None; +}; + +class SVGLength final : public SVGProperty { +public: + SVGLength(PropertyID id, LengthDirection direction, LengthNegativeMode negativeMode, float value = 0, LengthUnits units = LengthUnits::None) + : SVGProperty(id) + , m_direction(direction) + , m_negativeMode(negativeMode) + , m_value(value, units) + {} + + bool isPercent() const { return m_value.units() == LengthUnits::Percent; } + + LengthDirection direction() const { return m_direction; } + LengthNegativeMode negativeMode() const { return m_negativeMode; } + const Length& value() const { return m_value; } + bool parse(std::string_view input) final; + +private: + const LengthDirection m_direction; + const LengthNegativeMode m_negativeMode; + Length m_value; +}; + +class LengthContext { +public: + LengthContext(const SVGElement* element, Units units = Units::UserSpaceOnUse) + : m_element(element), m_units(units) + {} + + float valueForLength(const Length& length, LengthDirection direction) const; + float valueForLength(const SVGLength& length) const { return valueForLength(length.value(), length.direction()); } + +private: + float viewportDimension(LengthDirection direction) const; + const SVGElement* m_element; + const Units m_units; +}; + +using LengthList = std::vector; + +class SVGLengthList final : public SVGProperty { +public: + SVGLengthList(PropertyID id, LengthDirection direction, LengthNegativeMode negativeMode) + : SVGProperty(id) + , m_direction(direction) + , m_negativeMode(negativeMode) + {} + + LengthDirection direction() const { return m_direction; } + LengthNegativeMode negativeMode() const { return m_negativeMode; } + const LengthList& values() const { return m_values; } + bool parse(std::string_view input) final; + +private: + const LengthDirection m_direction; + const LengthNegativeMode m_negativeMode; + LengthList m_values; +}; + +class BaselineShift { +public: + enum class Type { + Baseline, + Sub, + Super, + Length + }; + + BaselineShift() = default; + BaselineShift(Type type) : m_type(type) {} + BaselineShift(const Length& length) : m_type(Type::Length), m_length(length) {} + + Type type() const { return m_type; } + const Length& length() const { return m_length; } + +private: + Type m_type{Type::Baseline}; + Length m_length; +}; + +class SVGNumber : public SVGProperty { +public: + SVGNumber(PropertyID id, float value) + : SVGProperty(id) + , m_value(value) + {} + + float value() const { return m_value; } + bool parse(std::string_view input) override; + +private: + float m_value; +}; + +class SVGNumberPercentage final : public SVGProperty { +public: + SVGNumberPercentage(PropertyID id, float value) + : SVGProperty(id) + , m_value(value) + {} + + float value() const { return m_value; } + bool parse(std::string_view input) final; + +private: + float m_value; +}; + +using NumberList = std::vector; + +class SVGNumberList final : public SVGProperty { +public: + explicit SVGNumberList(PropertyID id) + : SVGProperty(id) + {} + + const NumberList& values() const { return m_values; } + bool parse(std::string_view input) final; + +private: + NumberList m_values; +}; + +class SVGPath final : public SVGProperty { +public: + explicit SVGPath(PropertyID id) + : SVGProperty(id) + {} + + const Path& value() const { return m_value; } + bool parse(std::string_view input) final; + +private: + Path m_value; +}; + +class SVGPoint final : public SVGProperty { +public: + explicit SVGPoint(PropertyID id) + : SVGProperty(id) + {} + + const Point& value() const { return m_value; } + bool parse(std::string_view input) final; + +private: + Point m_value; +}; + +using PointList = std::vector; + +class SVGPointList final : public SVGProperty { +public: + explicit SVGPointList(PropertyID id) + : SVGProperty(id) + {} + + const PointList& values() const { return m_values; } + bool parse(std::string_view input) final; + +private: + PointList m_values; +}; + +class SVGRect final : public SVGProperty { +public: + explicit SVGRect(PropertyID id) + : SVGProperty(id) + , m_value(Rect::Invalid) + {} + + const Rect& value() const { return m_value; } + bool parse(std::string_view input) final; + +private: + Rect m_value; +}; + +class SVGTransform final : public SVGProperty { +public: + explicit SVGTransform(PropertyID id) + : SVGProperty(id) + {} + + const Transform& value() const { return m_value; } + bool parse(std::string_view input) final; + +private: + Transform m_value; +}; + +class SVGPreserveAspectRatio final : public SVGProperty { +public: + enum class AlignType { + None, + xMinYMin, + xMidYMin, + xMaxYMin, + xMinYMid, + xMidYMid, + xMaxYMid, + xMinYMax, + xMidYMax, + xMaxYMax + }; + + enum class MeetOrSlice { + Meet, + Slice + }; + + explicit SVGPreserveAspectRatio(PropertyID id) + : SVGProperty(id) + {} + + AlignType alignType() const { return m_alignType; } + MeetOrSlice meetOrSlice() const { return m_meetOrSlice; } + bool parse(std::string_view input) final; + + Rect getClipRect(const Rect& viewBoxRect, const Size& viewportSize) const; + Transform getTransform(const Rect& viewBoxRect, const Size& viewportSize) const; + void transformRect(Rect& dstRect, Rect& srcRect) const; + +private: + AlignType m_alignType = AlignType::xMidYMid; + MeetOrSlice m_meetOrSlice = MeetOrSlice::Meet; +}; + +} // namespace lunasvg + +#endif // LUNASVG_SVGPROPERTY_H diff --git a/vendor/lunasvg/source/svgrenderstate.cpp b/vendor/lunasvg/source/svgrenderstate.cpp new file mode 100644 index 0000000..550d389 --- /dev/null +++ b/vendor/lunasvg/source/svgrenderstate.cpp @@ -0,0 +1,61 @@ +#include "svgrenderstate.h" + +namespace lunasvg { + +SVGBlendInfo::SVGBlendInfo(const SVGElement* element) + : m_clipper(element->clipper()) + , m_masker(element->masker()) + , m_opacity(element->opacity()) +{ +} + +bool SVGBlendInfo::requiresCompositing(SVGRenderMode mode) const +{ + return (m_clipper && m_clipper->requiresMasking()) || (mode == SVGRenderMode::Painting && (m_masker || m_opacity < 1.f)); +} + +bool SVGRenderState::hasCycleReference(const SVGElement* element) const +{ + auto current = this; + do { + if(element == current->element()) + return true; + current = current->parent(); + } while(current); + return false; +} + +void SVGRenderState::beginGroup(const SVGBlendInfo& blendInfo) +{ + auto requiresCompositing = blendInfo.requiresCompositing(m_mode); + if(requiresCompositing) { + auto boundingBox = m_currentTransform.mapRect(m_element->paintBoundingBox()); + boundingBox.intersect(m_canvas->extents()); + m_canvas = Canvas::create(boundingBox); + } else { + m_canvas->save(); + } + + if(!requiresCompositing && blendInfo.clipper()) { + blendInfo.clipper()->applyClipPath(*this); + } +} + +void SVGRenderState::endGroup(const SVGBlendInfo& blendInfo) +{ + if(m_canvas == m_parent->canvas()) { + m_canvas->restore(); + return; + } + + auto opacity = m_mode == SVGRenderMode::Clipping ? 1.f : blendInfo.opacity(); + if(blendInfo.clipper()) + blendInfo.clipper()->applyClipMask(*this); + if(m_mode == SVGRenderMode::Painting && blendInfo.masker()) { + blendInfo.masker()->applyMask(*this); + } + + m_parent->m_canvas->blendCanvas(*m_canvas, BlendMode::Src_Over, opacity); +} + +} // namespace lunasvg diff --git a/vendor/lunasvg/source/svgrenderstate.h b/vendor/lunasvg/source/svgrenderstate.h new file mode 100644 index 0000000..1e0d434 --- /dev/null +++ b/vendor/lunasvg/source/svgrenderstate.h @@ -0,0 +1,69 @@ +#ifndef LUNASVG_SVGRENDERSTATE_H +#define LUNASVG_SVGRENDERSTATE_H + +#include "svgelement.h" + +namespace lunasvg { + +enum class SVGRenderMode { + Painting, + Clipping +}; + +class SVGBlendInfo { +public: + explicit SVGBlendInfo(const SVGElement* element); + SVGBlendInfo(const SVGClipPathElement* clipper, const SVGMaskElement* masker, float opacity) + : m_clipper(clipper), m_masker(masker), m_opacity(opacity) + {} + + bool requiresCompositing(SVGRenderMode mode) const; + const SVGClipPathElement* clipper() const { return m_clipper; } + const SVGMaskElement* masker() const { return m_masker; } + float opacity() const { return m_opacity; } + +private: + const SVGClipPathElement* m_clipper; + const SVGMaskElement* m_masker; + const float m_opacity; +}; + +class SVGRenderState { +public: + SVGRenderState(const SVGElement* element, const SVGRenderState& parent, const Transform& localTransform) + : m_element(element), m_parent(&parent), m_currentTransform(parent.currentTransform() * localTransform) + , m_mode(parent.mode()), m_canvas(parent.canvas()) + {} + + SVGRenderState(const SVGElement* element, const SVGRenderState* parent, const Transform& currentTransform, SVGRenderMode mode, std::shared_ptr canvas) + : m_element(element), m_parent(parent), m_currentTransform(currentTransform), m_mode(mode), m_canvas(std::move(canvas)) + {} + + Canvas& operator*() const { return *m_canvas; } + Canvas* operator->() const { return &*m_canvas; } + + const SVGElement* element() const { return m_element; } + const SVGRenderState* parent() const { return m_parent; } + const Transform& currentTransform() const { return m_currentTransform; } + const SVGRenderMode mode() const { return m_mode; } + const std::shared_ptr& canvas() const { return m_canvas; } + + Rect fillBoundingBox() const { return m_element->fillBoundingBox(); } + Rect paintBoundingBox() const { return m_element->paintBoundingBox(); } + + bool hasCycleReference(const SVGElement* element) const; + + void beginGroup(const SVGBlendInfo& blendInfo); + void endGroup(const SVGBlendInfo& blendInfo); + +private: + const SVGElement* m_element; + const SVGRenderState* m_parent; + const Transform m_currentTransform; + const SVGRenderMode m_mode; + std::shared_ptr m_canvas; +}; + +} // namespace lunasvg + +#endif // LUNASVG_SVGRENDERSTATE_H diff --git a/vendor/lunasvg/source/svgtextelement.cpp b/vendor/lunasvg/source/svgtextelement.cpp new file mode 100644 index 0000000..6d43e57 --- /dev/null +++ b/vendor/lunasvg/source/svgtextelement.cpp @@ -0,0 +1,579 @@ +#include "svgtextelement.h" +#include "svglayoutstate.h" +#include "svgrenderstate.h" + +#include + +namespace lunasvg { + +inline const SVGTextNode* toSVGTextNode(const SVGNode* node) +{ + assert(node && node->isTextNode()); + return static_cast(node); +} + +inline const SVGTextPositioningElement* toSVGTextPositioningElement(const SVGNode* node) +{ + assert(node && node->isTextPositioningElement()); + return static_cast(node); +} + +static AlignmentBaseline resolveDominantBaseline(const SVGTextPositioningElement* element) +{ + switch(element->dominant_baseline()) { + case DominantBaseline::Auto: + if(element->isVerticalWritingMode()) + return AlignmentBaseline::Central; + return AlignmentBaseline::Alphabetic; + case DominantBaseline::UseScript: + case DominantBaseline::NoChange: + case DominantBaseline::ResetSize: + return AlignmentBaseline::Auto; + case DominantBaseline::Ideographic: + return AlignmentBaseline::Ideographic; + case DominantBaseline::Alphabetic: + return AlignmentBaseline::Alphabetic; + case DominantBaseline::Hanging: + return AlignmentBaseline::Hanging; + case DominantBaseline::Mathematical: + return AlignmentBaseline::Mathematical; + case DominantBaseline::Central: + return AlignmentBaseline::Central; + case DominantBaseline::Middle: + return AlignmentBaseline::Middle; + case DominantBaseline::TextAfterEdge: + return AlignmentBaseline::TextAfterEdge; + case DominantBaseline::TextBeforeEdge: + return AlignmentBaseline::TextBeforeEdge; + default: + assert(false); + } + + return AlignmentBaseline::Auto; +} + +static float calculateBaselineOffset(const SVGTextPositioningElement* element) +{ + auto offset = element->baseline_offset(); + auto parent = element->parentElement(); + while(parent->isTextPositioningElement()) { + offset += toSVGTextPositioningElement(parent)->baseline_offset(); + parent = parent->parentElement(); + } + + auto baseline = element->alignment_baseline(); + if(baseline == AlignmentBaseline::Auto || baseline == AlignmentBaseline::Baseline) { + baseline = resolveDominantBaseline(element); + } + + const auto& font = element->font(); + switch(baseline) { + case AlignmentBaseline::BeforeEdge: + case AlignmentBaseline::TextBeforeEdge: + offset -= font.ascent(); + break; + case AlignmentBaseline::Middle: + offset -= font.xHeight() / 2.f; + break; + case AlignmentBaseline::Central: + offset -= (font.ascent() + font.descent()) / 2.f; + break; + case AlignmentBaseline::AfterEdge: + case AlignmentBaseline::TextAfterEdge: + case AlignmentBaseline::Ideographic: + offset -= font.descent(); + break; + case AlignmentBaseline::Hanging: + offset -= font.ascent() * 8.f / 10.f; + break; + case AlignmentBaseline::Mathematical: + offset -= font.ascent() / 2.f; + break; + default: + break; + } + + return offset; +} + +static bool needsTextAnchorAdjustment(const SVGTextPositioningElement* element) +{ + auto direction = element->direction(); + switch(element->text_anchor()) { + case TextAnchor::Start: + return direction == Direction::Rtl; + case TextAnchor::Middle: + return true; + case TextAnchor::End: + return direction == Direction::Ltr; + default: + assert(false); + } + + return false; +} + +static float calculateTextAnchorOffset(const SVGTextPositioningElement* element, float width) +{ + auto direction = element->direction(); + switch(element->text_anchor()) { + case TextAnchor::Start: + if(direction == Direction::Ltr) + return 0.f; + return -width; + case TextAnchor::Middle: + return -width / 2.f; + case TextAnchor::End: + if(direction == Direction::Ltr) + return -width; + return 0.f; + default: + assert(false); + } + + return 0.f; +} + +using SVGTextFragmentIterator = SVGTextFragmentList::iterator; + +static float calculateTextChunkLength(SVGTextFragmentIterator begin, SVGTextFragmentIterator end, bool isVerticalText) +{ + float chunkLength = 0; + const SVGTextFragment* lastFragment = nullptr; + for(auto it = begin; it != end; ++it) { + const SVGTextFragment& fragment = *it; + chunkLength += isVerticalText ? fragment.height : fragment.width; + if(!lastFragment) { + lastFragment = &fragment; + continue; + } + + if(isVerticalText) { + chunkLength += fragment.y - (lastFragment->y + lastFragment->height); + } else { + chunkLength += fragment.x - (lastFragment->x + lastFragment->width); + } + + lastFragment = &fragment; + } + + return chunkLength; +} + +static void handleTextChunk(SVGTextFragmentIterator begin, SVGTextFragmentIterator end) +{ + const SVGTextFragment& firstFragment = *begin; + const auto isVerticalText = firstFragment.element->isVerticalWritingMode(); + if(firstFragment.element->hasAttribute(PropertyID::TextLength)) { + LengthContext lengthContext(firstFragment.element); + auto textLength = lengthContext.valueForLength(firstFragment.element->textLength()); + auto chunkLength = calculateTextChunkLength(begin, end, isVerticalText); + if(textLength > 0.f && chunkLength > 0.f) { + size_t numCharacters = 0; + for(auto it = begin; it != end; ++it) { + const SVGTextFragment& fragment = *it; + numCharacters += fragment.length; + } + + if(firstFragment.element->lengthAdjust() == LengthAdjust::SpacingAndGlyphs) { + auto textLengthScale = textLength / chunkLength; + auto lengthAdjustTransform = Transform::translated(firstFragment.x, firstFragment.y); + if(isVerticalText) { + lengthAdjustTransform.scale(1.f, textLengthScale); + } else { + lengthAdjustTransform.scale(textLengthScale, 1.f); + } + + lengthAdjustTransform.translate(-firstFragment.x, -firstFragment.y); + for(auto it = begin; it != end; ++it) { + SVGTextFragment& fragment = *it; + fragment.lengthAdjustTransform = lengthAdjustTransform; + } + } else if(numCharacters > 1) { + assert(firstFragment.element->lengthAdjust() == LengthAdjust::Spacing); + size_t characterOffset = 0; + auto textLengthShift = (textLength - chunkLength) / (numCharacters - 1); + for(auto it = begin; it != end; ++it) { + SVGTextFragment& fragment = *it; + if(isVerticalText) { + fragment.y += textLengthShift * characterOffset; + } else { + fragment.x += textLengthShift * characterOffset; + } + + characterOffset += fragment.length; + } + } + } + } + + if(needsTextAnchorAdjustment(firstFragment.element)) { + auto chunkLength = calculateTextChunkLength(begin, end, isVerticalText); + auto chunkOffset = calculateTextAnchorOffset(firstFragment.element, chunkLength); + for(auto it = begin; it != end; ++it) { + SVGTextFragment& fragment = *it; + if(isVerticalText) { + fragment.y += chunkOffset; + } else { + fragment.x += chunkOffset; + } + } + } +} + +SVGTextFragmentsBuilder::SVGTextFragmentsBuilder(std::u32string& text, SVGTextFragmentList& fragments) + : m_text(text), m_fragments(fragments) +{ + m_text.clear(); + m_fragments.clear(); +} + +void SVGTextFragmentsBuilder::build(const SVGTextElement* textElement) +{ + handleElement(textElement); + for(const auto& position : m_textPositions) { + fillCharacterPositions(position); + } + + std::u32string_view wholeText(m_text); + for(const auto& textPosition : m_textPositions) { + if(!textPosition.node->isTextNode()) + continue; + auto element = toSVGTextPositioningElement(textPosition.node->parentElement()); + const auto isVerticalText = element->isVerticalWritingMode(); + const auto isUprightText = element->isUprightTextOrientation(); + const auto& font = element->font(); + + SVGTextFragment fragment(element); + auto recordTextFragment = [&](auto startOffset, auto endOffset) { + auto text = wholeText.substr(startOffset, endOffset - startOffset); + fragment.offset = startOffset; + fragment.length = text.length(); + fragment.width = font.measureText(text); + fragment.height = font.height() + font.lineGap(); + if(isVerticalText) { + m_y += isUprightText ? fragment.height : fragment.width; + } else { + m_x += fragment.width; + } + + m_fragments.push_back(fragment); + }; + + auto needsTextLengthSpacing = element->lengthAdjust() == LengthAdjust::Spacing && element->hasAttribute(PropertyID::TextLength); + auto baselineOffset = calculateBaselineOffset(element); + auto startOffset = textPosition.startOffset; + auto textOffset = textPosition.startOffset; + auto didStartTextFragment = false; + auto applySpacingToNextCharacter = false; + auto lastCharacter = 0u; + auto lastAngle = 0.f; + while(textOffset < textPosition.endOffset) { + SVGCharacterPosition characterPosition; + if(auto it = m_characterPositions.find(m_characterOffset); it != m_characterPositions.end()) { + characterPosition = it->second; + } + + auto currentCharacter = wholeText.at(textOffset); + auto angle = characterPosition.rotate.value_or(0); + auto dx = characterPosition.dx.value_or(0); + auto dy = characterPosition.dy.value_or(0); + + auto shouldStartNewFragment = needsTextLengthSpacing || isVerticalText || applySpacingToNextCharacter + || characterPosition.x || characterPosition.y || dx || dy || angle || angle != lastAngle; + if(shouldStartNewFragment && didStartTextFragment) { + recordTextFragment(startOffset, textOffset); + applySpacingToNextCharacter = false; + startOffset = textOffset; + } + + auto startsNewTextChunk = (characterPosition.x || characterPosition.y) && textOffset == textPosition.startOffset; + if(startsNewTextChunk || shouldStartNewFragment || !didStartTextFragment) { + m_x = dx + characterPosition.x.value_or(m_x); + m_y = dy + characterPosition.y.value_or(m_y); + fragment.x = isVerticalText ? m_x + baselineOffset : m_x; + fragment.y = isVerticalText ? m_y : m_y - baselineOffset; + fragment.angle = angle; + if(isVerticalText) { + if(isUprightText) { + fragment.y += font.height(); + } else { + fragment.angle += 90.f; + } + } + + fragment.startsNewTextChunk = startsNewTextChunk; + didStartTextFragment = true; + } + + auto spacing = element->letter_spacing(); + if(currentCharacter && lastCharacter && element->word_spacing()) { + if(currentCharacter == ' ' && lastCharacter != ' ') { + spacing += element->word_spacing(); + } + } + + if(spacing) { + applySpacingToNextCharacter = true; + if(isVerticalText) { + m_y += spacing; + } else { + m_x += spacing; + } + } + + lastAngle = angle; + lastCharacter = currentCharacter; + ++textOffset; + ++m_characterOffset; + } + + recordTextFragment(startOffset, textOffset); + } + + if(m_fragments.empty()) + return; + auto it = m_fragments.begin(); + auto begin = m_fragments.begin(); + auto end = m_fragments.end(); + for(++it; it != end; ++it) { + const SVGTextFragment& fragment = *it; + if(!fragment.startsNewTextChunk) + continue; + handleTextChunk(begin, it); + begin = it; + } + + handleTextChunk(begin, it); +} + +void SVGTextFragmentsBuilder::handleText(const SVGTextNode* node) +{ + const auto& text = node->data(); + if(text.empty()) + return; + auto element = toSVGTextPositioningElement(node->parentElement()); + const auto startOffset = m_text.length(); + uint32_t lastCharacter = ' '; + if(!m_text.empty()) { + lastCharacter = m_text.back(); + } + + plutovg_text_iterator_t it; + plutovg_text_iterator_init(&it, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF8); + while(plutovg_text_iterator_has_next(&it)) { + auto currentCharacter = plutovg_text_iterator_next(&it); + if(currentCharacter == '\t' || currentCharacter == '\n' || currentCharacter == '\r') + currentCharacter = ' '; + if(currentCharacter == ' ' && lastCharacter == ' ' && element->white_space() == WhiteSpace::Default) + continue; + m_text.push_back(currentCharacter); + lastCharacter = currentCharacter; + } + + if(startOffset < m_text.length()) { + m_textPositions.emplace_back(node, startOffset, m_text.length()); + } +} + +void SVGTextFragmentsBuilder::handleElement(const SVGTextPositioningElement* element) +{ + if(element->isDisplayNone()) + return; + const auto itemIndex = m_textPositions.size(); + m_textPositions.emplace_back(element, m_text.length(), m_text.length()); + for(const auto& child : element->children()) { + if(child->isTextNode()) { + handleText(toSVGTextNode(child.get())); + } else if(child->isTextPositioningElement()) { + handleElement(toSVGTextPositioningElement(child.get())); + } + } + + auto& position = m_textPositions[itemIndex]; + assert(position.node == element); + position.endOffset = m_text.length(); +} + +void SVGTextFragmentsBuilder::fillCharacterPositions(const SVGTextPosition& position) +{ + if(!position.node->isTextPositioningElement()) + return; + auto element = toSVGTextPositioningElement(position.node); + const auto& xList = element->x(); + const auto& yList = element->y(); + const auto& dxList = element->dx(); + const auto& dyList = element->dy(); + const auto& rotateList = element->rotate(); + + auto xListSize = xList.size(); + auto yListSize = yList.size(); + auto dxListSize = dxList.size(); + auto dyListSize = dyList.size(); + auto rotateListSize = rotateList.size(); + if(!xListSize && !yListSize && !dxListSize && !dyListSize && !rotateListSize) { + return; + } + + LengthContext lengthContext(element); + std::optional lastRotation; + for(auto offset = position.startOffset; offset < position.endOffset; ++offset) { + auto index = offset - position.startOffset; + if(index >= xListSize && index >= yListSize && index >= dxListSize && index >= dyListSize && index >= rotateListSize) + break; + auto& characterPosition = m_characterPositions[offset]; + if(index < xListSize) + characterPosition.x = lengthContext.valueForLength(xList[index], LengthDirection::Horizontal); + if(index < yListSize) + characterPosition.y = lengthContext.valueForLength(yList[index], LengthDirection::Vertical); + if(index < dxListSize) + characterPosition.dx = lengthContext.valueForLength(dxList[index], LengthDirection::Horizontal); + if(index < dyListSize) + characterPosition.dy = lengthContext.valueForLength(dyList[index], LengthDirection::Vertical); + if(index < rotateListSize) { + characterPosition.rotate = rotateList[index]; + lastRotation = characterPosition.rotate; + } + } + + if(lastRotation == std::nullopt) + return; + auto offset = position.startOffset + rotateList.size(); + while(offset < position.endOffset) { + m_characterPositions[offset++].rotate = lastRotation; + } +} + +SVGTextPositioningElement::SVGTextPositioningElement(Document* document, ElementID id) + : SVGGraphicsElement(document, id) + , m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow) + , m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow) + , m_dx(PropertyID::Dx, LengthDirection::Horizontal, LengthNegativeMode::Allow) + , m_dy(PropertyID::Dy, LengthDirection::Vertical, LengthNegativeMode::Allow) + , m_rotate(PropertyID::Rotate) + , m_textLength(PropertyID::TextLength, LengthDirection::Horizontal, LengthNegativeMode::Forbid) + , m_lengthAdjust(PropertyID::LengthAdjust, LengthAdjust::Spacing) +{ + addProperty(m_x); + addProperty(m_y); + addProperty(m_dx); + addProperty(m_dy); + addProperty(m_rotate); + addProperty(m_textLength); + addProperty(m_lengthAdjust); +} + +void SVGTextPositioningElement::layoutElement(const SVGLayoutState& state) +{ + m_font = state.font(); + m_fill = getPaintServer(state.fill(), state.fill_opacity()); + m_stroke = getPaintServer(state.stroke(), state.stroke_opacity()); + SVGGraphicsElement::layoutElement(state); + + LengthContext lengthContext(this); + m_stroke_width = lengthContext.valueForLength(state.stroke_width(), LengthDirection::Diagonal); + m_letter_spacing = lengthContext.valueForLength(state.letter_spacing(), LengthDirection::Diagonal); + m_word_spacing = lengthContext.valueForLength(state.word_spacing(), LengthDirection::Diagonal); + + m_baseline_offset = convertBaselineOffset(state.baseline_shift()); + m_alignment_baseline = state.alignment_baseline(); + m_dominant_baseline = state.dominant_baseline(); + m_text_anchor = state.text_anchor(); + m_white_space = state.white_space(); + m_writing_mode = state.writing_mode(); + m_text_orientation = state.text_orientation(); + m_direction = state.direction(); +} + +float SVGTextPositioningElement::convertBaselineOffset(const BaselineShift& baselineShift) const +{ + if(baselineShift.type() == BaselineShift::Type::Baseline) + return 0.f; + if(baselineShift.type() == BaselineShift::Type::Sub) + return -m_font.height() / 2.f; + if(baselineShift.type() == BaselineShift::Type::Super) { + return m_font.height() / 2.f; + } + + const auto& length = baselineShift.length(); + if(length.units() == LengthUnits::Percent) + return length.value() * m_font.size() / 100.f; + if(length.units() == LengthUnits::Ex) + return length.value() * m_font.size() / 2.f; + if(length.units() == LengthUnits::Em) + return length.value() * m_font.size(); + return length.value(); +} + +SVGTSpanElement::SVGTSpanElement(Document* document) + : SVGTextPositioningElement(document, ElementID::Tspan) +{ +} + +SVGTextElement::SVGTextElement(Document* document) + : SVGTextPositioningElement(document, ElementID::Text) +{ +} + +void SVGTextElement::layout(SVGLayoutState& state) +{ + SVGTextPositioningElement::layout(state); + SVGTextFragmentsBuilder(m_text, m_fragments).build(this); +} + +void SVGTextElement::render(SVGRenderState& state) const +{ + if(m_fragments.empty() || isVisibilityHidden() || isDisplayNone()) + return; + SVGBlendInfo blendInfo(this); + SVGRenderState newState(this, state, localTransform()); + newState.beginGroup(blendInfo); + if(newState.mode() == SVGRenderMode::Clipping) { + newState->setColor(Color::White); + } + + std::u32string_view wholeText(m_text); + for(const auto& fragment : m_fragments) { + if(fragment.element->isVisibilityHidden()) + continue; + auto transform = newState.currentTransform() * Transform::rotated(fragment.angle, fragment.x, fragment.y) * fragment.lengthAdjustTransform; + auto text = wholeText.substr(fragment.offset, fragment.length); + auto origin = Point(fragment.x, fragment.y); + + const auto& font = fragment.element->font(); + if(newState.mode() == SVGRenderMode::Clipping) { + newState->fillText(text, font, origin, transform); + } else { + const auto& fill = fragment.element->fill(); + const auto& stroke = fragment.element->stroke(); + auto stroke_width = fragment.element->stroke_width(); + if(fill.applyPaint(newState)) + newState->fillText(text, font, origin, transform); + if(stroke.applyPaint(newState)) { + newState->strokeText(text, stroke_width, font, origin, transform); + } + } + } + + newState.endGroup(blendInfo); +} + +Rect SVGTextElement::boundingBox(bool includeStroke) const +{ + auto boundingBox = Rect::Invalid; + for(const auto& fragment : m_fragments) { + const auto& font = fragment.element->font(); + const auto& stroke = fragment.element->stroke(); + auto fragmentTranform = Transform::rotated(fragment.angle, fragment.x, fragment.y) * fragment.lengthAdjustTransform; + auto fragmentRect = Rect(fragment.x, fragment.y - font.ascent(), fragment.width, fragment.height); + if(includeStroke && stroke.isRenderable()) + fragmentRect.inflate(fragment.element->stroke_width() / 2.f); + boundingBox.unite(fragmentTranform.mapRect(fragmentRect)); + } + + if(!boundingBox.isValid()) + boundingBox = Rect::Empty; + return boundingBox; +} + +} // namespace lunasvg diff --git a/vendor/lunasvg/source/svgtextelement.h b/vendor/lunasvg/source/svgtextelement.h new file mode 100644 index 0000000..71b0f11 --- /dev/null +++ b/vendor/lunasvg/source/svgtextelement.h @@ -0,0 +1,155 @@ +#ifndef LUNASVG_SVGTEXTELEMENT_H +#define LUNASVG_SVGTEXTELEMENT_H + +#include "svgelement.h" + +#include + +namespace lunasvg { + +class SVGTextPositioningElement; +class SVGTextElement; + +struct SVGCharacterPosition { + std::optional x; + std::optional y; + std::optional dx; + std::optional dy; + std::optional rotate; +}; + +using SVGCharacterPositions = std::map; + +struct SVGTextPosition { + SVGTextPosition(const SVGNode* node, size_t startOffset, size_t endOffset) + : node(node), startOffset(startOffset), endOffset(endOffset) + {} + + const SVGNode* node; + size_t startOffset; + size_t endOffset; +}; + +using SVGTextPositionList = std::vector; + +struct SVGTextFragment { + explicit SVGTextFragment(const SVGTextPositioningElement* element) : element(element) {} + const SVGTextPositioningElement* element; + Transform lengthAdjustTransform; + size_t offset = 0; + size_t length = 0; + bool startsNewTextChunk = false; + float x = 0; + float y = 0; + float width = 0; + float height = 0; + float angle = 0; +}; + +using SVGTextFragmentList = std::vector; + +class SVGTextFragmentsBuilder { +public: + SVGTextFragmentsBuilder(std::u32string& text, SVGTextFragmentList& fragments); + + void build(const SVGTextElement* textElement); + +private: + void handleText(const SVGTextNode* node); + void handleElement(const SVGTextPositioningElement* element); + void fillCharacterPositions(const SVGTextPosition& position); + std::u32string& m_text; + SVGTextFragmentList& m_fragments; + SVGCharacterPositions m_characterPositions; + SVGTextPositionList m_textPositions; + size_t m_characterOffset = 0; + float m_x = 0; + float m_y = 0; +}; + +class SVGTextPositioningElement : public SVGGraphicsElement { +public: + SVGTextPositioningElement(Document* document, ElementID id); + + bool isTextPositioningElement() const final { return true; } + + const LengthList& x() const { return m_x.values(); } + const LengthList& y() const { return m_y.values(); } + const LengthList& dx() const { return m_dx.values(); } + const LengthList& dy() const { return m_dy.values(); } + const NumberList& rotate() const { return m_rotate.values(); } + + const SVGLength& textLength() const { return m_textLength; } + LengthAdjust lengthAdjust() const { return m_lengthAdjust.value(); } + + const Font& font() const { return m_font; } + const SVGPaintServer& fill() const { return m_fill; } + const SVGPaintServer& stroke() const { return m_stroke; } + + bool isVerticalWritingMode() const { return m_writing_mode == WritingMode::Vertical; } + bool isUprightTextOrientation() const { return m_text_orientation == TextOrientation::Upright; } + + float stroke_width() const { return m_stroke_width; } + float letter_spacing() const { return m_letter_spacing; } + float word_spacing() const { return m_word_spacing; } + float baseline_offset() const { return m_baseline_offset; } + AlignmentBaseline alignment_baseline() const { return m_alignment_baseline; } + DominantBaseline dominant_baseline() const { return m_dominant_baseline; } + TextAnchor text_anchor() const { return m_text_anchor; } + WhiteSpace white_space() const { return m_white_space; } + Direction direction() const { return m_direction; } + + void layoutElement(const SVGLayoutState& state) override; + +private: + float convertBaselineOffset(const BaselineShift& baselineShift) const; + SVGLengthList m_x; + SVGLengthList m_y; + SVGLengthList m_dx; + SVGLengthList m_dy; + SVGNumberList m_rotate; + + SVGLength m_textLength; + SVGEnumeration m_lengthAdjust; + + Font m_font; + SVGPaintServer m_fill; + SVGPaintServer m_stroke; + + float m_stroke_width = 1.f; + float m_letter_spacing = 0.f; + float m_word_spacing = 0.f; + float m_baseline_offset = 0.f; + AlignmentBaseline m_alignment_baseline = AlignmentBaseline::Auto; + DominantBaseline m_dominant_baseline = DominantBaseline::Auto; + TextAnchor m_text_anchor = TextAnchor::Start; + WhiteSpace m_white_space = WhiteSpace::Default; + WritingMode m_writing_mode = WritingMode::Horizontal; + TextOrientation m_text_orientation = TextOrientation::Mixed; + Direction m_direction = Direction::Ltr; +}; + +class SVGTSpanElement final : public SVGTextPositioningElement { +public: + SVGTSpanElement(Document* document); +}; + +class SVGTextElement final : public SVGTextPositioningElement { +public: + SVGTextElement(Document* document); + + Rect fillBoundingBox() const final { return boundingBox(false); } + Rect strokeBoundingBox() const final { return boundingBox(true); } + + void layout(SVGLayoutState& state) final; + void render(SVGRenderState& state) const final; + +private: + Rect boundingBox(bool includeStroke) const; + SVGTextFragmentList m_fragments; + std::u32string m_text; +}; + +} // namespace lunasvg + +#endif // LUNASVG_SVGTEXTELEMENT_H