Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package hyprgraphics for openSUSE:Factory checked in at 2025-11-09 21:07:56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/hyprgraphics (Old) and /work/SRC/openSUSE:Factory/.hyprgraphics.new.1980 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "hyprgraphics" Sun Nov 9 21:07:56 2025 rev:4 rq:1316499 version:0.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/hyprgraphics/hyprgraphics.changes 2025-09-11 14:43:26.702211112 +0200 +++ /work/SRC/openSUSE:Factory/.hyprgraphics.new.1980/hyprgraphics.changes 2025-11-09 21:08:20.432246274 +0100 @@ -1,0 +2,19 @@ +Fri Nov 7 21:21:42 UTC 2025 - Florian "spirit" <[email protected]> + +- Update to version 0.3.0: + + This update breaks ABI. + + resources/text: fix alignment layout with center/right + + add buffer option to image resource + +- Changes from version 0.2.0: + + This update breaks ABI. + + Image: added svg support + + text: various fixes and improvements + +- Changes from version 0.1.6: + + New module: AsyncResourceGatherer + + formats: add optional AVIF image support with libheif + + formats: include vector header + + png: fix gray pix formats + +------------------------------------------------------------------- Old: ---- hyprgraphics-0.1.5.tar.xz New: ---- hyprgraphics-0.3.0.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ hyprgraphics.spec ++++++ --- /var/tmp/diff_new_pack.G0XTrx/_old 2025-11-09 21:08:20.956268189 +0100 +++ /var/tmp/diff_new_pack.G0XTrx/_new 2025-11-09 21:08:20.956268189 +0100 @@ -1,8 +1,9 @@ # # spec file for package hyprgraphics # +# Copyright (c) 2025 SUSE LLC # Copyright (c) 2025 SUSE LLC and contributors -# Copyright (c) 2024 Florian "sp1rit" <[email protected]> +# Copyright (c) 2024/25 Florian "sp1rit" <[email protected]> # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,10 +18,10 @@ # -%define sover 0 +%define sover 2 Name: hyprgraphics -Version: 0.1.5 +Version: 0.3.0 Release: 0 Summary: Hyprland graphics / resource utilities License: BSD-3-Clause @@ -30,13 +31,14 @@ BuildRequires: gcc-c++ BuildRequires: pkg-config BuildRequires: pkgconfig(cairo) -BuildRequires: pkgconfig(hyprutils) +BuildRequires: pkgconfig(hyprutils) >= 0.8.0 BuildRequires: pkgconfig(libjpeg) BuildRequires: pkgconfig(libjxl) BuildRequires: pkgconfig(libjxl_cms) BuildRequires: pkgconfig(libjxl_threads) BuildRequires: pkgconfig(libmagic) BuildRequires: pkgconfig(libpng16) +BuildRequires: pkgconfig(librsvg-2.0) BuildRequires: pkgconfig(libwebp) BuildRequires: pkgconfig(pixman-1) ++++++ hyprgraphics-0.1.5.tar.xz -> hyprgraphics-0.3.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/.github/workflows/arch.yml new/hyprgraphics-0.3.0/.github/workflows/arch.yml --- old/hyprgraphics-0.1.5/.github/workflows/arch.yml 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/.github/workflows/arch.yml 2025-11-06 21:47:32.000000000 +0100 @@ -17,7 +17,7 @@ run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman cairo hyprutils libjpeg-turbo libjxl libwebp libspng + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pango pixman cairo hyprutils libjpeg-turbo libjxl libwebp libpng ttf-dejavu librsvg - name: Build hyprgraphics with gcc run: | @@ -44,7 +44,7 @@ run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman cairo hyprutils libjpeg-turbo libjxl libwebp libspng + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pango pixman cairo hyprutils libjpeg-turbo libjxl libwebp libpng ttf-dejavu librsvg - name: Build hyprgraphics with clang run: | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/CMakeLists.txt new/hyprgraphics-0.3.0/CMakeLists.txt --- old/hyprgraphics-0.1.5/CMakeLists.txt 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/CMakeLists.txt 2025-11-06 21:47:32.000000000 +0100 @@ -54,11 +54,13 @@ IMPORTED_TARGET pixman-1 cairo + pangocairo hyprutils libjpeg libwebp libmagic - libpng) + libpng + librsvg-2.0) pkg_check_modules( JXL @@ -74,18 +76,35 @@ add_compile_definitions(JXL_FOUND) endif() +pkg_check_modules( + HEIF + IMPORTED_TARGET + libheif +) + +if(NOT HEIF_FOUND) + file(GLOB_RECURSE HEIFFILES CONFIGURE_DEPENDS "src/*Avif.cpp") + list(REMOVE_ITEM SRCFILES ${HEIFFILES}) +else() + add_compile_definitions(HEIF_FOUND) +endif() + add_library(hyprgraphics SHARED ${SRCFILES}) target_include_directories( hyprgraphics PUBLIC "./include" PRIVATE "./src") set_target_properties(hyprgraphics PROPERTIES VERSION ${HYPRGRAPHICS_VERSION} - SOVERSION 0) + SOVERSION 2) target_link_libraries(hyprgraphics PkgConfig::deps) if(JXL_FOUND) target_link_libraries(hyprgraphics PkgConfig::JXL) endif() +if(HEIF_FOUND) + target_link_libraries(hyprgraphics PkgConfig::HEIF) +endif() + # tests add_custom_target(tests) @@ -96,6 +115,13 @@ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprgraphics_image "image") add_dependencies(tests hyprgraphics_image) +add_executable(hyprgraphics_arg "tests/arg.cpp") +target_link_libraries(hyprgraphics_arg PRIVATE hyprgraphics PkgConfig::deps) +add_test( + NAME "ARG" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests + COMMAND hyprgraphics_arg "image") +add_dependencies(tests hyprgraphics_arg) # Installation install(TARGETS hyprgraphics) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/README.md new/hyprgraphics-0.3.0/README.md --- old/hyprgraphics-0.1.5/README.md 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/README.md 2025-11-06 21:47:32.000000000 +0100 @@ -20,7 +20,8 @@ - libjxl_cms [optional] - libjxl_threads [optional] - libmagic - - libspng + - libpng + - librsvg2 ## Building diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/VERSION new/hyprgraphics-0.3.0/VERSION --- old/hyprgraphics-0.1.5/VERSION 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/VERSION 2025-11-06 21:47:32.000000000 +0100 @@ -1 +1 @@ -0.1.5 +0.3.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/flake.lock new/hyprgraphics-0.3.0/flake.lock --- old/hyprgraphics-0.1.5/flake.lock 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/flake.lock 2025-11-06 21:47:32.000000000 +0100 @@ -10,11 +10,11 @@ ] }, "locked": { - "lastModified": 1749135356, - "narHash": "sha256-Q8mAKMDsFbCEuq7zoSlcTuxgbIBVhfIYpX0RjE32PS0=", + "lastModified": 1756117388, + "narHash": "sha256-oRDel6pNl/T2tI+nc/USU9ZP9w08dxtl7hiZxa0C/Wc=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "e36db00dfb3a3d3fdcc4069cb292ff60d2699ccb", + "rev": "b2ae3204845f5f2f79b4703b441252d8ad2ecfd0", "type": "github" }, "original": { @@ -25,11 +25,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1748929857, - "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=", + "lastModified": 1757745802, + "narHash": "sha256-hLEO2TPj55KcUFUU1vgtHE9UEIOjRcH/4QbmfHNF820=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4", + "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", "type": "github" }, "original": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/include/hyprgraphics/image/Image.hpp new/hyprgraphics-0.3.0/include/hyprgraphics/image/Image.hpp --- old/hyprgraphics-0.1.5/include/hyprgraphics/image/Image.hpp 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/include/hyprgraphics/image/Image.hpp 2025-11-06 21:47:32.000000000 +0100 @@ -7,14 +7,15 @@ #include <hyprutils/memory/SharedPtr.hpp> namespace Hyprgraphics { - enum eImageFormat { - IMAGE_FORMAT_PNG + enum eImageFormat : uint8_t { + IMAGE_FORMAT_PNG, + IMAGE_FORMAT_AVIF, }; class CImage { public: - CImage(const std::string& path); - CImage(const std::span<uint8_t>&, eImageFormat); + CImage(const std::string& path, const Hyprutils::Math::Vector2D& size = {} /* for SVG */); + CImage(const std::span<const uint8_t>, eImageFormat); ~CImage(); CImage(const CImage&) = delete; @@ -30,6 +31,7 @@ private: std::string lastError, filepath, mime; + Hyprutils::Math::Vector2D m_svgSize; Hyprutils::Memory::CSharedPointer<CCairoSurface> pCairoSurface; bool imageHasAlpha = true, loadSuccess = false; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/include/hyprgraphics/resource/AsyncResourceGatherer.hpp new/hyprgraphics-0.3.0/include/hyprgraphics/resource/AsyncResourceGatherer.hpp --- old/hyprgraphics-0.1.5/include/hyprgraphics/resource/AsyncResourceGatherer.hpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/include/hyprgraphics/resource/AsyncResourceGatherer.hpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,38 @@ +#pragma once + +#include <thread> +#include <atomic> +#include <vector> +#include <unordered_map> +#include <condition_variable> +#include "../cairo/CairoSurface.hpp" +#include "./resources/AsyncResource.hpp" +#include <hyprutils/memory/Atomic.hpp> + +namespace Hyprgraphics { + class CAsyncResourceGatherer { + public: + CAsyncResourceGatherer(); + ~CAsyncResourceGatherer(); + + void enqueue(Hyprutils::Memory::CAtomicSharedPointer<IAsyncResource> resource); + + private: + std::thread m_gatherThread; + + struct { + std::mutex requestMutex; + std::condition_variable requestsCV; + + bool exit = false; + bool needsToProcess = false; + } m_asyncLoopState; + + std::vector<Hyprutils::Memory::CAtomicSharedPointer<IAsyncResource>> m_targetsToLoad; + std::mutex m_targetsToLoadMutex; + + // + void asyncAssetSpinLock(); + void wakeUpMainThread(); + }; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/include/hyprgraphics/resource/resources/AsyncResource.hpp new/hyprgraphics-0.3.0/include/hyprgraphics/resource/resources/AsyncResource.hpp --- old/hyprgraphics-0.1.5/include/hyprgraphics/resource/resources/AsyncResource.hpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/include/hyprgraphics/resource/resources/AsyncResource.hpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,33 @@ +#pragma once + +#include <hyprutils/memory/UniquePtr.hpp> +#include <hyprutils/math/Vector2D.hpp> +#include <hyprutils/signal/Signal.hpp> +#include "../../cairo/CairoSurface.hpp" +#include <atomic> + +namespace Hyprgraphics { + class IAsyncResource { + public: + IAsyncResource() = default; + virtual ~IAsyncResource() = default; + + virtual void render() = 0; + + struct { + // this signal fires on the worker thread. **Really** consider making this signal handler call something to wake your + // main event loop up and do things there. + Hyprutils::Signal::CSignalT<> finished; + } m_events; + + // you probably shouldn't use this but it's here just in case. + std::atomic<bool> m_ready = false; + + struct { + // This pointer can be made not thread safe as after .finished the worker thread will not touch it anymore + // and before that you shouldnt touch it either + Hyprutils::Memory::CSharedPointer<CCairoSurface> cairoSurface; + Hyprutils::Math::Vector2D pixelSize; + } m_asset; + }; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/include/hyprgraphics/resource/resources/ImageResource.hpp new/hyprgraphics-0.3.0/include/hyprgraphics/resource/resources/ImageResource.hpp --- old/hyprgraphics-0.1.5/include/hyprgraphics/resource/resources/ImageResource.hpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/include/hyprgraphics/resource/resources/ImageResource.hpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,29 @@ +#pragma once + +#include <string> +#include <hyprutils/math/Vector2D.hpp> +#include "./AsyncResource.hpp" +#include "../../color/Color.hpp" + +#include <optional> + +namespace Hyprgraphics { + class CImageResource : public IAsyncResource { + public: + enum eTextAlignmentMode : uint8_t { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT, + }; + + CImageResource(const std::string& path); + CImageResource(const std::string& svg, const Hyprutils::Math::Vector2D& size); + virtual ~CImageResource() = default; + + virtual void render(); + + private: + std::string m_path; + Hyprutils::Math::Vector2D m_svgSize; + }; +}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/include/hyprgraphics/resource/resources/StaticImageResource.hpp new/hyprgraphics-0.3.0/include/hyprgraphics/resource/resources/StaticImageResource.hpp --- old/hyprgraphics-0.1.5/include/hyprgraphics/resource/resources/StaticImageResource.hpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/include/hyprgraphics/resource/resources/StaticImageResource.hpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,29 @@ +#pragma once + +#include "./AsyncResource.hpp" +#include "../../color/Color.hpp" +#include "hyprgraphics/image/Image.hpp" + +#include <optional> + +#include <hyprutils/math/Vector2D.hpp> + +namespace Hyprgraphics { + class CStaticImageResource : public IAsyncResource { + public: + enum eTextAlignmentMode : uint8_t { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT, + }; + + CStaticImageResource(const std::span<const uint8_t> data, eImageFormat format); + virtual ~CStaticImageResource() = default; + + virtual void render(); + + private: + const std::span<const uint8_t> m_data; + const eImageFormat m_format = eImageFormat::IMAGE_FORMAT_PNG; + }; +}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/include/hyprgraphics/resource/resources/TextResource.hpp new/hyprgraphics-0.3.0/include/hyprgraphics/resource/resources/TextResource.hpp --- old/hyprgraphics-0.1.5/include/hyprgraphics/resource/resources/TextResource.hpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/include/hyprgraphics/resource/resources/TextResource.hpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,42 @@ +#pragma once + +#include "AsyncResource.hpp" +#include "../../color/Color.hpp" + +#include <cairo/cairo.h> + +#include <optional> + +#include <hyprutils/math/Vector2D.hpp> + +namespace Hyprgraphics { + class CTextResource : public IAsyncResource { + public: + enum eTextAlignmentMode : uint8_t { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT, + }; + + struct STextResourceData { + std::string text = "Sample Text"; + std::string font = "Sans Serif"; + size_t fontSize = 16; + CColor color = CColor{CColor::SSRGB{.r = 1.F, .g = 1.F, .b = 1.F}}; + eTextAlignmentMode align = TEXT_ALIGN_LEFT; + std::optional<Hyprutils::Math::Vector2D> maxSize = std::nullopt; + cairo_antialias_t antialias = CAIRO_ANTIALIAS_GOOD; + cairo_hint_style_t hintStyle = CAIRO_HINT_STYLE_SLIGHT; + bool ellipsize = false; + bool wrap = true; + }; + + CTextResource(STextResourceData&& data); + virtual ~CTextResource() = default; + + virtual void render(); + + private: + STextResourceData m_data; + }; +}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/nix/default.nix new/hyprgraphics-0.3.0/nix/default.nix --- old/hyprgraphics-0.1.5/nix/default.nix 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/nix/default.nix 2025-11-06 21:47:32.000000000 +0100 @@ -7,10 +7,13 @@ cairo, file, hyprutils, + libheif, libjpeg, libjxl, + librsvg, libspng, libwebp, + pango, pixman, version ? "git", doCheck ? false, @@ -49,10 +52,13 @@ cairo file hyprutils + libheif libjpeg libjxl + librsvg libspng libwebp + pango pixman ]; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/color/Color.cpp new/hyprgraphics-0.3.0/src/color/Color.cpp --- old/hyprgraphics-0.1.5/src/color/Color.cpp 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/src/color/Color.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -37,9 +37,15 @@ ); return CMatrix3(std::array<std::array<double, 3>, 3>{ - (m[1][1] * m[2][2] - m[2][1] * m[1][2]) * invDet, (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * invDet, (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * invDet, // - (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * invDet, (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * invDet, (m[1][0] * m[0][2] - m[0][0] * m[1][2]) * invDet, // - (m[1][0] * m[2][1] - m[2][0] * m[1][1]) * invDet, (m[2][0] * m[0][1] - m[0][0] * m[2][1]) * invDet, (m[0][0] * m[1][1] - m[1][0] * m[0][1]) * invDet, // + (m[1][1] * m[2][2] - m[2][1] * m[1][2]) * invDet, + (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * invDet, + (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * invDet, // + (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * invDet, + (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * invDet, + (m[1][0] * m[0][2] - m[0][0] * m[1][2]) * invDet, // + (m[1][0] * m[2][1] - m[2][0] * m[1][1]) * invDet, + (m[2][0] * m[0][1] - m[0][0] * m[2][1]) * invDet, + (m[0][0] * m[1][1] - m[1][0] * m[0][1]) * invDet, // }); } @@ -69,9 +75,15 @@ const CMatrix3& CMatrix3::identity() { static const CMatrix3 Identity3 = CMatrix3(std::array<std::array<double, 3>, 3>{ - 1, 0, 0, // - 0, 1, 0, // - 0, 0, 1, // + 1, + 0, + 0, // + 0, + 1, + 0, // + 0, + 0, + 1, // }); return Identity3; } @@ -84,14 +96,20 @@ } static CMatrix3 Bradford = CMatrix3(std::array<std::array<double, 3>, 3>{ - 0.8951, 0.2664, -0.1614, // - -0.7502, 1.7135, 0.0367, // - 0.0389, -0.0685, 1.0296, // + 0.8951, + 0.2664, + -0.1614, // + -0.7502, + 1.7135, + 0.0367, // + 0.0389, + -0.0685, + 1.0296, // }); static CMatrix3 BradfordInv = Bradford.invert(); -CMatrix3 Hyprgraphics::adaptWhite(const CColor::xy& src, const CColor::xy& dst) { +CMatrix3 Hyprgraphics::adaptWhite(const CColor::xy& src, const CColor::xy& dst) { if (src == dst) return CMatrix3::identity(); @@ -101,9 +119,15 @@ return BradfordInv * CMatrix3(std::array<std::array<double, 3>, 3>{ - factors.x, 0.0, 0.0, // - 0.0, factors.y, 0.0, // - 0.0, 0.0, factors.z, // + factors.x, + 0.0, + 0.0, // + 0.0, + factors.y, + 0.0, // + 0.0, + 0.0, + factors.z, // }) * Bradford; } @@ -115,9 +139,15 @@ const auto w = xy2xyz(white); const auto invMat = CMatrix3(std::array<std::array<double, 3>, 3>{ - r.x, g.x, b.x, // - r.y, g.y, b.y, // - r.z, g.z, b.z, // + r.x, + g.x, + b.x, // + r.y, + g.y, + b.y, // + r.z, + g.z, + b.z, // }) .invert(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/image/Image.cpp new/hyprgraphics-0.3.0/src/image/Image.cpp --- old/hyprgraphics-0.1.5/src/image/Image.cpp 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/src/image/Image.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -4,21 +4,34 @@ #ifdef JXL_FOUND #include "formats/JpegXL.hpp" #endif +#ifdef HEIF_FOUND +#include "formats/Avif.hpp" +#endif #include "formats/Webp.hpp" #include "formats/Png.hpp" +#include "formats/Svg.hpp" #include <magic.h> #include <format> using namespace Hyprgraphics; using namespace Hyprutils::Memory; +using namespace Hyprutils::Math; -Hyprgraphics::CImage::CImage(const std::span<uint8_t>& data, eImageFormat format) { +Hyprgraphics::CImage::CImage(const std::span<const uint8_t> data, eImageFormat format) { std::expected<cairo_surface_t*, std::string> CAIROSURFACE; if (format == eImageFormat::IMAGE_FORMAT_PNG) { CAIROSURFACE = PNG::createSurfaceFromPNG(data); mime = "image/png"; + } else if (format == eImageFormat::IMAGE_FORMAT_AVIF) { +#ifndef HEIF_FOUND + lastError = "hyprgraphics compiled without HEIF support"; + return; +#else + CAIROSURFACE = AVIF::createSurfaceFromAvif(data); + mime = "image/avif"; +#endif } else { - lastError = "Currently only PNG images are supported for embedding"; + lastError = "Currently only PNG and AVIF images are supported for embedding"; return; } @@ -36,24 +49,26 @@ pCairoSurface = makeShared<CCairoSurface>(CAIROSURFACE.value()); } -Hyprgraphics::CImage::CImage(const std::string& path) : filepath(path) { +Hyprgraphics::CImage::CImage(const std::string& path, const Vector2D& size) : filepath(path), m_svgSize(size) { std::expected<cairo_surface_t*, std::string> CAIROSURFACE; - const auto len = path.length(); - if (path.find(".png") == len - 4 || path.find(".PNG") == len - 4) { + if (path.ends_with(".png") || path.ends_with(".PNG")) { CAIROSURFACE = PNG::createSurfaceFromPNG(path); mime = "image/png"; - } else if (path.find(".jpg") == len - 4 || path.find(".JPG") == len - 4 || path.find(".jpeg") == len - 5 || path.find(".JPEG") == len - 5) { + } else if (path.ends_with(".jpg") || path.ends_with(".JPG") || path.ends_with(".jpeg") || path.ends_with(".JPEG")) { CAIROSURFACE = JPEG::createSurfaceFromJPEG(path); imageHasAlpha = false; mime = "image/jpeg"; - } else if (path.find(".bmp") == len - 4 || path.find(".BMP") == len - 4) { + } else if (path.ends_with(".bmp") || path.ends_with(".BMP")) { CAIROSURFACE = BMP::createSurfaceFromBMP(path); imageHasAlpha = false; mime = "image/bmp"; - } else if (path.find(".webp") == len - 5 || path.find(".WEBP") == len - 5) { + } else if (path.ends_with(".webp") || path.ends_with(".WEBP")) { CAIROSURFACE = WEBP::createSurfaceFromWEBP(path); mime = "image/webp"; - } else if (path.find(".jxl") == len - 4 || path.find(".JXL") == len - 4) { + } else if (path.ends_with(".svg") || path.ends_with(".SVG")) { + CAIROSURFACE = SVG::createSurfaceFromSVG(path, m_svgSize); + mime = "image/svg"; + } else if (path.ends_with(".jxl") || path.ends_with(".JXL")) { #ifdef JXL_FOUND CAIROSURFACE = JXL::createSurfaceFromJXL(path); @@ -63,12 +78,25 @@ return; #endif + } else if (path.ends_with(".avif") || path.ends_with(".AVIF")) { + +#ifdef HEIF_FOUND + CAIROSURFACE = AVIF::createSurfaceFromAvif(path); + mime = "image/avif"; +#else + lastError = "hyprgraphics compiled without HEIF support"; + return; +#endif + } else { // magic is slow, so only use it when no recognized extension is found auto handle = magic_open(MAGIC_NONE | MAGIC_COMPRESS | MAGIC_SYMLINK); magic_load(handle, nullptr); - const auto type_str = std::string(magic_file(handle, path.c_str())); + const auto type_str = std::string(magic_file(handle, path.c_str())); + + magic_close(handle); + const auto first_word = type_str.substr(0, type_str.find(' ')); if (first_word == "PNG") { @@ -86,10 +114,22 @@ lastError = "hyprgraphics compiled without JXL support"; return; #endif + } else if (type_str.contains("AVIF")) { // libmagic can identify AVIF images as "ISO Media, AVIF Image" +#ifdef HEIF_FOUND + CAIROSURFACE = AVIF::createSurfaceFromAvif(path); + mime = "image/avif"; +#else + lastError = "hyprgraphics compiled without AVIF support"; + return; +#endif } else if (first_word == "BMP") { CAIROSURFACE = BMP::createSurfaceFromBMP(path); imageHasAlpha = false; mime = "image/bmp"; + } else if (first_word == "SVG") { + CAIROSURFACE = SVG::createSurfaceFromSVG(path, m_svgSize); + imageHasAlpha = false; + mime = "image/svg"; } else { lastError = "unrecognized image"; return; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/image/formats/Avif.cpp new/hyprgraphics-0.3.0/src/image/formats/Avif.cpp --- old/hyprgraphics-0.1.5/src/image/formats/Avif.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/src/image/formats/Avif.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,92 @@ +#include "Avif.hpp" +#include <cairo.h> +#include <cstdint> +#include <cstring> +#include <expected> +#include <filesystem> +#include <hyprutils/utils/ScopeGuard.hpp> +#include <libheif/heif.h> +#include <vector> +using namespace Hyprutils::Utils; + +static std::expected<cairo_surface_t*, std::string> loadFromContext(heif_context* ctx) { + heif_image_handle* handle; + heif_context_get_primary_image_handle(ctx, &handle); + + heif_image* img; + struct heif_error err = heif_decode_image(handle, &img, heif_colorspace_RGB, heif_chroma_interleaved_RGBA, nullptr); + + if (err.code != heif_error_Ok) + return std::unexpected("loading avif: failed to decode image"); + + size_t width = heif_image_get_width(img, heif_channel_interleaved); + size_t height = heif_image_get_height(img, heif_channel_interleaved); + + if (width == static_cast<size_t>(-1) || height == static_cast<size_t>(-1)) + return std::unexpected("loading avif: failed to get width or height"); + + int stride; + const uint8_t* data = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride); + + if (!data) + return std::unexpected("loading avif: get_plane_readonly failed"); + + std::vector<uint8_t> rawData; + rawData.resize(width * height * 4); + + for (size_t y = 0; y < height; y++) { + const uint8_t* src = data + (y * stride); + uint32_t* dst = (uint32_t*)(rawData.data() + (y * width * 4)); + for (size_t x = 0; x < width; x++) { + uint8_t r = src[(4 * x) + 0]; + uint8_t g = src[(4 * x) + 1]; + uint8_t b = src[(4 * x) + 2]; + uint8_t a = src[(4 * x) + 3]; + + r = (r * a) / 255.F; + g = (g * a) / 255.F; + b = (b * a) / 255.F; + + dst[x] = (a << 24) | (r << 16) | (g << 8) | b; + } + } + + auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + if (!CAIROSURFACE) + return std::unexpected("loading avif: cairo failed"); + + memcpy(cairo_image_surface_get_data(CAIROSURFACE), rawData.data(), rawData.size()); + cairo_surface_mark_dirty(CAIROSURFACE); + + heif_image_release(img); + heif_image_handle_release(handle); + return CAIROSURFACE; +} + +std::expected<cairo_surface_t*, std::string> AVIF::createSurfaceFromAvif(const std::string& path) { + if (!std::filesystem::exists(path)) + return std::unexpected("loading avif: file doesn't exist"); + + heif_context* ctx = heif_context_alloc(); + struct heif_error err = heif_context_read_from_file(ctx, path.c_str(), nullptr); + + if (err.code != heif_error_Ok) + return std::unexpected("loading avif: failed to load from file"); + + auto result = loadFromContext(ctx); + heif_context_free(ctx); + + return result; +} +std::expected<cairo_surface_t*, std::string> AVIF::createSurfaceFromAvif(const std::span<const uint8_t> buf) { + heif_context* ctx = heif_context_alloc(); + struct heif_error err = heif_context_read_from_memory(ctx, buf.data(), buf.size(), nullptr); + + if (err.code != heif_error_Ok) + return std::unexpected("loading avif: failed to load from memory"); + + auto result = loadFromContext(ctx); + heif_context_free(ctx); + + return result; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/image/formats/Avif.hpp new/hyprgraphics-0.3.0/src/image/formats/Avif.hpp --- old/hyprgraphics-0.1.5/src/image/formats/Avif.hpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/src/image/formats/Avif.hpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,12 @@ +#pragma once + +#include <cairo/cairo.h> +#include <cstdint> +#include <span> +#include <string> +#include <expected> + +namespace AVIF { + std::expected<cairo_surface_t*, std::string> createSurfaceFromAvif(const std::string&); + std::expected<cairo_surface_t*, std::string> createSurfaceFromAvif(const std::span<const uint8_t>); +}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/image/formats/Png.cpp new/hyprgraphics-0.3.0/src/image/formats/Png.cpp --- old/hyprgraphics-0.1.5/src/image/formats/Png.cpp 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/src/image/formats/Png.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -38,8 +38,8 @@ } struct SReadState { - const std::span<uint8_t>& data; - size_t offset; + const std::span<const uint8_t> data; + size_t offset; }; static void customReadFunction(png_structp png, png_bytep data, png_size_t length) { @@ -53,7 +53,7 @@ state->offset += length; } -std::expected<cairo_surface_t*, std::string> PNG::createSurfaceFromPNG(const std::span<uint8_t>& data) { +std::expected<cairo_surface_t*, std::string> PNG::createSurfaceFromPNG(const std::span<const uint8_t> data) { png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); png_infop info = png_create_info_struct(png); if (!png || !info) @@ -86,13 +86,13 @@ png_set_palette_to_rgb(png); if (COLOR_TYPE == PNG_COLOR_TYPE_GRAY && BPP < 8) png_set_expand_gray_1_2_4_to_8(png); + if (COLOR_TYPE == PNG_COLOR_TYPE_GRAY || COLOR_TYPE == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); if (COLOR_TYPE == PNG_COLOR_TYPE_RGB || COLOR_TYPE == PNG_COLOR_TYPE_GRAY || COLOR_TYPE == PNG_COLOR_TYPE_PALETTE) png_set_filler(png, 0xFF, PNG_FILLER_AFTER); - else if (COLOR_TYPE == PNG_COLOR_TYPE_GRAY_ALPHA) - png_set_gray_to_rgb(png); png_read_update_info(png, info); @@ -107,10 +107,10 @@ png_read_image(png, rowPointers.data()); for (size_t i = 0; i < W * H * 4; i += 4) { - uint8_t r = rawData[i + 0]; - uint8_t g = rawData[i + 1]; - uint8_t b = rawData[i + 2]; - uint8_t a = rawData[i + 3]; + uint8_t r = rawData[i + 0]; + uint8_t g = rawData[i + 1]; + uint8_t b = rawData[i + 2]; + uint8_t a = rawData[i + 3]; r *= ((float)a) / 255.F; g *= ((float)a) / 255.F; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/image/formats/Png.hpp new/hyprgraphics-0.3.0/src/image/formats/Png.hpp --- old/hyprgraphics-0.1.5/src/image/formats/Png.hpp 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/src/image/formats/Png.hpp 2025-11-06 21:47:32.000000000 +0100 @@ -9,5 +9,5 @@ namespace PNG { std::expected<cairo_surface_t*, std::string> createSurfaceFromPNG(const std::string&); - std::expected<cairo_surface_t*, std::string> createSurfaceFromPNG(const std::span<uint8_t>&); + std::expected<cairo_surface_t*, std::string> createSurfaceFromPNG(const std::span<const uint8_t>); }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/image/formats/Svg.cpp new/hyprgraphics-0.3.0/src/image/formats/Svg.cpp --- old/hyprgraphics-0.1.5/src/image/formats/Svg.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/src/image/formats/Svg.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,63 @@ +#include "Svg.hpp" +#include <filesystem> +#include <fstream> +#include <librsvg/rsvg.h> +#include <hyprutils/utils/ScopeGuard.hpp> +#include <hyprutils/string/String.hpp> + +using namespace Hyprutils::Utils; +using namespace Hyprutils::Math; +using namespace Hyprutils::String; + +static std::optional<std::string> readFileAsString(const std::string& path) { + std::error_code ec; + + if (!std::filesystem::exists(path, ec) || ec) + return std::nullopt; + + std::ifstream file(path); + if (!file.good()) + return std::nullopt; + + return trim(std::string((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>()))); +} + +std::expected<cairo_surface_t*, std::string> SVG::createSurfaceFromSVG(const std::string& path, const Vector2D& size) { + if (!std::filesystem::exists(path)) + return std::unexpected("loading svg: file doesn't exist"); + + if (size.x < 1 || size.y < 1) + return std::unexpected("loading svg: invalid size"); + + auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size.x, size.y); + + const auto PCAIRO = cairo_create(cairoSurface); + + cairo_save(PCAIRO); + cairo_set_operator(PCAIRO, CAIRO_OPERATOR_CLEAR); + cairo_paint(PCAIRO); + cairo_restore(PCAIRO); + + GError* error = nullptr; + auto file = readFileAsString(path); + + if (!file) + return std::unexpected("loading png: file doesn't exist / inaccessible"); + + RsvgHandle* handle = rsvg_handle_new_from_data((unsigned char*)file->data(), file->size(), &error); + + if (!handle) + return std::unexpected("loading svg: rsvg failed to read data"); + + RsvgRectangle rect = {0, 0, (double)size.x, (double)size.y}; + + if (!rsvg_handle_render_document(handle, PCAIRO, &rect, &error)) + return std::unexpected("loading svg: rsvg failed to render"); + + // done + cairo_surface_flush(cairoSurface); + cairo_destroy(PCAIRO); + g_object_unref(handle); + + return cairoSurface; +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/image/formats/Svg.hpp new/hyprgraphics-0.3.0/src/image/formats/Svg.hpp --- old/hyprgraphics-0.1.5/src/image/formats/Svg.hpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/src/image/formats/Svg.hpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,11 @@ +#pragma once + +#include <cairo/cairo.h> +#include <string> +#include <expected> +#include <png.h> +#include <hyprutils/math/Vector2D.hpp> + +namespace SVG { + std::expected<cairo_surface_t*, std::string> createSurfaceFromSVG(const std::string&, const Hyprutils::Math::Vector2D& size); +}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/resource/AsyncResourceGatherer.cpp new/hyprgraphics-0.3.0/src/resource/AsyncResourceGatherer.cpp --- old/hyprgraphics-0.1.5/src/resource/AsyncResourceGatherer.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/src/resource/AsyncResourceGatherer.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,63 @@ +#include <hyprgraphics/resource/AsyncResourceGatherer.hpp> + +using namespace Hyprgraphics; + +CAsyncResourceGatherer::CAsyncResourceGatherer() { + m_gatherThread = std::thread([this]() { asyncAssetSpinLock(); }); +} + +CAsyncResourceGatherer::~CAsyncResourceGatherer() { + m_asyncLoopState.exit = true; + wakeUpMainThread(); + + if (m_gatherThread.joinable()) + m_gatherThread.join(); +} + +void CAsyncResourceGatherer::wakeUpMainThread() { + m_asyncLoopState.needsToProcess = true; + m_asyncLoopState.requestsCV.notify_all(); +} + +void CAsyncResourceGatherer::enqueue(Hyprutils::Memory::CAtomicSharedPointer<IAsyncResource> resource) { + { + std::lock_guard<std::mutex> lg(m_targetsToLoadMutex); + m_targetsToLoad.emplace_back(resource); + } + + wakeUpMainThread(); +} + +void CAsyncResourceGatherer::asyncAssetSpinLock() { + while (!m_asyncLoopState.exit) { + + std::unique_lock lk(m_asyncLoopState.requestMutex); + if (!m_asyncLoopState.needsToProcess) // avoid a lock if a thread managed to request something already since we .unlock()ed + m_asyncLoopState.requestsCV.wait_for(lk, std::chrono::seconds(5), [this] { return m_asyncLoopState.needsToProcess; }); // wait for events + + if (m_asyncLoopState.exit) + break; + + m_asyncLoopState.needsToProcess = false; + lk.unlock(); + m_targetsToLoadMutex.lock(); + + if (m_targetsToLoad.empty()) { + m_targetsToLoadMutex.unlock(); + continue; + } + + auto requests = m_targetsToLoad; + m_targetsToLoad.clear(); + + m_targetsToLoadMutex.unlock(); + + // process requests + for (auto& r : requests) { + r->render(); + + r->m_ready = true; + r->m_events.finished.emit(); + } + } +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/resource/resources/ImageResource.cpp new/hyprgraphics-0.3.0/src/resource/resources/ImageResource.cpp --- old/hyprgraphics-0.1.5/src/resource/resources/ImageResource.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/src/resource/resources/ImageResource.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,25 @@ +#include <hyprgraphics/image/Image.hpp> +#include <hyprgraphics/resource/resources/ImageResource.hpp> +#include <hyprutils/memory/Atomic.hpp> +#include <hyprutils/memory/Casts.hpp> + +#include <cairo/cairo.h> +#include <pango/pangocairo.h> + +using namespace Hyprgraphics; +using namespace Hyprutils::Memory; + +CImageResource::CImageResource(const std::string& path) : m_path(path) { + ; +} + +CImageResource::CImageResource(const std::string& svg, const Hyprutils::Math::Vector2D& size) : m_path(svg), m_svgSize(size) { + ; +} + +void CImageResource::render() { + auto image = CImage(m_path, m_svgSize); + + m_asset.cairoSurface = image.cairoSurface(); + m_asset.pixelSize = m_asset.cairoSurface && m_asset.cairoSurface->cairo() ? m_asset.cairoSurface->size() : Hyprutils::Math::Vector2D{}; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/resource/resources/StaticImageResource.cpp new/hyprgraphics-0.3.0/src/resource/resources/StaticImageResource.cpp --- old/hyprgraphics-0.1.5/src/resource/resources/StaticImageResource.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/src/resource/resources/StaticImageResource.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,20 @@ +#include <hyprgraphics/resource/resources/StaticImageResource.hpp> +#include <hyprutils/memory/Atomic.hpp> +#include <hyprutils/memory/Casts.hpp> + +#include <cairo/cairo.h> +#include <pango/pangocairo.h> + +using namespace Hyprgraphics; +using namespace Hyprutils::Memory; + +CStaticImageResource::CStaticImageResource(const std::span<const uint8_t> data, eImageFormat format) : m_data(data), m_format(format) { + ; +} + +void CStaticImageResource::render() { + auto image = CImage(m_data, m_format); + + m_asset.cairoSurface = image.cairoSurface(); + m_asset.pixelSize = m_asset.cairoSurface && m_asset.cairoSurface->cairo() ? m_asset.cairoSurface->size() : Hyprutils::Math::Vector2D{}; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/src/resource/resources/TextResource.cpp new/hyprgraphics-0.3.0/src/resource/resources/TextResource.cpp --- old/hyprgraphics-0.1.5/src/resource/resources/TextResource.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/src/resource/resources/TextResource.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,109 @@ +#include <hyprgraphics/resource/resources/TextResource.hpp> +#include <hyprutils/memory/Atomic.hpp> +#include <hyprutils/memory/Casts.hpp> + +#include <cairo/cairo.h> +#include <pango/pangocairo.h> + +using namespace Hyprgraphics; +using namespace Hyprutils::Memory; + +CTextResource::CTextResource(CTextResource::STextResourceData&& data) : m_data(std::move(data)) { + ; +} + +void CTextResource::render() { + auto CAIROSURFACE = makeUnique<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1 /* dummy value */)); + auto CAIRO = cairo_create(CAIROSURFACE->cairo()); + + PangoLayout* layout = pango_cairo_create_layout(CAIRO); + + PangoFontDescription* fontDesc = pango_font_description_from_string(m_data.font.c_str()); + pango_font_description_set_size(fontDesc, m_data.fontSize * PANGO_SCALE); + pango_layout_set_font_description(layout, fontDesc); + pango_font_description_free(fontDesc); + + cairo_font_options_t* options = cairo_font_options_create(); + cairo_font_options_set_antialias(options, m_data.antialias); + cairo_font_options_set_hint_style(options, m_data.hintStyle); + pango_cairo_context_set_font_options(pango_layout_get_context(layout), options); + cairo_font_options_destroy(options); + + PangoAlignment pangoAlign = PANGO_ALIGN_LEFT; + + switch (m_data.align) { + case TEXT_ALIGN_LEFT: break; + case TEXT_ALIGN_CENTER: pangoAlign = PANGO_ALIGN_CENTER; break; + case TEXT_ALIGN_RIGHT: pangoAlign = PANGO_ALIGN_RIGHT; break; + default: break; + } + + pango_layout_set_alignment(layout, pangoAlign); + + PangoAttrList* attrList = nullptr; + GError* gError = nullptr; + char* buf = nullptr; + if (pango_parse_markup(m_data.text.c_str(), -1, 0, &attrList, &buf, nullptr, &gError)) + pango_layout_set_text(layout, buf, -1); + else { + g_error_free(gError); + pango_layout_set_text(layout, m_data.text.c_str(), -1); + } + + if (!attrList) + attrList = pango_attr_list_new(); + + if (buf) + free(buf); + + pango_attr_list_insert(attrList, pango_attr_scale_new(1)); + pango_layout_set_attributes(layout, attrList); + pango_attr_list_unref(attrList); + + PangoRectangle ink, logical; + pango_layout_get_pixel_extents(layout, &ink, &logical); + + if (m_data.maxSize) { + if (m_data.ellipsize) + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + if (m_data.maxSize->x >= 0) + pango_layout_set_width(layout, std::min(logical.width * PANGO_SCALE, sc<int>(m_data.maxSize->x * PANGO_SCALE))); + if (m_data.maxSize->y >= 0) + pango_layout_set_height(layout, std::min(logical.height * PANGO_SCALE, sc<int>(m_data.maxSize->y * PANGO_SCALE))); + if (m_data.wrap) + pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); + + pango_layout_get_pixel_extents(layout, &ink, &logical); + } + + pango_layout_get_pixel_extents(layout, &ink, &logical); + + // TODO: avoid this? + cairo_destroy(CAIRO); + + CAIROSURFACE.reset(); + + m_asset.cairoSurface = makeShared<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, logical.width, logical.height)); + CAIRO = cairo_create(m_asset.cairoSurface->cairo()); + + // clear the pixmap + cairo_save(CAIRO); + cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR); + cairo_paint(CAIRO); + cairo_restore(CAIRO); + + // render the thing + const auto RGB = m_data.color.asRgb(); + cairo_set_source_rgba(CAIRO, RGB.r, RGB.g, RGB.b, 1.F); + + cairo_move_to(CAIRO, -logical.x, -logical.y); + pango_cairo_show_layout(CAIRO, layout); + + g_object_unref(layout); + + cairo_surface_flush(m_asset.cairoSurface->cairo()); + + m_asset.pixelSize = {logical.width, logical.height}; + + cairo_destroy(CAIRO); +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/tests/arg.cpp new/hyprgraphics-0.3.0/tests/arg.cpp --- old/hyprgraphics-0.1.5/tests/arg.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/tests/arg.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,139 @@ +#include <algorithm> +#include <print> +#include <format> +#include <filesystem> +#include <fstream> +#include <vector> +#include <hyprgraphics/resource/AsyncResourceGatherer.hpp> +#include <hyprgraphics/resource/resources/TextResource.hpp> +#include <hyprgraphics/resource/resources/ImageResource.hpp> +#include <hyprutils/memory/UniquePtr.hpp> +#include <hyprutils/memory/Atomic.hpp> +#include <hyprutils/math/Vector2D.hpp> +#include "shared.hpp" + +using namespace Hyprutils::Memory; +using namespace Hyprutils::Math; +using namespace Hyprgraphics; + +#define UP CUniquePointer + +static UP<CAsyncResourceGatherer> g_asyncResourceGatherer; + +static struct { + std::mutex wakeupMutex; + std::condition_variable wakeup; + + bool exit = false; + bool needsToProcess = false; + + int loadedAssets = 0; + + std::mutex resourcesMutex; + std::vector<CAtomicSharedPointer<IAsyncResource>> resources; +} state; + +// + +static bool renderText(const std::string& text, Vector2D max = {}) { + // this stinks a bit but it's due to our ASP impl. + auto resource = + makeAtomicShared<CTextResource>(CTextResource::STextResourceData{.text = text, .fontSize = 72, .maxSize = max.x == 0 ? std::nullopt : std::optional<Vector2D>(max)}); + CAtomicSharedPointer<IAsyncResource> resourceGeneric(resource); + + g_asyncResourceGatherer->enqueue(resourceGeneric); + + state.resourcesMutex.lock(); + state.resources.emplace_back(std::move(resourceGeneric)); + state.resourcesMutex.unlock(); + + resource->m_events.finished.listenStatic([]() { + state.needsToProcess = true; + state.wakeup.notify_all(); + }); + + std::println("Enqueued \"{}\" successfully.", text); + + return true; +} + +static bool renderImage(const std::string& path) { + // this stinks a bit but it's due to our ASP impl. + auto resource = makeAtomicShared<CImageResource>(path); + CAtomicSharedPointer<IAsyncResource> resourceGeneric(resource); + + g_asyncResourceGatherer->enqueue(resourceGeneric); + + state.resourcesMutex.lock(); + state.resources.emplace_back(std::move(resourceGeneric)); + state.resourcesMutex.unlock(); + + resource->m_events.finished.listenStatic([]() { + state.needsToProcess = true; + state.wakeup.notify_all(); + }); + + std::println("Enqueued \"{}\" successfully.", path); + + return true; +} + +int main(int argc, char** argv, char** envp) { + int ret = 0; + + g_asyncResourceGatherer = makeUnique<CAsyncResourceGatherer>(); + + EXPECT(renderText("Hello World"), true); + EXPECT(renderText("<b><i>Test markup</i></b>"), true); + EXPECT(renderText("Test ellipsis!!!!!", {512, 190}), + true); + EXPECT(renderImage("./resource/images/hyprland.png"), true); + + while (!state.exit) { + std::unique_lock lk(state.wakeupMutex); + if (!state.needsToProcess) // avoid a lock if a thread managed to request something already since we .unlock()ed + state.wakeup.wait_for(lk, std::chrono::seconds(5), [] { return state.needsToProcess; }); // wait for events + + if (state.exit) + break; + + state.needsToProcess = false; + + state.resourcesMutex.lock(); + + const bool SHOULD_EXIT = std::ranges::all_of(state.resources, [](const auto& e) { return !!e->m_ready; }); + + state.resourcesMutex.unlock(); + + if (SHOULD_EXIT) + break; + + lk.unlock(); + } + + // all assets should be done, let's render them + size_t idx = 0; + for (const auto& r : state.resources) { + const auto TEST_DIR = std::filesystem::current_path().string() + "/test_output"; + + // try to write it for inspection + if (!std::filesystem::exists(TEST_DIR)) + std::filesystem::create_directory(TEST_DIR); + + std::string name = std::format("render-arg-{}", idx); + + EXPECT(!!r->m_asset.cairoSurface->cairo(), true); + + //NOLINTNEXTLINE + if (!r->m_asset.cairoSurface->cairo()) + continue; + + EXPECT(cairo_surface_write_to_png(r->m_asset.cairoSurface->cairo(), (TEST_DIR + "/" + name + ".png").c_str()), CAIRO_STATUS_SUCCESS); + + idx++; + } + + g_asyncResourceGatherer.reset(); + + return ret; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/tests/image.cpp new/hyprgraphics-0.3.0/tests/image.cpp --- old/hyprgraphics-0.1.5/tests/image.cpp 2025-07-10 14:05:40.000000000 +0200 +++ new/hyprgraphics-0.3.0/tests/image.cpp 2025-11-06 21:47:32.000000000 +0100 @@ -10,7 +10,7 @@ using namespace Hyprgraphics; static bool tryLoadImageFromFile(const std::string& path) { - auto image = CImage(path); + auto image = path.ends_with("svg") ? CImage(path, {512, 512}) : CImage(path); if (!image.success()) { std::println("Failed to load {}: {}", path, image.getError()); @@ -32,8 +32,8 @@ return cairo_surface_write_to_png(image.cairoSurface()->cairo(), (TEST_DIR + "/" + name + ".png").c_str()) == CAIRO_STATUS_SUCCESS; } -static bool tryLoadImageFromBuffer(const std::span<uint8_t>& data) { - auto image = CImage(data, IMAGE_FORMAT_PNG); +static bool tryLoadImageFromBuffer(const std::span<uint8_t>& data, eImageFormat format) { + auto image = CImage(data, format); if (!image.success()) { std::println("Failed to load embedded image: {}", image.getError()); @@ -58,7 +58,7 @@ static std::vector<uint8_t> getImageBuffer(const std::string& path) { std::vector<uint8_t> buffer; - std::ifstream file("./resource/images/hyprland.png", std::ios::binary | std::ios::ate); + std::ifstream file(path, std::ios::binary | std::ios::ate); std::streamsize size = file.tellg(); file.seekg(0, std::ios::beg); @@ -80,11 +80,20 @@ if (file.path().filename() == "hyprland.jxl") expectation = false; #endif +#ifndef HEIF_FOUND + if (file.path().filename() == "hyprland.avif") + expectation = false; +#endif EXPECT(tryLoadImageFromFile(file.path()), expectation); } - auto buffer = getImageBuffer("./resource/images/hyprland.png"); - EXPECT(tryLoadImageFromBuffer(buffer), true); + auto pngBuffer = getImageBuffer("./resource/images/hyprland.png"); + EXPECT(tryLoadImageFromBuffer(pngBuffer, Hyprgraphics::IMAGE_FORMAT_PNG), true); + +#ifdef HEIF_FOUND + auto avifBuffer = getImageBuffer("./resource/images/hyprland.avif"); + EXPECT(tryLoadImageFromBuffer(avifBuffer, Hyprgraphics::IMAGE_FORMAT_AVIF), true); +#endif return ret; } Binary files old/hyprgraphics-0.1.5/tests/resource/images/hyprland.avif and new/hyprgraphics-0.3.0/tests/resource/images/hyprland.avif differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.5/tests/resource/images/hyprland.svg new/hyprgraphics-0.3.0/tests/resource/images/hyprland.svg --- old/hyprgraphics-0.1.5/tests/resource/images/hyprland.svg 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.3.0/tests/resource/images/hyprland.svg 2025-11-06 21:47:32.000000000 +0100 @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg id="Livello_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 1006.49 1006.49"> + <defs> + <style> + .st0 { + fill: url(#a); + } + </style> + <linearGradient id="a" x1="493.93" y1="901.88" x2="493.93" y2="104.6" gradientUnits="userSpaceOnUse"> + <stop offset="0" stop-color="#00a8f4"/> + <stop offset="1" stop-color="#00e5d0"/> + </linearGradient> + </defs> + <path class="st0" d="M681.59,325.09c-6.06-8.7-11.78-16.91-17.43-25.14-8.03-11.73-18.9-26.74-31.49-44.09-28.76-39.72-73.68-100.94-104.06-151.25v125.92c31.29,44.88,61.67,85.34,77.88,108.99,35.52,51.8,76.66,107,101.06,162.21,73.34,165.94-29.82,328.29-209.07,330.23h-3.21c-.45,0-.89-.02-1.35-.02-.45,0-.89.02-1.35.02h-3.21c-179.25-1.94-282.41-164.29-209.07-330.23,24.41-55.21,65.54-110.41,101.06-162.21,16.21-23.65,46.59-64.11,77.88-108.99v-125.92c-30.38,50.32-75.3,111.54-104.06,151.25-12.59,17.36-23.45,32.37-31.49,44.09-5.65,8.23-11.37,16.44-17.43,25.14-32.82,47.18-66.78,95.97-89.93,148.35-22.49,50.89-32.35,102.89-29.28,154.54,2.96,50.07,18.54,98.28,45.04,139.46,26.2,40.69,63.03,74.42,106.51,97.55,44.94,23.88,95.4,36.31,150,36.89,1.33.02,2.64.02,3.96.02.45,0,.9-.02,1.35-.02.45,0,.89.02,1.35.02,1.33,0,2.64,0,3.96-.02,54.6-.57,105.06-13,150-36.89,43.48-23.13,80.32-56.86,106.51-97.55,26.5-41.17,42.09-89.39,45.04-139.46,3.07-51.64-6.8-103.65-29.28-154.54-23.15-52.38-57.11-101.17-89.93-148.35 Z"/> +</svg>
