ImGuiManager: Load FreeType/plutosvg dynamically

pull/3794/head
Stenzek 2 weeks ago
parent b42b6981e0
commit f1f6b0ee79
No known key found for this signature in database

@ -1,4 +1,6 @@
set(SRCS
include/dyn_freetype.h
include/dyn_plutosvg.h
include/imconfig.h
include/imgui.h
include/imgui_freetype.h
@ -17,6 +19,12 @@ set(SRCS
)
add_library(imgui ${SRCS})
target_include_directories(imgui PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/src")
get_target_property(FREETYPE_INCLUDE_DIRS freetype INTERFACE_INCLUDE_DIRECTORIES)
get_target_property(PLUTOSVG_INCLUDE_DIRS plutosvg::plutosvg INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(imgui PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/src"
"${FREETYPE_INCLUDE_DIRS}"
"${PLUTOSVG_INCLUDE_DIRS}"
)
target_include_directories(imgui INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(imgui PUBLIC Freetype::Freetype plutosvg::plutosvg)

@ -2,6 +2,8 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\vsprops\Configurations.props" />
<ItemGroup>
<ClInclude Include="include\dyn_freetype.h" />
<ClInclude Include="include\dyn_plutosvg.h" />
<ClInclude Include="include\imconfig.h" />
<ClInclude Include="include\imgui.h" />
<ClInclude Include="include\imgui_freetype.h" />

@ -9,6 +9,8 @@
<ClInclude Include="include\imgui_stdlib.h" />
<ClInclude Include="include\imgui_freetype.h" />
<ClInclude Include="include\imgui_internal.h" />
<ClInclude Include="include\dyn_freetype.h" />
<ClInclude Include="include\dyn_plutosvg.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\imgui_widgets.cpp" />

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include <ft2build.h>
#include FT_FREETYPE_H // <freetype/freetype.h>
#include FT_GLYPH_H // <freetype/ftglyph.h>
#include FT_MULTIPLE_MASTERS_H // <freetype/ftmm.h>
#include FT_MODULE_H // <freetype/ftmodapi.h>
#include FT_SIZES_H // <freetype/ftsizes.h>
#include FT_SYNTHESIS_H // <freetype/ftsynth.h>
class Error;
#define DYN_FREETYPE_FUNCTIONS(X) \
X(FT_New_Memory_Face) \
X(FT_Select_Charmap) \
X(FT_Get_MM_Var) \
X(FT_Done_MM_Var) \
X(FT_Done_Face) \
X(FT_Get_Char_Index) \
X(FT_Load_Glyph) \
X(FT_GlyphSlot_Embolden) \
X(FT_GlyphSlot_Oblique) \
X(FT_New_Library) \
X(FT_Add_Default_Modules) \
X(FT_Property_Set) \
X(FT_Done_Library) \
X(FT_New_Size) \
X(FT_Activate_Size) \
X(FT_Set_Var_Design_Coordinates) \
X(FT_Request_Size) \
X(FT_Done_Size) \
X(FT_Render_Glyph)
struct DynFreeType
{
#define ADD_FUNC(F) decltype(&::F) F;
DYN_FREETYPE_FUNCTIONS(ADD_FUNC)
#undef ADD_FUNC
bool Open(Error* const error);
};
extern DynFreeType g_dyn_freetype;

@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include <plutosvg.h>
class Error;
#define DYN_PLUTOSVG_FUNCTIONS(X) \
X(plutosvg_ft_svg_hooks) \
X(plutosvg_document_load_from_data) \
X(plutosvg_document_destroy) \
X(plutosvg_document_get_width) \
X(plutosvg_document_get_height) \
X(plutosvg_document_render_to_surface) \
X(plutovg_surface_destroy) \
X(plutovg_surface_get_width) \
X(plutovg_surface_get_height) \
X(plutovg_surface_get_data) \
X(plutovg_surface_get_stride)
struct DynPlutoSvg
{
#define ADD_FUNC(F) decltype(&::F) F;
DYN_PLUTOSVG_FUNCTIONS(ADD_FUNC)
#undef ADD_FUNC
bool Open(Error* const error = nullptr);
};
extern DynPlutoSvg g_dyn_plutosvg;

@ -41,6 +41,7 @@
#ifndef IMGUI_DISABLE
#include "imgui_freetype.h"
#include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
#include "dyn_freetype.h"
#include <stdint.h>
#include <ft2build.h>
#include FT_FREETYPE_H // <freetype/freetype.h>
@ -58,7 +59,7 @@
#error Lunasvg is not supported
#endif
#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
#include <plutosvg.h>
#include "dyn_plutosvg.h"
#endif
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined (IMGUI_ENABLE_FREETYPE_PLUTOSVG)
#if !((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
@ -170,16 +171,16 @@ struct ImGui_ImplFreeType_FontSrcBakedData
bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_font_loader_flags)
{
FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)src->FontData, (FT_Long)src->FontDataSize, (FT_Long)src->FontNo, &FtFace);
FT_Error error = g_dyn_freetype.FT_New_Memory_Face(ft_library, (const FT_Byte*)src->FontData, (FT_Long)src->FontDataSize, (FT_Long)src->FontNo, &FtFace);
if (error != 0)
return false;
error = FT_Select_Charmap(FtFace, FT_ENCODING_UNICODE);
error = g_dyn_freetype.FT_Select_Charmap(FtFace, FT_ENCODING_UNICODE);
if (error != 0)
return false;
FT_MM_Var* mmvar;
WeightCoordIndex = -1;
if (FT_IS_SFNT(FtFace) && ((error = FT_Get_MM_Var(FtFace, &mmvar)) == 0))
if (FT_IS_SFNT(FtFace) && ((error = g_dyn_freetype.FT_Get_MM_Var(FtFace, &mmvar)) == 0))
{
VarDesignCoords = new FT_Fixed[mmvar->num_axis];
VarDesignNumAxis = mmvar->num_axis;
@ -199,7 +200,7 @@ bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, const ImFon
VarDesignNumAxis = 0;
}
FT_Done_MM_Var(ft_library, mmvar);
g_dyn_freetype.FT_Done_MM_Var(ft_library, mmvar);
}
// Convert to FreeType flags (NB: Bold and Oblique are processed separately)
@ -239,14 +240,14 @@ void ImGui_ImplFreeType_FontSrcData::CloseFont()
if (FtFace)
{
FT_Done_Face(FtFace);
g_dyn_freetype.FT_Done_Face(FtFace);
FtFace = nullptr;
}
}
static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_FontSrcData* src_data, uint32_t codepoint)
{
uint32_t glyph_index = FT_Get_Char_Index(src_data->FtFace, codepoint);
uint32_t glyph_index = g_dyn_freetype.FT_Get_Char_Index(src_data->FtFace, codepoint);
if (glyph_index == 0)
return nullptr;
@ -255,7 +256,7 @@ static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_F
// - https://github.com/ocornut/imgui/issues/4567
// - https://github.com/ocornut/imgui/issues/4566
// You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version.
FT_Error error = FT_Load_Glyph(src_data->FtFace, glyph_index, src_data->LoadFlags);
FT_Error error = g_dyn_freetype.FT_Load_Glyph(src_data->FtFace, glyph_index, src_data->LoadFlags);
if (error)
return nullptr;
@ -272,10 +273,10 @@ static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_F
// Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting)
if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bold)
FT_GlyphSlot_Embolden(slot);
g_dyn_freetype.FT_GlyphSlot_Embolden(slot);
if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Oblique)
{
FT_GlyphSlot_Oblique(slot);
g_dyn_freetype.FT_GlyphSlot_Oblique(slot);
//FT_BBox bbox;
//FT_Outline_Get_BBox(&slot->outline, &bbox);
//slot->metrics.width = bbox.xMax - bbox.xMin;
@ -381,7 +382,7 @@ static bool ImGui_ImplFreeType_LoaderInit(ImFontAtlas* atlas)
bd->MemoryManager.realloc = &FreeType_Realloc;
// https://www.freetype.org/freetype2/docs/reference/ft2-module_management.html#FT_New_Library
FT_Error error = FT_New_Library(&bd->MemoryManager, &bd->Library);
FT_Error error = g_dyn_freetype.FT_New_Library(&bd->MemoryManager, &bd->Library);
if (error != 0)
{
IM_DELETE(bd);
@ -389,11 +390,11 @@ static bool ImGui_ImplFreeType_LoaderInit(ImFontAtlas* atlas)
}
// If you don't call FT_Add_Default_Modules() the rest of code may work, but FreeType won't use our custom allocator.
FT_Add_Default_Modules(bd->Library);
g_dyn_freetype.FT_Add_Default_Modules(bd->Library);
#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
// With plutosvg, use provided hooks
FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", plutosvg_ft_svg_hooks());
g_dyn_freetype.FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", g_dyn_plutosvg.plutosvg_ft_svg_hooks());
#endif // IMGUI_ENABLE_FREETYPE_PLUTOSVG
// Store our data
@ -406,7 +407,7 @@ static void ImGui_ImplFreeType_LoaderShutdown(ImFontAtlas* atlas)
{
ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
IM_ASSERT(bd != nullptr);
FT_Done_Library(bd->Library);
g_dyn_freetype.FT_Done_Library(bd->Library);
IM_DELETE(bd);
atlas->FontLoaderData = nullptr;
}
@ -451,8 +452,8 @@ static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* s
IM_ASSERT(bd_baked_data != nullptr);
IM_PLACEMENT_NEW(bd_baked_data) ImGui_ImplFreeType_FontSrcBakedData();
FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize);
FT_Activate_Size(bd_baked_data->FtSize);
g_dyn_freetype.FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize);
g_dyn_freetype.FT_Activate_Size(bd_baked_data->FtSize);
bd_baked_data->FtWeight = 0;
if (bd_font_data->WeightCoordIndex >= 0)
{
@ -460,11 +461,11 @@ static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* s
if (bd_baked_data->FtWeight != 0)
{
bd_font_data->VarDesignCoords[bd_font_data->WeightCoordIndex] = bd_baked_data->FtWeight;
FT_Set_Var_Design_Coordinates(bd_font_data->FtFace, bd_font_data->VarDesignNumAxis, bd_font_data->VarDesignCoords);
g_dyn_freetype.FT_Set_Var_Design_Coordinates(bd_font_data->FtFace, bd_font_data->VarDesignNumAxis, bd_font_data->VarDesignCoords);
}
else
{
FT_Set_Var_Design_Coordinates(bd_font_data->FtFace, 0, nullptr);
g_dyn_freetype.FT_Set_Var_Design_Coordinates(bd_font_data->FtFace, 0, nullptr);
}
}
@ -483,7 +484,7 @@ static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* s
req.height = (uint32_t)(size * 64 * rasterizer_density);
req.horiResolution = 0;
req.vertResolution = 0;
FT_Request_Size(bd_font_data->FtFace, &req);
g_dyn_freetype.FT_Request_Size(bd_font_data->FtFace, &req);
// Output
if (src->MergeMode == false)
@ -509,14 +510,14 @@ static void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
IM_ASSERT(bd_baked_data != nullptr);
bd_font_data->LastSize = (bd_font_data->LastSize == bd_baked_data->FtSize) ? nullptr : bd_font_data->LastSize;
FT_Done_Size(bd_baked_data->FtSize);
g_dyn_freetype.FT_Done_Size(bd_baked_data->FtSize);
bd_baked_data->~ImGui_ImplFreeType_FontSrcBakedData(); // ~IM_PLACEMENT_DELETE()
}
static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x)
{
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
uint32_t glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
uint32_t glyph_index = g_dyn_freetype.FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
if (glyph_index == 0)
return false;
@ -527,7 +528,7 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf
if ( bd_font_data->LastSize != bd_baked_data->FtSize)
{
bd_font_data->LastSize = bd_baked_data->FtSize;
FT_Activate_Size(bd_baked_data->FtSize);
g_dyn_freetype.FT_Activate_Size(bd_baked_data->FtSize);
}
if (bd_font_data->WeightCoordIndex >= 0 && bd_font_data->LastWeight != bd_baked_data->FtWeight)
{
@ -535,11 +536,11 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf
if (bd_baked_data->FtWeight != 0)
{
bd_font_data->VarDesignCoords[bd_font_data->WeightCoordIndex] = bd_baked_data->FtWeight;
FT_Set_Var_Design_Coordinates(bd_font_data->FtFace, bd_font_data->VarDesignNumAxis, bd_font_data->VarDesignCoords);
g_dyn_freetype.FT_Set_Var_Design_Coordinates(bd_font_data->FtFace, bd_font_data->VarDesignNumAxis, bd_font_data->VarDesignCoords);
}
else
{
FT_Set_Var_Design_Coordinates(bd_font_data->FtFace, 0, nullptr);
g_dyn_freetype.FT_Set_Var_Design_Coordinates(bd_font_data->FtFace, 0, nullptr);
}
}
@ -566,7 +567,7 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf
// Render glyph into a bitmap (currently held by FreeType)
FT_Render_Mode render_mode = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Monochrome) ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL;
error = FT_Render_Glyph(slot, render_mode);
error = g_dyn_freetype.FT_Render_Glyph(slot, render_mode);
const FT_Bitmap* ft_bitmap = &slot->bitmap;
if (error != 0 || ft_bitmap == nullptr)
return false;
@ -623,7 +624,7 @@ static bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontCo
{
IM_UNUSED(atlas);
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
int glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
int glyph_index = g_dyn_freetype.FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
return glyph_index != 0;
}

