diff options
| author | Jean-Philippe Bruyère <jp_bruyere@hotmail.com> | 2026-10-04 17:01:10 +0200 |
|---|---|---|
| committer | Jean-Philippe Bruyère <jp_bruyere@hotmail.com> | 2026-10-04 17:13:41 +0200 |
| commit | 61fc5439991549bc8060a376dbf2c6d39c77bcb8 (patch) | |
| tree | e01d149d839abf8faee602078d685995a452e1ad | |
| parent | cb4dd3f4274aecd243f6de9e5c162a1b2a1b0ed8 (diff) | |
first test string ok
| -rw-r--r-- | CMakeLists.txt | 6 | ||||
| -rw-r--r-- | include/vkvg.h | 47 | ||||
| -rw-r--r-- | src/vkvg_context.c | 63 | ||||
| -rw-r--r-- | src/vkvg_context_internal.c | 4 | ||||
| -rw-r--r-- | src/vkvg_context_internal.h | 22 | ||||
| -rw-r--r-- | src/vkvg_device_internal.h | 2 | ||||
| -rw-r--r-- | src/vkvg_fonts.c | 537 | ||||
| -rw-r--r-- | src/vkvg_fonts.h | 149 | ||||
| -rw-r--r-- | src/vkvg_internal.h | 27 | ||||
| -rw-r--r-- | tests/font_tests/font_tests.c | 23 |
10 files changed, 598 insertions, 282 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ead44f5..32b430d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,7 +135,7 @@ endif() FIND_PACKAGE(Doxygen QUIET) CMAKE_DEPENDENT_OPTION(VKVG_USE_FREETYPE "use freetype to load and render font glyphs" ON "Freetype_FOUND" OFF) -CMAKE_DEPENDENT_OPTION(VKVG_USE_FONTCONFIG "use FontConfig to resolve font names" OFF "Fontconfig_FOUND" OFF) +CMAKE_DEPENDENT_OPTION(VKVG_USE_FONTCONFIG "use FontConfig to resolve font names" ON "Fontconfig_FOUND" OFF) CMAKE_DEPENDENT_OPTION(VKVG_USE_HARFBUZZ "use Harbuzz for text layouting" ON "HarfBuzz_FOUND" OFF) CMAKE_DEPENDENT_OPTION(VKVG_BUILD_TESTS "build tests with glfw" ON "GLFW3_FOUND" OFF) CMAKE_DEPENDENT_OPTION(VKVG_BUILD_DOCS "build documentation" OFF "DOXYGEN_FOUND" OFF) @@ -217,9 +217,7 @@ IF(GLSLC AND XXD) FOREACH(shad_spv ${SHADER_OUTPUTS}) GET_FILENAME_COMPONENT(SPV ${shad_spv} NAME) - ADD_CUSTOM_COMMAND ( - TARGET BuildShadersHeader - PRE_BUILD + ADD_CUSTOM_COMMAND (TARGET BuildShadersHeader PRE_BUILD WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${SHADER_DIR} COMMAND ${XXD} -i ${SPV} >> ${SHADERS_H} ) diff --git a/include/vkvg.h b/include/vkvg.h index 0a52a0d..c55a9ad 100644 --- a/include/vkvg.h +++ b/include/vkvg.h @@ -286,20 +286,6 @@ typedef struct _glyph_info_t { } vkvg_glyph_info_t; /** - * @brief Opaque pointer on a vkvg text run. - * - * A VkvgText is an intermediate representation of a text to be drawn. - * It contains the measurements computed for character positioning. - * - * This object is used to speed up the rendering of the same text with the same font multiple times - * by storing typographic computations. - * - * Drawing text with @ref vkvg_show_text() implicitly create such intermediate structure - * that is destroyed imediatly after the function call. - */ -typedef struct _vkvg_text_run_t* VkvgText; - -/** * @brief The Vkvg drawing Context. * @ingroup context * @@ -333,7 +319,33 @@ typedef struct _vkvg_device_t* VkvgDevice; * configurable parameters such as the wrap mode, the filtering, etc... */ typedef struct _vkvg_pattern_t* VkvgPattern; +/** + * @brief Opaque pointer on a vkvg text run. + * + * A VkvgText is an intermediate representation of a text to be drawn. + * It contains the measurements computed for character positioning. + * + * This object is used to speed up the rendering of the same text with the same font multiple times + * by storing typographic computations. + * + * Drawing text with @ref vkvg_show_text() implicitly create such intermediate structure + * that is destroyed imediatly after the function call. + */ +typedef struct _vkvg_text_run_t* VkvgText; +/** + * @brief Opaque pointer on a vkvg font. + * + * VkvgFont + * + */ +typedef struct _vkvg_font_t* VkvgFont; + +vkvg_public VkvgFont vkvg_font_create (VkvgDevice dev, const char* queryString, float pointSize); +vkvg_public VkvgFont vkvg_font_reference (VkvgFont font); +vkvg_public uint32_t vkvg_font_get_reference_count (VkvgFont font); +vkvg_public vkvg_status_t vkvg_font_status (VkvgFont font); +vkvg_public void vkvg_font_destroy (VkvgFont font); #if VKVG_DBG_STATS /** * @brief vkvg memory and vulkan statistiques. @@ -1677,6 +1689,13 @@ vkvg_public void vkvg_load_font_from_memory(VkvgContext ctx, unsigned char* font */ vkvg_public void vkvg_set_font_size(VkvgContext ctx, uint32_t size); /** + * @brief set current font for context, see @ref vkvg_font_create to create fonts. + * + * @param ctx a valid vkvg @ref context + * @param font a valid @ref VkvgFont object. + */ +vkvg_public void vkvg_set_font (VkvgContext ctx, VkvgFont font); +/** * @brief Show a string of text. * * Show the provided null-terminated utf8 string of text a the current position using the diff --git a/src/vkvg_context.c b/src/vkvg_context.c index 2acf2e7..fb3b86b 100644 --- a/src/vkvg_context.c +++ b/src/vkvg_context.c @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2024 Jean-Philippe Bruyère <jp_bruyere@hotmail.com> +// Copyright (c) 2018-2026 Jean-Philippe Bruyère <jp_bruyere@hotmail.com> // // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) @@ -33,10 +33,9 @@ static const vkvg_context_save_t CTX_INIT = { VKVG_LINE_JOIN_MITER, VKVG_FILL_RULE_NON_ZERO, VKVG_HORIZONTAL, - 10 << 6, - 0, {0}, - NULL + NULL, + NULL, }; void _init_ctx(VkvgContext ctx) { @@ -161,8 +160,8 @@ VkvgContext vkvg_create(VkvgSurface surf) { ctx->cmd = ctx->cmdBuffers[0]; // current recording buffer - ctx->references = 1; - ctx->status = VKVG_STATUS_SUCCESS; + ctx->status = VKVG_STATUS_SUCCESS; + atomic_init (&ctx->references, 1); LOG(VKVG_LOG_DBG_ARRAYS, "INIT\tctx = %p; pathes:%ju pts:%ju vch:%d vbo:%d ich:%d ibo:%d\n", ctx, (uint64_t)ctx->sizePathes, (uint64_t)ctx->sizePoints, ctx->sizeVertices, ctx->sizeVBO, ctx->sizeIndices, @@ -242,8 +241,7 @@ void vkvg_destroy(VkvgContext ctx) { if (vkvg_status(ctx)) return; - ctx->references--; - if (ctx->references > 0) + if (atomic_fetch_sub_explicit(&ctx->references, 1, memory_order_acq_rel) != 1) return; LOG(VKVG_LOG_INFO, "DESTROY Context: ctx = %p (status:%d); surf = %p\n", ctx, ctx->status, ctx->pSurf); @@ -315,14 +313,14 @@ float vkvg_get_opacity(VkvgContext ctx) { } vkvg_status_t vkvg_status(VkvgContext ctx) { return !ctx ? VKVG_STATUS_NULL_POINTER : ctx->status; } VkvgContext vkvg_reference(VkvgContext ctx) { - if (!ctx->status) - ctx->references++; + if (!vkvg_status(ctx)) + atomic_fetch_add_explicit(&ctx->references, 1, memory_order_relaxed); return ctx; } uint32_t vkvg_get_reference_count(VkvgContext ctx) { if (vkvg_status(ctx)) return 0; - return ctx->references; + return atomic_load_explicit(&ctx->references, memory_order_relaxed); } void vkvg_new_sub_path(VkvgContext ctx) { if (vkvg_status(ctx)) @@ -1177,6 +1175,14 @@ VkvgPattern vkvg_get_source(VkvgContext ctx) { return ctx->pattern; } +void vkvg_set_font (VkvgContext ctx, VkvgFont font) { + if (ctx->currentFont == font) + return; + if (ctx->currentFont) + vkvg_font_destroy(ctx->currentFont); + ctx->currentFont = font; + vkvg_font_reference (font); +} void vkvg_select_font_face(VkvgContext ctx, const char* name) { if (vkvg_status(ctx)) return; @@ -1187,20 +1193,20 @@ void vkvg_load_font_from_path(VkvgContext ctx, const char* path, const char* nam if (vkvg_status(ctx)) return; RECORD(ctx, VKVG_CMD_SET_FONT_PATH, name); - _vkvg_font_identity_t* fid = _font_cache_add_font_identity(ctx, path, name); + /*vkvg_font_face_t* fid = _font_cache_add_font_identity(ctx, path, name); if (!_font_cache_load_font_file_in_memory(fid)) { ctx->status = VKVG_STATUS_FILE_NOT_FOUND; return; } - _select_font_face(ctx, name); + _select_font_face(ctx, name);*/ } void vkvg_load_font_from_memory(VkvgContext ctx, unsigned char* fontBuffer, long fontBufferByteSize, const char* name) { if (vkvg_status(ctx)) return; // RECORD(ctx, VKVG_CMD_SET_FONT_PATH, name); - _vkvg_font_identity_t* fid = _font_cache_add_font_identity(ctx, NULL, name); + /*vkvg_font_face_t* fid = _font_cache_add_font_identity(ctx, NULL, name); fid->fontBuffer = fontBuffer; - fid->fontBufSize = fontBufferByteSize; + fid->fontBufSize = fontBufferByteSize;*/ _select_font_face(ctx, name); } @@ -1213,14 +1219,16 @@ void vkvg_set_font_size(VkvgContext ctx, uint32_t size) { #else uint32_t newSize = size; #endif - if (ctx->selectedCharSize == newSize) - return; - ctx->selectedCharSize = newSize; + //if (ctx->selectedCharSize == newSize) + // return; + //ctx->selectedCharSize = newSize; //ctx->currentFont = NULL; //ctx->currentFontSize = NULL; } -void vkvg_set_text_direction(vkvg_context* ctx, vkvg_direction_t direction) {} +void vkvg_set_text_direction(vkvg_context* ctx, vkvg_direction_t direction) { + +} void vkvg_show_text(VkvgContext ctx, const char* text) { if (vkvg_status(ctx)) @@ -1236,14 +1244,14 @@ VkvgText vkvg_text_run_create(VkvgContext ctx, const char* text) { if (vkvg_status(ctx)) return NULL; VkvgText tr = (vkvg_text_run_t*)calloc(1, sizeof(vkvg_text_run_t)); - _font_cache_create_text_run(ctx, text, -1, tr); + _font_cache_init_text_run(ctx, text, -1, tr); return tr; } VkvgText vkvg_text_run_create_with_length(VkvgContext ctx, const char* text, uint32_t length) { if (vkvg_status(ctx)) return NULL; VkvgText tr = (vkvg_text_run_t*)calloc(1, sizeof(vkvg_text_run_t)); - _font_cache_create_text_run(ctx, text, length, tr); + _font_cache_init_text_run(ctx, text, length, tr); return tr; } uint32_t vkvg_text_run_get_glyph_count(VkvgText textRun) { return textRun->glyph_count; } @@ -1259,7 +1267,7 @@ void vkvg_text_run_get_glyph_position(VkvgText textRun, uint32_t index, vkvg #endif } void vkvg_text_run_destroy(VkvgText textRun) { - _font_cache_destroy_text_run(textRun); + _font_cache_term_text_run(textRun); free(textRun); } void vkvg_show_text_run(VkvgContext ctx, VkvgText textRun) { @@ -1395,6 +1403,8 @@ void vkvg_save(VkvgContext ctx) { if (ctx->pattern) vkvg_pattern_reference(ctx->pattern); + if (ctx->currentFont) + vkvg_font_reference(ctx->currentFont); ctx->ctxSaveCount++; } @@ -1512,6 +1522,15 @@ void vkvg_restore(VkvgContext ctx) { } else { _update_cur_pattern(ctx, NULL); } + if (sav->currentFont) { + if (sav->currentFont == ctx->currentFont) { + vkvg_font_destroy(sav->currentFont); + } else { + if (ctx->currentFont) + vkvg_font_destroy(ctx->currentFont); + ctx->currentFont = sav->currentFont; + } + } ctx->ctxSaveCount--; } diff --git a/src/vkvg_context_internal.c b/src/vkvg_context_internal.c index 358b39a..effc40b 100644 --- a/src/vkvg_context_internal.c +++ b/src/vkvg_context_internal.c @@ -1950,9 +1950,9 @@ void _draw_full_screen_quad(VkvgContext ctx, vec4* scissor) { void _select_font_face(VkvgContext ctx, const char* name) { uint64_t h = fnv1a_64_str(name); - if (ctx->selectedFont == h) + /*if (ctx->selectedFont == h) return; - ctx->selectedFont = h; + ctx->selectedFont = h;*/ /*ctx->currentFont = NULL; ctx->currentFontSize = NULL;*/ } diff --git a/src/vkvg_context_internal.h b/src/vkvg_context_internal.h index 2fb5712..a186b47 100644 --- a/src/vkvg_context_internal.h +++ b/src/vkvg_context_internal.h @@ -22,8 +22,8 @@ #ifndef VKVG_CONTEXT_INTERNAL_H #define VKVG_CONTEXT_INTERNAL_H -#include "vkvg_internal.h" #include "vkvg_fonts.h" +#include "vkvg_internal.h" #if VKVG_RECORDING #include "recording/vkvg_record_internal.h" @@ -112,18 +112,17 @@ typedef struct _vkvg_context_save_t { vkvg_line_join_t lineJoint; vkvg_fill_rule_t curFillRule; vkvg_direction_t textDirection; - uint32_t selectedCharSize; /* Font size*/ - uint64_t selectedFont; - //_vkvg_font_identity_t selectedFont; // hold current face and size before cache addition - //_vkvg_font_identity_t* currentFont; // font ready for lookup + //vkvg_font_face_t selectedFont; // hold current face and size before cache addition + //vkvg_font_face_t* currentFont; // font ready for lookup push_constants pushConsts; VkvgPattern pattern; + VkvgFont currentFont; vkvg_clip_state_t clippingState; } vkvg_context_save_t; typedef struct _vkvg_context_t { - vkvg_status_t status; - uint32_t references; // reference count + vkvg_status_t status; + atomic_int references; // reference count uint32_t curColor; float lineWidth; @@ -137,14 +136,13 @@ typedef struct _vkvg_context_t { vkvg_line_join_t lineJoin; vkvg_fill_rule_t curFillRule; vkvg_direction_t textDirection; - uint32_t selectedCharSize; /* Font size*/ - uint64_t selectedFont; - // _vkvg_font_identity_t* currentFont; // font pointing to cached fonts identity + // vkvg_font_face_t* currentFont; // font pointing to cached fonts identity /****************************/ - //_vkvg_font_t selectedFont; //hold current face and size before cache addition - //_vkvg_font_t* currentFontSize; // font structure by size ready for lookup + //vkvg_font_t selectedFont; //hold current face and size before cache addition + //vkvg_font_t* currentFontSize; // font structure by size ready for lookup push_constants pushConsts; VkvgPattern pattern; + VkvgFont currentFont; //-------------------------------------------------------------------------------- uint32_t indCount; // current indice count diff --git a/src/vkvg_device_internal.h b/src/vkvg_device_internal.h index 46f755e..5dced18 100644 --- a/src/vkvg_device_internal.h +++ b/src/vkvg_device_internal.h @@ -22,8 +22,8 @@ #ifndef VKVG_DEVICE_INTERNAL_H #define VKVG_DEVICE_INTERNAL_H -#include "vkvg_internal.h" #include "vkvg_fonts.h" +#include "vkvg_internal.h" #define STENCIL_FILL_BIT 0x1 #define STENCIL_CLIP_BIT 0x2 diff --git a/src/vkvg_fonts.c b/src/vkvg_fonts.c index 2ddbb93..f000827 100644 --- a/src/vkvg_fonts.c +++ b/src/vkvg_fonts.c @@ -51,12 +51,40 @@ void _fonts_cache_create(VkvgDevice dev, const char* fontDirs) { free(cache); return; } - cache->config = FcConfigGetCurrent(); - if (!cache->config) { - LOG(VKVG_LOG_DEBUG, - "Font config initialisation failed, consider using 'FONTCONFIG_PATH' and 'FONTCONFIG_FILE' environmane\ - variables to point to 'fonts.conf' needed for FontConfig startup"); - assert(cache->config); + if (fontDirs) { + cache->config = FcConfigCreate(); + if (!cache->config) { + LOG(VKVG_LOG_ERR, "Failed to create custom Fontconfig configuration.\n"); + dev->status = VKVG_STATUS_DEVICE_ERROR; + free(cache); + return; + } + const char *start = fontDirs; + const char *end; + char fontDir[1024]; + + while (start && *start != '\0') { + end = strchr(start, ','); + size_t len = end ? (size_t)(end - start) : strlen(start); + strncpy(fontDir, start, len); + + if (!FcConfigAppFontAddDir(cache->config, (FcChar8*)fontDir)) + LOG(VKVG_LOG_DEBUG, "Failed to add '%s' to Fontconfig configuration.\n", fontDir); + + start = end ? end + 1 : NULL; + } + FcConfigSetCurrent(cache->config); + } else { + cache->config = FcConfigGetCurrent(); + if (!cache->config) { + LOG(VKVG_LOG_ERR, + "Font config initialisation failed, consider using 'FONTCONFIG_PATH' and 'FONTCONFIG_FILE' environmane\ + variables to point to 'fonts.conf' needed for FontConfig startup"); + dev->status = VKVG_STATUS_DEVICE_ERROR; + free(cache); + return; + } + cache->fcReqElts = FcObjectSetBuild(FC_FAMILY, FC_STYLE, FC_FILE, FC_INDEX, (char *)0); } #endif @@ -104,6 +132,8 @@ void _fonts_cache_create(VkvgDevice dev, const char* fontDirs) { cache->hostBuff = (uint8_t*)malloc(buffLength); cache->pensY = (int*)calloc(cache->texLength, sizeof(int)); + cache->fontBuffers = array_create_vkvg_font_buffer_t(4); + dev->fontCache = cache; } /// increase layer count of 2d texture array used as font cache. @@ -168,7 +198,7 @@ void _increase_font_tex_array(VkvgDevice dev) { } // flush font stagging buffer to cache texture array // Trigger stagging buffer to be uploaded in font cache. Groupping upload improve performances. -void _flush_chars_to_tex(VkvgDevice dev, _vkvg_font_t* f) { +void _flush_chars_to_tex(VkvgDevice dev, vkvg_font_t* f) { _font_cache_t* cache = dev->fontCache; if (cache->stagingX == 0) // no char in stagging buff to flush @@ -212,7 +242,7 @@ void _flush_chars_to_tex(VkvgDevice dev, _vkvg_font_t* f) { memset(cache->hostBuff, 0, (uint64_t)FONT_PAGE_SIZE * FONT_PAGE_SIZE * cache->texPixelSize); } /// Start a new line in font cache, increase texture layer count if needed. -void _init_next_line_in_tex_cache(VkvgDevice dev, _vkvg_font_t* f) { +void _init_next_line_in_tex_cache(VkvgDevice dev, vkvg_font_t* f) { _font_cache_t* cache = dev->fontCache; int i; for (i = 0; i < cache->texLength; ++i) { @@ -233,10 +263,30 @@ void _font_cache_destroy(VkvgDevice dev) { free(cache->hostBuff); - for (int i = 0; i < cache->fontsCount; ++i) { - _vkvg_font_identity_t* f = &cache->fonts[i]; + vkvg_font_buffer_t* buffs = cache->fontBuffers.elements; + for (int i = 0; i < cache->fontBuffers.count; ++i) { + vkvg_font_face_t* faces = buffs[i].faces.elements; + for (int j = 0; j < buffs[i].faces.count; ++j) { + VkvgFont *fonts = faces[j].sizes.elements; + for (int k = 0; k < faces[j].sizes.count; ++k) { + vkvg_font_destroy(fonts[k]); + } + vkvg_font_face_t* face = &faces[j]; + array_destroy_VkvgFont(&face->sizes); + array_destroy_uint64_t(&face->queryHashes); + FT_Done_Face(face->face); + mtx_destroy(&face->mutex); + } + vkvg_font_buffer_t* buff = &buffs[i]; + free(buff->buffer); + array_destroy_vkvg_font_face_t(&buff->faces); + } + array_destroy_vkvg_font_buffer_t(&cache->fontBuffers); + +/* for (int i = 0; i < cache->fontsCount; ++i) { + vkvg_font_face_t* f = &cache->fonts[i]; for (uint32_t j = 0; j < f->sizeCount; j++) { - _vkvg_font_t* s = &f->sizes[j]; + vkvg_font_t* s = &f->sizes[j]; #ifdef VKVG_USE_FREETYPE for (int g = 0; g < s->face->num_glyphs; ++g) { if (s->charLookup[g] != NULL) @@ -250,9 +300,6 @@ void _font_cache_destroy(VkvgDevice dev) { } #endif -#ifdef VKVG_USE_HARFBUZZ - hb_font_destroy(s->hb_font); -#endif free(s->charLookup); } @@ -263,7 +310,7 @@ void _font_cache_destroy(VkvgDevice dev) { free(f->fontBuffer); } - free(cache->fonts); + free(cache->fonts);*/ free(cache->pensY); vkh_buffer_reset(&cache->buff); @@ -274,6 +321,8 @@ void _font_cache_destroy(VkvgDevice dev) { FT_Done_FreeType(cache->library); #endif #ifdef VKVG_USE_FONTCONFIG + FcObjectSetDestroy(cache->fcReqElts); + //FcPatternDestroy(cache->fcPattern); FcConfigDestroy(cache->config); FcFini(); #endif @@ -298,17 +347,17 @@ void _font_cache_update_context_descset(VkvgContext ctx) { UNLOCK_FONTCACHE(ctx->dev) } // create a new char entry and put glyph in stagging buffer, ready for upload. -_char_ref* _prepare_char(VkvgDevice dev, VkvgText tr, uint32_t gindex) { - _vkvg_font_t* f = tr->font; +_char_ref _prepare_char(VkvgDevice dev, VkvgText tr, uint32_t gindex) { + VkvgFont f = tr->font; #ifdef VKVG_USE_FREETYPE #if defined(VKVG_LCD_FONT_FILTER) && defined(FT_CONFIG_OPTION_SUBPIXEL_RENDERING) FT_CHECK_RESULT(FT_Load_Glyph(f->face, gindex, FT_LOAD_TARGET_NORMAL)); FT_CHECK_RESULT(FT_Render_Glyph(f->face->glyph, FT_RENDER_MODE_LCD)); #else - FT_CHECK_RESULT(FT_Load_Glyph(f->face, gindex, FT_LOAD_RENDER)); + FT_CHECK_RESULT(FT_Load_Glyph(f->face->face, gindex, FT_LOAD_RENDER)); #endif - FT_GlyphSlot slot = f->face->glyph; + FT_GlyphSlot slot = f->face->face->glyph; FT_Bitmap bmp = slot->bitmap; uint32_t bmpByteWidth = bmp.width; uint32_t bmpPixelWidth = bmp.width; @@ -333,7 +382,7 @@ _char_ref* _prepare_char(VkvgDevice dev, VkvgText tr, uint32_t gindex) { _init_next_line_in_tex_cache(dev, f); } - _char_ref* cr = (_char_ref*)malloc(sizeof(_char_ref)); + _char_ref cr = {slot->glyph_index}; int penX = dev->fontCache->stagingX; #ifdef VKVG_USE_FREETYPE @@ -353,30 +402,30 @@ _char_ref* _prepare_char(VkvgDevice dev, VkvgText tr, uint32_t gindex) { #endif } } - cr->bmpDiff.x = (int16_t)slot->bitmap_left; - cr->bmpDiff.y = (int16_t)slot->bitmap_top; - cr->advance = slot->advance; + cr.bmpDiff.x = (int16_t)slot->bitmap_left; + cr.bmpDiff.y = (int16_t)slot->bitmap_top; + cr.advance = slot->advance; #else int advance; int lsb; stbtt_GetGlyphHMetrics(pStbInfo, gindex, &advance, &lsb); stbtt_MakeGlyphBitmap(pStbInfo, data + penX, bmpPixelWidth, bmpRows, FONT_PAGE_SIZE, f->scale, f->scale, gindex); - cr->bmpDiff.x = (int16_t)c_x1; - cr->bmpDiff.y = (int16_t)-c_y1; - cr->advance = (vec2){(uint32_t)roundf(f->scale * advance) << 6, 0}; + cr.bmpDiff.x = (int16_t)c_x1; + cr.bmpDiff.y = (int16_t)-c_y1; + cr.advance = (vec2){(uint32_t)roundf(f->scale * advance) << 6, 0}; #endif vec4 uvBounds = {{(float)(penX + f->curLine.penX) / (float)FONT_PAGE_SIZE}, {(float)f->curLine.penY / (float)FONT_PAGE_SIZE}, {(float)bmpPixelWidth}, {(float)bmpRows}}; - cr->bounds = uvBounds; - cr->pageIdx = f->curLine.pageIdx; + cr.bounds = uvBounds; + cr.pageIdx = f->curLine.pageIdx; f->charLookup[gindex] = cr; dev->fontCache->stagingX += bmpPixelWidth; return cr; } -void _font_add_name(_vkvg_font_identity_t* font, const char* name) { +void _font_add_name(vkvg_font_face_t* font, const char* name) { /*if (++font->namesCount == 1) font->names = (char**)malloc(sizeof(char*)); else @@ -384,40 +433,11 @@ void _font_add_name(_vkvg_font_identity_t* font, const char* name) { font->names[font->namesCount - 1] = (char*)calloc(strlen(name) + 1, sizeof(char)); strcpy(font->names[font->namesCount - 1], name);*/ } -bool _font_cache_load_font_file_in_memory(_vkvg_font_identity_t* fontId) { - FILE* fontFile = fopen(fontId->fontFile, "rb"); - if (!fontFile) - return false; - fseek(fontFile, 0, SEEK_END); - fontId->fontBufSize = ftell(fontFile); /* how long is the file ? */ - fseek(fontFile, 0, SEEK_SET); /* reset */ - fontId->fontBuffer = malloc(fontId->fontBufSize); - fread(fontId->fontBuffer, fontId->fontBufSize, 1, fontFile); - fclose(fontFile); - return true; -} -_vkvg_font_identity_t* _font_cache_add_font_identity(VkvgContext ctx, const char* fontFilePath, const char* name) { - _font_cache_t* cache = (_font_cache_t*)ctx->dev->fontCache; - if (++cache->fontsCount == 1) - cache->fonts = (_vkvg_font_identity_t*)malloc(cache->fontsCount * sizeof(_vkvg_font_identity_t)); - else - cache->fonts = (_vkvg_font_identity_t*)realloc(cache->fonts, cache->fontsCount * sizeof(_vkvg_font_identity_t)); - _vkvg_font_identity_t nf = {0}; - - if (fontFilePath) { - int fflength = strlen(fontFilePath) + 1; - nf.fontFile = (char*)malloc(fflength * sizeof(char)); - strcpy(nf.fontFile, fontFilePath); - } - _font_add_name(&nf, name); - cache->fonts[cache->fontsCount - 1] = nf; - return &cache->fonts[cache->fontsCount - 1]; -} // select current font for context -_vkvg_font_t* _find_or_create_font_size(VkvgContext ctx) { - _vkvg_font_identity_t* font;// = ctx->currentFont; +vkvg_font_t* _find_or_create_font_size(VkvgContext ctx) { + /*vkvg_font_face_t* font;// = ctx->currentFont; for (uint32_t i = 0; i < font->sizeCount; ++i) { if (font->sizes[i].charSize == ctx->selectedCharSize) @@ -425,39 +445,12 @@ _vkvg_font_t* _find_or_create_font_size(VkvgContext ctx) { } // if not found, create a new font size structure if (++font->sizeCount == 1) - font->sizes = (_vkvg_font_t*)malloc(sizeof(_vkvg_font_t)); + font->sizes = (vkvg_font_t*)malloc(sizeof(vkvg_font_t)); else - font->sizes = (_vkvg_font_t*)realloc(font->sizes, font->sizeCount * sizeof(_vkvg_font_t)); - _vkvg_font_t newSize = {.charSize = ctx->selectedCharSize}; + font->sizes = (vkvg_font_t*)realloc(font->sizes, font->sizeCount * sizeof(vkvg_font_t)); + vkvg_font_t newSize = {.charSize = ctx->selectedCharSize}; VkvgDevice dev = ctx->dev; -#ifdef VKVG_USE_FREETYPE - _font_cache_t* cache = (_font_cache_t*)ctx->dev->fontCache; - FT_CHECK_RESULT(FT_New_Memory_Face(cache->library, font->fontBuffer, font->fontBufSize, 0, &newSize.face)); - FT_CHECK_RESULT(FT_Set_Char_Size(newSize.face, 0, newSize.charSize, dev->hdpi, dev->vdpi)); - - newSize.charLookup = (_char_ref**)calloc(newSize.face->num_glyphs, sizeof(_char_ref*)); - - if (FT_IS_SCALABLE(newSize.face)) - newSize.curLine.height = newSize.face->size->metrics.height >> 6; - else - newSize.curLine.height = newSize.face->height >> 6; -#else - int result = stbtt_InitFont(&font->stbInfo, font->fontBuffer, 0); - assert(result && "stbtt_initFont failed"); - if (!result) { - ctx->status = VKVG_STATUS_INVALID_FONT; - return NULL; - } - stbtt_GetFontVMetrics(&font->stbInfo, &font->ascent, &font->descent, &font->lineGap); - newSize.charLookup = (_char_ref**)calloc(font->stbInfo.numGlyphs, sizeof(_char_ref*)); - // newSize.scale = stbtt_ScaleForPixelHeight(&font->stbInfo, newSize.charSize); - newSize.scale = stbtt_ScaleForMappingEmToPixels(&font->stbInfo, newSize.charSize); - newSize.curLine.height = roundf(newSize.scale * (font->ascent - font->descent + font->lineGap)); - newSize.ascent = roundf(newSize.scale * font->ascent); - newSize.descent = roundf(newSize.scale * font->descent); - newSize.lineGap = roundf(newSize.scale * font->lineGap); -#endif #ifdef VKVG_USE_HARFBUZZ newSize.hb_font = hb_ft_font_create(newSize.face, NULL); @@ -466,11 +459,11 @@ _vkvg_font_t* _find_or_create_font_size(VkvgContext ctx) { _init_next_line_in_tex_cache(dev, &newSize); font->sizes[font->sizeCount - 1] = newSize; - return &font->sizes[font->sizeCount - 1]; + return &font->sizes[font->sizeCount - 1];*/ } // try find font already resolved with fontconfig by font name -bool _tryFindFontByName(VkvgContext ctx, _vkvg_font_identity_t** font) { +bool _tryFindFontByName(VkvgContext ctx, vkvg_font_face_t** font) { /*_font_cache_t* cache = ctx->dev->fontCache; for (int i = 0; i < cache->fontsCount; ++i) { for (uint32_t j = 0; j < cache->fonts[i].namesCount; j++) { @@ -483,83 +476,185 @@ bool _tryFindFontByName(VkvgContext ctx, _vkvg_font_identity_t** font) { return false; } -#ifdef VKVG_USE_FONTCONFIG -bool _tryResolveFontNameWithFontConfig(VkvgContext ctx, _vkvg_font_identity_t** resolvedFont) { - _font_cache_t* cache = (_font_cache_t*)ctx->dev->fontCache; - char* fontFile = NULL; +/*void _fcpattern_append(FcPattern* dest, const FcPattern* src) { + FcPatternIter iter; + FcPatternIterStart(src, &iter); + while (FcPatternIterIsValid(src, &iter)) { + const char* object_name = FcPatternIterGetObject(src, &iter); + int value_count = FcPatternIterValueCount(src, &iter); + for (int i = 0; i < value_count; i++) { + FcValue value; + if (FcPatternIterGetValue(src, &iter, i, &value, NULL) == FcResultMatch) { + FcPatternAdd(dest, object_name, value, FcTrue); + } + } + FcPatternIterNext(src, &iter); + } +} +FcPattern* _get_font_config_pattern(VkvgContext ctx, const char* fontQueryStr, ...) { + FcPattern* request = NULL; + va_list args; + va_start(args, fontQueryStr); + if (va_arg(args, const char*) == NULL) { //only one font request + request = FcNameParse((const FcChar8*)fontQueryStr); + } else { + va_end(args); + va_start(args, fontQueryStr); - /*FcPattern* pat = FcNameParse((const FcChar8*)ctx->selectedFontName); - FcConfigSubstitute(cache->config, pat, FcMatchPattern); - FcDefaultSubstitute(pat); - FcResult result; - FcPattern* font = FcFontMatch(cache->config, pat, &result); - if (font) - FcPatternGetString(font, FC_FILE, 0, (FcChar8**)&fontFile); - *resolvedFont = NULL; - if (fontFile) { - // try find font in cache by path - for (int i = 0; i < cache->fontsCount; ++i) { - if (cache->fonts[i].fontFile && strcmp(cache->fonts[i].fontFile, fontFile) == 0) { - _font_add_name(&cache->fonts[i], ctx->selectedFontName); - *resolvedFont = &cache->fonts[i]; - break; + request = FcPatternCreate(); + + const char* str = fontQueryStr; + int i = 0; + while (str != NULL) { + FcPattern* subPat = FcNameParse((const FcChar8*)str); + if (subPat) { + _fcpattern_append(request, subPat); } + FcPatternDestroy(subPat); + str = va_arg(args, const char*); } - if (!*resolvedFont) { - // if not found, create a new vkvg_font - _vkvg_font_identity_t* fid = _font_cache_add_font_identity(ctx, fontFile, ctx->selectedFontName); - _font_cache_load_font_file_in_memory(fid); - *resolvedFont = &cache->fonts[cache->fontsCount - 1]; + + + } + va_end(args); + FcConfigSubstitute(ctx->dev->fontCache->config, request, FcMatchPattern); + FcDefaultSubstitute(request); + return request; +} +#define get_font_config_pattern(...) _get_font_config_pattern(__VA_ARGS__, NULL)*/ +bool tryGetFontFaceFromQueryHash(_font_cache_t* cache, uint64_t queryHash, vkvg_font_face_t **const face) { + *face = NULL; + vkvg_font_buffer_t* buffs = cache->fontBuffers.elements; + for (int i = 0; i < cache->fontBuffers.count; ++i) { + vkvg_font_face_t* faces = buffs[i].faces.elements; + for (int j = 0; j < buffs[i].faces.count; ++j) { + uint64_t* queries = faces[j].queryHashes.elements; + for (int k = 0; k < faces[j].queryHashes.count; ++k) { + if (queries[k] == queryHash) { + *face = &faces[j]; + return true; + } + } } } + return false; +} +bool tryGetFontBufferFromPathHash(_font_cache_t* cache, uint64_t pathHash, vkvg_font_buffer_t **const fontBuff) { + *fontBuff = NULL; + for (int i = 0; i < cache->fontBuffers.count; ++i) { + if (cache->fontBuffers.elements[i].fontPathHash == pathHash) { + *fontBuff = &cache->fontBuffers.elements[i]; + return true; + } - FcPatternDestroy(pat); - FcPatternDestroy(font);*/ - - return (fontFile != NULL); + } + return false; } -#endif +#ifdef VKVG_USE_FONTCONFIG +bool _tryResolveFontNameWithFontConfig(VkvgDevice dev, const char* fontString, vkvg_font_face_t **const face) { + _font_cache_t* cache = dev->fontCache; + uint64_t queryHash = fnv1a_64_str(fontString); -// try to find corresponding font in cache (defined by context selectedFont) and create a new font entry if not found. -void _update_current_font(VkvgContext ctx) { - /*if (ctx->currentFont == NULL) { - LOCK_FONTCACHE(ctx->dev) - if (ctx->selectedFontName[0] == 0) - _select_font_face(ctx, "sans"); + if (tryGetFontFaceFromQueryHash(cache, queryHash, face)) + return true; - if (!_tryFindFontByName(ctx, &ctx->currentFont)) { -#ifdef VKVG_USE_FONTCONFIG - _tryResolveFontNameWithFontConfig(ctx, &ctx->currentFont); + + char *fontFile = NULL; + + FcPattern* request = FcNameParse((const FcChar8*)fontString); + FcConfigSubstitute(cache->config, request, FcMatchPattern); + FcDefaultSubstitute(request); + + FcResult result; + FcPattern* font = FcFontMatch(cache->config, request, &result); + vkvg_font_buffer_t *buffPtr = NULL; + + if (font) { + int index = 0; + FcPatternGetString (font, FC_FILE, 0, (FcChar8**)&fontFile); + FcPatternGetInteger (font, FC_INDEX, 0, &index); + uint64_t fontFileHash = fnv1a_64_str(fontFile); + + if (tryGetFontBufferFromPathHash(cache, fontFileHash, &buffPtr)) { + for (int f = 0; f < buffPtr->faces.count; ++f) { + if (buffPtr->faces.elements[f].face->face_index == index) { + *face = &buffPtr->faces.elements[f]; + array_add_uint64_t (&(*face)->queryHashes, queryHash); + return true; + } + } + } else { + vkvg_font_buffer_t buff = {fontFileHash}; + FILE *file = fopen(fontFile, "rb"); + if (!file) { + LOG(VKVG_LOG_ERR, "Error opening font file: %s\n", fontFile); + return false; + } + fseek(file, 0, SEEK_END); + buff.bufferSize = ftell(file); + fseek(file, 0, SEEK_SET); + buff.buffer = malloc(buff.bufferSize); + if (!buff.buffer) { + LOG(VKVG_LOG_ERR, "Error opening font file: %s\n", fontFile); + fclose(file); + return false; + } + size_t bytes_read = fread(buff.buffer, 1, buff.bufferSize, file); + fclose(file); + buff.faces = array_create_vkvg_font_face_t(2); + buffPtr = &cache->fontBuffers.elements[cache->fontBuffers.count]; + array_add_vkvg_font_buffer_t(&cache->fontBuffers, buff); + } + + vkvg_font_face_t newFace = {0}; + newFace.queryHashes = array_create_uint64_t(2); + array_add_uint64_t(&newFace.queryHashes, queryHash); + if (dev->threadAware) + mtx_init(&newFace.mutex, mtx_plain); + +#ifdef VKVG_USE_FREETYPE + FT_CHECK_RESULT(FT_New_Memory_Face(cache->library, buffPtr->buffer, buffPtr->bufferSize, index, &newFace.face)); #else - LOG(VKVG_LOG_ERR, "Unresolved font: %s\n", ctx->selectedFontName); - UNLOCK_FONTCACHE(ctx->dev) + int result = stbtt_InitFont(&font->stbInfo, font->fontBuffer, 0); + assert(result && "stbtt_initFont failed"); + if (!result) { ctx->status = VKVG_STATUS_INVALID_FONT; - return; -#endif + return NULL; } - - ctx->currentFontSize = _find_or_create_font_size(ctx); - UNLOCK_FONTCACHE(ctx->dev) - }*/ + stbtt_GetFontVMetrics(&font->stbInfo, &font->ascent, &font->descent, &font->lineGap); + newSize.charLookup = (_char_ref**)calloc(font->stbInfo.numGlyphs, sizeof(_char_ref*)); + // newSize.scale = stbtt_ScaleForPixelHeight(&font->stbInfo, newSize.charSize); + newSize.scale = stbtt_ScaleForMappingEmToPixels(&font->stbInfo, newSize.charSize); + newSize.curLine.height = roundf(newSize.scale * (font->ascent - font->descent + font->lineGap)); + newSize.ascent = roundf(newSize.scale * font->ascent); + newSize.descent = roundf(newSize.scale * font->descent); + newSize.lineGap = roundf(newSize.scale * font->lineGap); +#endif + *face = &buffPtr->faces.elements[buffPtr->faces.count]; + array_add_vkvg_font_face_t(&buffPtr->faces, newFace); + return true; + } + FcPatternDestroy(request); + return false; } +#endif #ifdef VKVG_USE_HARFBUZZ // Get harfBuzz buffer for provided text. -hb_buffer_t* _get_hb_buffer(_vkvg_font_t* font, const char* text, int length) { +hb_buffer_t* _get_hb_buffer(vkvg_font_t* font, const char* text, int length) { hb_buffer_t* buf = hb_buffer_create(); hb_script_t script = HB_SCRIPT_LATIN; hb_unicode_funcs_t* ucfunc = hb_unicode_funcs_get_default(); - wchar_t firstChar = 0; - if (mbstowcs(&firstChar, text, 1)) - script = hb_unicode_script(ucfunc, firstChar); + uint32_t firstChar = decode_unicode_codepoint((uint8_t*)text); + + script = hb_unicode_script(ucfunc, firstChar); hb_direction_t dir = hb_script_get_horizontal_direction(script); hb_buffer_set_direction(buf, dir); hb_buffer_set_script(buf, script); // hb_buffer_set_language (buf, hb_language_from_string (lng, (int)strlen(lng))); hb_buffer_add_utf8(buf, text, length, 0, length); - hb_shape(font->hb_font, buf, NULL, 0); return buf; @@ -574,7 +669,7 @@ void _font_cache_font_extents(VkvgContext ctx, vkvg_font_extents_t* extents) { return; // TODO: ensure correct metrics are returned (scalled/unscalled, etc..) - _vkvg_font_t* font = ctx->currentFontSize; + vkvg_font_t* font = ctx->currentFontSize; #ifdef VKVG_USE_FREETYPE FT_BBox* bbox = &font->face->bbox; FT_Size_Metrics* metrics = &font->face->size->metrics; @@ -601,32 +696,26 @@ void _font_cache_text_extents(VkvgContext ctx, const char* text, int length, vkv } vkvg_text_run_t tr = {0}; - _font_cache_create_text_run(ctx, text, length, &tr); + _font_cache_init_text_run(ctx, text, length, &tr); if (ctx->status) return; *extents = tr.extents; - _font_cache_destroy_text_run(&tr); + _font_cache_term_text_run(&tr); } // text is expected as utf8 encoded // if length is < 0, text must be null terminated, else it contains glyph count -void _font_cache_create_text_run(VkvgContext ctx, const char* text, int length, VkvgText textRun) { - - _update_current_font(ctx); - - if (ctx->status) - return; - - //textRun->fontId = ctx->currentFont; - //textRun->font = ctx->currentFontSize; +void _font_cache_init_text_run(VkvgContext ctx, const char* text, int length, VkvgText textRun) { textRun->dev = ctx->dev; + textRun->font = ctx->currentFont; + vkvg_font_reference (textRun->font); LOCK_FONTCACHE(ctx->dev) #ifdef VKVG_USE_HARFBUZZ - //textRun->hbBuf = _get_hb_buffer(ctx->currentFontSize, text, length); + textRun->hbBuf = _get_hb_buffer(textRun->font, text, length); textRun->glyphs = hb_buffer_get_glyph_positions(textRun->hbBuf, &textRun->glyph_count); #else @@ -664,9 +753,9 @@ void _font_cache_create_text_run(VkvgContext ctx, const char* text, int length, for (uint32_t i = 0; i < textRun->glyph_count; ++i) string_width_in_pixels += textRun->glyphs[i].x_advance >> 6; #ifdef VKVG_USE_FREETYPE - /*FT_Size_Metrics* metrics = &ctx->currentFontSize->face->size->metrics; - textRun->extents.height = (float)(FT_MulFix(ctx->currentFontSize->face->height, metrics->y_scale) >> - 6); // (metrics->ascender + metrics->descender) >> 6;*/ + FT_Size_Metrics* metrics = &textRun->font->ftSize->metrics; + textRun->extents.height = (float)(FT_MulFix(textRun->font->face->face->height, metrics->y_scale) >> + 6); // (metrics->ascender + metrics->descender) >> 6; #else textRun->extents.height = textRun->font->ascent - textRun->font->descent + textRun->font->lineGap; #endif @@ -679,13 +768,14 @@ void _font_cache_create_text_run(VkvgContext ctx, const char* text, int length, textRun->extents.width = textRun->extents.x_advance; } -void _font_cache_destroy_text_run(VkvgText textRun) { +void _font_cache_term_text_run(VkvgText textRun) { #ifdef VKVG_USE_HARFBUZZ hb_buffer_destroy(textRun->hbBuf); #else if (textRun->glyph_count > 0) free(textRun->glyphs); #endif + vkvg_font_destroy(textRun->font); } #ifdef DEBUG void _show_texture(vkvg_context* ctx) { @@ -722,37 +812,37 @@ void _font_cache_show_text_run(VkvgContext ctx, VkvgText tr) { LOCK_FONTCACHE(ctx->dev) for (uint32_t i = 0; i < glyph_count; ++i) { - _char_ref* cr = tr->font->charLookup[glyph_info[i].codepoint]; + _char_ref cr = tr->font->charLookup[glyph_info[i].codepoint]; #ifdef VKVG_USE_HARFBUZZ - if (cr == NULL) + if (!cr.index) cr = _prepare_char(tr->dev, tr, glyph_info[i].codepoint); #endif - float uvWidth = cr->bounds.width / (float)FONT_PAGE_SIZE; - float uvHeight = cr->bounds.height / (float)FONT_PAGE_SIZE; - vec2 p0 = {pen.x + cr->bmpDiff.x + (tr->glyphs[i].x_offset >> 6), - pen.y - cr->bmpDiff.y + (tr->glyphs[i].y_offset >> 6)}; + float uvWidth = cr.bounds.width / (float)FONT_PAGE_SIZE; + float uvHeight = cr.bounds.height / (float)FONT_PAGE_SIZE; + vec2 p0 = {pen.x + cr.bmpDiff.x + (tr->glyphs[i].x_offset >> 6), + pen.y - cr.bmpDiff.y + (tr->glyphs[i].y_offset >> 6)}; v.pos = p0; VKVG_IBO_INDEX_TYPE firstIdx = (VKVG_IBO_INDEX_TYPE)(ctx->vertCount - ctx->curVertOffset); - v.uv.x = cr->bounds.x; - v.uv.y = cr->bounds.y; - v.uv.z = cr->pageIdx; + v.uv.x = cr.bounds.x; + v.uv.y = cr.bounds.y; + v.uv.z = cr.pageIdx; _add_vertex(ctx, v); - v.pos.y += cr->bounds.height; + v.pos.y += cr.bounds.height; v.uv.y += uvHeight; _add_vertex(ctx, v); - v.pos.x += cr->bounds.width; + v.pos.x += cr.bounds.width; v.pos.y = p0.y; v.uv.x += uvWidth; - v.uv.y = cr->bounds.y; + v.uv.y = cr.bounds.y; _add_vertex(ctx, v); - v.pos.y += cr->bounds.height; + v.pos.y += cr.bounds.height; v.uv.y += uvHeight; _add_vertex(ctx, v); @@ -775,16 +865,15 @@ void _font_cache_show_text_run(VkvgContext ctx, VkvgText tr) { } void _font_cache_show_text(VkvgContext ctx, const char* text) { - vkvg_text_run_t tr = {0}; - _font_cache_create_text_run(ctx, text, -1, &tr); + _font_cache_init_text_run(ctx, text, -1, &tr); if (ctx->status) return; _font_cache_show_text_run(ctx, &tr); - _font_cache_destroy_text_run(&tr); + _font_cache_term_text_run(&tr); //_show_texture(ctx); return; } @@ -826,3 +915,99 @@ void _font_cache_show_text(VkvgContext ctx, const char* text) { FT_Done_Face ( face ); FT_Done_FreeType( library ); }*/ + +bool _try_get_font_size (vkvg_font_face_t* face, uint32_t pt26_6, VkvgFont *font) { + *font = NULL; + for (int i = 0; i < face->sizes.count; ++i) { + if (face->sizes.elements[i]->charSize == pt26_6) { + *font = face->sizes.elements[i]; + return true; + } + } + return false; +} + +VkvgFont vkvg_font_create (VkvgDevice dev, const char* query, float pointSize) { + uint32_t pt26_6 = (uint32_t)(pointSize * 64.f); + VkvgFont font = NULL; + vkvg_font_face_t* face = NULL; + if (_tryResolveFontNameWithFontConfig(dev, query, &face)) { + if (_try_get_font_size(face, pt26_6, &font)) { + vkvg_font_reference(font); + return font; + } + } + + font = (vkvg_font_t*)calloc(1, sizeof(vkvg_font_t)); + if (!font) { + return (VkvgFont)&_vkvg_status_no_memory; + } + + font->charSize = pt26_6; + font->face = face; + + _font_cache_t* cache = dev->fontCache; + +#ifdef VKVG_USE_FREETYPE + if (face->sizes.count) { + FT_CHECK_RESULT(FT_New_Size (face->face, &font->ftSize)); + FT_Activate_Size(font->ftSize); + } else { + face->sizes = array_create_VkvgFont(5); + } + array_add_VkvgFont(&face->sizes, font); + FT_CHECK_RESULT(FT_Set_Char_Size(face->face, 0, font->charSize, dev->hdpi, dev->vdpi)); + + font->ftSize = face->face->size; + font->charLookup = (_char_ref*)calloc(face->face->num_glyphs, sizeof(_char_ref)); + + if (FT_IS_SCALABLE(face->face)) + font->curLine.height = face->face->size->metrics.height >> 6; + else + font->curLine.height = face->face->height >> 6; +#else + stbtt_GetFontVMetrics(&font->stbInfo, &font->ascent, &font->descent, &font->lineGap); + font->charLookup = (_char_ref**)calloc(font->stbInfo.numGlyphs, sizeof(_char_ref*)); + // newSize.scale = stbtt_ScaleForPixelHeight(&font->stbInfo, newSize.charSize); + font->scale = stbtt_ScaleForMappingEmToPixels(&font->stbInfo, newSize.charSize); + font->curLine.height = roundf(font->scale * (font->ascent - font->descent + font->lineGap)); + font->ascent = roundf(font->scale * font->ascent); + font->descent = roundf(font->scale * font->descent); + font->lineGap = roundf(font->scale * font->lineGap); +#endif +#ifdef VKVG_USE_HARFBUZZ + font->hb_font = hb_ft_font_create(face->face, NULL); +#endif + atomic_init(&font->references, 1); + return font; +} + +VkvgFont vkvg_font_reference(VkvgFont font) { + if (!vkvg_font_status(font)) + atomic_fetch_add_explicit(&font->references, 1, memory_order_relaxed); + + return font; +} +uint32_t vkvg_font_get_reference_count(VkvgFont font) { + if (vkvg_font_status(font)) + return 0; + return atomic_load_explicit(&font->references, memory_order_relaxed); +} +vkvg_status_t vkvg_font_status(VkvgFont font) { return !font ? VKVG_STATUS_NULL_POINTER : font->status; } + +void vkvg_font_destroy(VkvgFont font) { + if (vkvg_font_status(font)) + return; + if (atomic_fetch_sub_explicit(&font->references, 1, memory_order_acq_rel) != 1) + return; +#ifdef VKVG_USE_FREETYPE + FT_Done_Size(font->ftSize); +#endif +#ifdef VKVG_USE_HARFBUZZ + hb_font_destroy(font->hb_font); +#endif + + free(font->charLookup); + free(font); + font = NULL; +} diff --git a/src/vkvg_fonts.h b/src/vkvg_fonts.h index 037c254..6ccac88 100644 --- a/src/vkvg_fonts.h +++ b/src/vkvg_fonts.h @@ -31,6 +31,7 @@ #ifdef VKVG_USE_FREETYPE #include <ft2build.h> #include FT_FREETYPE_H +#include FT_SIZES_H #if defined(VKVG_LCD_FONT_FILTER) && defined(FT_CONFIG_OPTION_SUBPIXEL_RENDERING) #include <freetype/ftlcdfil.h> #endif @@ -129,19 +130,48 @@ static const uint8_t utf8_len_table[256] = { 1,1,1,1,1,1,1,1 }; +static inline uint32_t decode_unicode_codepoint(const uint8_t* text) { + if (text == NULL || *text == '\0') + return 0; + + uint32_t state = UTF8_ACCEPT; + uint32_t codepoint = 0; + int i = 0; + while (text[i] != '\0') { + decode_utf8_byte(&state, &codepoint, text[i]); + + if (state == UTF8_ACCEPT) + return codepoint; + + if (state == UTF8_REJECT) { + LOG(VKVG_LOG_DEBUG, "Malformed utf8 string.\n"); + return 0; + } + + i++; + } + + // If the loop finished but we never reached UTF8_ACCEPT, the string cut off abruptly + if (state == UTF8_ACCEPT) + return codepoint; + LOG(VKVG_LOG_DEBUG, "Error: Incomplete UTF-8 character sequence.\n"); + return 0; +} + inline int get_utf8_char_length(uint8_t first_byte) { return utf8_len_table[first_byte]; } // texture coordinates of one character in font cache array texture. typedef struct { - vec4 bounds; /* normalized float bounds of character bitmap in font cache texture. */ - vec2i16 bmpDiff; /* Difference in pixel between char bitmap top left corner and char glyph*/ - uint8_t pageIdx; /* Page index in font cache texture array */ + uint32_t index; /* stored here only to detect empty char_ref */ + vec4 bounds; /* normalized float bounds of character bitmap in font cache texture. */ + vec2i16 bmpDiff; /* Difference in pixel between char bitmap top left corner and char glyph*/ #ifdef VKVG_USE_FREETYPE - FT_Vector advance; /* horizontal or vertical advance */ + FT_Vector advance; /* horizontal or vertical advance */ #else vec2 advance; #endif + uint8_t pageIdx; /* Page index in font cache texture array */ } _char_ref; // Current location in font cache texture array for new character addition. Each font holds such structure to locate @@ -153,44 +183,62 @@ typedef struct { int height; /* Height of current line pointed by this structure */ } _tex_ref_t; -// Loaded font structure, one per size, holds informations for glyphes upload in cache and the lookup table of -// characters. -typedef struct { -#ifdef VKVG_USE_FREETYPE - FT_F26Dot6 charSize; /* Font size*/ - FT_Face face; /* FreeType face*/ -#else - uint32_t charSize; /* Font size in pixel */ - float scale; /* scale factor for the given size */ - int ascent; /* unscalled stb font metrics */ - int descent; - int lineGap; -#endif -#ifdef VKVG_USE_HARFBUZZ - hb_font_t* hb_font; /* HarfBuzz font instance*/ -#endif - _char_ref** charLookup; /* Lookup table of characteres in cache, if not found, upload is queued*/ - - _tex_ref_t curLine; /* tex coord where to add new char bmp's */ -} _vkvg_font_t; +CTOR_ARRAY(uint64_t) +CTOR_ARRAY(VkvgFont) /* Font identification structure */ typedef struct { - uint64_t names[5]; /* Resolved Input names to this font by fontConfig or custom name set by @ref vkvg_load_from_path. Maximum count is 5.*/ - uint32_t namesCount; /* Count of resolved names by fontConfig */ - unsigned char* fontBuffer; /* stb_truetype in memory buffer */ - long fontBufSize; /* */ - char* fontFile; /* Font file full path*/ + array_uint64_t queryHashes; +#ifdef VKVG_USE_FREETYPE + FT_Face face; /* FreeType face*/ + mtx_t mutex; /* Only one font size at a time may use this face */ +#endif + array_VkvgFont sizes; /* loaded font size array */ + #ifndef VKVG_USE_FREETYPE stbtt_fontinfo stbInfo; /* stb_truetype structure */ int ascent; /* unscalled stb font metrics */ int descent; int lineGap; #endif - uint32_t sizeCount; /* available font size loaded */ - _vkvg_font_t* sizes; /* loaded font size array */ -} _vkvg_font_identity_t; +} vkvg_font_face_t; + +CTOR_ARRAY(vkvg_font_face_t) + +typedef struct +{ + uint64_t fontPathHash; + size_t bufferSize; + unsigned char* buffer; + array_vkvg_font_face_t faces; +} vkvg_font_buffer_t; + +CTOR_ARRAY(vkvg_font_buffer_t) + +typedef struct _vkvg_font_t { + vkvg_status_t status; + atomic_int references; // reference count + + vkvg_font_face_t* face; +#ifdef VKVG_USE_FREETYPE + FT_F26Dot6 charSize; /* Font size in Point as fixed float 26.6 */ + FT_Size ftSize; /* FT size rec */ +#else + uint32_t charSize; /* Font size in pixel */ + float scale; /* scale factor for the given size */ + int ascent; /* unscalled stb font metrics */ + int descent; + int lineGap; +#endif + +#ifdef VKVG_USE_HARFBUZZ + hb_font_t* hb_font; /* HarfBuzz font instance*/ +#endif + _char_ref* charLookup; /* Lookup table of characteres in cache, if not found, upload is queued*/ + _tex_ref_t curLine; /* texture reference where to add new glyph bmp's in cache*/ +} vkvg_font_t; + // Font cache global structure, entry point for all font related operations. typedef struct { @@ -199,11 +247,13 @@ typedef struct { #else #endif #ifdef VKVG_USE_FONTCONFIG - FcConfig* config; /* Font config, used to find font files by font names*/ + FcConfig* config; /* Font config, used to find font files by font names*/ + //FcPattern* fcPattern; /* patter used for all Font config queries */ + FcObjectSet* fcReqElts; /* Font config requested metadata elements */ #endif - int stagingX; /* x pen in host buffer */ - uint8_t* hostBuff; /* host memory where bitmaps are first loaded */ + int stagingX; /* x pen in host buffer */ + uint8_t* hostBuff; /* host memory where bitmaps are first loaded */ VkCommandBuffer cmd; /* vulkan command buffer for font textures upload */ vkh_buffer_t buff; /* stagin buffer */ @@ -216,9 +266,7 @@ typedef struct { VkFence uploadFence; /* Signaled when upload is finished */ mtx_t mutex; /* font cache global mutex, used only if device is in thread aware mode (see: vkvg_device_set_thread_aware) */ - - _vkvg_font_identity_t* fonts; /* Loaded fonts structure array */ - int32_t fontsCount; /* Loaded fonts array count*/ + array_vkvg_font_buffer_t fontBuffers; } _font_cache_t; #define LOCK_FONTCACHE(dev) \ @@ -230,17 +278,16 @@ typedef struct { // Precompute everything necessary to measure and draw one line of text, usefull to draw the same text multiple times. typedef struct _vkvg_text_run_t { - _vkvg_font_identity_t* fontId; /* vkvg font structure pointer */ - _vkvg_font_t* font; /* vkvg font structure pointer */ - VkvgDevice dev; /* vkvg device associated with this text run */ - vkvg_text_extents_t extents; /* store computed text extends */ - const char* text; /* utf8 char array of text*/ - unsigned int glyph_count; /* Total glyph count */ + VkvgFont font; /* vkvg font structure pointer */ + VkvgDevice dev; /* vkvg device associated with this text run */ + vkvg_text_extents_t extents; /* store computed text extends */ + const char* text; /* utf8 char array of text*/ + unsigned int glyph_count;/* Total glyph count */ #ifdef VKVG_USE_HARFBUZZ - hb_buffer_t* hbBuf; /* HarfBuzz buffer of text */ - hb_glyph_position_t* glyphs; /* HarfBuzz computed glyph positions array */ + hb_buffer_t* hbBuf; /* HarfBuzz buffer of text */ + hb_glyph_position_t* glyphs; /* HarfBuzz computed glyph positions array */ #else - vkvg_glyph_info_t* glyphs; /* computed glyph positions array */ + vkvg_glyph_info_t* glyphs; /* computed glyph positions array */ #endif } vkvg_text_run_t; @@ -248,8 +295,8 @@ typedef struct _vkvg_text_run_t { void _fonts_cache_create(VkvgDevice dev, const char *fontDirs); // Release all ressources of font cache. void _font_cache_destroy(VkvgDevice dev); -_vkvg_font_identity_t* _font_cache_add_font_identity(VkvgContext ctx, const char* fontFile, const char* name); -bool _font_cache_load_font_file_in_memory(_vkvg_font_identity_t* fontId); +vkvg_font_face_t* _font_cache_add_font_identity(VkvgContext ctx, const char* fontFile, const char* name); +bool _font_cache_load_font_file_in_memory(vkvg_font_face_t* fontId); // Draw text void _font_cache_show_text(VkvgContext ctx, const char* text); // Get text dimmensions @@ -257,9 +304,9 @@ void _font_cache_text_extents(VkvgContext ctx, const char* text, int length, vkv // Get font global dimmensions void _font_cache_font_extents(VkvgContext ctx, vkvg_font_extents_t* extents); // Create text object that could be drawn multiple times minimizing harfbuzz and compute processing. -void _font_cache_create_text_run(VkvgContext ctx, const char* text, int length, VkvgText textRun); +void _font_cache_init_text_run(VkvgContext ctx, const char* text, int length, VkvgText textRun); // Release ressources held by a text run. -void _font_cache_destroy_text_run(VkvgText textRun); +void _font_cache_term_text_run(VkvgText textRun); // Draw text run void _font_cache_show_text_run(VkvgContext ctx, VkvgText tr); // update context font cache descriptor set diff --git a/src/vkvg_internal.h b/src/vkvg_internal.h index dfafd48..c1fb758 100644 --- a/src/vkvg_internal.h +++ b/src/vkvg_internal.h @@ -39,6 +39,7 @@ #include <stdint.h> #include <stdio.h> // needed before stdarg.h on Windows #include <stdarg.h> +#include <stdatomic.h> // should be supported by c11 // #include <threads.h> @@ -53,6 +54,32 @@ #define M_2_PIF 0.63661977236758134308f // 2/pi #endif +#define CTOR_ARRAY(type) \ +typedef struct { \ + uint32_t count; \ + uint32_t size; \ + type *elements; \ +} array_##type; \ +static array_##type array_create_##type (uint32_t reservedSize) { \ + type *elts = (type*)malloc(reservedSize * sizeof(type)); \ + return (array_##type) {0, reservedSize, elts}; \ +} \ +static int array_add_##type (array_##type* arr, type elt) { \ + if (arr->size <= arr->count) { \ + uint32_t newSize = arr->size * 2; \ + type *elts = (type*)realloc(arr->elements, newSize * sizeof(type)); \ + if (elts == NULL) \ + return 0; \ + arr->elements = elts; \ + arr->size = newSize; \ + } \ + arr->elements[arr->count++] = elt; \ + return 1; \ +} \ +static void array_destroy_##type (array_##type* arr) { \ + free(arr->elements); \ +} + /** * @brief Computes the 64-bit FNV-1a hash of a ascii string lowering case. * diff --git a/tests/font_tests/font_tests.c b/tests/font_tests/font_tests.c index 8d29a45..ed2a279 100644 --- a/tests/font_tests/font_tests.c +++ b/tests/font_tests/font_tests.c @@ -861,6 +861,29 @@ FcPattern* request = FcPatternCreate(); FcPatternAddString(request, FC_FAMILY, (const FcChar8*)"Roboto Flex"); FcPatternAddString(request, FC_FONT_VARIATIONS, (const FcChar8*)"wght=625,wdth=80"); */ +/* Fallback chain + // 1. Initialize Fontconfig library and load system configuration + FcConfig* config = FcInitLoadConfigAndFonts(); + if (!config) return 1; + + // 2. Create an empty pattern + FcPattern* pat = FcPatternCreate(); + + // 3. Programmatically define the fallback chain (Order: Highest -> Lowest) + // The last argument 'FcTrue' appends the font to the end of the list + FcPatternAddString(pat, FC_FAMILY, (const FcChar8*)"CustomWebFont", FcTrue); + FcPatternAddString(pat, FC_FAMILY, (const FcChar8*)"Liberation Sans", FcTrue); + FcPatternAddString(pat, FC_FAMILY, (const FcChar8*)"DejaVu Sans", FcTrue); + FcPatternAddString(pat, FC_FAMILY, (const FcChar8*)"sans-serif", FcTrue); // Generic system fallback + + // Add other attributes if needed (e.g., matching bold) + FcPatternAddInteger(pat, FC_WEIGHT, FC_WEIGHT_BOLD); + + // 4. Perform standard configuration-defined substitutions + // This hooks into system/user XML configurations (~/.config/fontconfig/fonts.conf) + // to fill in missing properties or alias maps. + FcConfigSubstitute(config, pat, +*/ void setup_variable_font(FcPattern* matched_font) { // 1. Verify if the font handles open vector variables |