pax_global_header00006660000000000000000000000064150114016020014500gustar00rootroot0000000000000052 comment=31f7d2675416cd777c8e86220b035364873b2a8b plutosvg-0.0.7/000077500000000000000000000000001501140160200133675ustar00rootroot00000000000000plutosvg-0.0.7/.github/000077500000000000000000000000001501140160200147275ustar00rootroot00000000000000plutosvg-0.0.7/.github/FUNDING.yml000066400000000000000000000001661501140160200165470ustar00rootroot00000000000000# These are supported funding model platforms buy_me_a_coffee: sammycage custom: ['https://www.paypal.me/sammycage'] plutosvg-0.0.7/.github/workflows/000077500000000000000000000000001501140160200167645ustar00rootroot00000000000000plutosvg-0.0.7/.github/workflows/main.yml000066400000000000000000000024411501140160200204340ustar00rootroot00000000000000name: Build on: [push, pull_request] jobs: linux: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: submodules: true - name: Build with meson run: | pip install meson pip install ninja meson setup build meson compile -C build - name: Build with cmake run: | cmake . cmake --build . macos: runs-on: macos-latest steps: - name: Checkout code uses: actions/checkout@v4 with: submodules: true - name: Build with meson run: | pip install meson pip install ninja meson setup build meson compile -C build - name: Build with cmake run: | cmake . cmake --build . windows: runs-on: windows-latest steps: - name: Checkout code uses: actions/checkout@v4 with: submodules: true - name: Build with meson run: | pip install meson pip install ninja meson setup build meson compile -C build - name: Build with cmake run: | cmake . cmake --build . plutosvg-0.0.7/.gitignore000066400000000000000000000000161501140160200153540ustar00rootroot00000000000000/build *.user plutosvg-0.0.7/.gitmodules000066400000000000000000000001261501140160200155430ustar00rootroot00000000000000[submodule "plutovg"] path = plutovg url = https://github.com/sammycage/plutovg.git plutosvg-0.0.7/CMakeLists.txt000066400000000000000000000077241501140160200161410ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.15) set(PLUTOSVG_VERSION_MAJOR 0) set(PLUTOSVG_VERSION_MINOR 0) set(PLUTOSVG_VERSION_MICRO 7) project(plutosvg LANGUAGES C VERSION ${PLUTOSVG_VERSION_MAJOR}.${PLUTOSVG_VERSION_MINOR}.${PLUTOSVG_VERSION_MICRO}) find_package(plutovg 1.0.0 QUIET) if(NOT plutovg_FOUND) add_subdirectory(plutovg) endif() set(plutosvg_sources source/plutosvg.h source/plutosvg-ft.h source/plutosvg.c ) add_library(plutosvg ${plutosvg_sources}) add_library(plutosvg::plutosvg ALIAS plutosvg) set_target_properties(plutosvg PROPERTIES SOVERSION ${PLUTOSVG_VERSION_MAJOR} C_VISIBILITY_PRESET hidden C_STANDARD_REQUIRED ON C_STANDARD 99 ) target_include_directories(plutosvg PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/source ) target_include_directories(plutosvg PUBLIC $ $ ) target_link_libraries(plutosvg PUBLIC plutovg::plutovg) find_library(MATH_LIBRARY m) if(MATH_LIBRARY) target_link_libraries(plutosvg PRIVATE m) endif() target_compile_definitions(plutosvg PRIVATE PLUTOSVG_BUILD) if(NOT BUILD_SHARED_LIBS) target_compile_definitions(plutosvg PUBLIC PLUTOSVG_BUILD_STATIC) endif() option(PLUTOSVG_ENABLE_FREETYPE "Enable Freetype integration" OFF) if(PLUTOSVG_ENABLE_FREETYPE) find_package(Freetype 2.12 REQUIRED) target_compile_definitions(plutosvg PUBLIC PLUTOSVG_HAS_FREETYPE) target_link_libraries(plutosvg PUBLIC Freetype::Freetype) endif() include(GNUInstallDirs) include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/plutosvgConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/plutosvgConfig.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/plutosvg ) write_basic_package_version_file(plutosvgConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/source/plutosvg.h ${CMAKE_CURRENT_SOURCE_DIR}/source/plutosvg-ft.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/plutosvg ) install(TARGETS plutosvg EXPORT plutosvgTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) install(EXPORT plutosvgTargets FILE plutosvgTargets.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/plutosvg NAMESPACE plutosvg:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plutosvgConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/plutosvgConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/plutosvg ) export(EXPORT plutosvgTargets FILE ${CMAKE_CURRENT_BINARY_DIR}/plutosvgTargets.cmake NAMESPACE plutosvg:: ) file(RELATIVE_PATH plutosvg_pc_prefix_relative "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/pkgconfig" "${CMAKE_INSTALL_PREFIX}" ) set(plutosvg_pc_cflags "") set(plutosvg_pc_libs_private "") set(plutosvg_pc_requires "") if(MATH_LIBRARY) string(APPEND plutosvg_pc_libs_private " -lm") endif() if(NOT BUILD_SHARED_LIBS) string(APPEND plutosvg_pc_cflags " -DPLUTOSVG_BUILD_STATIC") endif() if(PLUTOSVG_ENABLE_FREETYPE) string(APPEND plutosvg_pc_cflags " -DPLUTOSVG_HAS_FREETYPE") string(APPEND plutosvg_pc_requires " freetype2 >= 2.12") endif() string(CONFIGURE [[ prefix=${pcfiledir}/@plutosvg_pc_prefix_relative@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ Name: PlutoSVG Description: Tiny SVG rendering library in C Version: @PROJECT_VERSION@ Requires: plutovg@plutosvg_pc_requires@ Cflags: -I${includedir}/plutosvg@plutosvg_pc_cflags@ Libs: -L${libdir} -lplutosvg Libs.private:@plutosvg_pc_libs_private@ ]] plutosvg_pc @ONLY) file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/plutosvg.pc" "${plutosvg_pc}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/plutosvg.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" ) option(PLUTOSVG_BUILD_EXAMPLES "Build examples" ON) if(PLUTOSVG_BUILD_EXAMPLES) add_subdirectory(examples) endif() plutosvg-0.0.7/LICENSE000066400000000000000000000021201501140160200143670ustar00rootroot00000000000000MIT License 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. plutosvg-0.0.7/README.md000066400000000000000000000037771501140160200146640ustar00rootroot00000000000000![emoji-collection.png](https://github.com/user-attachments/assets/a5de9b70-39a8-4a15-a012-22ab3cb93054) # PlutoSVG PlutoSVG is a compact and efficient SVG rendering library written in C. It is specifically designed for parsing and rendering SVG documents embedded in OpenType fonts, providing an optimal balance between speed and minimal memory usage. It is also suitable for rendering scalable icons. ## Basic Usage ```c #include #include int main(void) { plutosvg_document_t* document = plutosvg_document_load_from_file("camera.svg", -1, -1); if(document == NULL) { printf("Unable to load: camera.svg\n"); return -1; } plutovg_surface_t* surface = plutosvg_document_render_to_surface(document, NULL, -1, -1, NULL, NULL, NULL); plutovg_surface_write_to_png(surface, "camera.png"); plutosvg_document_destroy(document); plutovg_surface_destroy(surface); return 0; } ``` ![camera.png](https://github.com/sammycage/plutosvg/blob/master/camera.png) ## Integrating with FreeType ```c #include #include #include FT_FREETYPE_H #include FT_MODULE_H int main(void) { FT_Library library; // Initialize the FreeType library if(FT_Init_FreeType(&library)) { // Handle error return -1; } // Set PlutoSVG hooks for the SVG module if(FT_Property_Set(library, "ot-svg", "svg-hooks", &plutosvg_ft_hooks)) { // Handle error return -1; } // Your code here // Clean up FT_Done_FreeType(library); return 0; } ``` ## Installation Follow the steps below to install PlutoSVG using either [Meson](https://mesonbuild.com/) or [CMake](https://cmake.org/). ### Using Meson ```bash git clone https://github.com/sammycage/plutosvg.git cd plutosvg meson setup build meson compile -C build meson install -C build ``` ### Using CMake ```bash git clone --recursive https://github.com/sammycage/plutosvg.git cd plutosvg cmake -B build . cmake --build build cmake --install build ``` plutosvg-0.0.7/camera.png000066400000000000000000001651621501140160200153400ustar00rootroot00000000000000PNG  IHDR9̜#9IDATx^c!0!0!0!0!0!0!0!0!0Bq{y̙baa1$нR g4hhzݕs?sPV.SLabbz4H;1+ +45!0!@F+.`E >yϟrvIVV JvPU.^ +\JH3hU. e4Oh!^*8aÛ ASL6 JC T*X{. 6Hl4ahh [00X[aiiWRy+`Yb"3RU, 뽌\FhhV* baV.` `.ӷ) hzfUIzX\`P`V.j4FC`4ȫ\@e*| lH T*`\@dac8`  X@ 2&{\FhhJTƢW.8z-{0牱y*I&zrA`\Fhh Tc@\@2P m"{I\&LڷV Cd6\Fhh߿g s-UbH,  rV0' XA=,@OV& rcc``ˑAҋw1ܹsM|+@iCTTARRDZ1ׯ3ܻwӧ41rBACCʊatu)"ArȦCˁVH@hb;Dĉ뀅D#;;;kA cAԇU.ׯ_@ȫW< ((NP P\?~۷ BBB  X=ze%K0Q2(**22HHHh!wPC \\\ qۺ*Wyxx=>>>͓ &a=PT"\@e'`9Wҵz6G %gϞ1 @?uCff&''h1C ׯgػw/)FsN-[0xzz2$&&{9 (݀Zm H˗/iB֐ hP#ZoаpÇ ȫ2A o]]]`MfA N`A ##3ˆ?{qVz-4۰ $ l :LG@uE=`XT"A W/V 'tPby ʰݻw.]XTTTF+rѢE XA~Cww7p$8C\r4d Dӧ@3j1A c#AH>|`Xjq,t;A::: bbb6zVymm-ŋ@p4l |!(tq[)QK?hPVVܳm.))y-( G CCCiiA^x,w)= +={͠2/>rV*  ` [ k+ eXE3L7oހ[AAAAA-0`ڵk ^RSS@gR<J!ho߾͠@N*֮]R@QFܧO a@Q! ǎc Z1͘1AMMBBmU` !ERDNAa>hB0O@~5@z+PZcdf/Px Wzz7x焾 r9P0*P{(@r,h2r!`ffp!P~@c @vaA4rAROI%Ϡh96h($Mrȅr /as5qƍ |)r3(MV"GH l?a=rFK6G/Kai `i  }%*_֘V`''m(zӳrBݑ*D ΁샭0 B2 4*Ai ¦w4 nP&MPJ0wk *Xa +`ZꝀSWWgZ@|X ֛>`; +p@ s *saaA VY*2JW,P܄ȽXESWy')lin ᒰ`\=PU>@hY>CT&A;]FIҩ=xBeRX!VXRCԳU$0#UJj׮] j F@/ZZZ๮AhV†@4GC-ha< YCapE.ArG $\`A4 ׆jbh`|KX:b0"_F8Z+Wdx9[Р&P ꭀ25(6@i.P kY` Z`l a?zUj:;@KAn`CTAz5QQQ[8?Vr;ȏ N@UcE[j@ (N`~=P LJX^ԢWZr*, R kL*!- Jsʅ ɾȅyD"a'S 9X#4<Z52w0唠 h?OkaT([ZZ2`CM y6\(Ӄ)hdвaP0(0h.XJJzvvv H J s/ȏ9/P;@rbEnD@ 7%ЇCae:\F XE +oa4032dP !UBH`a-;P6Z?0+`AuA@ a8vwwg,u_Xa* lXa`N [ȅ Ԩ\` @ Ty P Z~ 7X%x PgMze h7hqH? Jt DZ A ȝpU] Vh ]Eq q#̃ K{k y8^ACF=(ad&ȿ W =YjCaP k^ Xv/[ >PZ`T18;;*Ag]23 eH@RSjAm9DF@/S .Y VѣGCf:=<*P ﰰUhY z +A~q`jaY 9@N °4KN8C 03+$j*1#01ePP ita h/(*Z6<@( 2*@r qH-\2Pj!Nur aXAɀ̃ ɐɰa7y)Ja@a"-K0yX 3@yV1  7r *xXZİɃ*l=b`WXx C'3Jfz\AVi tue1z Ux|H\@c룥 P :O VXf?D60eTဠwϞ= V+0 ԠdNT.fYNP*DP b +@h6^5a%XA U 5 w"VC '{ F !̀ Kba LV#Ƀ7ٰBP/E(``:G@=P`Zh 7*ali:T00hT0Ղ yP/TA6cj#k`Ҍ2Pa P 40`xA H^\\<*Af*KX  _٠[^ r */O zf 1D!!l4W.`06lB4ry *X%WllJ [\vz3w\@AcɣEh ς2 (,-ZGC9@# :V@ C9A,E°&0`-bPa 5@=P*A` Q, yP I(@C\T0`2 V:AU8 @=FA1(-z  WLU2 Y zA+ sXxW0sa,` qCN^dֽPGK.\ާ*̩JTX&Z[ u T@2(d@lPcR|d %n@')޽|"2h TzF * 1P(A.ƒt`!l;p`\@qX ?0} [/@z` tyt1XD @ C~aZXI͸ ȵ ڑAԅ07Xft XtV:(À IJ+X3 d/*A2(]29 a<,Xu rhx T`n@|P 7PkX }n 9A ZnfU4 { lXeB]*ll=\tqd31n:`>5@4H-htz4/5,"clq6d{.t<'P1a$ꠂ~ҥ @i T0c+8AG ]vXT ` 4jzhn6DfDPZ m]K6Q St{@n@_a4]ͪ1{٠ r%B rXVz.CR|t9rJly SP-CIԴk zj@t& AwV,ïVe* b*P }̻* P[@EJ^Aza 9P *@$S 2td 5c  ] , A{o`=6@qs*@JAA`X` )%Jtm@ff&(``@/AaCd1Xo=lYp(2((!:d$2\n6{.bNN d(a1+Hh6 *ІFP*<@{0ȉSXK/(* А wZ hlbFKԊijr:l1PE;)4r,A0w!T!X@e C `fƒaP%R& Ո  X[dz. +kU ?l:{酅>9f+J9Lԑj>z@.p :]G6yB_JV9b {duZߠt*0wAdh44V9 R&*A6at-|Po Fda2Pe>}K C J7:QeB٠칀\Z  IT8* YB[0rd:,R¦Al =\`|r* lz;H W|$_:|>ڷ*)3@h _% rҠ _/h;l( ;An@+/0@O#|~-z8wj\Pc 4 [r [ ʃR@z@W# G@ ً-҃.O0_J#0lrޠi|AC D Aز:P&CR@Dl'M$2BUUxo|* P2@h8rDN"[`@6 W(J8 Pl6`4 WSGҁ[ +P04_@?5=A٠S (- (P_Z ɀz17KGO ,M&]F1N  hP ʃ|n66 A ">8h!(~A˵ACT.z<ֈQ-?Et==_#iXJ4\Tw@ Tذ4j)*PRj-L HFA TP-q(,\Dl7zb.~:tqlf>{qG\f ]/ 3m8 3[@v(Abg 0".]"2@ AUx4 J ?hjj"mhgaXnPyr]4iP [CLFv%ze485B@lPԫ rmPlnfC:@{a@-+5H{yK()iML3rFEua,@4ADEլ q6|'#RXP0æ<:pE/6>C36uʄ-U4 R2?(!Sеt" ;/ylGL"&cSo FB􈃕;h\MEX0r/Ѱ@遉jUD)HvWhxy >ȣ iX 0T@G뽠 rB@v76uAOD y| Y?9 d6>}BJ@(A ^\j@Ea{a=`e ŠQSN1}@C7601la9– U" svF*``ȁ ,`{{% #W, O†`1(! #R'@mnC\ ^:J^ [B!&rqf7yrLY83z؁aM/H09аlY/h}w9(!JOyvD hS6h-}@z0r:&F CvLr>5yhV# 0c{,PX`S jm*V1A- AP9qrBBfCO =A 9%Ll6 z  R+L4 OBPek\0 *#l8 }+=mbKT->t{qK6ۅ8¦= `iWo|d (MMACc72hA*W@4 Z kKӸ],"ǧ[YSqM>0Cc0ҬrfhvX |PR=b*XU. %z ,@.JX[ہNĄ,-A1`$NpE6WCv67aK\ u0`z\#Y/zaΆGoia\@ q#raDPzÖoA*@?!pw|/\y=f.PXhsaGqAlXe\@h/LFT`lejh]c<#[`CN@0yR*dw W0>lb@w/H>a /Ew36>>!ˁ ^P0:0 UU.Ge'yrd@lG/;ɋ yly;g 1P :v> 9t+Qr΅ z.,@U? yXk4% R t`8af` F*IW'IMlǥ[C7_|aM&G+IPf50aW䀃 KA/+(D`vSE_]:Z[VNSc Ya+$AbFc+a  + [!4rم~,?r`*0Є̽ [&LH zD o\ =RU0 %4\+1bS-|9`,\ ^t,da{FF5XAKؑoD/85`F %&!8DLÕA n0 ȟĖO&"T.Kwϟ?ψ!PFaĆ&` +D@b[ ҇\ ca F0A }H }frc(|brA'63`bٽVhrأg0db9:\*,@ACd] `4Bh#XBWCC(?8\z@G9!V ӵ2Pa8b#_6  빀 Pa4z`;($p `c LHEO8z`U_EǦ^1 0"a? vvVͻca ;8v,(o^A2ؠ0"h*(,@=\waW &"$&_[F6_SM h6}A(IPe= #{$. qXa | Ya3i&Pojj U\:b.bĂlb<:tqS-EW/qeDB-3ʘvT.A+0h.4j<,EzT.)+İ 6̄<H/H61 \@l=ȅzAlTPF][ dPf G8l .y8BWBCOp-J||zACV/ OlqƸ @@ ,-0 #ZN AAT`A5@y @΋ >H/%<(_*XFYYAGG| "^{ @ݼyT)ŖAbWAOf07+O|l|,* a jgT.@K< <Gf*XakAԃAYz"G0e`X 2 hR @r9 "@Ǧ9` [% ҇`]̀ b+4t}0?/6!l?(0/czerRPot(_@y}@PE(`󞠊֒7n`0U\ t'`7Y.!]Z `/тF-@ K䴋ö́iM>X'oAr_| ݎ&|Ih`cC$rZP+ a\@\A } 2)a!AqE1lx y V 03A H-GAB Ѡ+a^@8&@j}" ] [!LHr!'&G<5*%l2X*\j5#/G* 8qî]@P 4yxx0*І_P.H=̟Ħ#ldLnP4  -U6Jp˖- z[۶mc8|0~Z\+(\p?b$j]Azql^@4 ۄ*tXJ(&JXyV-zkX*,@z@j"`Ԁ@f2CLL )SLa*0(a2)1ą-`zՋ@`zp%Ht3Ձ%fdr3!g6u[悉zP0HRvԠrssce@ P mcCzxd/ Ӡ q( <ưvZ| zм hՕg%r/$ g_`Xbˇy W^VWJJ !cli]-+6zB&v0@w32i4jX 2 J h(݂j@s(T`>@@ @@Cw0kkkаŋ@Ce/^dnSPP`mEkJ 7J ÆȐ>t7L/1[zƗqE.ԃ \%ɂV  1d T`R yX t:@ȁ7z,  (–+S [DKL+QŖ%hd#ӏYȽ ؉4j0 "ZB~zpT)@Ԛb` Mu:W(k(AlRmŊ!W2&z0 2Z@W)A z@#(@#=0X$le f5VQdaӰBnm*(`@i16?jB[\@ iP…E8]@ȕ (a*ncX8(A0(|999p7f>.}\ &Ͽ A/dwaႮ[bǕNLǖȉ͜~nWP~@V3_@%8@ III `@=hjdvPm@s89ߠ; zu Av@=&@CD G@ ^P>d"6RXP8q"úu@G3@{f %k2 *=z\P 6"Õ7 9-9 } ?a񈫌#7is c^ ȃ{U A>ANܸ"d.fكH8l6,h͖V(c xX$|BxXJ؄ǖX$^Q)܌ؑ@C`Z@*W^Z P*Aeت+rh40-ZN zsNP1P0H=lehI[֬Y>OOOAKK$Tw̅́EDD+ɓ'3*`fF/q@ (`-\4n2U. rb ',Y?+&`JAGvP 䮴`,p#$E3qqq 6P+(߀*МPbU$+{a=Ppf( Ay T*JZUjh4hL||<(A @,X>_Ɔ =Ie˖1ƀzw 62P` .c@Ps_ŕ^qatF/?IW܉`ːA2W># 6 KjV.Kg Lȁ ( r ,Ay$J 6ԁƂAfr ؠ P@ư@enPֺ͹@Va[#]-01\#Wȭ&ċM/L  l~COYDV*DA|P:J a@CJgTV7% P ڔ*<2PZ|9hTh :KAl`@CD^"5@Cd+A@˨ϟ`ffH9AXQ63gdU`smҤ$@+AiThPz֐Cs0 y|v`zA=`L,@ʅ8 \ ܔ AV1*J]sPԁ1Ӱn<2\`2AT`a x@ \c/ l[6u2zfAl&1=`zpU zAiTh*zV.tqF-:+99Hv *A eܹs'hVQhPA^q #a >1P--U"&-- <rf- Sx V!&Mp5PP&#ql:r5|yL҃.P 'P9^` 1d ˇ*kjT.ݸEeo>BFAV yGA%ZV" Pa [UR[ b@ |l-PG01I@DD d]/~l|b2 [BD c2Wϩi6< &c!Mt^b Eՠ J Sg#*pYpJ2V#jc`ajA67ʟFh4.] >tX`'A+ʦOp1[@CGy~ ( + K谊F. Aj@*

