mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
svg: replaced nanosvg by librsvg for broader compatibility with SVG2 standard
This commit is contained in:
+51
-22
@@ -4,37 +4,66 @@ Noctalia is made possible by the incredible work of many open-source projects an
|
||||
|
||||
## Design & Branding
|
||||
|
||||
- **MrDowntempo** - Creator of the Noctalia Owl and moon logo
|
||||
- **[SaberJ2X](https://www.reddit.com/user/SaberJ64/)** - Creator of Talia, the Noctalia mascot
|
||||
- **[Tabler icons](https://tabler.io/icons)** - The fantastic tabler icons.
|
||||
|
||||
## Runtime Dependencies
|
||||
|
||||
### Vendored Libraries
|
||||
|
||||
- **[fzy](https://github.com/jhawthorn/fzy)** - Fuzzy matching algorithm used for launcher ranking
|
||||
|
||||
### System Integration
|
||||
- **[wlsunset](https://sr.ht/~kennylevinsen/wlsunset/)** - Night light and blue light filter support
|
||||
- **[ddcutil](https://www.ddcutil.com/)** - External display brightness control
|
||||
|
||||
### Media & Audio
|
||||
- **[gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/about/)** - Hardware-accelerated screen recording
|
||||
|
||||
## Icons
|
||||
- **[Tabler Icons](https://tabler.io/icons)** - Icon set used throughout the shell
|
||||
- **[Riyan Resdian on Noun Project](https://thenounproject.com/creator/yaicon/)** - Plug icon
|
||||
- **MrDowntempo** — Creator of the Noctalia Owl and moon logo
|
||||
- **[SaberJ2X](https://www.reddit.com/user/SaberJ64/)** — Creator of Talia, the Noctalia mascot
|
||||
- **[Tabler Icons](https://tabler.io/icons)** — Icon set used throughout the shell
|
||||
- **[Riyan Resdian on Noun Project](https://thenounproject.com/creator/yaicon/)** — Plug icon
|
||||
|
||||
## Audio Assets
|
||||
- **[Universfield on Pixabay](https://pixabay.com/users/universfield-28281460/)** - Notification sound effect
|
||||
- **[Lucas McCallister on Freesound](http://www.freesound.org/samplesViewSingle.php?id=67091)** - Volume change feedback sound effect
|
||||
|
||||
- **[Universfield on Pixabay](https://pixabay.com/users/universfield-28281460/)** — Notification sound effect
|
||||
- **[Lucas McCallister on Freesound](http://www.freesound.org/samplesViewSingle.php?id=67091)** — Volume change feedback sound effect
|
||||
|
||||
## System Libraries
|
||||
|
||||
Linked dynamically at runtime:
|
||||
|
||||
- **[Wayland](https://wayland.freedesktop.org/)** (`wayland-client`, `wayland-protocols`, `wayland-egl`) — Display protocol
|
||||
- **[Mesa / EGL / GLES2](https://www.mesa3d.org/)** (or **[libepoxy](https://github.com/anholt/libepoxy)** as fallback) — OpenGL ES context and dispatch
|
||||
- **[Cairo](https://www.cairographics.org/)** — 2D graphics surface used for text and SVG rasterization
|
||||
- **[Pango](https://pango.gnome.org/)** / **PangoCairo** — Text layout and shaping
|
||||
- **[FreeType](https://freetype.org/)** — Font rasterization
|
||||
- **[Fontconfig](https://www.fontconfig.org/)** — Font discovery
|
||||
- **[librsvg](https://wiki.gnome.org/Projects/LibRsvg)** — SVG rendering (filters, clipPaths, masks)
|
||||
- **[GLib / GObject / GIO](https://gitlab.gnome.org/GNOME/glib)** — Core utilities used by Pango/Cairo/librsvg
|
||||
- **[libxkbcommon](https://xkbcommon.org/)** — Keyboard handling
|
||||
- **[sdbus-c++](https://github.com/Kistler-Group/sdbus-cpp)** — D-Bus client bindings
|
||||
- **[PipeWire](https://pipewire.org/)** — Audio capture and playback
|
||||
- **[libcurl](https://curl.se/libcurl/)** — HTTP client
|
||||
- **[libwebp](https://developers.google.com/speed/webp)** — WebP decoding
|
||||
- **[polkit](https://gitlab.freedesktop.org/polkit/polkit)** (`polkit-agent`, `polkit-gobject`) — Authentication agent
|
||||
- **[Linux-PAM](https://github.com/linux-pam/linux-pam)** — Lockscreen authentication
|
||||
|
||||
## Vendored Libraries
|
||||
|
||||
Bundled in `third_party/` and built from source:
|
||||
|
||||
- **[dr_wav](https://github.com/mackron/dr_libs)** — Single-file WAV decoder (MIT-0 / public domain)
|
||||
- **[fzy](https://github.com/jhawthorn/fzy)** — Fuzzy matching algorithm used by the launcher, search pickers, and other shell ranking (MIT)
|
||||
- **[Luau](https://luau.org/)** — Lua dialect used for theme/template scripting (MIT)
|
||||
- **[Material Color Utilities](https://github.com/material-foundation/material-color-utilities)** — Material 3 palette generation (Apache-2.0)
|
||||
- **[nlohmann/json](https://github.com/nlohmann/json)** — JSON for Modern C++ (MIT)
|
||||
- **[stb](https://github.com/nothings/stb)** — Single-file utilities, primarily image I/O (MIT / public domain)
|
||||
- **[tinyexpr](https://github.com/codeplea/tinyexpr)** — Math expression parser (zlib)
|
||||
- **[toml++](https://github.com/marzer/tomlplusplus)** — TOML parser (MIT)
|
||||
- **[Wuffs](https://github.com/google/wuffs)** — Memory-safe image decoders (Apache-2.0)
|
||||
|
||||
## System Integration
|
||||
|
||||
External tools Noctalia integrates with at runtime when present:
|
||||
|
||||
- **[wlsunset](https://sr.ht/~kennylevinsen/wlsunset/)** — Night light and blue light filter
|
||||
- **[ddcutil](https://www.ddcutil.com/)** — External display brightness control
|
||||
- **[gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/about/)** — Hardware-accelerated screen recording
|
||||
|
||||
## Special Thanks
|
||||
|
||||
- The **Wayland** community for building the future of Linux desktop graphics
|
||||
- The **Niri**, **Hyprland**, **Sway**, **Labwc**, and **MangoWC** teams for their excellent Wayland compositors
|
||||
- All the contributors and users who have helped make Noctalia better
|
||||
|
||||
## License
|
||||
|
||||
Noctalia is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
||||
|
||||
Each dependency listed above is governed by its own respective license. Please refer to their individual projects for licensing information.
|
||||
|
||||
+2
-2
@@ -41,6 +41,7 @@ cairo_dep = dependency('cairo')
|
||||
cairo_ft_dep = dependency('cairo-ft')
|
||||
pango_dep = dependency('pango')
|
||||
pangocairo_dep = dependency('pangocairo')
|
||||
librsvg_dep = dependency('librsvg-2.0')
|
||||
xkbcommon_dep = dependency('xkbcommon')
|
||||
glib_dep = dependency('glib-2.0')
|
||||
gobject_dep = dependency('gobject-2.0')
|
||||
@@ -385,7 +386,6 @@ _noctalia_sources = files(
|
||||
'src/render/core/shader_program.cpp',
|
||||
'src/render/core/thumbnail_service.cpp',
|
||||
'src/render/core/image_decoder.cpp',
|
||||
'src/render/image_loaders.cpp',
|
||||
'src/render/programs/glyph_program.cpp',
|
||||
'src/render/programs/image_program.cpp',
|
||||
'src/render/programs/linear_gradient_program.cpp',
|
||||
@@ -626,7 +626,6 @@ endif
|
||||
# ── Include directories ────────────────────────────────────────────────────────
|
||||
_noctalia_inc = [
|
||||
include_directories('src'),
|
||||
include_directories('third_party/nanosvg'),
|
||||
include_directories('third_party/tomlplusplus'),
|
||||
include_directories('third_party/wuffs', is_system: true),
|
||||
# nlohmann is header-only and uses GCC extensions in some paths; treat as system.
|
||||
@@ -651,6 +650,7 @@ executable('noctalia',
|
||||
cairo_ft_dep,
|
||||
pango_dep,
|
||||
pangocairo_dep,
|
||||
librsvg_dep,
|
||||
xkbcommon_dep,
|
||||
glib_dep,
|
||||
gobject_dep,
|
||||
|
||||
@@ -1,23 +1,144 @@
|
||||
#include "render/core/image_file_loader.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wconversion"
|
||||
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
||||
#pragma GCC diagnostic ignored "-Wfloat-conversion"
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
#include <nanosvg.h>
|
||||
#include <nanosvgrast.h>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include "render/core/image_decoder.h"
|
||||
#include "util/file_utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cairo.h>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <librsvg/rsvg.h>
|
||||
|
||||
namespace {} // namespace
|
||||
namespace {
|
||||
|
||||
// Convert a cairo ARGB32 image surface (premultiplied BGRA on little-endian)
|
||||
// into the non-premultiplied RGBA buffer the rest of the pipeline expects.
|
||||
void argb32ToRgba(const unsigned char* src, int srcStride, std::uint8_t* dst, int width, int height) {
|
||||
for (int y = 0; y < height; ++y) {
|
||||
const std::uint32_t* row = reinterpret_cast<const std::uint32_t*>(src + (y * srcStride));
|
||||
std::uint8_t* outRow = dst + (static_cast<std::size_t>(y) * static_cast<std::size_t>(width) * 4U);
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const std::uint32_t pixel = row[x];
|
||||
const std::uint8_t a = static_cast<std::uint8_t>((pixel >> 24) & 0xFF);
|
||||
std::uint8_t r = static_cast<std::uint8_t>((pixel >> 16) & 0xFF);
|
||||
std::uint8_t g = static_cast<std::uint8_t>((pixel >> 8) & 0xFF);
|
||||
std::uint8_t b = static_cast<std::uint8_t>(pixel & 0xFF);
|
||||
if (a != 0 && a != 255) {
|
||||
// Un-premultiply, rounding to nearest.
|
||||
r = static_cast<std::uint8_t>(std::min(255, ((r * 255) + (a / 2)) / a));
|
||||
g = static_cast<std::uint8_t>(std::min(255, ((g * 255) + (a / 2)) / a));
|
||||
b = static_cast<std::uint8_t>(std::min(255, ((b * 255) + (a / 2)) / a));
|
||||
}
|
||||
outRow[(x * 4) + 0] = r;
|
||||
outRow[(x * 4) + 1] = g;
|
||||
outRow[(x * 4) + 2] = b;
|
||||
outRow[(x * 4) + 3] = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<LoadedImageFile> rasterizeSvg(const std::vector<std::uint8_t>& fileData, int targetSize,
|
||||
std::string* errorMessage) {
|
||||
GError* gerror = nullptr;
|
||||
RsvgHandle* handle = rsvg_handle_new_from_data(fileData.data(), fileData.size(), &gerror);
|
||||
if (handle == nullptr) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = std::string("failed to parse SVG: ") + (gerror != nullptr ? gerror->message : "unknown");
|
||||
}
|
||||
if (gerror != nullptr) {
|
||||
g_error_free(gerror);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Determine intrinsic pixel size. Many real-world SVGs (e.g. viewBox-only)
|
||||
// do not advertise pixel dimensions, so fall back to the viewBox or to a
|
||||
// sensible default before computing the render scale.
|
||||
gdouble intrinsicW = 0.0;
|
||||
gdouble intrinsicH = 0.0;
|
||||
gboolean hasIntrinsic = rsvg_handle_get_intrinsic_size_in_pixels(handle, &intrinsicW, &intrinsicH);
|
||||
if (hasIntrinsic == FALSE || intrinsicW <= 0.0 || intrinsicH <= 0.0) {
|
||||
gboolean outHasW = FALSE;
|
||||
RsvgLength outW{};
|
||||
gboolean outHasH = FALSE;
|
||||
RsvgLength outH{};
|
||||
gboolean outHasViewbox = FALSE;
|
||||
RsvgRectangle outViewbox{};
|
||||
rsvg_handle_get_intrinsic_dimensions(handle, &outHasW, &outW, &outHasH, &outH, &outHasViewbox, &outViewbox);
|
||||
if (outHasViewbox == TRUE && outViewbox.width > 0.0 && outViewbox.height > 0.0) {
|
||||
intrinsicW = outViewbox.width;
|
||||
intrinsicH = outViewbox.height;
|
||||
} else {
|
||||
intrinsicW = 512.0;
|
||||
intrinsicH = 512.0;
|
||||
}
|
||||
}
|
||||
|
||||
int width = static_cast<int>(std::round(intrinsicW));
|
||||
int height = static_cast<int>(std::round(intrinsicH));
|
||||
if (targetSize > 0) {
|
||||
const double maxSide = std::max(intrinsicW, intrinsicH);
|
||||
const double scale = static_cast<double>(targetSize) / maxSide;
|
||||
width = std::max(1, static_cast<int>(std::round(intrinsicW * scale)));
|
||||
height = std::max(1, static_cast<int>(std::round(intrinsicH * scale)));
|
||||
}
|
||||
if (width <= 0 || height <= 0) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "invalid SVG dimensions";
|
||||
}
|
||||
g_object_unref(handle);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
|
||||
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "failed to create cairo surface";
|
||||
}
|
||||
cairo_surface_destroy(surface);
|
||||
g_object_unref(handle);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
cairo_t* cr = cairo_create(surface);
|
||||
RsvgRectangle viewport{
|
||||
.x = 0.0,
|
||||
.y = 0.0,
|
||||
.width = static_cast<double>(width),
|
||||
.height = static_cast<double>(height),
|
||||
};
|
||||
GError* renderError = nullptr;
|
||||
if (rsvg_handle_render_document(handle, cr, &viewport, &renderError) == FALSE) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage =
|
||||
std::string("failed to render SVG: ") + (renderError != nullptr ? renderError->message : "unknown");
|
||||
}
|
||||
if (renderError != nullptr) {
|
||||
g_error_free(renderError);
|
||||
}
|
||||
cairo_destroy(cr);
|
||||
cairo_surface_destroy(surface);
|
||||
g_object_unref(handle);
|
||||
return std::nullopt;
|
||||
}
|
||||
cairo_destroy(cr);
|
||||
cairo_surface_flush(surface);
|
||||
|
||||
LoadedImageFile loaded{
|
||||
.rgba = std::vector<std::uint8_t>(static_cast<std::size_t>(width) * static_cast<std::size_t>(height) * 4U),
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
argb32ToRgba(cairo_image_surface_get_data(surface), cairo_image_surface_get_stride(surface), loaded.rgba.data(),
|
||||
width, height);
|
||||
|
||||
cairo_surface_destroy(surface);
|
||||
g_object_unref(handle);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<LoadedImageFile> loadImageFile(const std::string& path, int targetSize, std::string* errorMessage) {
|
||||
if (path.empty()) {
|
||||
@@ -36,55 +157,7 @@ std::optional<LoadedImageFile> loadImageFile(const std::string& path, int target
|
||||
}
|
||||
|
||||
if (path.ends_with(".svg") || path.ends_with(".SVG")) {
|
||||
// nsvgParse needs a null-terminated mutable string.
|
||||
fileData.push_back(0);
|
||||
auto* image = nsvgParse(reinterpret_cast<char*>(fileData.data()), "px", 96.0f);
|
||||
if (image == nullptr) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "failed to parse SVG";
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int width = static_cast<int>(image->width);
|
||||
int height = static_cast<int>(image->height);
|
||||
if (targetSize > 0 && image->width > 0.0f && image->height > 0.0f) {
|
||||
// Preserve source aspect ratio and constrain the longer side to targetSize.
|
||||
const float maxSide = std::max(image->width, image->height);
|
||||
const float scale = static_cast<float>(targetSize) / maxSide;
|
||||
width = std::max(1, static_cast<int>(std::round(image->width * scale)));
|
||||
height = std::max(1, static_cast<int>(std::round(image->height * scale)));
|
||||
}
|
||||
if (width <= 0 || height <= 0) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "invalid SVG dimensions";
|
||||
}
|
||||
nsvgDelete(image);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const float scaleX = static_cast<float>(width) / image->width;
|
||||
const float scaleY = static_cast<float>(height) / image->height;
|
||||
const float scale = std::min(scaleX, scaleY);
|
||||
|
||||
auto* rast = nsvgCreateRasterizer();
|
||||
if (rast == nullptr) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "failed to create SVG rasterizer";
|
||||
}
|
||||
nsvgDelete(image);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
LoadedImageFile loaded{
|
||||
.rgba = std::vector<std::uint8_t>(static_cast<std::size_t>(width) * static_cast<std::size_t>(height) * 4U),
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
nsvgRasterize(rast, image, 0, 0, scale, loaded.rgba.data(), width, height, width * 4);
|
||||
nsvgDeleteRasterizer(rast);
|
||||
nsvgDelete(image);
|
||||
return loaded;
|
||||
return rasterizeSvg(fileData, targetSize, errorMessage);
|
||||
}
|
||||
|
||||
if (auto decoded = decodeRasterImage(fileData.data(), fileData.size(), errorMessage)) {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wconversion"
|
||||
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
||||
#pragma GCC diagnostic ignored "-Wfloat-conversion"
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
|
||||
#define NANOSVG_IMPLEMENTATION
|
||||
#include <nanosvg.h>
|
||||
|
||||
#define NANOSVGRAST_IMPLEMENTATION
|
||||
#include <nanosvgrast.h>
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
Vendored
-18
@@ -1,18 +0,0 @@
|
||||
Copyright (c) 2013-14 Mikko Mononen memon@inside.org
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Vendored
-3132
File diff suppressed because it is too large
Load Diff
Vendored
-1472
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user