@ -45,6 +45,7 @@ declare -a MANUAL_LIBS=(
"libz.so.1"
"libdiscord-rpc.so"
"libplutosvg.so.0"
"libshaderc_shared.so"
"libspirv-cross-c-shared.so.0"
"libsqlite3.so.3"

@ -18,8 +18,8 @@
#include <QtWidgets/QStyleOption>
#include <cmath>
#include <dyn_plutosvg.h>
#include <limits>
#include <plutosvg.h>
#include "moc_svgiconengine.cpp"
@ -55,7 +55,7 @@ static QString BuildCacheKey(const QString& resource_path, const QSize& size, qr
/// Callback used when freeing the QImage.
static void CleanupPlutoSVGSurface(void* surface)
{
plutovg_surface_destroy(static_cast<plutovg_surface_t*>(surface));
g_dyn_plutosvg.plutovg_surface_destroy(static_cast<plutovg_surface_t*>(surface));
}
/// Renders the document at the given pixel dimensions with the given colour and inserts the
@ -75,13 +75,13 @@ static bool RenderSVGToPixmap(QPixmap& pm, const plutosvg_document* doc, const Q
// Determine SVG intrinsic size and compute a uniform scale that fits inside physical,
// preserving aspect ratio.
const float svg_w = plutosvg_document_get_width(doc);
const float svg_h = plutosvg_document_get_height(doc);
const float svg_w = g_dyn_plutosvg.plutosvg_document_get_width(doc);
const float svg_h = g_dyn_plutosvg.plutosvg_document_get_height(doc);
QSize render_size = size;
if (svg_w > 0.0f && svg_h > 0.0f)
render_size = QSizeF(svg_w, svg_h).scaled(size, Qt::KeepAspectRatio).toSize();
plutovg_surface_t* surface = plutosvg_document_render_to_surface(
plutovg_surface_t* surface = g_dyn_plutosvg.plutosvg_document_render_to_surface(
doc, nullptr, render_size.width(), render_size.height(), &current_color, nullptr, nullptr);
if (!surface)
return false;
@ -89,9 +89,10 @@ static bool RenderSVGToPixmap(QPixmap& pm, const plutosvg_document* doc, const Q
// plutovg surfaces are premultiplied ARGB (0xAARRGGBB, native-endian), which matches
// QImage::Format_ARGB32_Premultiplied exactly, no pixel conversion required.
// The cleanup function ensures the surface is destroyed when the QImage is done with the pixel data.
const QImage img(plutovg_surface_get_data(surface), plutovg_surface_get_width(surface),
plutovg_surface_get_height(surface), plutovg_surface_get_stride(surface),
QImage::Format_ARGB32_Premultiplied, CleanupPlutoSVGSurface, surface);
const QImage img(g_dyn_plutosvg.plutovg_surface_get_data(surface), g_dyn_plutosvg.plutovg_surface_get_width(surface),
g_dyn_plutosvg.plutovg_surface_get_height(surface),
g_dyn_plutosvg.plutovg_surface_get_stride(surface), QImage::Format_ARGB32_Premultiplied,
CleanupPlutoSVGSurface, surface);
pm = QPixmap::fromImage(img);
if (pm.isNull())
@ -125,7 +126,7 @@ SVGIconEngine::SVGIconEngine(const QString& resource_path) : m_resource_path(res
SVGIconEngine::~SVGIconEngine()
{
if (m_document)
plutosvg_document_destroy(m_document);
g_dyn_plutosvg.plutosvg_document_destroy(m_document);
}
bool SVGIconEngine::ensureLoaded() const
@ -146,8 +147,9 @@ bool SVGIconEngine::ensureLoaded() const
}
// The document borrows the data pointer; m_svg_data must remain valid for the document's lifetime.
m_document = plutosvg_document_load_from_data(reinterpret_cast<const char*>(m_svg_data.data()),
static_cast<int>(m_svg_data.size()), -1.0f, -1.0f, nullptr, nullptr);
m_document = g_dyn_plutosvg.plutosvg_document_load_from_data(reinterpret_cast<const char*>(m_svg_data.data()),
static_cast<int>(m_svg_data.size()), -1.0f, -1.0f,
nullptr, nullptr);
if (!m_document)
{
qCritical() << "Failed to parse SVG data: " << m_resource_path;
@ -238,6 +240,9 @@ QString SVGIconEngine::iconName()
QIconEngine* SVGIconEnginePlugin::create(const QString& resource_path)
{
if (!g_dyn_plutosvg.Open()) [[unlikely]]
return nullptr;
if (resource_path.endsWith(".svg", Qt::CaseInsensitive))
return new SVGIconEngine(resource_path);
@ -249,7 +254,7 @@ SVGImageHandler::SVGImageHandler() = default;
SVGImageHandler::~SVGImageHandler()
{
if (m_document)
plutosvg_document_destroy(m_document);
g_dyn_plutosvg.plutosvg_document_destroy(m_document);
}
bool SVGImageHandler::canRead() const
@ -265,8 +270,9 @@ bool SVGImageHandler::canRead() const
return false;
}
m_document = plutosvg_document_load_from_data(reinterpret_cast<const char*>(m_svg_data.data()),
static_cast<int>(m_svg_data.size()), -1.0f, -1.0f, nullptr, nullptr);
m_document = g_dyn_plutosvg.plutosvg_document_load_from_data(reinterpret_cast<const char*>(m_svg_data.data()),
static_cast<int>(m_svg_data.size()), -1.0f, -1.0f,
nullptr, nullptr);
if (!m_document)
{
qCritical() << "Failed to parse SVG data";
@ -286,15 +292,15 @@ bool SVGImageHandler::read(QImage* image)
QSize render_size = m_scaled_size;
if (!render_size.isValid())
{
render_size = QSize(qMax(1, qRound(plutosvg_document_get_width(m_document))),
qMax(1, qRound(plutosvg_document_get_height(m_document))));
render_size = QSize(qMax(1, qRound(g_dyn_plutosvg.plutosvg_document_get_width(m_document))),
qMax(1, qRound(g_dyn_plutosvg.plutosvg_document_get_height(m_document))));
}
// Render with a solid currentColor so the SVG's own fill/stroke colours are preserved.
// Callers that want palette tinting should use ThemeSVGIconEngine instead.
const plutovg_color_t current_color = {.r = 1.0f, .g = 1.0f, .b = 1.0f, .a = 1.0f};
plutovg_surface_t* surface = plutosvg_document_render_to_surface(
plutovg_surface_t* surface = g_dyn_plutosvg.plutosvg_document_render_to_surface(
m_document, nullptr, render_size.width(), render_size.height(), &current_color, nullptr, nullptr);
if (!surface)
return false;
@ -302,8 +308,9 @@ bool SVGImageHandler::read(QImage* image)
// Wrap the surface pixels in a QImage; the cleanup function destroys the surface once Qt is done.
// plutovg uses premultiplied ARGB (0xAARRGGBB, native-endian) == QImage::Format_ARGB32_Premultiplied.
*image =
QImage(plutovg_surface_get_data(surface), plutovg_surface_get_width(surface), plutovg_surface_get_height(surface),
plutovg_surface_get_stride(surface), QImage::Format_ARGB32_Premultiplied, CleanupPlutoSVGSurface, surface);
QImage(g_dyn_plutosvg.plutovg_surface_get_data(surface), g_dyn_plutosvg.plutovg_surface_get_width(surface),
g_dyn_plutosvg.plutovg_surface_get_height(surface), g_dyn_plutosvg.plutovg_surface_get_stride(surface),
QImage::Format_ARGB32_Premultiplied, CleanupPlutoSVGSurface, surface);
return !image->isNull();
}
@ -323,8 +330,8 @@ QVariant SVGImageHandler::option(ImageOption option) const
if (!canRead())
return {};
return QSize(qMax(1, qRound(plutosvg_document_get_width(m_document))),
qMax(1, qRound(plutosvg_document_get_height(m_document))));
return QSize(qMax(1, qRound(g_dyn_plutosvg.plutosvg_document_get_width(m_document))),
qMax(1, qRound(g_dyn_plutosvg.plutosvg_document_get_height(m_document))));
}
return {};
@ -354,6 +361,9 @@ QImageIOPlugin::Capabilities SVGImageHandlerPlugin::capabilities(QIODevice* devi
QImageIOHandler* SVGImageHandlerPlugin::create(QIODevice* device, const QByteArray& format) const
{
if (!g_dyn_plutosvg.Open()) [[unlikely]]
return nullptr;
SVGImageHandler* const handler = new SVGImageHandler();
handler->setDevice(device);
handler->setFormat(format.isEmpty() ? QByteArray("svg") : format);

@ -9,14 +9,14 @@
#include <QtGui/QPainter>
#include <QtGui/QPixmap>
#include <dyn_plutosvg.h>
#include <limits>
#include <plutosvg.h>
#include "moc_svgwidget.cpp"
static void CleanupSurface(void* surface)
{
plutovg_surface_destroy(static_cast<plutovg_surface_t*>(surface));
g_dyn_plutosvg.plutovg_surface_destroy(static_cast<plutovg_surface_t*>(surface));
}
SVGWidget::SVGWidget(QWidget* parent) : QWidget(parent)
@ -48,7 +48,7 @@ void SVGWidget::setSource(const QString& resource_path)
m_pixmap = {};
m_last_raster_size = {};
if (resource_path.isEmpty())
if (resource_path.isEmpty() || !g_dyn_plutosvg.Open())
{
update();
return;
@ -63,8 +63,9 @@ void SVGWidget::setSource(const QString& resource_path)
}
// plutosvg borrows the raw pointer; m_svg_data must outlive m_document.
m_document = plutosvg_document_load_from_data(reinterpret_cast<const char*>(m_svg_data.data()),
static_cast<int>(m_svg_data.size()), -1.0f, -1.0f, nullptr, nullptr);
m_document = g_dyn_plutosvg.plutosvg_document_load_from_data(reinterpret_cast<const char*>(m_svg_data.data()),
static_cast<int>(m_svg_data.size()), -1.0f, -1.0f,
nullptr, nullptr);
if (!m_document)
{
qCritical() << "PlutoSVGWidget: failed to parse SVG" << resource_path;
@ -82,7 +83,7 @@ void SVGWidget::destroyDocument()
{
if (m_document)
{
plutosvg_document_destroy(m_document);
g_dyn_plutosvg.plutosvg_document_destroy(m_document);
m_document = nullptr;
}
m_svg_data.deallocate();
@ -113,8 +114,8 @@ void SVGWidget::rasterize()
// Determine SVG intrinsic size and compute a uniform scale that fits inside physical,
// preserving aspect ratio.
const float svg_w = plutosvg_document_get_width(m_document);
const float svg_h = plutosvg_document_get_height(m_document);
const float svg_w = g_dyn_plutosvg.plutosvg_document_get_width(m_document);
const float svg_h = g_dyn_plutosvg.plutosvg_document_get_height(m_document);
int render_w = physical.width();
int render_h = physical.height();
@ -139,8 +140,8 @@ void SVGWidget::rasterize()
.a = static_cast<float>(color.alphaF()),
};
plutovg_surface_t* const surface =
plutosvg_document_render_to_surface(m_document, nullptr, render_w, render_h, &current_color, nullptr, nullptr);
plutovg_surface_t* const surface = g_dyn_plutosvg.plutosvg_document_render_to_surface(
m_document, nullptr, render_w, render_h, &current_color, nullptr, nullptr);
if (!surface)
{
qCritical() << "PlutoSVGWidget: render failed for" << m_resource_path;
@ -148,9 +149,10 @@ void SVGWidget::rasterize()
}
// plutovg surfaces are premultiplied ARGB (native-endian) == QImage::Format_ARGB32_Premultiplied.
const QImage img(plutovg_surface_get_data(surface), plutovg_surface_get_width(surface),
plutovg_surface_get_height(surface), plutovg_surface_get_stride(surface),
QImage::Format_ARGB32_Premultiplied, CleanupSurface, surface);
const QImage img(g_dyn_plutosvg.plutovg_surface_get_data(surface), g_dyn_plutosvg.plutovg_surface_get_width(surface),
g_dyn_plutosvg.plutovg_surface_get_height(surface),
g_dyn_plutosvg.plutovg_surface_get_stride(surface), QImage::Format_ARGB32_Premultiplied,
CleanupSurface, surface);
m_pixmap = QPixmap::fromImage(img);
m_pixmap.setDevicePixelRatio(dpr);
@ -197,6 +199,9 @@ void SVGWidget::changeEvent(QEvent* event)
QPixmap SVGWidget::renderSVGToPixmap(const QString& resource_path, const QSize& size, qreal device_pixel_ratio,
const QColor& color)
{
if (!g_dyn_plutosvg.Open())
return {};
DynamicHeapArray<u8> svg_data;
QFile file(resource_path);
if (!file.open(QFile::ReadOnly) || !QtUtils::ReadFileToByteArray(&file, svg_data))
@ -206,7 +211,7 @@ QPixmap SVGWidget::renderSVGToPixmap(const QString& resource_path, const QSize&
}
// plutosvg borrows the raw pointer; svg_data must outlive m_document.
plutosvg_document* const document = plutosvg_document_load_from_data(
plutosvg_document* const document = g_dyn_plutosvg.plutosvg_document_load_from_data(
reinterpret_cast<const char*>(svg_data.data()), static_cast<int>(svg_data.size()), -1.0f, -1.0f, nullptr, nullptr);
if (!document)
{
@ -216,8 +221,8 @@ QPixmap SVGWidget::renderSVGToPixmap(const QString& resource_path, const QSize&
// Determine SVG intrinsic size and compute a uniform scale that fits inside physical,
// preserving aspect ratio.
const float svg_w = plutosvg_document_get_width(document);
const float svg_h = plutosvg_document_get_height(document);
const float svg_w = g_dyn_plutosvg.plutosvg_document_get_width(document);
const float svg_h = g_dyn_plutosvg.plutosvg_document_get_height(document);
const QSize physical = QtUtils::ApplyDevicePixelRatioToSize(size, device_pixel_ratio);
int render_w = physical.width();
@ -237,22 +242,23 @@ QPixmap SVGWidget::renderSVGToPixmap(const QString& resource_path, const QSize&
// Swap in a palette colour here if tinting is ever desired.
const plutovg_color_t current_color = {.r = 1.0f, .g = 1.0f, .b = 1.0f, .a = 1.0f};
plutovg_surface_t* const surface =
plutosvg_document_render_to_surface(document, nullptr, render_w, render_h, &current_color, nullptr, nullptr);
plutovg_surface_t* const surface = g_dyn_plutosvg.plutosvg_document_render_to_surface(
document, nullptr, render_w, render_h, &current_color, nullptr, nullptr);
if (!surface)
{
qCritical() << "PlutoSVGWidget: render failed for" << resource_path;
plutosvg_document_destroy(document);
g_dyn_plutosvg.plutosvg_document_destroy(document);
return {};
}
// plutovg surfaces are premultiplied ARGB (native-endian) == QImage::Format_ARGB32_Premultiplied.
const QImage img(plutovg_surface_get_data(surface), plutovg_surface_get_width(surface),
plutovg_surface_get_height(surface), plutovg_surface_get_stride(surface),
QImage::Format_ARGB32_Premultiplied, CleanupSurface, surface);
const QImage img(g_dyn_plutosvg.plutovg_surface_get_data(surface), g_dyn_plutosvg.plutovg_surface_get_width(surface),
g_dyn_plutosvg.plutovg_surface_get_height(surface),
g_dyn_plutosvg.plutovg_surface_get_stride(surface), QImage::Format_ARGB32_Premultiplied,
CleanupSurface, surface);
QPixmap pm = QPixmap::fromImage(img);
pm.setDevicePixelRatio(device_pixel_ratio);
plutosvg_document_destroy(document);
g_dyn_plutosvg.plutosvg_document_destroy(document);
return pm;
}

@ -31,6 +31,7 @@ add_library(util
dyn_shaderc.h
dyn_spirv_cross.h
dyn_libpng.h
dyn_libs.cpp
dyn_sdl.h
dyn_sqlite.h
elf_file.cpp
@ -203,9 +204,16 @@ if(ENABLE_VULKAN)
endif()
# shaderc/spirv-cross is loaded dynamically to reduce module loads on startup.
get_target_property(FREETYPE_INCLUDE_DIRS freetype INTERFACE_INCLUDE_DIRECTORIES)
get_target_property(PLUTOSVG_INCLUDE_DIRS plutosvg::plutosvg INTERFACE_INCLUDE_DIRECTORIES)
get_target_property(SHADERC_INCLUDE_DIR Shaderc::shaderc_shared INTERFACE_INCLUDE_DIRECTORIES)
get_target_property(SPIRV_CROSS_INCLUDE_DIR spirv-cross-c-shared INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(util PUBLIC ${SHADERC_INCLUDE_DIR} ${SPIRV_CROSS_INCLUDE_DIR})
target_include_directories(util PUBLIC
${FREETYPE_INCLUDE_DIRS}
${PLUTOSVG_INCLUDE_DIRS}
${SHADERC_INCLUDE_DIR}
${SPIRV_CROSS_INCLUDE_DIR}
)
if(NOT ANDROID)
target_sources(util PRIVATE
@ -283,9 +291,6 @@ function(add_util_resources target)
# Copy dynamically-loaded libraries into the bundle.
add_runtime_libraries(${target} spirv-cross-c-shared Shaderc::shaderc_shared SQLite3::sqlite3-shared SDL3::SDL3
libjpeg-turbo::jpeg PNG::png_shared WebP::webp WebP::sharpyuv WebP::webpdecoder
WebP::webpdemux WebP::libwebpmux)
if(APPLE)
add_runtime_libraries(${target} harfbuzz::harfbuzz)
endif()
freetype harfbuzz::harfbuzz plutosvg::plutosvg libjpeg-turbo::jpeg PNG::png_shared WebP::webp
WebP::sharpyuv WebP::webpdecoder WebP::webpdemux WebP::libwebpmux)
endfunction()

@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "dyn_freetype.h"
#include "dyn_plutosvg.h"
#include "common/dynamic_library.h"
#include "common/error.h"
#include "common/log.h"
LOG_CHANNEL(DynamicLibrary);
static_assert(std::is_trivially_copyable_v<DynFreeType> && std::is_standard_layout_v<DynFreeType>);
static_assert(std::is_trivially_copyable_v<DynPlutoSvg> && std::is_standard_layout_v<DynPlutoSvg>);
// Because of course friggin linux is different...
#ifdef _WIN32
static constexpr int FREETYPE_MAJOR_VERSION = -1;
static constexpr int PLUTOSVG_MAJOR_VERSION = -1;
#else
static constexpr int FREETYPE_MAJOR_VERSION = 6;
static constexpr int PLUTOSVG_MAJOR_VERSION = 0;
#endif
namespace {
struct Locals
{
// Dynamic libraries
DynamicLibrary freetype_library;
std::once_flag freetype_init_flag;
DynamicLibrary plutosvg_library;
std::once_flag plutosvg_init_flag;
};
} // namespace
DynFreeType g_dyn_freetype;
DynPlutoSvg g_dyn_plutosvg;
static Locals s_locals;
static bool LoadDynLib(const char* libname, int major_version, DynamicLibrary& dynlib, std::once_flag& once_flag,
std::span<const DynamicLibrary::SymbolTable> symbols, Error* const error)
{
std::call_once(once_flag, [&libname, &major_version, &dynlib, &symbols, error]() {
Error lerror;
DynamicLibrary lib;
if (!lib.Open(DynamicLibrary::GetVersionedFilename(libname, major_version).c_str(), &lerror) ||
!lib.ResolveSymbols(symbols.data(), symbols.size(), &lerror))
{
ERROR_LOG("Failed to load {}: {}", libname, lerror.GetDescription());
Error::SetStringFmt(error, "Failed to load {}: {}", libname, lerror.GetDescription());
return;
}
dynlib = std::move(lib);
});
return dynlib.IsOpen();
}
static const DynamicLibrary::SymbolTable s_freetype_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_freetype.F},
DYN_FREETYPE_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
bool DynFreeType::Open(Error* const error)
{
if (s_locals.freetype_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("freetype", FREETYPE_MAJOR_VERSION, s_locals.freetype_library, s_locals.freetype_init_flag,
s_freetype_symbols, error);
}
static const DynamicLibrary::SymbolTable s_plutosvg_symbols[] = {
#define RESOLVE_SYMBOL(F) {#F, (void**)&g_dyn_plutosvg.F},
DYN_PLUTOSVG_FUNCTIONS(RESOLVE_SYMBOL)
#undef RESOLVE_SYMBOL
};
bool DynPlutoSvg::Open(Error* const error)
{
if (s_locals.plutosvg_library.IsOpen()) [[likely]]
return true;
return LoadDynLib("plutosvg", PLUTOSVG_MAJOR_VERSION, s_locals.plutosvg_library, s_locals.plutosvg_init_flag,
s_plutosvg_symbols, error);
}

@ -20,6 +20,7 @@
#include "common/string_util.h"
#include <cmath>
#include <dyn_plutosvg.h>
#include <jpeglib.h>
#include <limits>
#include <plutosvg.h>
@ -611,10 +612,14 @@ bool Image::RasterizeSVG(const std::span<const u8> data, u32 width, u32 height,
return false;
}
if (!g_dyn_plutosvg.Open(error)) [[unlikely]]
return false;
std::unique_ptr<plutosvg_document, void (*)(plutosvg_document*)> doc(
plutosvg_document_load_from_data(reinterpret_cast<const char*>(data.data()), static_cast<int>(data.size_bytes()),
static_cast<float>(width), static_cast<float>(height), nullptr, nullptr),
plutosvg_document_destroy);
g_dyn_plutosvg.plutosvg_document_load_from_data(reinterpret_cast<const char*>(data.data()),
static_cast<int>(data.size_bytes()), static_cast<float>(width),
static_cast<float>(height), nullptr, nullptr),
g_dyn_plutosvg.plutosvg_document_destroy);
if (!doc)
{
Error::SetStringView(error, "plutosvg_document_load_from_data() failed");
@ -629,8 +634,8 @@ bool Image::RasterizeSVG(const std::span<const u8> data, u32 width, u32 height,
if (maintain_aspect_ratio)
{
const float doc_width = plutosvg_document_get_width(doc.get());
const float doc_height = plutosvg_document_get_height(doc.get());
const float doc_width = g_dyn_plutosvg.plutosvg_document_get_width(doc.get());
const float doc_height = g_dyn_plutosvg.plutosvg_document_get_height(doc.get());
if (doc_width > 0.0f && doc_height > 0.0f)
{
const float fwidth = static_cast<float>(width);
@ -660,9 +665,9 @@ bool Image::RasterizeSVG(const std::span<const u8> data, u32 width, u32 height,
}
std::unique_ptr<plutovg_surface, void (*)(plutovg_surface*)> bitmap(
plutosvg_document_render_to_surface(doc.get(), nullptr, static_cast<int>(width), static_cast<int>(height),
&current_color, nullptr, nullptr),
plutovg_surface_destroy);
g_dyn_plutosvg.plutosvg_document_render_to_surface(doc.get(), nullptr, static_cast<int>(width),
static_cast<int>(height), &current_color, nullptr, nullptr),
g_dyn_plutosvg.plutovg_surface_destroy);
if (!bitmap)
{
Error::SetStringView(error, "plutosvg_document_render_to_surface() failed");
@ -683,7 +688,8 @@ bool Image::RasterizeSVG(const std::span<const u8> data, u32 width, u32 height,
// lunasvg works in BGRA, swap to RGBA
SwapBGRAToRGBA(m_pixels.get() + (canvas_offset_y * m_pitch) + (canvas_offset_x * sizeof(u32)), m_pitch,
plutovg_surface_get_data(bitmap.get()), plutovg_surface_get_stride(bitmap.get()), width, height);
g_dyn_plutosvg.plutovg_surface_get_data(bitmap.get()),
g_dyn_plutosvg.plutovg_surface_get_stride(bitmap.get()), width, height);
return true;
}

@ -28,12 +28,14 @@
#include "common/threading.h"
#include "common/timer.h"
#include "IconsEmoji.h"
#include "IconsFontAwesome.h"
#include "fmt/format.h"
#include "imgui.h"
#include "imgui_freetype.h"
#include "imgui_internal.h"
#include <IconsEmoji.h>
#include <IconsFontAwesome.h>
#include <dyn_freetype.h>
#include <dyn_plutosvg.h>
#include <fmt/format.h>
#include <imgui.h>
#include <imgui_freetype.h>
#include <imgui_internal.h>
#include <atomic>
#include <cmath>
@ -366,6 +368,10 @@ void ImGuiManager::UpdateFixedFont()
bool ImGuiManager::Initialize(Error* error)
{
// Requires both freetype and plutosvg.
if (!g_dyn_freetype.Open(error) || !g_dyn_plutosvg.Open(error)) [[unlikely]]
return false;
s_state.text_font_index = GetTextFontIndex();
s_state.fixed_font_index = GetFixedFontIndex();

@ -25,7 +25,7 @@
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DepsIncludeDir)spirv_cross</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);freetype.lib;plutosvg.lib;soundtouch.lib;zip.lib;zlib.lib;zstd.lib</AdditionalDependencies>
<AdditionalDependencies>%(AdditionalDependencies);soundtouch.lib;zip.lib;zlib.lib;zstd.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>

@ -122,6 +122,7 @@
<ItemGroup>
<ClCompile Include="animated_image.cpp" />
<ClCompile Include="audio_stream.cpp" />
<ClCompile Include="dyn_libs.cpp" />
<ClCompile Include="http_cache.cpp" />
<ClCompile Include="object_archive.cpp" />
<ClCompile Include="cd_image.cpp" />
@ -278,7 +279,7 @@
<Import Project="util.props" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\reshadefx\include;$(SolutionDir)dep\lzma\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\reshadefx\include;$(SolutionDir)dep\lzma\include;$(DepsIncludeDir)freetype2</AdditionalIncludeDirectories>
<ObjectFileName>$(IntDir)/%(RelativeDir)/</ObjectFileName>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>

@ -175,6 +175,7 @@
<ClCompile Include="xaudio2_audio_stream.cpp" />
<ClCompile Include="sqlite_helpers.cpp" />
<ClCompile Include="zip_helpers.cpp" />
<ClCompile Include="dyn_libs.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="metal_shaders.metal" />

Loading…
Cancel
Save