瀏覽代碼

Fonts: Added support for OpenType SVG fonts using lunasvg (#6591, #6607)

sakiodre 2 年之前
父節點
當前提交
2ad8c60abc
共有 3 個文件被更改,包括 173 次插入0 次删除
  1. 8 0
      imconfig.h
  2. 8 0
      misc/freetype/README.md
  3. 157 0
      misc/freetype/imgui_freetype.cpp

+ 8 - 0
imconfig.h

@@ -80,6 +80,14 @@
 // The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend.
 //#define IMGUI_ENABLE_STB_TRUETYPE
 
+//---- Use lunasvg library to render OpenType SVG fonts (SVGinOT)
+// Requires lunasvg headers to be available in the include path. Requires program to be compiled with the lunasvg library (not provided).
+// See https://github.com/ocornut/imgui/tree/master/misc/freetype
+// Only works in combination with IMGUI_ENABLE_FREETYPE.
+// The implementation is based on the demo https://gitlab.freedesktop.org/freetype/freetype-demos/-/blob/master/src/rsvg-port.c
+// which is licensed under CeCILL-C Free Software License Agreement
+//#define IMGUI_ENABLE_FREETYPE_LUNASVG
+
 //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4.
 // This will be inlined as part of ImVec2 and ImVec4 class declarations.
 /*

+ 8 - 0
misc/freetype/README.md

@@ -35,3 +35,11 @@ You can use the `ImGuiFreeTypeBuilderFlags_LoadColor` flag to load certain color
 ["Using Colorful Glyphs/Emojis"](https://github.com/ocornut/imgui/blob/master/docs/FONTS.md#using-colorful-glyphsemojis) section of FONTS.md.
 
 ![colored glyphs](https://user-images.githubusercontent.com/8225057/106171241-9dc4ba80-6191-11eb-8a69-ca1467b206d1.png)
+
+
+### Using OpenType SVG fonts (SVGinOT)
+- *SVG in Open Type* is a standard by Adobe and Mozilla for color OpenType and Open Font Format fonts. It allows font creators to embed complete SVG files within a font enabling full color and even animations.
+- Popular fonts such as [twemoji](https://github.com/13rac1/twemoji-color-font) and fonts made with [scfbuild](https://github.com/13rac1/scfbuild) is SVGinOT
+- Requires: [lunasvg](https://github.com/sammycage/lunasvg) v2.3.2 and above
+    1. Add `#define IMGUI_ENABLE_FREETYPE_OTSVG` in your `imconfig.h`.
+    2. Get latest lunasvg binaries or build yourself. Under Windows you may use vcpkg with: `vcpkg install lunasvg --triplet=x64-windows`.

+ 157 - 0
misc/freetype/imgui_freetype.cpp

@@ -6,6 +6,7 @@
 
 // CHANGELOG
 // (minor and older changes stripped away, please see git history for details)
+//  2023/07/12: added support for SVG fonts, enable by using '#define IMGUI_ENABLE_FREETYPE_LUNASVG'
 //  2023/01/04: fixed a packing issue which in some occurrences would prevent large amount of glyphs from being packed correctly.
 //  2021/08/23: fixed crash when FT_Render_Glyph() fails to render a glyph and returns NULL.
 //  2021/03/05: added ImGuiFreeTypeBuilderFlags_Bitmap to load bitmap glyphs.
@@ -44,6 +45,13 @@
 #include FT_GLYPH_H             // <freetype/ftglyph.h>
 #include FT_SYNTHESIS_H         // <freetype/ftsynth.h>
 
+#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
+#include FT_OTSVG_H             // <freetype/otsvg.h>
+#include FT_BBOX_H              // <freetype/ftbbox.h>
+#include <lunasvg.h>
+#include <memory>
+#endif
+
 #ifdef _MSC_VER
 #pragma warning (push)
 #pragma warning (disable: 4505)     // unreferenced local function has been removed (stb stuff)
@@ -70,6 +78,14 @@ static void* (*GImGuiFreeTypeAllocFunc)(size_t size, void* user_data) = ImGuiFre
 static void  (*GImGuiFreeTypeFreeFunc)(void* ptr, void* user_data) = ImGuiFreeTypeDefaultFreeFunc;
 static void* GImGuiFreeTypeAllocatorUserData = nullptr;
 
+
+#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
+FT_Error ImGuiLunasvgPortInit(FT_Pointer* state);
+void     ImGuiLunasvgPortFree(FT_Pointer* state);
+FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state);
+FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state);
+#endif
+
 //-------------------------------------------------------------------------
 // Code
 //-------------------------------------------------------------------------
@@ -244,7 +260,19 @@ namespace
 
         // Need an outline for this to work
         FT_GlyphSlot slot = Face->glyph;
+
+#if ((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
+#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
+        IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP || slot->format == FT_GLYPH_FORMAT_SVG);
+#else
+        IM_ASSERT(slot->format != FT_GLYPH_FORMAT_SVG &&
+            "The font contains SVG glyphs, you'll need to enable IMGUI_ENABLE_FREETYPE_LUNASVG"
+            "in imconfig.h and install required libraries in order to use this font");
+        IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP);
+#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
+#else
         IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP);
+#endif // ((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
 
         // Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting)
         if (UserFlags & ImGuiFreeTypeBuilderFlags_Bold)
@@ -770,6 +798,18 @@ static bool ImFontAtlasBuildWithFreeType(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(ft_library);
 
+#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
+#if ((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
+    // Install svg hooks for FreeType
+    // https://freetype.org/freetype2/docs/reference/ft2-properties.html#svg-hooks
+    // https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html#svg_fonts
+    SVG_RendererHooks  hooks = {ImGuiLunasvgPortInit, ImGuiLunasvgPortFree, ImGuiLunasvgPortRender, ImGuiLunasvgPortPresetSlot};
+    FT_Property_Set(ft_library, "ot-svg", "svg-hooks", &hooks);
+#else
+    IM_ASSERT(!"IMGUI_ENABLE_FREETYPE_LUNASVG requires FreeType version >= 2.12");
+#endif // ((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
+#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
+
     bool ret = ImFontAtlasBuildWithFreeTypeEx(ft_library, atlas, atlas->FontBuilderFlags);
     FT_Done_Library(ft_library);
 
@@ -790,6 +830,123 @@ void ImGuiFreeType::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* u
     GImGuiFreeTypeAllocatorUserData = user_data;
 }
 
+#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
+// For more details, see https://gitlab.freedesktop.org/freetype/freetype-demos/-/blob/master/src/rsvg-port.c
+// The original code from the demo is licensed under CeCILL-C Free Software License Agreement
+// https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/LICENSE.TXT
+typedef struct  LunasvgPortState_
+{
+    FT_Error err = FT_Err_Ok;
+    std::unique_ptr<lunasvg::Document> svg = nullptr;
+    lunasvg::Matrix matrix;                             // Scaled and translated matrix for rendering
+} LunasvgPortState, *PLunasvgPortState;
+
+FT_Error ImGuiLunasvgPortInit(FT_Pointer* _state)
+{
+    *_state = new LunasvgPortState();
+    return FT_Err_Ok;
+}
+
+void ImGuiLunasvgPortFree(FT_Pointer* _state)
+{
+    delete *_state;
+}
+
+FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state)
+{
+    PLunasvgPortState state = *(PLunasvgPortState*)_state;
+
+    // If there was an error while loading the svg in
+    // `ImGuiLunasvgPortPresetSlot`, the renderer hook
+    // still get called, so just returns the error.
+    if (state->err != FT_Err_Ok) return state->err;
+
+    // rows is height, pitch (or stride) equals to width * sizeof(int32)
+    lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch);
+
+    state->svg->setMatrix(state->svg->matrix().identity()); // Reset the svg matrix to the default value
+    state->svg->render(bitmap, state->matrix);              // state->matrix is already scaled and translated
+
+    state->err = FT_Err_Ok;
+    return state->err;
+}
+
+FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state)
+{
+    FT_SVG_Document   document = (FT_SVG_Document)slot->other;
+    PLunasvgPortState state = *(PLunasvgPortState*)_state;
+    FT_Size_Metrics&  metrics = document->metrics;
+
+    // This function is called twice, once in the `FT_Load_Glyph`
+    // and another right before `ImGuiLunasvgPortRender`.
+    // If it's the latter, don't do anything because it's
+    // already done in the former.
+    if (cache) return state->err;
+
+    state->svg = lunasvg::Document::loadFromData((const char*)document->svg_document, document->svg_document_length);
+    if (state->svg == nullptr)
+    {
+        state->err = FT_Err_Invalid_SVG_Document;
+        return state->err;
+    }
+
+    lunasvg::Box box = state->svg->box();
+
+    double scale = std::min(metrics.x_ppem / box.w, metrics.y_ppem / box.h);
+    double xx = (double)document->transform.xx / (1 << 16);
+    double xy = -(double)document->transform.xy / (1 << 16);
+    double yx = -(double)document->transform.yx / (1 << 16);
+    double yy = (double)document->transform.yy / (1 << 16);
+    double x0 = (double)document->delta.x / 64 * box.w / metrics.x_ppem;
+    double y0 = -(double)document->delta.y / 64 * box.h / metrics.y_ppem;
+
+    // This reset the matrix to its default value
+    state->matrix.identity();
+
+    // Scale and transform, we don't translate the svg yet
+    state->matrix.scale(scale, scale);
+    state->matrix.transform(xx, xy, yx, yy, x0, y0);
+    state->svg->setMatrix(state->matrix);
+
+    // Pre-translate the matrix for the rendering step
+    state->matrix.translate(-box.x, -box.y);
+
+    // Get the box again after the transformation
+    box = state->svg->box();
+
+    // Calculate the bitmap size
+    slot->bitmap_left = FT_Int(box.x);
+    slot->bitmap_top = FT_Int(-box.y);
+    slot->bitmap.rows = (unsigned int)(ceil(box.h));
+    slot->bitmap.width = (unsigned int)(ceil(box.w));
+    slot->bitmap.pitch = slot->bitmap.width * 4;
+    slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+
+    // Compute all the bearings and set them correctly. The outline is
+    // scaled already, we just need to use the bounding box.
+    double metrics_width = box.w;
+    double metrics_height = box.h;
+    double horiBearingX = box.x;
+    double horiBearingY = -box.y;
+
+    double vertBearingX = slot->metrics.horiBearingX / 64.0 - slot->metrics.horiAdvance / 64.0 / 2.0;
+    double vertBearingY = (slot->metrics.vertAdvance / 64.0 - slot->metrics.height / 64.0) / 2.0;
+
+    slot->metrics.width = FT_Pos(round(metrics_width * 64.0));
+    slot->metrics.height = FT_Pos(round(metrics_height * 64.0));
+
+    slot->metrics.horiBearingX = FT_Pos(horiBearingX * 64);
+    slot->metrics.horiBearingY = FT_Pos(horiBearingY * 64);
+    slot->metrics.vertBearingX = FT_Pos(vertBearingX * 64);
+    slot->metrics.vertBearingY = FT_Pos(vertBearingY * 64);
+
+    if (slot->metrics.vertAdvance == 0)
+        slot->metrics.vertAdvance = FT_Pos(metrics_height * 1.2 * 64.0);
+
+    state->err = FT_Err_Ok;
+    return state->err;
+}
+#endif // !IMGUI_ENABLE_FREETYPE_LUNASVG
 //-----------------------------------------------------------------------------
 
 #ifdef __GNUC__