ѝC/1y] (~H&r wL 7eVzwh Z$JV h &aC0sIɫ =iLn$VFJV>"Ӹ7ɕ pW U w@Ka rADPz^s ݀ؠ X<݃%(Y(p{. q;`0Z-,HBlPDn9Z"05s 1J30AtRWAGO( Ż[ء=DAOb03Iы-,al~:PѠvt$D=,0!b̀ZT@-qP# [6 Vy(πA@< 6|r3` ڛ|h4(bѐZ C];@g*1M>,r-v ` t R^-=b"@ W0*d` 6gq9쀕J(h@kAU. p@LY 9P"Z#%@㙠V :Pa DebPaa` Vq2?,`A4Hֺv{p2PD܊-`K"=JkHZ\@w:l~Dv7[u+L~lqRGp|; Ԃ_P U~P.lr߰ߠ5(hhPʏ dh-K }|ʃg`@yt h4*ɭXr"4`牁͐ 2^FRФ?ln=@C.X- vÆTAj1t1tMr=#.[8!03X*R*n`RGfU^ %DvT8(с<Jx! )(QP 4<jZI?(B^0 l #P@zkuA`5+±e*X$G8zaKȉ&+1ۍ+AcK$|{+L?Q|!pB3p Kdh8yT gZ|a:4Z^ FO0 J@a[@( HP:\ @Aڠ8K,aS7J&P*rss@Gp ('LLS` M|Πk r@΋CCltwbӋK H[9j<`?r ld>ѕ b &@ jrYk!"tBy@ \l@ ld( P[ (Y 3` 9&Ɂذ' ` vXM<"G,8LV`SO .9b*"G/,1JTaNlq,ƀnX!gjT.JTx&Ai[= ?PA ja k'A P r hS0b Mi#P~- mU^QQQ +q1ւ(h$<1AqzH hH vhM BtXF@Ol|t1X0AaNԂqJXAAI 4j1!t~HЪ0P@ ̭ w0?`Cy빀F @cDX,zp% 819`K( iG+laM0uϰ͹[?hnd[d+ 'PKt ( ." w0ȭ Vh2h!H$d/A=P!:, 6'ITaV a (l@'᠅ V2lP9AiV#~Bi>By ]:Ĩ$ \7d=[q:yX+A r9eBPĀV@ X@ꂭnjZ (" jX@ "W*:XbU0+:`:g>Hf/FCNt0" =!#ab PBDw;~t'D|zɡW&2-!8oT. yryPc t1<1 (mH`NcU, q9Z(߀zL C0(A':0槈OXx*pМhr0  -FOc!NXY >duy"Pz cKtwJL¦] 3P~ ~ z,mpͷĐ+T2(B@ M*H QLpP9a:! a=Zl- t땀҃\aj`bȑ01Xt|y>du$. yX"l Y-%\ |Rk~PѠ›Pr>AC=s`{`|@惆@z߰̍O >JT@v*$PAI2 A 3АmjjbZ Ol^Aϛ ~:JW0#ce@MVP` ^=*Ya|=>99P L;1\erĥ9=%>;s^f6P$ raL2T*6L 5qGPw4 jŁ"4y+ @Z@+L@~ 3@V㰖rُܢqA#r%Ѱb ;@|XF@PBW.lEćM.u܃%x {0'6 OdasL1X8!0Aq )ENOK/-А)aت%\]jDP4 U*뱀bUP C 0mPItww3TWW340>(P\EGG\Lxx&hgԩ 4h4bZVBe|&OH 9ȍf>XG$raV!1PZ@:r܃E \P@ \`Ch dX Z AlX, /XV-Al%^䌀pE490?#ۃ,PÕEb2~?+\t7#al3/lv P͝&Az M ?pl0ֈA   4-U@Q P*Ps ˻xDΛlWAe ,P4<ÖGA褂"}P.C ^Pj zq|j4.-ZUli@8zf| .BP>03A[j%'| 0"iPT\ؠ yn.(X'H@=P2U.H D@ 9#gJyɡba1XT`R\pWaȅW@9Jzal.&NȿV2 r&!}032nP0 EX>pZ^,"X`+bF+@ؠ,@y q ;4 8 ŀ *AA~-1[a<(r4*AimPAH &X` XGAWw233`V__\ `3C |̇̓i@fcs+ʅ1d9(RA,(* Xj]jxP *A5>G> 2(#Z{D%@eJn̙ KݠѠHE R1 uȕ  1 @'2W" 聏O/>uf'\nd}2H(?JP!4)JǠB Ai_PZUpaXa @! Xj[H0̟rTf@~M@q_d`4HT* м,KP$a+@vAͿz`2 V~,]8>PE7[ق.d9^li] GntpV.J@ rPXaJ 6(@r!1PN@p@A@P9@c 1КoP0H-`r!́X8 6"ÖG݅I8+dll64ʘ d^ \a ɃԃƋA %eNd(C(AzC ǁ2(!Ab#wYqEH0aao=P2;zdك # 葉6w`3=#ҋ} 711'5@hP 2(vŃҰ(-O>#M/ SXDtAh6 R*%jT,Bq PZjaZZP#dl 4t3(@:.a^2kXx*s@rA#%T. s n80lEP_~Bw;z~>lb ld9 gI4!@u-n2`\yosȔ eDXl 4JXHIX%ɀ`Csȅ?Ll `nU.PLH,qAzMS/hB7=3LzCv;8 93L-^Pvn!ZpA-]Po}\$Na9L@lXsXzx 6Jo=(B<d(F@ l2 ga% TTyeA N 6X@w5a|d~ }Pv5:r>7E!,^:f|/ puuea=5JAK?0qlBQs3,AÕWH*B֕e2Pvׯ]r%xI{~T y J`C@@TZqhԃsjVXŃ,za`%lF,!8An@d [`˘aa]26= JqKzXFj`  j8<ׁA Aa؂U( A=#=ȽPT_{pȽ wa= ݰ *C@.8 %a彭[2@I8-G@"͠LШ hRW!{V*4M T~!#lаhtS`S\0w➁W 4QP9ZVq{fXd2(12(#.4APfǀmeJеƠ 9?e|3dzH e,`V8rFEb"{pE2160F6 LOf!FW` G\q҃n?z 6l"[1hԳ^fD>(-VAn!];a`PKONU( {`iV*dVԀ;`#9 HO!Dy*YP8t%$e; [؃G @e zxz~ 1Ъ15?,@a!=a^@ SjCO = Py @]*\ Va  ?`S7X\ 0{Q 9 $ E<,2[6 X*Pe$ @KPe]n%ǀ24Ñ+XFAX@Ґ+Pk>P@2rƁ 3@!zĂCl%Ld1:lف.t L/%gƄf4:L^;PKAsԃA 'PVx*=Ƀ VPkāhi- @Mc\`FT؃ pPVu:o d.̿BO 9zX -h aQ hSCLrHH]va#L [ {_" K T"X7Fs`@ xB`Ab!0Ptz)(@c @fZ 3%SB-*,*X !PD܈mWd[Ħn\_dCx84F.ħL]`ы-]2 j9Т (P,1H  l $ݠ(ϡÕ ̌ LL ,?_F``0o  A$6>a Za  o$.]Ql J]섭JW 3 t]gjmme C+h5Ht'q;5>\L,]0P\aA@U9 <#uhPE"ꮃPo@_!1i Z.'Ofޠ+Nac A [@Va<\ +`~G,lnAN|fB䄉ar0>ȏm 9ZD0u^lWGOL^lq6T"_J/P}JA,-@|X:BN-(O#|3% d 2Ѡ43ab`e`g``dbfX021gd`?? X+? ;w_F@a[ 6b r'( z F%hV hXv H=r|!YP؃@WF)67Vȁ 2~S&`f?4~zQ?hee%ḱʿHFull,ΠKKKcُ?07˟Ŗaq Y 1Y X+`בaHie6P-ll _ iz }%"XDBE Z[ (TрdA2r UA(;r ذJ9P@Y~\n7z$bK DX AawKԀ*rpCWæW 9|Ǧ[\a3}6 JOPA J3<,܂޸5`4l~HOP>U߿a. 3?fF, ݖ `` O 'PP? /`ĺA,(@4hTt%+@vAqZa+F,\t@f` Kt^̅*̈́69H=hS)h  2,p,2ً-ʃ0{`@|t#O< 0,'5NB D`CW_@ L\` 6i,t@6 ˜%6 w"gX U 6 ?w#Y?܎.G6&×` ^P*<Gn@Ot01tpaH ;|).u0WX:(Aiyl^P 2\ Ar vJ2H  w*  !A!^P r+ [nz>oV.\>!;/4Y @ ς ( u A* PB%LXȭN:, $K䰈@xX"g~9 =D2[AM 9` =tp% :dzA%TP*|@kvg  p WfB#JbKK$|x-9=܍t 7P ;7W j0 a (A) BR6Kz2+n6PTAz- v\/^ p2  1`% =f\ #g3x1_Al| ?xT ACcxwA⠰ޠ@(.@"s@j郥_lytFhN4WJ?gZ`ɒ% .dC>l$r@#6 H1zbK( X'X:GrX9 V=8w< v؂.@PH@54JPdVU*PͿZȉ Oºq9Anuea3HGVȘ =#$O(! @V r3R J䠣K@g7~%\\ :dzqelz%7<,"/L={ 8@EP/drP yFkqa|P;d& [ҏk sA=SX 6d( ^+`υ@s.y P$51*12Xp$ 쩰3sӀL8wX &ٿ|Jl5n'Px.H I}( @fa4Pz }fyPa˓|2ytKˠ%99\VAsƠPYAt ;hCGGCzz:B NvN>rd,>O(16a, $yJ ruMA @@]APj/e$ yP€ 25\*ANh 7aF =ȁ ʰ ;@4z`cd9b3:~l DAAXnxrXDN3HW`i\a.̇ f(@@i>A4.w &<( 6t,*<+9Pe3T02 @ k5@s,0Аbaem1U`_P{aO@=>a@+`\ـ3fP$\`Wod+" !/"k8 K01*42z-P0sA44^ 3f` ddK| ||(X+d1( ?N`TÎjفJ nU> u6ۑ Q[NȠV@܇a+x`H,فHa4:f?1ă>:d~eP52%hEM `Cj`Bv2%/n}0}=aG\@pP5`iO0 !Wrȷa ٝ @BѠ {>+#pBsW!`4I X&3l@듁 5L ^ ts7< Ks4=1 f Yv rpA H U.%ȅ= *5POxXz7 V[COJ!O*00Vz> [Ԁ+k@XA=ϠX!k53?A0<#.; @in(@ P+  0PB%οЉJ} 5(`l 0t9H[-n']M :B [lP&AbeUƠ@#_"#ԂfbY/,3r9AlAE_f KcMm |$j2V(;h\P DX r4X9 ;lX:#, ,']d k?LؘR<~B672.wTO>+P+hy+]Οk֬a6&J"BU|d,W42 +مr&}°`3ek 5{TZhe X/ `0`@ZA㥠6(q*"g@fú09H>evd=r₍c#oDD0zt7"E >=nDDt@4vA=EP4yH>y߰%$\a` clEV.&@K}(̀2:84R! 䴄Ά afk Ơ h4+ (X>GPаx <tβ00d?R'`_ =]2 @i;A1B6Ko Aqj ְd(_0U a/29݁ʴNМ&L  d@ _PP @E|y^釉hl NY&AsoW. D=+uz c* PFh(C"z50PZf|e|P20b J 5 xU r+$ rb` dnذ t"GǦ9rHI,0#,b* .0װ]#]ЉCO|02. ;`~'MPU$DPZ/P{*R 9b'@R kA;R@@ndf`fb e+xp8@xJKV'@4rbcM+8{,,L?X^PzT@G (@aJ˸ 2| [ pk89zΠ|2V 09P q7g-O @n^'<.P" S! #`#a\"u9X#3\Xn7p^]RRPm (@4A@ hIa`PwԺUH i8~\&j%*a-&_atd9tu26ǖjZDK^t ;|WZG6=az`8҃P-FǒQA(͂5,*XDrorCWP!Là`L r2+O<_>+WAʿJ:΀0 8 {A+&"ƐV9>(@e,XܡӠ+(.@e FP8IX0EO_he =-]zPe~VZU :$We2 ΰt^W>h%K×08+`@M<®6,# >4~ 0R2%yQȎMZc^(Az@s0 yXb TX! XG.`Y NXuM#[dPRi" 9ه^r/(A$-Ɛ#*a"'\10= &n@OFO96Yt yޑ+䴄=d>9  2$'a\@ˍS bιca@EaW gY@=|F#H-#dF׌1{+p4 K|= W.aJTN *A05_Zd,ZPc+!: jt&6!H h^s5An5A+Ų@'mckiB-/2 pU.yhTPZBM`8ؓ t o BEvy% (Ca{[`a|P&,@9"@ K`āH`|l-"X儜 3\0bK01d9l% j X bή O*2FhuZ$wY  Wb&4\@W͆Al@NG_X\ 5/zzG*FX" *@`ܴiCPPp^:ZY Z| ?G+01l H`Fkd1A 9x.;p)]2 ] ԏ 9u` 頌n>l$BE:h%_ >l8de &,m[@c]v䌏l>pVc+<09~ RA-Pv4 @Ll~#0&/<݊/`G.P@v 6+Aza=n\nB`f'#t<">̝ 3@lV'ra \; 'p5ןN~FjaaA6À"Z5w3vKY lf!L/r!Xz0 Oz. 9y9@ˑLЪCPe?PLJ4d Z\k[:L8@9^fac+@v\-3p#,"@@h { [ 2`aJv@u rVz?@T&@! 3A`{ @À<|6, @桷`,9,1a(,@c=1" =!G L? |ť0yКyᕠ@-fмh(1ydzB$$^|\faGg|aY,`8 =XU,FrzE&"$`q 0 : X3{@C\L~2<8\ : ( Հ&+a3Xp!Fh\ _ab@8r8mS6@a;V#8[9=4@j@d>\؂0 ?8;;31N\٠7Um 8<-kGn@a<\_كMY/xtK779y5 ZZjZ ^(` V0B[q  t^h  5G;AfZܰ釱A2(@y"UdDkYAf"d&l%dž\p  H _"U'qh nqn;h0hhH 43W`ayŖ 3_`a9Lg  N@4r&3 s{WOkJ8O6V2A/T@CU Љ{pXT!uʈgDd>x>8O11 BiFrĆ3rTqԁ.XyZXKװ  0u{eƠ^LϬ< *@4z'c^@_`vAA~Uh:,@ O^I/Ya߁-ޛ]r KWb`(@Gd9,cVv"T*ذ[ *AW(@/ Ra F,AvZ5ș&.pd%|rfXAOj@( 걀7 ƠM[JWf>%lX3yn&e$lq3l? a rfz@2`i/d,FƠcg* Pg9a  Vr$+W`N@O' PDw.`=OF&H=X#=n{A͛73a TVF~@_ňӒAX#_8ndW~@^J=:Av;:@0?}xf rB@694ŕHJ~vO$P<V8˄rб zt}pfLx$.#d6@V؁*}ۃ#z4̀,^?=@^[3,30 `AVV@ X#A˖Ago?xf`6Z _HBs(`e6@-V1B> A'cpQQht? h$@z-g* Mz-|<|JL@W-mi @%rԇ1^x8,Qh:L4j=Vr` ַ*2Є4 t8 9S'HeD C 6rm |{@vbAhfR3~\ ])A8h3h?iP azUT74M65f?&3?RBN3z`$+|p ne~l-A4Æb@laf\v`XOV V.#2B.b@ ^2h(#0@FhocCXz q /CAﱄ7wĦyp@ؠ']a V` CEoԀ̀|E7=ݠ˃VM*FP^  󭬬@[AKt"+1a s3NVVcK01l4H?ZEF| H## ~3~z~V ȅ5#Ҝ b@v1X0-z جPk glS􂎯 z;P~.5v$rr#@Er Q3#(A,(a FZ 9eVXa Pl$= :^sa hA\(܀̇af0Gpݟ [Tp`KAkaf2(En%~N(C1bT _a3 3rDw;,]l!6zԠʅ 2a&KnPCS  X032/y6pOT07HB^ ީ 2~ZCt@b d@wXW\JGȍ``9 =a_"LYp+`,*a[&|k(ԨU,tG64 4؝nU&-rP RoX9 G]_ ba}JVF0 VA&f$zz' s@B8P+`Chq kX t hYT!@+++c5AAb_;@ bg`!œo];Pa M r cPH@KA@t:(OPfj}Ԃ a7luwa C!qq~d葈BCVCNHand@ә3g`7H3 ɀf>:-[F ;c>|i=<6sͯ`P;)汩`xbGT` P܊o?0+,AWM6{f6pPOOX7HB6#/Њ'U.` h@c [U2 = p}X0ai wݏ-(~AyyxnAvڻ:AЦI?@ b>_` g\ar+ ˷0?"О {650T. ~3 dPb#X`HL/(aք.X@`Ǚ́(C " L\ 2=6;qsL_@֏/c"/yZZ,[p!xqTXfWl  &F&A=P\z,LfP#@2Gt R\` 2vr|`q[D0,]dz`_矯XXi1ǘ1FDLVth <\> _` !L$ !ePe sPиo b*XoP-(A׸@ ,|4bmm̀`@V`Ȱ+++-zFrz/ @TW~ KP`=PzȽ64څU@gj@UV,2(AKUX$J 3ѻrR:,pa0Gm%Br"DrLhآE@J `eX!=,X*$t́!qBl8ŕq7ZpEf#JӠ+|@Pzf(=A^X 0=ACh] a,*r2?߀zH ̼ &~Gnj P Ԟ \f /`o7+3+a^9AjA]\V*Saiᔸԃ܅<fab7^\L"d=q!r* <aN(ӂlD2l F" `Rf6 |1АhH T( 4JX EX\1AW_dwC+Ȱzb@QCN f*/Pځ@lX+ %m7 u hAPGΣ A6z?Rc{@ ,}zA(-f6 zb:L Zt}0> s(O1PiG0ۀ-< P@*PKT " X/tF*@jA ,(a-%r^l/Hbee@d bd` 1PaǕǦ=["O0a a%e.P BBw3.#al|zdRv1V6l vp]n-A hѣG߿ju3⨈aA TЎn@f8p`ab*`B[ J۰΁C\ Ĵ>h3˚vrBN SXU s@POTYc: l d%Ƞ9HP'g>dq#07bk$c &Cnlm]\Krl=:El ` r/ˀyh Hp%B>ؘ V*@CLK.08ʰpD\zPuAظ?h61 3n+<&3f@/ @|=A h(>A 9H ְ$Tc'Q-Nly=aKWbj8! VN=J ]5lV": HXV*P{0RCv, 1D*'YrG6@ 6,y@|0HRUȁ3#HE.| =rAA]y h9@ !*|+.;aW&"Zy0z0{n%ak% ,|XU, LM% 걀M@CcC(M&A A lS&(@cW? Ɛ+l!H 9ô\(#@Fa+@ @j܇`aާO2@=OPov0&87|j=+=r#{et}0>z '^%LI7i 8f``+ؼ g-P X ٠=@a!Ђe8 RC\b (pظbGO^ظ2h+ ;d| V@$~|.-\qY*%Jf,-\@f+PXV6 Zehh5@~]U;w\[l ,A Î;ǧj:Ap@_I(^`=w/y|:rzcD* (1@2x ?ΰA` upSb8:@P OP?PZ-G/3,.q'A; bs3H Y/t+@EB~+^;k[ *@둀* $CXղ->t,rURn?hDÆEUzv'l#'J~F"? j 8P\ݻ>s9@ فv DNVRRiPZ#X%2PJ 4*@dPa-A< \`gg@<ڀ j] Ah4Tr# ;A r7a1*$ P% ˟ p`Α* =܁{S\]]p-5 `(,Afakü ۯ[g$b$p=0UF {P:ָDndiW8P)$,aF9H X smԃO@)AC*p-euH==k,pݝ/Pj0h%0MB `ȜFC Tf$;4E^r`F)zPJDNw@ ~P _ ak}>9?R/#뇩CCl_Ö#gblivo#01\ *$F.L`=PƂ+4`V"'2d,s2,(&;a% A=ɓ'3lܸ!!!,8@?ڵk@]a7,*2]ls #rZ~ \@NU֠v ,"(oٲVX09 1%dzs ,݅ .#T^ M q ODd? -#@V !W(l8 0< J 1-R.+a4&`zp%0\ Ta&A(\Aށ ]Rx;CvWL٠0#|W0  Zؠ eft KZа Ti/(6mj8A@~Cr9#JVzjAAA #^@ 0QXA 5(W;r#(_K wGCd3p%BiWd{م+ς6# M*`!b9cU&0H+Ab+nA*dȑѰLP,H#ZAb = [J"&+UJ JԪ ŀ BP&D2h44i 9cKCjLVz-#V V4Kn|/Ms7@C^EIXlg>U$ȕ rփְV G6r" `V'p2F HT!>@E"6`D×P E7@aZ ҃@aAZZ~( Y1A:^bd'%(݀ XMq7rKz@j9D)n 59/DJjWȅ^ 6ct`$` ] 2l)2z@x@4hELLd' .-- -9c)6 C6Vl$&Ҁo>1l~/찥 hy`%Cbn @a PeaǃԢ-(3ԩS@i X` &A&1(J ty(wP PC .,M0Ϳ$͛@-‘hNeʠ SW4 ;@~V`װENڃN\ _>`a-͢'dŞ`]v@`@ cZeLЉs0a Dl T` sa.L ćT@umzc$aEDH. -q`(@0닜IvaK2[EC+[X,Wy,>D!Pod6hdNNB d(_\gg'H=h2zFW,r; zRg3N et`$h%`T^x?!0lN P<zϞ= 111 S ֭߼ @g%,ۉ+#̂+ByT"hTw@|n\Z@vѤr&'s.HX@Gp@`-MPA`ҋ 6PB o"< rd0ې ̝ {1c[B#5"E7,+L`j`. 5Ԫ\@#hr3ޣs3( 60{2poL"-~Z 6w*T5 6@WZ Z0vPeZ Rʯ9,? >(\-Z:h'TP<v]O;{\–OЃPzOf%0sE%1PEʈ\dHs0ȽJdФrZr 5?R#XjZXJ JD0z.}+V ZI lPL =bV".H?rJzz %h=z\6u2:K gƜ [@9h4l@sg Jw ž9s&pUJy[vP$*A >&PPa=v8lO`4b o%Yd8BAN 6XCW{ s@Ca ?CeĒ%K@ U@UVdH?90܂,FlW6!Wd\`q+(܃ !<`@ 6,GT`?r@\@` A % }*@rLPj1HDHD[—`%tR}ŽPŇfd06lX=X*APݻw'AaFP:MT̘1tqZZG`| zE*lAXͽ*CЙ^a ,?4 /baz{(,|Yb |% Cn[1\0\gd&h7,~a||y_!5P>wd} f]q?1\K$W&RFlJ.F l60{p,( P+[`+[@ ` s%pt{+!:|:#R sBGOlr>,e&z⇩CCt0;pe8X!9,J+Ϳ*(ߠΝ;  A.3U^V0X&L#w hgZV+DAG$OlA=*7 2FP>'!؄>l 3 K4 ԁ22HVـU`$̳'F z1 H`` ^ Gd}2'L?̈-͠B?4sK@Ϳ*X+tg'(=µtL hrVSѠ7`z@9(q@ՠ?䴌/ƠElha*wX_Sz>d+"DEOzħ@)l蘁ʀf cY` )?r`LH .lL ɱ#Р@c&Aajv D]V؂&A@@a@,{0 r/eXȕ(A0/aiX@@hWꭀYVcAOI~֠Z_i|j\|81[^AׇPO`_O/ wr;.2K z7S@Ed{Gr|ށ7 [!9{;'f  < (@ba @` H?4OPJ`L9\ |Pm&DJ ԲbB3A8BC#n5,"lb06!u0w!%>&,E*XX?F<{we4;G7,-an)$JB4 T@7ndFM`*\ @Tpp0C\\ah$2PSzCAfԁ @CNCX*lYaX@a 24j:UAGlC?,r 9p<7XAqRPH [U. VA $0>,z 6(` lXD2;(R2?(ZvcA'y[/zpGNhs:\Ɨc!7tql|lnP _؊1z\`* a pedP+ԪP j-w-uF 5 EФ?h2qJ "Uـy. W\/Ơ=:9AOYYY*[H #P܁ v gйp saz9=ݡn1.5܄-σ?0@Cwe6Yg@O"X Va l8 Dcf(cA$02#1ȑHĤ!H='J{ ? ["WH!Q_f'̭lEj}z0"l1}V=,Мh'004dڸy!0] :a : 2V #Z6A~U( m +p}l Ϡ7A j!7(.@a Z< lqgd> .tsp, #&#5N{y?R#[Ѳr'V#a?Rab"eXAl8H="ϻ&A XDl!-q`O>.o~G܄D,\GǦs9 T*@ZAKnA=*PZ/]:a  aP:PYY_4': 4$PET8zM 6yψV ?Pe<@#€ LJT;WD@P+a RcP[:%K SǖGtw::r}/*^U. ÁcV Wq^ZC^LA ayMѰ rb4\4XE>tdr2 WHn"F?R/Y-3, IlM jY*sf5@iTـatL hw>h_ : 9-0;(D 4/T2lTVh hԻ?r%nl$>WŃ]Zj leqBLm>ht2J *D`"WĀ1B%Q.?(9}LDFl'60s-TKE [굡d3@r@4?jLv3 b3h4r?h-Zf #4Eۙ3g@WAz@ $$$0:A;ht*Z9탆@'m0GOO0>9҇OjV4\ _, C:(̠D [^ SD"rDZu !UÖ )Wb5K/86uLĺ W\HB D{25d@s~J494 8&ݽou\;>b WWA`JԨ`Ǹ;@VCNb sl;@z.jDB@6@]mʀc:`iv hvx z."в\Pa *A74dZM g=_IjCjz4*AН6 ph~֖6<=Pq aCCaKOe6}0{GXU*nA71 Ȝ  rU,/"!PBCw) ;r§[XorD6P랔#Tɀ fPO4td_JA=hyМ!hh 49 ZV OTq*?PoP/4I~U_@G6CDX wЂ P0#訚m۶lb#"!A6 ^fsy?zi 4,ѠDI"%g5+b XB 7!!d/L$|pƥ8h h(a@i= oм@5@P~) 1KaCYI P>A* X  ၆@-YA!ȍD>p jQ{"##@~mbe ("r?Z/[б -%B+rÌd16u$"d/rԒb.A1áT@5@B@8hW;h'@sVVV V9h X4@4AdH?h vh:B $%%h@-^Pʏ j*آFd*P 4 r/"fဉ3I s{Aw0@ 4k4.hYHæ_!KI@g q!'ۈh@-|Аb3 XA SA%ACg cd@2U$ 1$ ez> 1Z@ 8%-h8 aGА|P?r r:z-EA z,0XF߀-E9?dxmePM*b+l?cs/%<|))qCLeCq'+PT sV@a;/ nA%dA=P  @ ih~ӓA|y9] } *br;P/ T`|F@W,4Mm%~ @Tpe^R3 1q!VaOjaMzBjQ'z) 6N*P E 4@.PU ar2TT 5\iz%hY7 +  WuA@dpT$nU"G@Ͼdx6G"F uȕ2FY!d- !@"hN`$pѰ l|q5T+B J5>@ V.Cr=0o3-;F(2Wʈ <PODŁbc^#ˣ $M(p@s#r!TdJOñINnU,#1-,3ry>\@]q.o!5!z.qXl.z*t9\ Y=nV r|2?n.lJR O А `4!:6@KGOlP \P4`ǴDz[`  AAe&? ;a/,abp#vM!15hPhA2CtFhit2遮! HU, $ͣh$] XTZZ sp`rF  G DCT&t@y3G*_F5@ɠ T–îl-5A&:^G#3Bl#hX TV*ؑ4H U*,--D㙇EAn_BUA܂LbR1KLEZR܃M-)@:c^h!2ͷ2F>h: îU&R2TԂ40(yݻto dѽ- ʏh y#-r +T@Or:С6p*$h-u@*9InH?H8=\Bn"E8 ʌ\sU$@t^ (}t ݻ6o:yh9>Bt1jPoL)XL+a Vpt#{T*P- :@sU"AA *\ ]m;kH 8ٰqIbE}HCÁICZĦY }hzXτX5٠: 5.@z+ t4g 9W^PCi4\ Z$4W.I{P +a }PXlBb, hXl: ws@E-аicc#CJJ ӧ@ss Ѻex(PX0z P{-U, rO X6o<[?_ ĕIWa oJԋU,$d`'< и_[PCA1j0UP2X31t!7JOnnn f!OLJP؏<+@Ϡaga-h>^ AVF4PV&< ̱B6݀ l.TQadz2v0@+l`%!,W9iؐ@VN, @aтe4FCAȕ U09y|nPU.qqV fbBu`s23&d&@@=PWo4 ?R-A 7GCMރ@ l>Ty \V<8P-[,(A{X@ a~X[2FC`4FC`45a(l ]lM hcV.@1{sKRU, r V&  BTrh }\7+`r %8\̣. < D5!0!@ZE;4-c>U^X@.b IMXX`CbJې\Fhh digital-camera digital 11 hardware photo digicam computer camera AJ Ashton AJ Ashton AJ Ashton image/svg+xml en plutosvg-0.0.7/examples/camera2png.c000066400000000000000000000016451501140160200173760ustar00rootroot00000000000000#include #include int main(void) { plutosvg_document_t* document = NULL; plutovg_surface_t* surface = NULL; fprintf(stdout, "Loading 'camera.svg'\n"); document = plutosvg_document_load_from_file("camera.svg", -1, -1); if(document == NULL) { fprintf(stderr, "Unable to load 'camera.svg'\n"); goto cleanup; } fprintf(stdout, "Rendering 'camera.svg'\n"); surface = plutosvg_document_render_to_surface(document, NULL, -1, -1, NULL, NULL, NULL); if(surface == NULL) { fprintf(stderr, "Unable to render 'camera.svg'\n"); goto cleanup; } fprintf(stdout, "Writing 'camera.png'\n"); if(!plutovg_surface_write_to_png(surface, "camera.png")) { fprintf(stderr, "Unable to write 'camera.png'\n"); goto cleanup; } cleanup: plutovg_surface_destroy(surface); plutosvg_document_destroy(document); return 0; } plutosvg-0.0.7/examples/emoji2png.c000066400000000000000000000032521501140160200172450ustar00rootroot00000000000000#include #include #include #include FT_FREETYPE_H #include FT_MODULE_H int main(int argc, char* argv[]) { if(argc != 4) { fprintf(stderr, "Usage: emoji2png font codepoint size\n"); return -1; } const char* filename = argv[1]; FT_ULong codepoint = strtoul(argv[2], NULL, 16); FT_ULong size = strtoul(argv[3], NULL, 10); FT_Library library = NULL; FT_Face face = NULL; FT_Error error = FT_Err_Ok; if((error = FT_Init_FreeType(&library))) goto cleanup; if((error = FT_Property_Set(library, "ot-svg", "svg-hooks", plutosvg_ft_svg_hooks()))) goto cleanup; if((error = FT_New_Face(library, filename, 0, &face))) goto cleanup; if((error = FT_Set_Pixel_Sizes(face, 0, size))) goto cleanup; if((error = FT_Load_Char(face, codepoint, FT_LOAD_RENDER | FT_LOAD_COLOR))) { goto cleanup; } if(face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { plutovg_surface_t* surface = plutovg_surface_create_for_data( face->glyph->bitmap.buffer, face->glyph->bitmap.width, face->glyph->bitmap.rows, face->glyph->bitmap.pitch ); char name[64]; sprintf(name, "emoji-%lx.png", codepoint); plutovg_surface_write_to_png(surface, name); plutovg_surface_destroy(surface); fprintf(stdout, "Generated Emoji: %s\n", name); } else { fprintf(stderr, "The glyph for codepoint %lx is not in color mode.\n", codepoint); } cleanup: if(error) fprintf(stderr, "freetype error: %s\n", FT_Error_String(error)); FT_Done_Face(face); FT_Done_FreeType(library); return error; } plutosvg-0.0.7/examples/meson.build000066400000000000000000000004461501140160200173530ustar00rootroot00000000000000fs = import('fs') fs.copyfile('camera.svg') executable('camera2png', 'camera2png.c', dependencies: plutosvg_dep) executable('svg2png', 'svg2png.c', dependencies: plutosvg_dep) if freetype_dep.found() executable('emoji2png', 'emoji2png.c', dependencies: [plutosvg_dep, freetype_dep]) endif plutosvg-0.0.7/examples/svg2png.c000066400000000000000000000031351501140160200167410ustar00rootroot00000000000000#include #include #include static double elapsed_time(clock_t start, clock_t end) { return ((double)(end - start)) / CLOCKS_PER_SEC; } int main(int argc, char* argv[]) { if(argc != 3 && argc != 4) { fprintf(stderr, "Usage: svg2png input output [id]\n"); return -1; } const char* input = argv[1]; const char* output = argv[2]; const char* id = NULL; if(argc == 4) { id = argv[3]; } plutosvg_document_t* document = NULL; plutovg_surface_t* surface = NULL; clock_t start, end; start = clock(); document = plutosvg_document_load_from_file(input, -1, -1); end = clock(); if(document == NULL) { fprintf(stderr, "Unable to load '%s'\n", input); goto cleanup; } fprintf(stdout, "Finished loading '%s' in %.3f seconds\n", input, elapsed_time(start, end)); start = clock(); surface = plutosvg_document_render_to_surface(document, id, -1, -1, NULL, NULL, NULL); end = clock(); if(surface == NULL) { fprintf(stderr, "Unable to render '%s'\n", input); goto cleanup; } fprintf(stdout, "Finished rendering '%s' in %.3f seconds\n", input, elapsed_time(start, end)); start = clock(); if(!plutovg_surface_write_to_png(surface, output)) { fprintf(stderr, "Unable to write '%s'\n", output); goto cleanup; } end = clock(); fprintf(stdout, "Finished writing '%s' in %.3f seconds\n", output, elapsed_time(start, end)); cleanup: plutovg_surface_destroy(surface); plutosvg_document_destroy(document); return 0; } plutosvg-0.0.7/meson.build000066400000000000000000000035731501140160200155410ustar00rootroot00000000000000project('plutosvg', 'c', version: '0.0.7', license: 'MIT', meson_version: '>=0.64.0', default_options: ['c_std=c99'] ) plutosvg_deps = [] plutosvg_compile_args = [] plutovg_dep = dependency('plutovg', required: true, version: '>=1.0.0', fallback: ['plutovg', 'plutovg_dep'] ) plutosvg_deps += [plutovg_dep] cc = meson.get_compiler('c') math_dep = cc.find_library('m', required: false) if math_dep.found() plutosvg_deps += [math_dep] endif freetype_dep = dependency('freetype2', required: get_option('freetype'), version: '>=2.12', allow_fallback: false ) if freetype_dep.found() and cc.has_type('SVG_RendererHooks', dependencies: freetype_dep, prefix: '#include ') plutosvg_deps += [freetype_dep] plutosvg_compile_args += ['-DPLUTOSVG_HAS_FREETYPE'] else freetype_dep = disabler() endif if get_option('default_library') == 'static' plutosvg_compile_args += ['-DPLUTOSVG_BUILD_STATIC'] endif plutosvg_lib = library('plutosvg', 'source/plutosvg.c', include_directories: include_directories('source'), dependencies: plutosvg_deps, version: meson.project_version(), c_args: ['-DPLUTOSVG_BUILD'] + plutosvg_compile_args, cpp_args: ['-DPLUTOSVG_BUILD'] + plutosvg_compile_args, gnu_symbol_visibility: 'hidden', install: true ) plutosvg_dep = declare_dependency( link_with: plutosvg_lib, dependencies: plutovg_dep, include_directories: include_directories('source'), compile_args: plutosvg_compile_args ) meson.override_dependency('plutosvg', plutosvg_dep) install_headers('source/plutosvg.h', 'source/plutosvg-ft.h', subdir: 'plutosvg') if not get_option('examples').disabled() subdir('examples') endif pkgmod = import('pkgconfig') pkgmod.generate(plutosvg_lib, name: 'PlutoSVG', description: 'Tiny SVG rendering library', filebase: 'plutosvg', subdirs: 'plutosvg' ) plutosvg-0.0.7/meson_options.txt000066400000000000000000000002341501140160200170230ustar00rootroot00000000000000option('examples', type : 'feature', value : 'auto') option('tests', type : 'feature', value : 'auto') option('freetype', type : 'feature', value : 'auto') plutosvg-0.0.7/plutovg/000077500000000000000000000000001501140160200150675ustar00rootroot00000000000000plutosvg-0.0.7/source/000077500000000000000000000000001501140160200146675ustar00rootroot00000000000000plutosvg-0.0.7/source/plutosvg-ft.h000066400000000000000000000240761501140160200173430ustar00rootroot00000000000000/* * 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. */ /** * @brief FreeType hooks for rendering SVG glyphs with PlutoSVG. * * This file implements the integration layer between FreeType’s SVG module (typically * named "ot-svg") and PlutoSVG. It defines the necessary functions to initialize and * free the SVG rendering state, render an SVG glyph into a glyph slot, load (and cache) * SVG documents, and pre-configure glyph slots with appropriate metrics and transforms. * * These functions are aggregated into the `plutosvg_ft_hooks` structure. * * Usage example: * @code * #include * * #include * #include FT_FREETYPE_H * #include FT_MODULE_H * * int main(void) * { * FT_Library library; * if (FT_Init_FreeType(&library)) * return -1; * * if (FT_Property_Set(library, "ot-svg", "svg-hooks", &plutosvg_ft_hooks)) * return -1; * * // ... your code ... * * FT_Done_FreeType(library); * return 0; * } * @endcode */ #ifndef PLUTOSVG_FT_H #define PLUTOSVG_FT_H #include "plutosvg.h" #include #include #include #include #include FT_OTSVG_H #include FT_COLOR_H typedef struct { plutosvg_document_t* document; const FT_Byte* data; FT_ULong length; } plutosvg_ft_document_entry_t; #define PLUTOSVG_FT_MAX_DOCS 16 typedef struct { plutosvg_document_t* document; plutovg_matrix_t matrix; plutovg_rect_t extents; plutosvg_ft_document_entry_t entries[PLUTOSVG_FT_MAX_DOCS]; FT_ULong num_entries; } plutosvg_ft_state_t; static FT_Error plutosvg_ft_init(FT_Pointer* ft_state) { plutosvg_ft_state_t* state = (plutosvg_ft_state_t*)malloc(sizeof(plutosvg_ft_state_t)); memset(state, 0, sizeof(plutosvg_ft_state_t)); *ft_state = state; return FT_Err_Ok; } static void plutosvg_ft_free(FT_Pointer* ft_state) { plutosvg_ft_state_t* state = (plutosvg_ft_state_t*)(*ft_state); for(FT_ULong i = 0; i < state->num_entries; ++i) plutosvg_document_destroy(state->entries[i].document); free(state); } #define PLUTOSVG_FT_PALETTE_INDEX 0 static bool plutosvg_ft_palette_func(void* closure, const char* name, int length, plutovg_color_t* color) { FT_Face ft_face = (FT_Face)(closure); if(length < 5 || strncmp(name, "color", 5) != 0) return false; FT_Palette_Data ft_palette_data; if(FT_Palette_Data_Get(ft_face, &ft_palette_data)) return false; FT_Color* ft_palette = NULL; if(FT_Palette_Select(ft_face, PLUTOSVG_FT_PALETTE_INDEX, &ft_palette)) { return false; } FT_Int index = 0; for(int i = 5; i < length; ++i) { const char ch = name[i]; if(ch < '0' || ch > '9') return false; index = index * 10 + ch - '0'; } if(index >= ft_palette_data.num_palette_entries) return false; FT_Color* ft_color = ft_palette + index; color->r = ft_color->red / 255.f; color->g = ft_color->green / 255.f; color->b = ft_color->blue / 255.f; color->a = ft_color->alpha / 255.f; return true; } static FT_Error plutosvg_ft_render(FT_GlyphSlot ft_slot, FT_Pointer* ft_state) { plutosvg_ft_state_t* state = (plutosvg_ft_state_t*)(*ft_state); if(state->document == NULL) return FT_Err_Invalid_SVG_Document; plutovg_surface_t* surface = plutovg_surface_create_for_data(ft_slot->bitmap.buffer, ft_slot->bitmap.width, ft_slot->bitmap.rows, ft_slot->bitmap.pitch); plutovg_canvas_t* canvas = plutovg_canvas_create(surface); FT_SVG_Document ft_document = (FT_SVG_Document)ft_slot->other; FT_UShort start_glyph_id = ft_document->start_glyph_id; FT_UShort end_glyph_id = ft_document->end_glyph_id; char buffer[64]; char* id = NULL; if(start_glyph_id < end_glyph_id) { sprintf(buffer, "glyph%u", ft_slot->glyph_index); id = buffer; } plutovg_canvas_translate(canvas, -state->extents.x, -state->extents.y); plutovg_canvas_transform(canvas, &state->matrix); plutosvg_document_render(state->document, id, canvas, NULL, plutosvg_ft_palette_func, ft_slot->face); ft_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; ft_slot->bitmap.num_grays = 256; ft_slot->format = FT_GLYPH_FORMAT_BITMAP; plutovg_canvas_destroy(canvas); plutovg_surface_destroy(surface); state->document = NULL; return FT_Err_Ok; } static plutosvg_document_t* plutosvg_ft_document_load(plutosvg_ft_state_t* state, const FT_Byte* data, FT_ULong length, FT_UShort units_per_EM) { for(FT_ULong i = 0; i < state->num_entries; ++i) { if(data == state->entries[i].data && length == state->entries[i].length) { plutosvg_ft_document_entry_t entry = state->entries[i]; memmove(&state->entries[1], &state->entries[0], i * sizeof(plutosvg_ft_document_entry_t)); state->entries[0] = entry; return entry.document; } } plutosvg_document_t* document = plutosvg_document_load_from_data((const char*)data, length, units_per_EM, units_per_EM, NULL, NULL); if(document == NULL) return NULL; if(state->num_entries == PLUTOSVG_FT_MAX_DOCS) { state->num_entries--; plutosvg_document_destroy(state->entries[state->num_entries].document); } memmove(&state->entries[1], &state->entries[0], state->num_entries * sizeof(plutosvg_ft_document_entry_t)); state->entries[0].document = document; state->entries[0].data = data; state->entries[0].length = length; state->num_entries++; return document; } static FT_Error plutosvg_ft_preset_slot(FT_GlyphSlot ft_slot, FT_Bool ft_cache, FT_Pointer* ft_state) { plutosvg_ft_state_t* state = (plutosvg_ft_state_t*)(*ft_state); FT_SVG_Document ft_document = (FT_SVG_Document)ft_slot->other; FT_Size_Metrics ft_metrics = ft_document->metrics; FT_UShort start_glyph_id = ft_document->start_glyph_id; FT_UShort end_glyph_id = ft_document->end_glyph_id; plutosvg_document_t* document = plutosvg_ft_document_load(state, ft_document->svg_document, ft_document->svg_document_length, ft_document->units_per_EM); if(document == NULL) { return FT_Err_Invalid_SVG_Document; } float document_width = plutosvg_document_get_width(document); float document_height = plutosvg_document_get_height(document); plutovg_matrix_t transform = { (float)ft_document->transform.xx / (1 << 16), -(float)ft_document->transform.xy / (1 << 16), -(float)ft_document->transform.yx / (1 << 16), (float)ft_document->transform.yy / (1 << 16), (float)ft_document->delta.x / 64 * document_width / ft_metrics.x_ppem, -(float)ft_document->delta.y / 64 * document_height / ft_metrics.y_ppem }; float x_svg_to_out = ft_metrics.x_ppem / document_width; float y_svg_to_out = ft_metrics.y_ppem / document_height; plutovg_matrix_t matrix; plutovg_matrix_init_scale(&matrix, x_svg_to_out, y_svg_to_out); plutovg_matrix_multiply(&matrix, &transform, &matrix); char buffer[64]; char* id = NULL; if(start_glyph_id < end_glyph_id) { sprintf(buffer, "glyph%u", ft_slot->glyph_index); id = buffer; } plutovg_rect_t extents; if(!plutosvg_document_extents(document, id, &extents)) { return FT_Err_Invalid_SVG_Document; } plutovg_matrix_map_rect(&matrix, &extents, &extents); ft_slot->bitmap_left = (FT_Int)extents.x; ft_slot->bitmap_top = (FT_Int)-extents.y; ft_slot->bitmap.rows = (unsigned int)ceilf(extents.h); ft_slot->bitmap.width = (unsigned int)ceilf(extents.w); ft_slot->bitmap.pitch = (int)ft_slot->bitmap.width * 4; ft_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; float metrics_width = extents.w; float metrics_height = extents.h; float horiBearingX = extents.x; float horiBearingY = -extents.y; float vertBearingX = ft_slot->metrics.horiBearingX / 64.f - ft_slot->metrics.horiAdvance / 64.f / 2; float vertBearingY = (ft_slot->metrics.vertAdvance / 64.f - ft_slot->metrics.height / 64.f) / 2; ft_slot->metrics.width = (FT_Pos)roundf(metrics_width * 64); ft_slot->metrics.height = (FT_Pos)roundf(metrics_height * 64); ft_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64); ft_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64); ft_slot->metrics.vertBearingX = (FT_Pos)(vertBearingX * 64); ft_slot->metrics.vertBearingY = (FT_Pos)(vertBearingY * 64); if(ft_slot->metrics.vertAdvance == 0) ft_slot->metrics.vertAdvance = (FT_Pos)(metrics_height * 1.2f * 64); if(ft_cache) { state->document = document; state->extents = extents; state->matrix = matrix; } return FT_Err_Ok; } /** * @brief FreeType SVG renderer hooks. * * This structure is passed to FreeType via FT_Property_Set to delegate SVG glyph * rendering to PlutoSVG. */ static SVG_RendererHooks plutosvg_ft_hooks = { (SVG_Lib_Init_Func)plutosvg_ft_init, (SVG_Lib_Free_Func)plutosvg_ft_free, (SVG_Lib_Render_Func)plutosvg_ft_render, (SVG_Lib_Preset_Slot_Func)plutosvg_ft_preset_slot }; #endif // PLUTOSVG_FT_H plutosvg-0.0.7/source/plutosvg.c000066400000000000000000002426231501140160200167270ustar00rootroot00000000000000#include "plutosvg.h" #include #include #include #include #include #include int plutosvg_version(void) { return PLUTOSVG_VERSION; } const char* plutosvg_version_string(void) { return PLUTOSVG_VERSION_STRING; } enum { TAG_UNKNOWN = 0, TAG_CIRCLE, TAG_CLIP_PATH, // TODO TAG_DEFS, TAG_ELLIPSE, TAG_G, TAG_IMAGE, TAG_LINE, TAG_LINEAR_GRADIENT, TAG_PATH, TAG_POLYGON, TAG_POLYLINE, TAG_RADIAL_GRADIENT, TAG_RECT, TAG_STOP, TAG_SVG, TAG_SYMBOL, TAG_USE }; enum { ATTR_UNKNOWN = 0, ATTR_CLIP_PATH, ATTR_CLIP_PATH_UNITS, ATTR_CLIP_RULE, ATTR_COLOR, ATTR_CX, ATTR_CY, ATTR_D, ATTR_DISPLAY, ATTR_FILL, ATTR_FILL_OPACITY, ATTR_FILL_RULE, ATTR_FX, ATTR_FY, ATTR_GRADIENT_TRANSFORM, ATTR_GRADIENT_UNITS, ATTR_HEIGHT, ATTR_HREF, ATTR_ID, ATTR_OFFSET, ATTR_OPACITY, ATTR_POINTS, ATTR_PRESERVE_ASPECT_RATIO, ATTR_R, ATTR_RX, ATTR_RY, ATTR_SPREAD_METHOD, ATTR_STOP_COLOR, ATTR_STOP_OPACITY, ATTR_STROKE, ATTR_STROKE_DASHARRAY, ATTR_STROKE_DASHOFFSET, ATTR_STROKE_LINECAP, ATTR_STROKE_LINEJOIN, ATTR_STROKE_MITERLIMIT, ATTR_STROKE_OPACITY, ATTR_STROKE_WIDTH, ATTR_STYLE, ATTR_TRANSFORM, ATTR_VIEW_BOX, ATTR_VISIBILITY, ATTR_WIDTH, ATTR_X, ATTR_X1, ATTR_X2, ATTR_Y, ATTR_Y1, ATTR_Y2 }; #define MAX_NAME 19 typedef struct { const char* name; int id; } name_entry_t; static int name_entry_compare(const void* a, const void* b) { const char* name = a; const name_entry_t* entry = b; return strcmp(name, entry->name); } static int lookupid(const char* data, size_t length, const name_entry_t* table, size_t count) { if(length > MAX_NAME) return 0; char name[MAX_NAME + 1]; for(int i = 0; i < length; i++) name[i] = data[i]; name[length] = '\0'; name_entry_t* entry = bsearch(name, table, count / sizeof(name_entry_t), sizeof(name_entry_t), name_entry_compare); if(entry == NULL) return 0; return entry->id; } static int elementid(const char* data, size_t length) { static const name_entry_t table[] = { {"circle", TAG_CIRCLE}, {"clipPath", TAG_CLIP_PATH}, {"defs", TAG_DEFS}, {"ellipse", TAG_ELLIPSE}, {"g", TAG_G}, {"image", TAG_IMAGE}, {"line", TAG_LINE}, {"linearGradient", TAG_LINEAR_GRADIENT}, {"path", TAG_PATH}, {"polygon", TAG_POLYGON}, {"polyline", TAG_POLYLINE}, {"radialGradient", TAG_RADIAL_GRADIENT}, {"rect", TAG_RECT}, {"stop", TAG_STOP}, {"svg", TAG_SVG}, {"symbol", TAG_SYMBOL}, {"use", TAG_USE} }; return lookupid(data, length, table, sizeof(table)); } static int attributeid(const char* data, size_t length) { static const name_entry_t table[] = { {"clip-path", ATTR_CLIP_PATH}, {"clip-rule", ATTR_CLIP_RULE}, {"clipPathUnits", ATTR_CLIP_PATH_UNITS}, {"color", ATTR_COLOR}, {"cx", ATTR_CX}, {"cy", ATTR_CY}, {"d", ATTR_D}, {"display", ATTR_DISPLAY}, {"fill", ATTR_FILL}, {"fill-opacity", ATTR_FILL_OPACITY}, {"fill-rule", ATTR_FILL_RULE}, {"fx", ATTR_FX}, {"fy", ATTR_FY}, {"gradientTransform", ATTR_GRADIENT_TRANSFORM}, {"gradientUnits", ATTR_GRADIENT_UNITS}, {"height", ATTR_HEIGHT}, {"href", ATTR_HREF}, {"id", ATTR_ID}, {"offset", ATTR_OFFSET}, {"opacity", ATTR_OPACITY}, {"points", ATTR_POINTS}, {"preserveAspectRatio", ATTR_PRESERVE_ASPECT_RATIO}, {"r", ATTR_R}, {"rx", ATTR_RX}, {"ry", ATTR_RY}, {"spreadMethod", ATTR_SPREAD_METHOD}, {"stop-color", ATTR_STOP_COLOR}, {"stop-opacity", ATTR_STOP_OPACITY}, {"stroke", ATTR_STROKE}, {"stroke-dasharray", ATTR_STROKE_DASHARRAY}, {"stroke-dashoffset", ATTR_STROKE_DASHOFFSET}, {"stroke-linecap", ATTR_STROKE_LINECAP}, {"stroke-linejoin", ATTR_STROKE_LINEJOIN}, {"stroke-miterlimit", ATTR_STROKE_MITERLIMIT}, {"stroke-opacity", ATTR_STROKE_OPACITY}, {"stroke-width", ATTR_STROKE_WIDTH}, {"style", ATTR_STYLE}, {"transform", ATTR_TRANSFORM}, {"viewBox", ATTR_VIEW_BOX}, {"visibility", ATTR_VISIBILITY}, {"width", ATTR_WIDTH}, {"x", ATTR_X}, {"x1", ATTR_X1}, {"x2", ATTR_X2}, {"xlink:href", ATTR_HREF}, {"y", ATTR_Y}, {"y1", ATTR_Y1}, {"y2", ATTR_Y2} }; return lookupid(data, length, table, sizeof(table)); } static int cssattributeid(const char* data, size_t length) { static const name_entry_t table[] = { {"clip-path", ATTR_CLIP_PATH}, {"clip-rule", ATTR_CLIP_RULE}, {"color", ATTR_COLOR}, {"display", ATTR_DISPLAY}, {"fill", ATTR_FILL}, {"fill-opacity", ATTR_FILL_OPACITY}, {"fill-rule", ATTR_FILL_RULE}, {"opacity", ATTR_OPACITY}, {"stop-color", ATTR_STOP_COLOR}, {"stop-opacity", ATTR_STOP_OPACITY}, {"stroke", ATTR_STROKE}, {"stroke-dasharray", ATTR_STROKE_DASHARRAY}, {"stroke-dashoffset", ATTR_STROKE_DASHOFFSET}, {"stroke-linecap", ATTR_STROKE_LINECAP}, {"stroke-linejoin", ATTR_STROKE_LINEJOIN}, {"stroke-miterlimit", ATTR_STROKE_MITERLIMIT}, {"stroke-opacity", ATTR_STROKE_OPACITY}, {"stroke-width", ATTR_STROKE_WIDTH}, {"visibility", ATTR_VISIBILITY} }; return lookupid(data, length, table, sizeof(table)); } typedef struct { const char* data; size_t length; } string_t; typedef struct attribute { int id; string_t value; struct attribute* next; } attribute_t; typedef struct element { int id; struct element* parent; struct element* last_child; struct element* first_child; struct element* next_sibling; struct attribute* attributes; } element_t; typedef struct heap_chunk { struct heap_chunk* next; } heap_chunk_t; typedef struct { heap_chunk_t* chunk; size_t size; } heap_t; static heap_t* heap_create(void) { heap_t* heap = malloc(sizeof(heap_t)); heap->chunk = NULL; heap->size = 0; return heap; } #define CHUNK_SIZE 4096 #define ALIGN_SIZE(size) (((size) + 7ul) & ~7ul) static void* heap_alloc(heap_t* heap, size_t size) { size = ALIGN_SIZE(size); if(heap->chunk == NULL || heap->size + size > CHUNK_SIZE) { heap_chunk_t* chunk = malloc(CHUNK_SIZE + sizeof(heap_chunk_t)); chunk->next = heap->chunk; heap->chunk = chunk; heap->size = 0; } void* data = (char*)(heap->chunk) + sizeof(heap_chunk_t) + heap->size; heap->size += size; return data; } static void heap_destroy(heap_t* heap) { while(heap->chunk) { heap_chunk_t* chunk = heap->chunk; heap->chunk = chunk->next; free(chunk); } free(heap); } typedef struct hashmap_entry { size_t hash; string_t name; void* value; struct hashmap_entry* next; } hashmap_entry_t; typedef struct { hashmap_entry_t** buckets; size_t size; size_t capacity; } hashmap_t; static hashmap_t* hashmap_create(void) { hashmap_t* map = malloc(sizeof(hashmap_t)); map->buckets = calloc(16, sizeof(hashmap_entry_t*)); map->size = 0; map->capacity = 16; return map; } static size_t hashmap_hash(const char* data, size_t length) { size_t h = length; for(size_t i = 0; i < length; i++) { h = h * 31 + *data; ++data; } return h; } static bool hashmap_eq(const hashmap_entry_t* entry, const char* data, size_t length) { const string_t* name = &entry->name; if(name->length != length) return false; for(size_t i = 0; i < length; i++) { if(data[i] != name->data[i]) { return false; } } return true; } static void hashmap_expand(hashmap_t* map) { if(map->size > (map->capacity * 3 / 4)) { size_t newcapacity = map->capacity << 1; hashmap_entry_t** newbuckets = calloc(newcapacity, sizeof(hashmap_entry_t*)); for(size_t i = 0; i < map->capacity; i++) { hashmap_entry_t* entry = map->buckets[i]; while(entry) { hashmap_entry_t* next = entry->next; size_t index = entry->hash & (newcapacity - 1); entry->next = newbuckets[index]; newbuckets[index] = entry; entry = next; } } free(map->buckets); map->buckets = newbuckets; map->capacity = newcapacity; } } static void hashmap_put(hashmap_t* map, heap_t* heap, const char* data, size_t length, void* value) { size_t hash = hashmap_hash(data, length); size_t index = hash & (map->capacity - 1); hashmap_entry_t** p = &map->buckets[index]; while(true) { hashmap_entry_t* current = *p; if(current == NULL) { hashmap_entry_t* entry = heap_alloc(heap, sizeof(hashmap_entry_t)); entry->name.data = data; entry->name.length = length; entry->hash = hash; entry->value = value; entry->next = NULL; *p = entry; map->size += 1; hashmap_expand(map); break; } if(current->hash == hash && hashmap_eq(current, data, length)) { current->value = value; break; } p = ¤t->next; } } static void* hashmap_get(const hashmap_t* map, const char* data, size_t length) { size_t hash = hashmap_hash(data, length); size_t index = hash & (map->capacity - 1); hashmap_entry_t* entry = map->buckets[index]; while(entry) { if(entry->hash == hash && hashmap_eq(entry, data, length)) return entry->value; entry = entry->next; } return NULL; } static void hashmap_destroy(hashmap_t* map) { if(map == NULL) return; free(map->buckets); free(map); } static inline const string_t* find_attribute(const element_t* element, int id, bool inherit) { do { const attribute_t* attribute = element->attributes; while(attribute != NULL) { if(attribute->id == id) { const string_t* value = &attribute->value; if(inherit && value->length == 7 && strncmp(value->data, "inherit", 7) == 0) break; return value; } attribute = attribute->next; } element = element->parent; } while(inherit && element); return NULL; } static inline bool has_attribute(const element_t* element, int id) { const attribute_t* attribute = element->attributes; while(attribute != NULL) { if(attribute->id == id) return true; attribute = attribute->next; } return false; } #define IS_NUM(c) ((c) >= '0' && (c) <= '9') static inline bool parse_float(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 != '.' && !IS_NUM(*it))) return false; if(IS_NUM(*it)) { do { integer = 10.f * integer + (*it++ - '0'); } while(it < end && IS_NUM(*it)); } if(it < end && *it == '.') { ++it; if(it >= end || !IS_NUM(*it)) return false; float divisor = 1.f; do { fraction = 10.f * fraction + (*it++ - '0'); divisor *= 10.f; } while(it < end && IS_NUM(*it)); fraction /= divisor; } if(it + 1 < end && (it[0] == 'e' || it[0] == 'E') && (it[1] != 'x' && it[1] != 'm')) { ++it; if(it < end && *it == '+') ++it; else if(it < end && *it == '-') { ++it; expsign = -1; } if(it >= end || !IS_NUM(*it)) return false; do { exponent = 10 * exponent + (*it++ - '0'); } while(it < end && 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 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 const char* string_find(const char* it, const char* end, const char* data) { while(it < end) { const char* begin = it; if(skip_string(&it, end, data)) return begin; ++it; } return NULL; } static inline bool 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; } #define IS_WS(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r') static inline bool skip_ws(const char** begin, const char* end) { const char* it = *begin; while(it < end && IS_WS(*it)) ++it; *begin = it; return it < end; } static inline bool skip_ws_delim(const char** begin, const char* end, char delim) { const char* it = *begin; if(it < end && !IS_WS(*it) && *it != delim) return false; if(skip_ws(&it, end)) { if(skip_delim(&it, end, delim)) { skip_ws(&it, end); } } *begin = it; return it < end; } static inline bool skip_ws_comma(const char** begin, const char* end) { return skip_ws_delim(begin, end, ','); } static inline const char* rtrim(const char* begin, const char* end) { while(end > begin && IS_WS(end[-1])) --end; return end; } #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define CLAMP(v, lo, hi) ((v) < (lo) ? (lo) : (hi) < (v) ? (hi) : (v)) static bool parse_number(const element_t* element, int id, float* number, bool percent, bool inherit) { const string_t* value = find_attribute(element, id, inherit); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(!parse_float(&it, end, number)) return false; if(percent) { if(skip_delim(&it, end, '%')) *number /= 100.f; *number = CLAMP(*number, 0.f, 1.f); } return true; } typedef enum { length_type_unknown, length_type_fixed, length_type_percent } length_type_t; typedef struct { float value; length_type_t type; } length_t; #define is_length_zero(length) ((length).value == 0) #define is_length_valid(length) ((length).type != length_type_unknown) static bool parse_length_value(const char** begin, const char* end, length_t* length, bool negative) { float value = 0; const char* it = *begin; if(!parse_float(&it, end, &value)) return false; if(!negative && value < 0.f) { return false; } char units[2] = {0, 0}; if(it + 0 < end) units[0] = it[0]; if(it + 1 < end) { units[1] = it[1]; } static const float dpi = 96.f; switch(units[0]) { case '%': length->value = value; length->type = length_type_percent; it += 1; break; case 'p': if(units[1] == 'x') length->value = value; else if(units[1] == 'c') length->value = value * dpi / 6.f; else if(units[1] == 't') length->value = value * dpi / 72.f; else return false; length->type = length_type_fixed; it += 2; break; case 'i': if(units[1] == 'n') length->value = value * dpi; else return false; length->type = length_type_fixed; it += 2; break; case 'c': if(units[1] == 'm') length->value = value * dpi / 2.54f; else return false; length->type = length_type_fixed; it += 2; break; case 'm': if(units[1] == 'm') length->value = value * dpi / 25.4f; else return false; length->type = length_type_fixed; it += 2; break; default: length->value = value; length->type = length_type_fixed; break; } *begin = it; return true; } static bool parse_length(const element_t* element, int id, length_t* length, bool negative, bool inherit) { const string_t* value = find_attribute(element, id, inherit); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(parse_length_value(&it, end, length, negative)) return it == end; return false; } static inline float convert_length(const length_t* length, float maximum) { if(length->type == length_type_percent) return length->value * maximum / 100.f; return length->value; } typedef enum { color_type_fixed, color_type_current } color_type_t; typedef struct { color_type_t type; uint32_t value; } color_t; typedef enum { paint_type_none, paint_type_color, paint_type_url, paint_type_var } paint_type_t; typedef struct { paint_type_t type; color_t color; string_t id; } paint_t; static bool parse_color_value(const char** begin, const char* end, color_t* color) { const char* it = *begin; if(skip_string(&it, end, "currentColor")) { color->type = color_type_current; color->value = 0xFF000000; } else { plutovg_color_t value; int length = plutovg_color_parse(&value, it, end - it); if(length == 0) return false; color->type = color_type_fixed; color->value = plutovg_color_to_argb32(&value); it += length; } *begin = it; skip_ws(begin, end); return true; } static bool parse_color(const element_t* element, int id, color_t* color, bool inherit) { const string_t* value = find_attribute(element, id, inherit); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(parse_color_value(&it, end, color)) return it == end; return false; } static bool parse_url_value(const char** begin, const char* end, string_t* id) { const char* it = *begin; if(!skip_string(&it, end, "url") || !skip_ws(&it, end) || !skip_delim(&it, end, '(') || !skip_ws(&it, end)) { return false; } if(!skip_delim(&it, end, '#')) return false; id->data = it; id->length = 0; while(it < end && *it != ')') { ++id->length; ++it; } if(!skip_delim(&it, end, ')')) return false; *begin = it; skip_ws(begin, end); return true; } #define IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) #define IS_STARTNAMECHAR(c) (IS_ALPHA(c) || (c) == '_' || (c) == ':') #define IS_NAMECHAR(c) (IS_STARTNAMECHAR(c) || IS_NUM(c) || (c) == '-' || (c) == '.') static bool parse_paint(const element_t* element, int id, paint_t* paint) { const string_t* value = find_attribute(element, id, true); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(skip_string(&it, end, "none")) { paint->type = paint_type_none; return !skip_ws(&it, end); } if(parse_url_value(&it, end, &paint->id)) { paint->type = paint_type_url; paint->color.value = 0x00000000; if(skip_ws(&it, end)) { if(!parse_color_value(&it, end, &paint->color)) { return false; } } return it == end; } if(skip_string(&it, end, "var")) { if(!skip_ws(&it, end) || !skip_delim(&it, end, '(') || !skip_ws(&it, end)) { return false; } if(!skip_string(&it, end, "--")) return false; const char* begin = it; while(it < end && IS_NAMECHAR(*it)) ++it; paint->type = paint_type_var; paint->id.data = begin; paint->id.length = it - begin; paint->color.value = 0x00000000; skip_ws(&it, end); if(skip_delim(&it, end, ',')) { skip_ws(&it, end); if(!parse_color_value(&it, end, &paint->color)) { return false; } } return skip_delim(&it, end, ')') && !skip_ws(&it, end); } if(parse_color_value(&it, end, &paint->color)) { paint->type = paint_type_color; return it == end; } return false; } static bool parse_view_box(const element_t* element, int id, plutovg_rect_t* view_box) { const string_t* value = find_attribute(element, id, false); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; float x, y, w, h; if(!parse_float(&it, end, &x) || !skip_ws_comma(&it, end) || !parse_float(&it, end, &y) || !skip_ws_comma(&it, end) || !parse_float(&it, end, &w) || !skip_ws_comma(&it, end) || !parse_float(&it, end, &h) || skip_ws(&it, end)) { return false; } if(w <= 0.f || h <= 0.f) return false; view_box->x = x; view_box->y = y; view_box->w = w; view_box->h = h; return true; } typedef enum { view_align_none, view_align_x_min_y_min, view_align_x_mid_y_min, view_align_x_max_y_min, view_align_x_min_y_mid, view_align_x_mid_y_mid, view_align_x_max_y_mid, view_align_x_min_y_max, view_align_x_mid_y_max, view_align_x_max_y_max } view_align_t; typedef enum { view_scale_meet, view_scale_slice } view_scale_t; typedef struct { view_align_t align; view_scale_t scale; } view_position_t; static bool parse_view_position(const element_t* element, int id, view_position_t* position) { const string_t* value = find_attribute(element, id, false); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(skip_string(&it, end, "none")) position->align = view_align_none; else if(skip_string(&it, end, "xMinYMin")) position->align = view_align_x_min_y_min; else if(skip_string(&it, end, "xMidYMin")) position->align = view_align_x_mid_y_min; else if(skip_string(&it, end, "xMaxYMin")) position->align = view_align_x_max_y_min; else if(skip_string(&it, end, "xMinYMid")) position->align = view_align_x_min_y_mid; else if(skip_string(&it, end, "xMidYMid")) position->align = view_align_x_mid_y_mid; else if(skip_string(&it, end, "xMaxYMid")) position->align = view_align_x_max_y_mid; else if(skip_string(&it, end, "xMinYMax")) position->align = view_align_x_min_y_max; else if(skip_string(&it, end, "xMidYMax")) position->align = view_align_x_mid_y_max; else if(skip_string(&it, end, "xMaxYMax")) position->align = view_align_x_max_y_max; else return false; position->scale = view_scale_meet; if(position->align != view_align_none) { skip_ws(&it, end); if(skip_string(&it, end, "meet")) position->scale = view_scale_meet; else if(skip_string(&it, end, "slice")) { position->scale = view_scale_slice; } } return !skip_ws(&it, end); } static bool parse_transform(const element_t* element, int id, plutovg_matrix_t* matrix) { const string_t* value = find_attribute(element, id, false); if(value == NULL) return false; return plutovg_matrix_parse(matrix, value->data, value->length); } static bool parse_points(const element_t* element, int id, plutovg_path_t* path) { const string_t* value = find_attribute(element, id, false); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; bool requires_move = true; while(it < end) { float x, y; if(!parse_float(&it, end, &x) || !skip_ws_comma(&it, end) || !parse_float(&it, end, &y)) { return false; } skip_ws_comma(&it, end); if(requires_move) plutovg_path_move_to(path, x, y); else plutovg_path_line_to(path, x, y); requires_move = false; } if(element->id == TAG_POLYGON) plutovg_path_close(path); return true; } static bool parse_path(const element_t* element, int id, plutovg_path_t* path) { const string_t* value = find_attribute(element, id, false); if(value == NULL) return false; return plutovg_path_parse(path, value->data, value->length); } #define MAX_DASHES 128 typedef struct { length_t data[MAX_DASHES]; size_t size; } stroke_dash_array_t; static bool parse_dash_array(const element_t* element, int id, stroke_dash_array_t* dash_array) { const string_t* value = find_attribute(element, id, true); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; while(it < end && dash_array->size < MAX_DASHES) { if(!parse_length_value(&it, end, dash_array->data + dash_array->size, false)) return false; skip_ws_comma(&it, end); dash_array->size += 1; } return true; } static bool parse_line_cap(const element_t* element, int id, plutovg_line_cap_t* line_cap) { const string_t* value = find_attribute(element, id, true); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(skip_string(&it, end, "butt")) *line_cap = PLUTOVG_LINE_CAP_BUTT; else if(skip_string(&it, end, "round")) *line_cap = PLUTOVG_LINE_CAP_ROUND; else if(skip_string(&it, end, "square")) *line_cap = PLUTOVG_LINE_CAP_SQUARE; return !skip_ws(&it, end); } static bool parse_line_join(const element_t* element, int id, plutovg_line_join_t* line_join) { const string_t* value = find_attribute(element, id, true); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(skip_string(&it, end, "miter")) *line_join = PLUTOVG_LINE_JOIN_MITER; else if(skip_string(&it, end, "round")) *line_join = PLUTOVG_LINE_JOIN_ROUND; else if(skip_string(&it, end, "bevel")) *line_join = PLUTOVG_LINE_JOIN_BEVEL; return !skip_ws(&it, end); } static bool parse_fill_rule(const element_t* element, int id, plutovg_fill_rule_t* fill_rule) { const string_t* value = find_attribute(element, id, true); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(skip_string(&it, end, "nonzero")) *fill_rule = PLUTOVG_FILL_RULE_NON_ZERO; else if(skip_string(&it, end, "evenodd")) *fill_rule = PLUTOVG_FILL_RULE_EVEN_ODD; return !skip_ws(&it, end); } static bool parse_spread_method(const element_t* element, int id, plutovg_spread_method_t* spread_method) { const string_t* value = find_attribute(element, id, false); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(skip_string(&it, end, "pad")) *spread_method = PLUTOVG_SPREAD_METHOD_PAD; else if(skip_string(&it, end, "reflect")) *spread_method = PLUTOVG_SPREAD_METHOD_REFLECT; else if(skip_string(&it, end, "repeat")) *spread_method = PLUTOVG_SPREAD_METHOD_REPEAT; return !skip_ws(&it, end); } typedef enum { display_inline, display_none } display_t; static bool parse_display(const element_t* element, int id, display_t* display) { const string_t* value = find_attribute(element, id, false); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(skip_string(&it, end, "inline")) *display = display_inline; else if(skip_string(&it, end, "none")) *display = display_none; return !skip_ws(&it, end); } typedef enum { visibility_visible, visibility_hidden, visibility_collapse } visibility_t; static bool parse_visibility(const element_t* element, int id, visibility_t* visibility) { const string_t* value = find_attribute(element, id, true); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(skip_string(&it, end, "visible")) *visibility = visibility_visible; else if(skip_string(&it, end, "hidden")) *visibility = visibility_hidden; else if(skip_string(&it, end, "collapse")) *visibility = visibility_collapse; return !skip_ws(&it, end); } typedef enum { units_type_object_bounding_box, units_type_user_space_on_use } units_type_t; static bool parse_units_type(const element_t* element, int id, units_type_t* units_type) { const string_t* value = find_attribute(element, id, false); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(skip_string(&it, end, "objectBoundingBox")) *units_type = units_type_object_bounding_box; else if(skip_string(&it, end, "userSpaceOnUse")) *units_type = units_type_user_space_on_use; return !skip_ws(&it, end); } struct plutosvg_document { heap_t* heap; plutovg_path_t* path; hashmap_t* id_cache; element_t* root_element; plutovg_destroy_func_t destroy_func; void* closure; float width; float height; }; static plutosvg_document_t* plutosvg_document_create(float width, float height, plutovg_destroy_func_t destroy_func, void* closure) { plutosvg_document_t* document = malloc(sizeof(plutosvg_document_t)); document->heap = heap_create(); document->path = plutovg_path_create(); document->id_cache = NULL; document->root_element = NULL; document->destroy_func = destroy_func; document->closure = closure; document->width = width; document->height = height; return document; } void plutosvg_document_destroy(plutosvg_document_t* document) { if(document == NULL) return; plutovg_path_destroy(document->path); hashmap_destroy(document->id_cache); heap_destroy(document->heap); if(document->destroy_func) document->destroy_func(document->closure); free(document); } static void add_attribute(element_t* element, plutosvg_document_t* document, int id, const char* data, size_t length) { attribute_t* attribute = heap_alloc(document->heap, sizeof(attribute_t)); attribute->id = id; attribute->value.data = data; attribute->value.length = length; attribute->next = element->attributes; element->attributes = attribute; } #define IS_CSS_STARTNAMECHAR(c) (IS_ALPHA(c) || c == '_') #define IS_CSS_NAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || IS_NUM(c) || c == '-') static void parse_style(const char* data, int length, element_t* element, plutosvg_document_t* document) { const char* it = data; const char* end = it + length; while(it < end && IS_CSS_STARTNAMECHAR(*it)) { data = it++; while(it < end && IS_CSS_NAMECHAR(*it)) ++it; int id = cssattributeid(data, it - data); skip_ws(&it, end); if(it >= end || *it != ':') return; ++it; skip_ws(&it, end); data = it; while(it < end && *it != ';') ++it; length = rtrim(data, it) - data; if(id && element) add_attribute(element, document, id, data, length); skip_ws_delim(&it, end, ';'); } } static bool parse_attributes(const char** begin, const char* end, element_t* element, plutosvg_document_t* document) { const char* it = *begin; while(it < end && IS_STARTNAMECHAR(*it)) { const char* data = it++; while(it < end && IS_NAMECHAR(*it)) ++it; int id = attributeid(data, it - data); skip_ws(&it, end); if(it >= end || *it != '=') return false; ++it; skip_ws(&it, end); if(it >= end || (*it != '"' && *it != '\'')) return false; const char quote = *it++; skip_ws(&it, end); data = it; while(it < end && *it != quote) ++it; if(it >= end || *it != quote) return false; int length = rtrim(data, it) - data; if(id && element) { if(id == ATTR_ID) { if(document->id_cache == NULL) document->id_cache = hashmap_create(); hashmap_put(document->id_cache, document->heap, data, length, element); } else if(id == ATTR_STYLE) { parse_style(data, length, element, document); } else { add_attribute(element, document, id, data, length); } } ++it; skip_ws(&it, end); } *begin = it; return true; } plutosvg_document_t* plutosvg_document_load_from_data(const char* data, int length, float width, float height, plutovg_destroy_func_t destroy_func, void* closure) { if(length == -1) length = strlen(data); const char* it = data; const char* end = it + length; plutosvg_document_t* document = plutosvg_document_create(width, height, destroy_func, closure); element_t* current = NULL; int ignoring = 0; while(it < end) { if(current == NULL) { while(it < end && IS_WS(*it)) ++it; if(it >= end) { break; } } else { while(it < end && *it != '<') { ++it; } } if(it >= end || *it != '<') goto error; ++it; if(it < end && *it == '?') { ++it; if(!skip_string(&it, end, "xml")) goto error; skip_ws(&it, end); if(!parse_attributes(&it, end, NULL, NULL)) goto error; if(!skip_string(&it, end, "?>")) goto error; skip_ws(&it, end); continue; } if(it < end && *it == '!') { ++it; if(skip_string(&it, end, "--")) { const char* begin = string_find(it, end, "-->"); if(begin == NULL) goto error; it = begin + 3; skip_ws(&it, end); continue; } if(skip_string(&it, end, "[CDATA[")) { const char* begin = string_find(it, end, "]]>"); if(begin == NULL) goto error; it = begin + 3; skip_ws(&it, end); continue; } if(skip_string(&it, end, "DOCTYPE")) { while(it < end && *it != '>') { if(*it == '[') { ++it; int depth = 1; while(it < end && depth > 0) { if(*it == '[') ++depth; else if(*it == ']') --depth; ++it; } } else { ++it; } } if(!skip_delim(&it, end, '>')) goto error; skip_ws(&it, end); continue; } goto error; } if(it < end && *it == '/') { if(current == NULL && ignoring == 0) goto error; ++it; if(it >= end || !IS_STARTNAMECHAR(*it)) goto error; const char* begin = it++; while(it < end && IS_NAMECHAR(*it)) ++it; skip_ws(&it, end); if(it >= end || *it != '>') goto error; if(ignoring == 0) { int id = elementid(begin, it - begin); if(id != current->id) goto error; current = current->parent; } else { --ignoring; } ++it; continue; } if(it >= end || !IS_STARTNAMECHAR(*it)) goto error; const char* begin = it++; while(it < end && IS_NAMECHAR(*it)) ++it; element_t* element = NULL; if(ignoring > 0) { ++ignoring; } else { int id = elementid(begin, it - begin); if(id == TAG_UNKNOWN) { ignoring = 1; } else { if(document->root_element && current == NULL) goto error; element = heap_alloc(document->heap, sizeof(element_t)); element->id = id; element->parent = NULL; element->next_sibling = NULL; element->first_child = NULL; element->last_child = NULL; element->attributes = NULL; if(document->root_element == NULL) { if(element->id != TAG_SVG) goto error; document->root_element = element; } else { element->parent = current; if(current->last_child) { current->last_child->next_sibling = element; current->last_child = element; } else { current->last_child = element; current->first_child = element; } } } } skip_ws(&it, end); if(!parse_attributes(&it, end, element, document)) goto error; if(it < end && *it == '>') { if(element) current = element; ++it; continue; } if(it < end && *it == '/') { ++it; if(it >= end || *it != '>') goto error; if(ignoring > 0) --ignoring; ++it; continue; } goto error; } if(it == end && ignoring == 0 && current == NULL && document->root_element) { length_t w = {100, length_type_percent}; length_t h = {100, length_type_percent}; parse_length(document->root_element, ATTR_WIDTH, &w, false, false); parse_length(document->root_element, ATTR_HEIGHT, &h, false, false); float intrinsic_width = convert_length(&w, width); float intrinsic_height = convert_length(&h, height); if(intrinsic_width <= 0.f || intrinsic_height <= 0.f) { plutovg_rect_t view_box = {0, 0, 0, 0}; if(parse_view_box(document->root_element, ATTR_VIEW_BOX, &view_box)) { float intrinsic_ratio = view_box.w / view_box.h; if(intrinsic_width <= 0.f && intrinsic_height > 0.f) { intrinsic_width = intrinsic_height * intrinsic_ratio; } else if(intrinsic_width > 0.f && intrinsic_height <= 0.f) { intrinsic_height = intrinsic_width / intrinsic_ratio; } else { intrinsic_width = view_box.w; intrinsic_height = view_box.h; } } else { if(intrinsic_width == -1) intrinsic_width = 300; if(intrinsic_height == -1) { intrinsic_height = 150; } } } if(intrinsic_width <= 0.f || intrinsic_height <= 0.f) goto error; document->width = intrinsic_width; document->height = intrinsic_height; return document; } error: plutosvg_document_destroy(document); return NULL; } plutosvg_document_t* plutosvg_document_load_from_file(const char* filename, float width, float height) { 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 plutosvg_document_load_from_data(data, length, width, height, free, data); } typedef enum render_mode { render_mode_painting, render_mode_clipping, render_mode_bounding } render_mode_t; typedef struct render_state { struct render_state* parent; const element_t* element; render_mode_t mode; float opacity; float view_width; float view_height; plutovg_matrix_t matrix; plutovg_rect_t extents; } render_state_t; #define INVALID_RECT PLUTOVG_MAKE_RECT(0, 0, -1, -1) #define EMPTY_RECT PLUTOVG_MAKE_RECT(0, 0, 0, 0) #define IS_INVALID_RECT(rect) ((rect).w < 0 || (rect).h < 0) #define IS_EMPTY_RECT(rect) ((rect).w <= 0 || (rect).h <= 0) static void render_state_begin(const element_t* element, render_state_t* state, render_state_t* parent) { state->parent = parent; state->element = element; state->mode = parent->mode; state->opacity = parent->opacity; state->matrix = parent->matrix; state->extents = INVALID_RECT; state->view_width = parent->view_width; state->view_height = parent->view_height; if(element->parent && parse_transform(element, ATTR_TRANSFORM, &state->matrix)) plutovg_matrix_multiply(&state->matrix, &state->matrix, &parent->matrix); if(state->mode == render_mode_painting) { if(parse_number(element, ATTR_OPACITY, &state->opacity, true, false)) { state->opacity *= parent->opacity; } } } static void render_state_end(render_state_t* state) { if(state->mode == render_mode_painting) return; if(IS_INVALID_RECT(state->extents)) { return; } plutovg_matrix_t matrix; plutovg_matrix_invert(&state->parent->matrix, &matrix); plutovg_matrix_multiply(&matrix, &state->matrix, &matrix); plutovg_rect_t extents; plutovg_matrix_map_rect(&matrix, &state->extents, &extents); if(IS_INVALID_RECT(state->parent->extents)) { state->parent->extents = extents; return; } float l = MIN(state->parent->extents.x, extents.x); float t = MIN(state->parent->extents.y, extents.y); float r = MAX(state->parent->extents.x + state->parent->extents.w, extents.x + extents.w); float b = MAX(state->parent->extents.y + state->parent->extents.h, extents.y + extents.h); state->parent->extents.x = l; state->parent->extents.y = t; state->parent->extents.w = r - l; state->parent->extents.h = b - t; } static bool has_cycle_reference(const render_state_t* state, const element_t* element) { do { if(element == state->element) return true; state = state->parent; } while(state); return false; } typedef struct { const plutosvg_document_t* document; plutovg_canvas_t* canvas; const plutovg_color_t* current_color; plutosvg_palette_func_t palette_func; void* closure; } render_context_t; static float resolve_length(const render_state_t* state, const length_t* length, char mode) { float maximum = 0.f; if(length->type == length_type_percent) { if(mode == 'x') { maximum = state->view_width; } else if(mode == 'y') { maximum = state->view_height; } else if(mode == 'o') { maximum = hypotf(state->view_width, state->view_height) / PLUTOVG_SQRT2; } } return convert_length(length, maximum); } static element_t* find_element(const plutosvg_document_t* document, const string_t* id) { if(document->id_cache && id->length > 0) return hashmap_get(document->id_cache, id->data, id->length); return NULL; } static element_t* resolve_href(const plutosvg_document_t* document, const element_t* element) { const string_t* value = find_attribute(element, ATTR_HREF, false); if(value && value->length > 1 && value->data[0] == '#') { string_t id = {value->data + 1, value->length - 1}; return find_element(document, &id); } return NULL; } static plutovg_color_t convert_color(const color_t* color) { plutovg_color_t value; plutovg_color_init_argb32(&value, color->value); return value; } static plutovg_color_t resolve_current_color(const render_context_t* context, const element_t* element) { color_t color = {color_type_current}; parse_color(element, ATTR_COLOR, &color, true); if(color.type == color_type_fixed) return convert_color(&color); if(element->parent == NULL) { if(context->current_color) return *context->current_color; return PLUTOVG_BLACK_COLOR; } return resolve_current_color(context, element->parent); } static plutovg_color_t resolve_color(const render_context_t* context, const element_t* element, const color_t* color) { if(color->type == color_type_fixed) return convert_color(color); return resolve_current_color(context, element); } #define MAX_STOPS 64 typedef struct { plutovg_gradient_stop_t data[MAX_STOPS]; size_t size; } gradient_stop_array_t; static void resolve_gradient_stops(const render_context_t* context, const element_t* element, gradient_stop_array_t* stops) { const element_t* child = element->first_child; while(child && stops->size < MAX_STOPS) { if(child->id == TAG_STOP) { float offset = 0.f; float stop_opacity = 1.f; color_t stop_color = {color_type_fixed, 0xFF000000}; parse_number(child, ATTR_OFFSET, &offset, true, false); parse_number(child, ATTR_STOP_OPACITY, &stop_opacity, true, false); parse_color(child, ATTR_STOP_COLOR, &stop_color, false); stops->data[stops->size].offset = offset; stops->data[stops->size].color = resolve_color(context, child, &stop_color); stops->data[stops->size].color.a *= stop_opacity; stops->size += 1; } child = child->next_sibling; } } static float resolve_gradient_length(const render_state_t* state, const length_t* length, units_type_t units, char mode) { if(units == units_type_user_space_on_use) return resolve_length(state, length, mode); return convert_length(length, 1.f); } typedef struct { const element_t* units; const element_t* spread; const element_t* transform; const element_t* stops; } gradient_attributes_t; static void collect_gradient_attributes(const element_t* element, gradient_attributes_t* attributes) { if(attributes->units == NULL && has_attribute(element, ATTR_GRADIENT_UNITS)) attributes->units = element; if(attributes->spread == NULL && has_attribute(element, ATTR_SPREAD_METHOD)) attributes->spread = element; if(attributes->transform == NULL && has_attribute(element, ATTR_GRADIENT_TRANSFORM)) attributes->transform = element; if(attributes->stops == NULL) { for(const element_t* child = element->first_child; child; child = child->next_sibling) { if(child->id == TAG_STOP) { attributes->stops = element; break; } } } } static void fill_gradient_attributes(const element_t* element, gradient_attributes_t* attributes) { if(attributes->units == NULL) attributes->units = element; if(attributes->spread == NULL) attributes->spread = element; if(attributes->transform == NULL) attributes->transform = element; if(attributes->stops == NULL) { attributes->stops = element; } } static void resolve_gradient_attributes(const render_context_t* context, const render_state_t* state, const gradient_attributes_t* attributes, units_type_t* units, plutovg_spread_method_t* spread, plutovg_matrix_t* transform, gradient_stop_array_t* stops) { parse_units_type(attributes->units, ATTR_GRADIENT_UNITS, units); parse_spread_method(attributes->spread, ATTR_SPREAD_METHOD, spread); parse_transform(attributes->transform, ATTR_GRADIENT_TRANSFORM, transform); resolve_gradient_stops(context, attributes->stops, stops); if(*units == units_type_object_bounding_box) { plutovg_matrix_t matrix; plutovg_matrix_init_translate(&matrix, state->extents.x, state->extents.y); plutovg_matrix_scale(&matrix, state->extents.w, state->extents.h); plutovg_matrix_multiply(transform, transform, &matrix); } } typedef struct { gradient_attributes_t base; const element_t* x1; const element_t* y1; const element_t* x2; const element_t* y2; } linear_gradient_attributes_t; #define MAX_GRADIENT_DEPTH 128 static bool apply_linear_gradient(render_state_t* state, const render_context_t* context, const element_t* element) { linear_gradient_attributes_t attributes = {0}; const element_t* current = element; for(int i = 0; i < MAX_GRADIENT_DEPTH; ++i) { collect_gradient_attributes(current, &attributes.base); if(current->id == TAG_LINEAR_GRADIENT) { if(attributes.x1 == NULL && has_attribute(current, ATTR_X1)) attributes.x1 = current; if(attributes.y1 == NULL && has_attribute(current, ATTR_Y1)) attributes.y1 = current; if(attributes.x2 == NULL && has_attribute(current, ATTR_X2)) attributes.x2 = current; if(attributes.y2 == NULL && has_attribute(current, ATTR_Y2)) { attributes.y2 = current; } } const element_t* ref = resolve_href(context->document, current); if(ref == NULL || !(ref->id == TAG_LINEAR_GRADIENT || ref->id == TAG_RADIAL_GRADIENT)) break; current = ref; } if(attributes.base.stops == NULL) return false; fill_gradient_attributes(element, &attributes.base); if(attributes.x1 == NULL) attributes.x1 = element; if(attributes.y1 == NULL) attributes.y1 = element; if(attributes.x2 == NULL) attributes.x2 = element; if(attributes.y2 == NULL) attributes.y2 = element; units_type_t units = units_type_object_bounding_box; plutovg_spread_method_t spread = PLUTOVG_SPREAD_METHOD_PAD; plutovg_matrix_t transform = {1, 0, 0, 1, 0, 0}; gradient_stop_array_t stops = {0}; resolve_gradient_attributes(context, state, &attributes.base, &units, &spread, &transform, &stops); length_t x1 = {0, length_type_fixed}; length_t y1 = {0, length_type_fixed}; length_t x2 = {100, length_type_percent}; length_t y2 = {0, length_type_fixed}; parse_length(attributes.x1, ATTR_X1, &x1, true, false); parse_length(attributes.y1, ATTR_Y1, &y1, true, false); parse_length(attributes.x2, ATTR_X2, &x2, true, false); parse_length(attributes.y2, ATTR_Y2, &y2, true, false); float _x1 = resolve_gradient_length(state, &x1, units, 'x'); float _y1 = resolve_gradient_length(state, &y1, units, 'y'); float _x2 = resolve_gradient_length(state, &x2, units, 'x'); float _y2 = resolve_gradient_length(state, &y2, units, 'y'); plutovg_canvas_set_linear_gradient(context->canvas, _x1, _y1, _x2, _y2, spread, stops.data, stops.size, &transform); return true; } typedef struct { gradient_attributes_t base; const element_t* cx; const element_t* cy; const element_t* r; const element_t* fx; const element_t* fy; } radial_gradient_attributes_t; static bool apply_radial_gradient(render_state_t* state, const render_context_t* context, const element_t* element) { radial_gradient_attributes_t attributes = {0}; const element_t* current = element; for(int i = 0; i < MAX_GRADIENT_DEPTH; ++i) { collect_gradient_attributes(current, &attributes.base); if(current->id == TAG_RADIAL_GRADIENT) { if(attributes.cx == NULL && has_attribute(current, ATTR_CX)) attributes.cx = current; if(attributes.cy == NULL && has_attribute(current, ATTR_CY)) attributes.cy = current; if(attributes.r == NULL && has_attribute(current, ATTR_R)) attributes.r = current; if(attributes.fx == NULL && has_attribute(current, ATTR_FX)) attributes.fx = current; if(attributes.fy == NULL && has_attribute(current, ATTR_FY)) { attributes.fy = current; } } const element_t* ref = resolve_href(context->document, current); if(ref == NULL || !(ref->id == TAG_LINEAR_GRADIENT || ref->id == TAG_RADIAL_GRADIENT)) break; current = ref; } if(attributes.base.stops == NULL) return false; fill_gradient_attributes(element, &attributes.base); if(attributes.cx == NULL) attributes.cx = element; if(attributes.cy == NULL) attributes.cy = element; if(attributes.r == NULL) attributes.r = element; units_type_t units = units_type_object_bounding_box; plutovg_spread_method_t spread = PLUTOVG_SPREAD_METHOD_PAD; plutovg_matrix_t transform = {1, 0, 0, 1, 0, 0}; gradient_stop_array_t stops = {0}; resolve_gradient_attributes(context, state, &attributes.base, &units, &spread, &transform, &stops); length_t cx = {50, length_type_percent}; length_t cy = {50, length_type_percent}; length_t r = {50, length_type_percent}; length_t fx = {50, length_type_percent}; length_t fy = {50, length_type_percent}; parse_length(attributes.cx, ATTR_CX, &cx, true, false); parse_length(attributes.cy, ATTR_CY, &cy, true, false); parse_length(attributes.r, ATTR_R, &r, false, false); if(attributes.fx) { parse_length(attributes.fx, ATTR_FX, &fx, true, false); } else { parse_length(attributes.cx, ATTR_CX, &fx, true, false); } if(attributes.fy) { parse_length(attributes.fy, ATTR_FY, &fy, true, false); } else { parse_length(attributes.cy, ATTR_CY, &fy, true, false); } float _cx = resolve_gradient_length(state, &cx, units, 'x'); float _cy = resolve_gradient_length(state, &cy, units, 'y'); float _r = resolve_gradient_length(state, &r, units, 'o'); float _fx = resolve_gradient_length(state, &fx, units, 'x'); float _fy = resolve_gradient_length(state, &fy, units, 'y'); plutovg_canvas_set_radial_gradient(context->canvas, _cx, _cy, _r, _fx, _fy, 0.f, spread, stops.data, stops.size, &transform); return true; } static bool apply_paint(render_state_t* state, const render_context_t* context, const paint_t* paint) { if(paint->type == paint_type_none) return false; if(paint->type == paint_type_color) { plutovg_color_t color = resolve_color(context, state->element, &paint->color); plutovg_canvas_set_color(context->canvas, &color); return true; } if(paint->type == paint_type_var) { plutovg_color_t color; if(!context->palette_func || !context->palette_func(context->closure, paint->id.data, paint->id.length, &color)) color = resolve_color(context, state->element, &paint->color); plutovg_canvas_set_color(context->canvas, &color); return true; } const element_t* ref = find_element(context->document, &paint->id); if(ref == NULL) { plutovg_color_t color = resolve_color(context, state->element, &paint->color); plutovg_canvas_set_color(context->canvas, &color); return true; } if(ref->id == TAG_LINEAR_GRADIENT) return apply_linear_gradient(state, context, ref); if(ref->id == TAG_RADIAL_GRADIENT) return apply_radial_gradient(state, context, ref); return false; } static void draw_shape(const element_t* element, const render_context_t* context, render_state_t* state) { paint_t stroke = {paint_type_none}; parse_paint(element, ATTR_STROKE, &stroke); length_t stroke_width = {1.f, length_type_fixed}; plutovg_line_cap_t line_cap = PLUTOVG_LINE_CAP_BUTT; plutovg_line_join_t line_join = PLUTOVG_LINE_JOIN_MITER; float miter_limit = 4.f; if(stroke.type > paint_type_none) { parse_length(element, ATTR_STROKE_WIDTH, &stroke_width, false, true); parse_line_cap(element, ATTR_STROKE_LINECAP, &line_cap); parse_line_join(element, ATTR_STROKE_LINEJOIN, &line_join); parse_number(element, ATTR_STROKE_MITERLIMIT, &miter_limit, false, true); } if(state->mode == render_mode_bounding) { if(stroke.type == paint_type_none) return; float line_width = resolve_length(state, &stroke_width, 'o'); float cap_limit = line_width / 2.f; if(line_cap == PLUTOVG_LINE_CAP_SQUARE) cap_limit *= PLUTOVG_SQRT2; float join_limit = line_width / 2.f; if(line_join == PLUTOVG_LINE_JOIN_MITER) { join_limit *= miter_limit; } float delta = MAX(cap_limit, join_limit); state->extents.x -= delta; state->extents.y -= delta; state->extents.w += delta * 2.f; state->extents.h += delta * 2.f; return; } paint_t fill = {paint_type_color, {color_type_fixed, 0xFF000000}}; parse_paint(element, ATTR_FILL, &fill); if(apply_paint(state, context, &fill)) { float fill_opacity = 1.f; parse_number(element, ATTR_FILL_OPACITY, &fill_opacity, true, true); plutovg_fill_rule_t fill_rule = PLUTOVG_FILL_RULE_NON_ZERO; parse_fill_rule(element, ATTR_FILL_RULE, &fill_rule); plutovg_canvas_set_fill_rule(context->canvas, fill_rule); plutovg_canvas_set_opacity(context->canvas, fill_opacity * state->opacity); plutovg_canvas_set_matrix(context->canvas, &state->matrix); plutovg_canvas_fill_path(context->canvas, context->document->path); } if(apply_paint(state, context, &stroke)) { float stroke_opacity = 1.f; parse_number(element, ATTR_STROKE_OPACITY, &stroke_opacity, true, true); length_t dash_offset = {0.f, length_type_fixed}; parse_length(element, ATTR_STROKE_DASHOFFSET, &dash_offset, false, true); stroke_dash_array_t dash_array = {0}; parse_dash_array(element, ATTR_STROKE_DASHARRAY, &dash_array); float dashes[MAX_DASHES]; for(int i = 0; i < dash_array.size; ++i) { dashes[i] = resolve_length(state, dash_array.data + i, 'o'); } plutovg_canvas_set_dash_offset(context->canvas, resolve_length(state, &dash_offset, 'o')); plutovg_canvas_set_dash_array(context->canvas, dashes, dash_array.size); plutovg_canvas_set_line_width(context->canvas, resolve_length(state, &stroke_width, 'o')); plutovg_canvas_set_line_cap(context->canvas, line_cap); plutovg_canvas_set_line_join(context->canvas, line_join); plutovg_canvas_set_miter_limit(context->canvas, miter_limit); plutovg_canvas_set_opacity(context->canvas, stroke_opacity * state->opacity); plutovg_canvas_set_matrix(context->canvas, &state->matrix); plutovg_canvas_stroke_path(context->canvas, context->document->path); } } static bool is_display_none(const element_t* element) { display_t display = display_inline; parse_display(element, ATTR_DISPLAY, &display); return display == display_none; } static bool is_visibility_hidden(const element_t* element) { visibility_t visibility = visibility_visible; parse_visibility(element, ATTR_VISIBILITY, &visibility); return visibility != visibility_visible; } static void render_element(const element_t* element, const render_context_t* context, render_state_t* state); static void render_children(const element_t* element, const render_context_t* context, render_state_t* state); static void apply_view_transform(render_state_t* state, float width, float height) { plutovg_rect_t view_box = {0, 0, 0, 0}; if(!parse_view_box(state->element, ATTR_VIEW_BOX, &view_box)) return; view_position_t position = {view_align_x_mid_y_mid, view_scale_meet}; parse_view_position(state->element, ATTR_PRESERVE_ASPECT_RATIO, &position); float scale_x = width / view_box.w; float scale_y = height / view_box.h; if(position.align == view_align_none) { plutovg_matrix_scale(&state->matrix, scale_x, scale_y); plutovg_matrix_translate(&state->matrix, -view_box.x, -view_box.y); } else { float scale = (position.scale == view_scale_meet) ? MIN(scale_x, scale_y) : MAX(scale_x, scale_y); float offset_x = -view_box.x * scale; float offset_y = -view_box.y * scale; float view_width = view_box.w * scale; float view_height = view_box.h * scale; switch(position.align) { case view_align_x_mid_y_min: case view_align_x_mid_y_mid: case view_align_x_mid_y_max: offset_x += (width - view_width) * 0.5f; break; case view_align_x_max_y_min: case view_align_x_max_y_mid: case view_align_x_max_y_max: offset_x += (width - view_width); break; default: break; } switch(position.align) { case view_align_x_min_y_mid: case view_align_x_mid_y_mid: case view_align_x_max_y_mid: offset_y += (height - view_height) * 0.5f; break; case view_align_x_min_y_max: case view_align_x_mid_y_max: case view_align_x_max_y_max: offset_y += (height - view_height); break; default: break; } plutovg_matrix_translate(&state->matrix, offset_x, offset_y); plutovg_matrix_scale(&state->matrix, scale, scale); } state->view_width = view_box.w; state->view_height = view_box.h; } static void render_symbol(const element_t* element, const render_context_t* context, render_state_t* state, float x, float y, float width, float height) { if(width <= 0.f || height <= 0.f || is_display_none(element)) return; render_state_t new_state; render_state_begin(element, &new_state, state); new_state.view_width = width; new_state.view_height = height; plutovg_matrix_translate(&new_state.matrix, x, y); apply_view_transform(&new_state, width, height); render_children(element, context, &new_state); render_state_end(&new_state); } static void render_svg(const element_t* element, const render_context_t* context, render_state_t* state) { if(element->parent == NULL) { render_symbol(element, context, state, 0.f, 0.f, context->document->width, context->document->height); return; } length_t x = {0, length_type_fixed}; length_t y = {0, length_type_fixed}; length_t w = {100, length_type_percent}; length_t h = {100, length_type_percent}; parse_length(element, ATTR_X, &x, true, false); parse_length(element, ATTR_Y, &y, true, false); parse_length(element, ATTR_WIDTH, &w, false, false); parse_length(element, ATTR_HEIGHT, &h, false, false); float _x = resolve_length(state, &x, 'x'); float _y = resolve_length(state, &y, 'y'); float _w = resolve_length(state, &w, 'x'); float _h = resolve_length(state, &h, 'y'); render_symbol(element, context, state, _x, _y, _w, _h); } static void render_use(const element_t* element, const render_context_t* context, render_state_t* state) { if(is_display_none(element) || has_cycle_reference(state, element)) return; element_t* ref = resolve_href(context->document, element); if(ref == NULL) return; length_t x = {0, length_type_fixed}; length_t y = {0, length_type_fixed}; parse_length(element, ATTR_X, &x, true, false); parse_length(element, ATTR_Y, &y, true, false); float _x = resolve_length(state, &x, 'x'); float _y = resolve_length(state, &y, 'y'); render_state_t new_state; render_state_begin(element, &new_state, state); plutovg_matrix_translate(&new_state.matrix, _x, _y); const element_t* parent = ref->parent; ref->parent = (element_t*)(element); if(ref->id == TAG_SVG || ref->id == TAG_SYMBOL) { render_svg(ref, context, &new_state); } else { render_element(ref, context, &new_state); } ref->parent = (element_t*)(parent); render_state_end(&new_state); } static void render_g(const element_t* element, const render_context_t* context, render_state_t* state) { if(is_display_none(element)) return; render_state_t new_state; render_state_begin(element, &new_state, state); render_children(element, context, &new_state); render_state_end(&new_state); } static void render_line(const element_t* element, const render_context_t* context, render_state_t* state) { if(is_display_none(element) || is_visibility_hidden(element)) return; length_t x1 = {0, length_type_fixed}; length_t y1 = {0, length_type_fixed}; length_t x2 = {0, length_type_fixed}; length_t y2 = {0, length_type_fixed}; parse_length(element, ATTR_X1, &x1, true, false); parse_length(element, ATTR_Y1, &y1, true, false); parse_length(element, ATTR_X2, &x2, true, false); parse_length(element, ATTR_Y2, &y2, true, false); float _x1 = resolve_length(state, &x1, 'x'); float _y1 = resolve_length(state, &y1, 'y'); float _x2 = resolve_length(state, &x2, 'x'); float _y2 = resolve_length(state, &y2, 'y'); render_state_t new_state; render_state_begin(element, &new_state, state); new_state.extents.x = MIN(_x1, _x2); new_state.extents.y = MIN(_y1, _y2); new_state.extents.w = fabsf(_x2 - _x1); new_state.extents.h = fabsf(_y2 - _y1); plutovg_path_reset(context->document->path); plutovg_path_move_to(context->document->path, _x1, _y1); plutovg_path_line_to(context->document->path, _x2, _y2); draw_shape(element, context, &new_state); render_state_end(&new_state); } static void render_ellipse(const element_t* element, const render_context_t* context, render_state_t* state) { if(is_display_none(element) || is_visibility_hidden(element)) return; length_t rx = {0, length_type_fixed}; length_t ry = {0, length_type_fixed}; parse_length(element, ATTR_RX, &rx, false, false); parse_length(element, ATTR_RY, &ry, false, false); if(is_length_zero(rx) || is_length_zero(ry)) return; length_t cx = {0, length_type_fixed}; length_t cy = {0, length_type_fixed}; parse_length(element, ATTR_CX, &cx, true, false); parse_length(element, ATTR_CY, &cy, true, false); float _cx = resolve_length(state, &cx, 'x'); float _cy = resolve_length(state, &cy, 'y'); float _rx = resolve_length(state, &rx, 'x'); float _ry = resolve_length(state, &ry, 'y'); render_state_t new_state; render_state_begin(element, &new_state, state); new_state.extents.x = _cx - _rx; new_state.extents.y = _cy - _ry; new_state.extents.w = _rx + _rx; new_state.extents.h = _ry + _ry; plutovg_path_reset(context->document->path); plutovg_path_add_ellipse(context->document->path, _cx, _cy, _rx, _ry); draw_shape(element, context, &new_state); render_state_end(&new_state); } static void render_circle(const element_t* element, const render_context_t* context, render_state_t* state) { if(is_display_none(element) || is_visibility_hidden(element)) return; length_t r = {0, length_type_fixed}; parse_length(element, ATTR_R, &r, false, false); if(is_length_zero(r)) return; length_t cx = {0, length_type_fixed}; length_t cy = {0, length_type_fixed}; parse_length(element, ATTR_CX, &cx, true, false); parse_length(element, ATTR_CY, &cy, true, false); float _cx = resolve_length(state, &cx, 'x'); float _cy = resolve_length(state, &cy, 'y'); float _r = resolve_length(state, &r, 'o'); render_state_t new_state; render_state_begin(element, &new_state, state); new_state.extents.x = _cx - _r; new_state.extents.y = _cy - _r; new_state.extents.w = _r + _r; new_state.extents.h = _r + _r; plutovg_path_reset(context->document->path); plutovg_path_add_circle(context->document->path, _cx, _cy, _r); draw_shape(element, context, &new_state); render_state_end(&new_state); } static void render_rect(const element_t* element, const render_context_t* context, render_state_t* state) { if(is_display_none(element) || is_visibility_hidden(element)) return; length_t w = {0, length_type_fixed}; length_t h = {0, length_type_fixed}; parse_length(element, ATTR_WIDTH, &w, false, false); parse_length(element, ATTR_HEIGHT, &h, false, false); if(is_length_zero(w) || is_length_zero(h)) return; length_t x = {0, length_type_fixed}; length_t y = {0, length_type_fixed}; parse_length(element, ATTR_X, &x, true, false); parse_length(element, ATTR_Y, &y, true, false); float _x = resolve_length(state, &x, 'x'); float _y = resolve_length(state, &y, 'y'); float _w = resolve_length(state, &w, 'x'); float _h = resolve_length(state, &h, 'y'); length_t rx = {0, length_type_unknown}; length_t ry = {0, length_type_unknown}; parse_length(element, ATTR_RX, &rx, false, false); parse_length(element, ATTR_RY, &ry, false, false); float _rx = resolve_length(state, &rx, 'x'); float _ry = resolve_length(state, &ry, 'y'); if(!is_length_valid(rx)) _rx = _ry; if(!is_length_valid(ry)) _ry = _rx; render_state_t new_state; render_state_begin(element, &new_state, state); new_state.extents.x = _x; new_state.extents.y = _y; new_state.extents.w = _w; new_state.extents.h = _h; plutovg_path_reset(context->document->path); plutovg_path_add_round_rect(context->document->path, _x, _y, _w, _h, _rx, _ry); draw_shape(element, context, &new_state); render_state_end(&new_state); } static void render_poly(const element_t* element, const render_context_t* context, render_state_t* state) { if(is_display_none(element) || is_visibility_hidden(element)) return; render_state_t new_state; render_state_begin(element, &new_state, state); plutovg_path_reset(context->document->path); parse_points(element, ATTR_POINTS, context->document->path); plutovg_path_extents(context->document->path, &new_state.extents, false); draw_shape(element, context, &new_state); render_state_end(&new_state); } static void render_path(const element_t* element, const render_context_t* context, render_state_t* state) { if(is_display_none(element) || is_visibility_hidden(element)) return; render_state_t new_state; render_state_begin(element, &new_state, state); plutovg_path_reset(context->document->path); parse_path(element, ATTR_D, context->document->path); plutovg_path_extents(context->document->path, &new_state.extents, false); draw_shape(element, context, &new_state); render_state_end(&new_state); } static void transform_view_rect(const view_position_t* position, plutovg_rect_t* dst_rect, plutovg_rect_t* src_rect) { if(position->align == view_align_none) return; float view_width = dst_rect->w; float view_height = dst_rect->h; float image_width = src_rect->w; float image_height = src_rect->h; if(position->scale == view_scale_meet) { float scale = image_height / image_width; if(view_height > view_width * scale) { dst_rect->h = view_width * scale; switch(position->align) { case view_align_x_min_y_mid: case view_align_x_mid_y_mid: case view_align_x_max_y_mid: dst_rect->y += (view_height - dst_rect->h) * 0.5f; break; case view_align_x_min_y_max: case view_align_x_mid_y_max: case view_align_x_max_y_max: dst_rect->y += view_height - dst_rect->h; break; default: break; } } if(view_width > view_height / scale) { dst_rect->w = view_height / scale; switch(position->align) { case view_align_x_mid_y_min: case view_align_x_mid_y_mid: case view_align_x_mid_y_max: dst_rect->x += (view_width - dst_rect->w) * 0.5f; break; case view_align_x_max_y_min: case view_align_x_max_y_mid: case view_align_x_max_y_max: dst_rect->x += view_width - dst_rect->w; break; default: break; } } } else if(position->scale == view_scale_slice) { float scale = image_height / image_width; if(view_height < view_width * scale) { src_rect->h = view_height * (image_width / view_width); switch(position->align) { case view_align_x_min_y_mid: case view_align_x_mid_y_mid: case view_align_x_max_y_mid: src_rect->y += (image_height - src_rect->h) * 0.5f; break; case view_align_x_min_y_max: case view_align_x_mid_y_max: case view_align_x_max_y_max: src_rect->y += image_height - src_rect->h; break; default: break; } } if(view_width < view_height / scale) { src_rect->w = view_width * (image_height / view_height); switch(position->align) { case view_align_x_mid_y_min: case view_align_x_mid_y_mid: case view_align_x_mid_y_max: src_rect->x += (image_width - src_rect->w) * 0.5f; break; case view_align_x_max_y_min: case view_align_x_max_y_mid: case view_align_x_max_y_max: src_rect->x += image_width - src_rect->w; break; default: break; } } } } static plutovg_surface_t* load_image(const element_t* element) { const string_t* value = find_attribute(element, ATTR_HREF, false); if(value == NULL) return false; const char* it = value->data; const char* end = it + value->length; if(!skip_string(&it, end, "data:image/png") && !skip_string(&it, end, "data:image/jpg") && !skip_string(&it, end, "data:image/jpeg")) { return NULL; } if(skip_string(&it, end, ";base64,")) return plutovg_surface_load_from_image_base64(it, end - it); return NULL; } static void draw_image(const element_t* element, const render_context_t* context, render_state_t* state, float x, float y, float width, float height) { if(state->mode == render_mode_bounding) return; plutovg_surface_t* image = load_image(element); if(image == NULL) return; float image_width = plutovg_surface_get_width(image); float image_height = plutovg_surface_get_height(image); plutovg_rect_t dst_rect = {x, y, width, height}; plutovg_rect_t src_rect = {0, 0, image_width, image_height}; view_position_t position = {view_align_x_mid_y_mid, view_scale_meet}; parse_view_position(element, ATTR_PRESERVE_ASPECT_RATIO, &position); transform_view_rect(&position, &dst_rect, &src_rect); float scale_x = dst_rect.w / src_rect.w; float scale_y = dst_rect.h / src_rect.h; plutovg_matrix_t matrix = {scale_x, 0, 0, scale_y, -src_rect.x * scale_x, -src_rect.y * scale_y}; plutovg_canvas_set_fill_rule(context->canvas, PLUTOVG_FILL_RULE_NON_ZERO); plutovg_canvas_set_opacity(context->canvas, state->opacity); plutovg_canvas_set_matrix(context->canvas, &state->matrix); plutovg_canvas_translate(context->canvas, dst_rect.x, dst_rect.y); plutovg_canvas_set_texture(context->canvas, image, PLUTOVG_TEXTURE_TYPE_PLAIN, 1, &matrix); plutovg_canvas_fill_rect(context->canvas, 0, 0, dst_rect.w, dst_rect.h); plutovg_surface_destroy(image); } static void render_image(const element_t* element, const render_context_t* context, render_state_t* state) { if(is_display_none(element) || is_visibility_hidden(element)) return; length_t w = {0, length_type_fixed}; length_t h = {0, length_type_fixed}; parse_length(element, ATTR_WIDTH, &w, false, false); parse_length(element, ATTR_HEIGHT, &h, false, false); if(is_length_zero(w) || is_length_zero(h)) return; length_t x = {0, length_type_fixed}; length_t y = {0, length_type_fixed}; parse_length(element, ATTR_X, &x, true, false); parse_length(element, ATTR_Y, &y, true, false); float _x = resolve_length(state, &x, 'x'); float _y = resolve_length(state, &y, 'y'); float _w = resolve_length(state, &w, 'x'); float _h = resolve_length(state, &h, 'y'); render_state_t new_state; render_state_begin(element, &new_state, state); new_state.extents.x = _x; new_state.extents.y = _y; new_state.extents.w = _w; new_state.extents.h = _h; draw_image(element, context, &new_state, _x, _y, _w, _h); render_state_end(&new_state); } static void render_element(const element_t* element, const render_context_t* context, render_state_t* state) { switch(element->id) { case TAG_SVG: render_svg(element, context, state); break; case TAG_USE: render_use(element, context, state); break; case TAG_G: render_g(element, context, state); break; case TAG_LINE: render_line(element, context, state); break; case TAG_ELLIPSE: render_ellipse(element, context, state); break; case TAG_CIRCLE: render_circle(element, context, state); break; case TAG_RECT: render_rect(element, context, state); break; case TAG_POLYLINE: case TAG_POLYGON: render_poly(element, context, state); break; case TAG_PATH: render_path(element, context, state); break; case TAG_IMAGE: render_image(element, context, state); break; } } static void render_children(const element_t* element, const render_context_t* context, render_state_t* state) { const element_t* child = element->first_child; while(child) { render_element(child, context, state); child = child->next_sibling; } } bool plutosvg_document_render(const plutosvg_document_t* document, const char* id, plutovg_canvas_t* canvas, const plutovg_color_t* current_color, plutosvg_palette_func_t palette_func, void* closure) { render_state_t state; state.parent = NULL; state.mode = render_mode_painting; state.opacity = 1.f; state.extents = INVALID_RECT; state.view_width = document->width; state.view_height = document->height; plutovg_canvas_get_matrix(canvas, &state.matrix); if(id == NULL) { state.element = document->root_element; } else { const string_t name = {id, strlen(id)}; const element_t* element = find_element(document, &name); if(element == NULL) return false; state.element = element; } render_context_t context = {document, canvas, current_color, palette_func, closure}; render_element(state.element, &context, &state); return true; } plutovg_surface_t* plutosvg_document_render_to_surface(const plutosvg_document_t* document, const char* id, int width, int height, const plutovg_color_t* current_color, plutosvg_palette_func_t palette_func, void* closure) { plutovg_rect_t extents = {0, 0, document->width, document->height}; if(id && !plutosvg_document_extents(document, id, &extents)) return NULL; if(extents.w <= 0.f || extents.h <= 0.f) return NULL; if(width <= 0 && height <= 0) { width = (int)(ceilf(extents.w)); height = (int)(ceilf(extents.h)); } else if(width > 0 && height <= 0) { height = (int)(ceilf(width * extents.h / extents.w)); } else if(height > 0 && width <= 0) { width = (int)(ceilf(height * extents.w / extents.h)); } plutovg_surface_t* surface = plutovg_surface_create(width, height); if(surface == NULL) return NULL; plutovg_canvas_t* canvas = plutovg_canvas_create(surface); plutovg_canvas_scale(canvas, width / extents.w, height / extents.h); plutovg_canvas_translate(canvas, -extents.x, -extents.y); if(!plutosvg_document_render(document, id, canvas, current_color, palette_func, closure)) { plutovg_canvas_destroy(canvas); plutovg_surface_destroy(surface); return NULL; } plutovg_canvas_destroy(canvas); return surface; } float plutosvg_document_get_width(const plutosvg_document_t* document) { return document->width; } float plutosvg_document_get_height(const plutosvg_document_t* document) { return document->height; } bool plutosvg_document_extents(const plutosvg_document_t* document, const char* id, plutovg_rect_t* extents) { render_state_t state; state.parent = NULL; state.mode = render_mode_bounding; state.opacity = 1.f; state.extents = INVALID_RECT; state.view_width = document->width; state.view_height = document->height; plutovg_matrix_init_identity(&state.matrix); if(id == NULL) { state.element = document->root_element; } else { const string_t name = {id, strlen(id)}; const element_t* element = find_element(document, &name); if(element == NULL) { *extents = EMPTY_RECT; return false; } state.element = element; } render_context_t context = {document, NULL, NULL, NULL, NULL}; render_element(state.element, &context, &state); if(IS_INVALID_RECT(state.extents)) { *extents = EMPTY_RECT; } else { *extents = state.extents; } return true; } #ifdef PLUTOSVG_HAS_FREETYPE #include "plutosvg-ft.h" const void* plutosvg_ft_svg_hooks(void) { return &plutosvg_ft_hooks; } #else const void* plutosvg_ft_svg_hooks(void) { return NULL; } #endif // PLUTOSVG_HAS_FREETYPE plutosvg-0.0.7/source/plutosvg.h000066400000000000000000000173741501140160200167370ustar00rootroot00000000000000/* * 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 PLUTOSVG_H #define PLUTOSVG_H #include #if !defined(PLUTOSVG_BUILD_STATIC) && (defined(_WIN32) || defined(__CYGWIN__)) #define PLUTOSVG_EXPORT __declspec(dllexport) #define PLUTOSVG_IMPORT __declspec(dllimport) #elif defined(__GNUC__) && (__GNUC__ >= 4) #define PLUTOSVG_EXPORT __attribute__((__visibility__("default"))) #define PLUTOSVG_IMPORT #else #define PLUTOSVG_EXPORT #define PLUTOSVG_IMPORT #endif #ifdef PLUTOSVG_BUILD #define PLUTOSVG_API PLUTOSVG_EXPORT #else #define PLUTOSVG_API PLUTOSVG_IMPORT #endif #define PLUTOSVG_VERSION_MAJOR 0 #define PLUTOSVG_VERSION_MINOR 0 #define PLUTOSVG_VERSION_MICRO 7 #define PLUTOSVG_VERSION PLUTOVG_VERSION_ENCODE(PLUTOSVG_VERSION_MAJOR, PLUTOSVG_VERSION_MINOR, PLUTOSVG_VERSION_MICRO) #define PLUTOSVG_VERSION_STRING PLUTOVG_VERSION_STRINGIZE(PLUTOSVG_VERSION_MAJOR, PLUTOSVG_VERSION_MINOR, PLUTOSVG_VERSION_MICRO) #ifdef __cplusplus extern "C" { #endif /** * @brief Returns the version number of PlutoSVG. * * @return The version number as an integer. */ PLUTOSVG_API int plutosvg_version(void); /** * @brief Returns the version string of PlutoSVG. * * @return Pointer to a string containing the version information. */ PLUTOSVG_API const char* plutosvg_version_string(void); /** * @brief plutosvg_document_t */ typedef struct plutosvg_document plutosvg_document_t; /** * @brief Callback type for resolving CSS color variables in SVG documents. * * @param closure User-defined data for the callback. * @param name Name of the color variable. * @param length Length of the color variable name. * @param color Pointer to `plutovg_color_t` where the resolved color will be stored. * @return `true` if the color variable was successfully resolved; `false` otherwise. */ typedef bool(*plutosvg_palette_func_t)(void* closure, const char* name, int length, plutovg_color_t* color); /** * @brief Loads an SVG document from a data buffer. * * @note The buffer pointed to by `data` must remain valid until the `plutosvg_document_t` object is destroyed. * * @param data Pointer to the SVG data buffer. * @param length Length of the data buffer. * @param width Container width for resolving the initial viewport. * @param height Container height for resolving the initial viewport. * @param destroy_func Custom function to call when the document is destroyed. * @param closure User-defined data for the `destroy_func` callback. * @return Pointer to the loaded `plutosvg_document_t` structure, or NULL if loading fails. */ PLUTOSVG_API plutosvg_document_t* plutosvg_document_load_from_data(const char* data, int length, float width, float height, plutovg_destroy_func_t destroy_func, void* closure); /** * @brief Loads an SVG document from a file. * * @param filename Path to the SVG file. * @param width Container width for resolving the initial viewport. * @param height Container height for resolving the initial viewport. * @return Pointer to the loaded `plutosvg_document_t` structure, or NULL if loading fails. */ PLUTOSVG_API plutosvg_document_t* plutosvg_document_load_from_file(const char* filename, float width, float height); /** * @brief Renders an SVG document or a specific element onto a canvas. * * @param document Pointer to the SVG document. * @param id ID of the SVG element to render, or `NULL` to render the entire document. * @param canvas Canvas onto which the SVG element or document will be rendered. * @param current_color Color used to resolve CSS `currentColor` values. * @param palette_func Callback for resolving CSS color variables. * @param closure User-defined data for the `palette_func` callback. * @return `true` if rendering was successful; `false` otherwise. */ PLUTOSVG_API bool plutosvg_document_render(const plutosvg_document_t* document, const char* id, plutovg_canvas_t* canvas, const plutovg_color_t* current_color, plutosvg_palette_func_t palette_func, void* closure); /** * @brief Renders an SVG document or a specific element onto a surface. * * @param document Pointer to the SVG document. * @param id ID of the SVG element to render, or `NULL` to render the entire document. * @param width Width of the surface, or `-1` if unspecified. * @param height Height of the surface, or `-1` if unspecified. * @param current_color Color for resolving CSS `currentColor` values. * @param palette_func Callback for resolving CSS color variables. * @param closure User-defined data for the `palette_func` callback. * @return Pointer to the rendered `plutovg_surface_t` structure, or `NULL` if rendering fails. */ PLUTOSVG_API plutovg_surface_t* plutosvg_document_render_to_surface(const plutosvg_document_t* document, const char* id, int width, int height, const plutovg_color_t* current_color, plutosvg_palette_func_t palette_func, void* closure); /** * @brief Returns the intrinsic width of the SVG document. * * @param document Pointer to the SVG document. * @return The intrinsic width of the SVG document. */ PLUTOSVG_API float plutosvg_document_get_width(const plutosvg_document_t* document); /** * @brief Returns the intrinsic height of the SVG document. * * @param document Pointer to the SVG document. * @return The intrinsic height of the SVG document. */ PLUTOSVG_API float plutosvg_document_get_height(const plutosvg_document_t* document); /** * @brief Retrieves the bounding box of a specific element or the entire SVG document. * * Calculates and retrieves the extents of an element identified by `id` or the whole document if `id` is `NULL`. * * @param document Pointer to the SVG document. * @param id ID of the element whose extents to retrieve, or `NULL` to retrieve the extents of the entire document. * @param extents Pointer to a `plutovg_rect_t` structure where the extents will be stored. * @return `true` if extents were successfully retrieved; `false` otherwise. */ PLUTOSVG_API bool plutosvg_document_extents(const plutosvg_document_t* document, const char* id, plutovg_rect_t* extents); /** * @brief Destroys an SVG document and frees its resources. * * @param document Pointer to a `plutosvg_document_t` structure to be destroyed. If `NULL`, the function does nothing. */ PLUTOSVG_API void plutosvg_document_destroy(plutosvg_document_t* document); /** * @deprecated Use `plutosvg_ft_hooks` in "plutosvg-ft.h" instead. * * @brief Retrieves PlutoSVG hooks for integrating with FreeType's SVG module. * * Provides hooks that allow FreeType to use PlutoSVG for rendering SVG graphics in fonts. * * @return Pointer to the structure containing PlutoSVG hooks for FreeType's SVG module, or `NULL` if FreeType integration is not enabled. */ PLUTOSVG_API const void* plutosvg_ft_svg_hooks(void); #ifdef __cplusplus } #endif #endif // PLUTOSVG_H plutosvg-0.0.7/subprojects/000077500000000000000000000000001501140160200157325ustar00rootroot00000000000000plutosvg-0.0.7/subprojects/plutovg.wrap000066400000000000000000000001651501140160200203270ustar00rootroot00000000000000[wrap-git] url = https://github.com/sammycage/plutovg.git revision = head depth = 1 [provide] plutovg = plutovg_dep