/* * Copyright (c) 2020-2025 Samuel Ugochukwu * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * @brief FreeType hooks for rendering SVG glyphs with PlutoSVG. * * This file implements the integration layer between FreeType’s SVG module (typically * named "ot-svg") and PlutoSVG. It defines the necessary functions to initialize and * free the SVG rendering state, render an SVG glyph into a glyph slot, load (and cache) * SVG documents, and pre-configure glyph slots with appropriate metrics and transforms. * * These functions are aggregated into the `plutosvg_ft_hooks` structure. * * Usage example: * @code * #include * * #include * #include FT_FREETYPE_H * #include FT_MODULE_H * * int main(void) * { * FT_Library library; * if (FT_Init_FreeType(&library)) * return -1; * * if (FT_Property_Set(library, "ot-svg", "svg-hooks", &plutosvg_ft_hooks)) * return -1; * * // ... your code ... * * FT_Done_FreeType(library); * return 0; * } * @endcode */ #ifndef PLUTOSVG_FT_H #define PLUTOSVG_FT_H #include "plutosvg.h" #include #include #include #include #include FT_OTSVG_H #include FT_COLOR_H typedef struct { plutosvg_document_t* document; const FT_Byte* data; FT_ULong length; } plutosvg_ft_document_entry_t; #define PLUTOSVG_FT_MAX_DOCS 16 typedef struct { plutosvg_document_t* document; plutovg_matrix_t matrix; plutovg_rect_t extents; plutosvg_ft_document_entry_t entries[PLUTOSVG_FT_MAX_DOCS]; FT_ULong num_entries; } plutosvg_ft_state_t; static FT_Error plutosvg_ft_init(FT_Pointer* ft_state) { plutosvg_ft_state_t* state = (plutosvg_ft_state_t*)malloc(sizeof(plutosvg_ft_state_t)); memset(state, 0, sizeof(plutosvg_ft_state_t)); *ft_state = state; return FT_Err_Ok; } static void plutosvg_ft_free(FT_Pointer* ft_state) { plutosvg_ft_state_t* state = (plutosvg_ft_state_t*)(*ft_state); for(FT_ULong i = 0; i < state->num_entries; ++i) plutosvg_document_destroy(state->entries[i].document); free(state); } #define PLUTOSVG_FT_PALETTE_INDEX 0 static bool plutosvg_ft_palette_func(void* closure, const char* name, int length, plutovg_color_t* color) { FT_Face ft_face = (FT_Face)(closure); if(length < 5 || strncmp(name, "color", 5) != 0) return false; FT_Palette_Data ft_palette_data; if(FT_Palette_Data_Get(ft_face, &ft_palette_data)) return false; FT_Color* ft_palette = NULL; if(FT_Palette_Select(ft_face, PLUTOSVG_FT_PALETTE_INDEX, &ft_palette)) { return false; } FT_Int index = 0; for(int i = 5; i < length; ++i) { const char ch = name[i]; if(ch < '0' || ch > '9') return false; index = index * 10 + ch - '0'; } if(index >= ft_palette_data.num_palette_entries) return false; FT_Color* ft_color = ft_palette + index; color->r = ft_color->red / 255.f; color->g = ft_color->green / 255.f; color->b = ft_color->blue / 255.f; color->a = ft_color->alpha / 255.f; return true; } static FT_Error plutosvg_ft_render(FT_GlyphSlot ft_slot, FT_Pointer* ft_state) { plutosvg_ft_state_t* state = (plutosvg_ft_state_t*)(*ft_state); if(state->document == NULL) return FT_Err_Invalid_SVG_Document; plutovg_surface_t* surface = plutovg_surface_create_for_data(ft_slot->bitmap.buffer, ft_slot->bitmap.width, ft_slot->bitmap.rows, ft_slot->bitmap.pitch); plutovg_canvas_t* canvas = plutovg_canvas_create(surface); FT_SVG_Document ft_document = (FT_SVG_Document)ft_slot->other; FT_UShort start_glyph_id = ft_document->start_glyph_id; FT_UShort end_glyph_id = ft_document->end_glyph_id; char buffer[64]; char* id = NULL; if(start_glyph_id < end_glyph_id) { sprintf(buffer, "glyph%u", ft_slot->glyph_index); id = buffer; } plutovg_canvas_translate(canvas, -state->extents.x, -state->extents.y); plutovg_canvas_transform(canvas, &state->matrix); plutosvg_document_render(state->document, id, canvas, NULL, plutosvg_ft_palette_func, ft_slot->face); ft_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; ft_slot->bitmap.num_grays = 256; ft_slot->format = FT_GLYPH_FORMAT_BITMAP; plutovg_canvas_destroy(canvas); plutovg_surface_destroy(surface); state->document = NULL; return FT_Err_Ok; } static plutosvg_document_t* plutosvg_ft_document_load(plutosvg_ft_state_t* state, const FT_Byte* data, FT_ULong length, FT_UShort units_per_EM) { for(FT_ULong i = 0; i < state->num_entries; ++i) { if(data == state->entries[i].data && length == state->entries[i].length) { plutosvg_ft_document_entry_t entry = state->entries[i]; memmove(&state->entries[1], &state->entries[0], i * sizeof(plutosvg_ft_document_entry_t)); state->entries[0] = entry; return entry.document; } } plutosvg_document_t* document = plutosvg_document_load_from_data((const char*)data, length, units_per_EM, units_per_EM, NULL, NULL); if(document == NULL) return NULL; if(state->num_entries == PLUTOSVG_FT_MAX_DOCS) { state->num_entries--; plutosvg_document_destroy(state->entries[state->num_entries].document); } memmove(&state->entries[1], &state->entries[0], state->num_entries * sizeof(plutosvg_ft_document_entry_t)); state->entries[0].document = document; state->entries[0].data = data; state->entries[0].length = length; state->num_entries++; return document; } static FT_Error plutosvg_ft_preset_slot(FT_GlyphSlot ft_slot, FT_Bool ft_cache, FT_Pointer* ft_state) { plutosvg_ft_state_t* state = (plutosvg_ft_state_t*)(*ft_state); FT_SVG_Document ft_document = (FT_SVG_Document)ft_slot->other; FT_Size_Metrics ft_metrics = ft_document->metrics; FT_UShort start_glyph_id = ft_document->start_glyph_id; FT_UShort end_glyph_id = ft_document->end_glyph_id; plutosvg_document_t* document = plutosvg_ft_document_load(state, ft_document->svg_document, ft_document->svg_document_length, ft_document->units_per_EM); if(document == NULL) { return FT_Err_Invalid_SVG_Document; } float document_width = plutosvg_document_get_width(document); float document_height = plutosvg_document_get_height(document); plutovg_matrix_t transform = { (float)ft_document->transform.xx / (1 << 16), -(float)ft_document->transform.xy / (1 << 16), -(float)ft_document->transform.yx / (1 << 16), (float)ft_document->transform.yy / (1 << 16), (float)ft_document->delta.x / 64 * document_width / ft_metrics.x_ppem, -(float)ft_document->delta.y / 64 * document_height / ft_metrics.y_ppem }; float x_svg_to_out = ft_metrics.x_ppem / document_width; float y_svg_to_out = ft_metrics.y_ppem / document_height; plutovg_matrix_t matrix; plutovg_matrix_init_scale(&matrix, x_svg_to_out, y_svg_to_out); plutovg_matrix_multiply(&matrix, &transform, &matrix); char buffer[64]; char* id = NULL; if(start_glyph_id < end_glyph_id) { sprintf(buffer, "glyph%u", ft_slot->glyph_index); id = buffer; } plutovg_rect_t extents; if(!plutosvg_document_extents(document, id, &extents)) { return FT_Err_Invalid_SVG_Document; } plutovg_matrix_map_rect(&matrix, &extents, &extents); ft_slot->bitmap_left = (FT_Int)extents.x; ft_slot->bitmap_top = (FT_Int)-extents.y; ft_slot->bitmap.rows = (unsigned int)ceilf(extents.h); ft_slot->bitmap.width = (unsigned int)ceilf(extents.w); ft_slot->bitmap.pitch = (int)ft_slot->bitmap.width * 4; ft_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; float metrics_width = extents.w; float metrics_height = extents.h; float horiBearingX = extents.x; float horiBearingY = -extents.y; float vertBearingX = ft_slot->metrics.horiBearingX / 64.f - ft_slot->metrics.horiAdvance / 64.f / 2; float vertBearingY = (ft_slot->metrics.vertAdvance / 64.f - ft_slot->metrics.height / 64.f) / 2; ft_slot->metrics.width = (FT_Pos)roundf(metrics_width * 64); ft_slot->metrics.height = (FT_Pos)roundf(metrics_height * 64); ft_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64); ft_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64); ft_slot->metrics.vertBearingX = (FT_Pos)(vertBearingX * 64); ft_slot->metrics.vertBearingY = (FT_Pos)(vertBearingY * 64); if(ft_slot->metrics.vertAdvance == 0) ft_slot->metrics.vertAdvance = (FT_Pos)(metrics_height * 1.2f * 64); if(ft_cache) { state->document = document; state->extents = extents; state->matrix = matrix; } return FT_Err_Ok; } /** * @brief FreeType SVG renderer hooks. * * This structure is passed to FreeType via FT_Property_Set to delegate SVG glyph * rendering to PlutoSVG. */ static SVG_RendererHooks plutosvg_ft_hooks = { (SVG_Lib_Init_Func)plutosvg_ft_init, (SVG_Lib_Free_Func)plutosvg_ft_free, (SVG_Lib_Render_Func)plutosvg_ft_render, (SVG_Lib_Preset_Slot_Func)plutosvg_ft_preset_slot }; #endif // PLUTOSVG_FT_H