diff --git a/Client/mods/deathmatch/logic/CClientVectorGraphic.cpp b/Client/mods/deathmatch/logic/CClientVectorGraphic.cpp index 2e3dcb675e..089f8481f0 100644 --- a/Client/mods/deathmatch/logic/CClientVectorGraphic.cpp +++ b/Client/mods/deathmatch/logic/CClientVectorGraphic.cpp @@ -54,6 +54,10 @@ bool CClientVectorGraphic::SetDocument(CXMLNode* node) m_pXMLDocument = node; m_pSVGDocument = lunasvg::Document::loadFromData(node->ToString()); + // Check if LunaSVG successfully parsed the document + if (!m_pSVGDocument) + return false; + m_pVectorGraphicDisplay->Update(); return true; diff --git a/Client/mods/deathmatch/logic/CClientVectorGraphicDisplay.cpp b/Client/mods/deathmatch/logic/CClientVectorGraphicDisplay.cpp index bcdd75ee3e..29c08ebe04 100644 --- a/Client/mods/deathmatch/logic/CClientVectorGraphicDisplay.cpp +++ b/Client/mods/deathmatch/logic/CClientVectorGraphicDisplay.cpp @@ -92,6 +92,10 @@ void CClientVectorGraphicDisplay::UpdateTexture() if (!surface) return; + // Check for valid SVG dimensions to avoid division by zero + if (svgDocument->width() <= 0 || svgDocument->height() <= 0) + return; + // SVG has a predefined width and height. We need transform it to the requested size const Matrix transformationMatrix(pVectorGraphicItem->m_uiSizeX / svgDocument->width(), 0, 0, pVectorGraphicItem->m_uiSizeY / svgDocument->height(), 0, 0); diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-font.c b/vendor/lunasvg/3rdparty/plutovg/plutovg-font.c deleted file mode 100644 index 2e67033cb4..0000000000 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-font.c +++ /dev/null @@ -1,414 +0,0 @@ -#include "plutovg.h" -#include "plutovg-utils.h" - -#include -#include - -#define STBTT_STATIC -#define STB_TRUETYPE_IMPLEMENTATION -#include "plutovg-stb-truetype.h" - -static int plutovg_text_iterator_length(const void* data, int length, plutovg_text_encoding_t encoding) -{ - if(length != -1) - return length; - length = 0; - switch(encoding) { - case PLUTOVG_TEXT_ENCODING_UTF8: - case PLUTOVG_TEXT_ENCODING_LATIN1: { - const uint8_t* text = data; - while(*text++) - length++; - break; - } case PLUTOVG_TEXT_ENCODING_UTF16: { - const uint16_t* text = data; - while(*text++) - length++; - break; - } case PLUTOVG_TEXT_ENCODING_UTF32: { - const uint32_t* text = data; - while(*text++) - length++; - break; - } default: - assert(false); - } - - return length; -} - -void plutovg_text_iterator_init(plutovg_text_iterator_t* it, const void* text, int length, plutovg_text_encoding_t encoding) -{ - it->text = text; - it->length = plutovg_text_iterator_length(text, length, encoding); - it->encoding = encoding; - it->index = 0; -} - -bool plutovg_text_iterator_has_next(const plutovg_text_iterator_t* it) -{ - return it->index < it->length; -} - -plutovg_codepoint_t plutovg_text_iterator_next(plutovg_text_iterator_t* it) -{ - plutovg_codepoint_t codepoint = 0; - switch(it->encoding) { - case PLUTOVG_TEXT_ENCODING_UTF8: { - static const uint8_t trailing[256] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 - }; - - static const uint32_t offsets[6] = { - 0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080 - }; - - const uint8_t* text = it->text; - int trailing_bytes = trailing[text[it->index]]; - if(it->index + trailing_bytes >= it->length) - trailing_bytes = 0; - switch(trailing_bytes) { - case 5: codepoint += text[it->index++]; codepoint <<= 6; - case 4: codepoint += text[it->index++]; codepoint <<= 6; - case 3: codepoint += text[it->index++]; codepoint <<= 6; - case 2: codepoint += text[it->index++]; codepoint <<= 6; - case 1: codepoint += text[it->index++]; codepoint <<= 6; - case 0: codepoint += text[it->index++]; - } - - codepoint -= offsets[trailing_bytes]; - break; - } case PLUTOVG_TEXT_ENCODING_UTF16: { - const uint16_t* text = it->text; - codepoint = text[it->index++]; - if(((codepoint) & 0xfffffc00) == 0xd800) { - if(it->index < it->length && (((codepoint) & 0xfffffc00) == 0xdc00)) { - uint16_t trail = text[it->index++]; - codepoint = (codepoint << 10) + trail - ((0xD800u << 10) - 0x10000u + 0xDC00u); - } - } - - break; - } case PLUTOVG_TEXT_ENCODING_UTF32: { - const uint32_t* text = it->text; - codepoint = text[it->index++]; - break; - } case PLUTOVG_TEXT_ENCODING_LATIN1: { - const uint8_t* text = it->text; - codepoint = text[it->index++]; - break; - } default: - assert(false); - } - - return codepoint; -} - -typedef struct { - stbtt_vertex* vertices; - int nvertices; - int index; - int advance_width; - int left_side_bearing; - int x1; - int y1; - int x2; - int y2; -} glyph_t; - -#define GLYPH_CACHE_SIZE 256 -struct plutovg_font_face { - int ref_count; - int ascent; - int descent; - int line_gap; - int x1; - int y1; - int x2; - int y2; - stbtt_fontinfo info; - glyph_t** glyphs[GLYPH_CACHE_SIZE]; - plutovg_destroy_func_t destroy_func; - void* closure; -}; - -plutovg_font_face_t* plutovg_font_face_load_from_file(const char* filename, int ttcindex) -{ - FILE* fp = fopen(filename, "rb"); - if(fp == NULL) { - return NULL; - } - - fseek(fp, 0, SEEK_END); - long length = ftell(fp); - if(length == -1L) { - fclose(fp); - return NULL; - } - - void* data = malloc(length); - if(data == NULL) { - fclose(fp); - return NULL; - } - - fseek(fp, 0, SEEK_SET); - size_t nread = fread(data, 1, length, fp); - fclose(fp); - - if(nread != length) { - free(data); - return NULL; - } - - return plutovg_font_face_load_from_data(data, length, ttcindex, free, data); -} - -plutovg_font_face_t* plutovg_font_face_load_from_data(const void* data, unsigned int length, int ttcindex, plutovg_destroy_func_t destroy_func, void* closure) -{ - stbtt_fontinfo info; - int offset = stbtt_GetFontOffsetForIndex(data, ttcindex); - if(offset == -1 || !stbtt_InitFont(&info, data, offset)) { - if(destroy_func) - destroy_func(closure); - return NULL; - } - - plutovg_font_face_t* face = malloc(sizeof(plutovg_font_face_t)); - face->ref_count = 1; - face->info = info; - stbtt_GetFontVMetrics(&face->info, &face->ascent, &face->descent, &face->line_gap); - stbtt_GetFontBoundingBox(&face->info, &face->x1, &face->y1, &face->x2, &face->y2); - memset(face->glyphs, 0, sizeof(face->glyphs)); - face->destroy_func = destroy_func; - face->closure = closure; - return face; -} - -plutovg_font_face_t* plutovg_font_face_reference(plutovg_font_face_t* face) -{ - if(face == NULL) - return NULL; - ++face->ref_count; - return face; -} - -void plutovg_font_face_destroy(plutovg_font_face_t* face) -{ - if(face == NULL) - return; - if(--face->ref_count == 0) { - for(int i = 0; i < GLYPH_CACHE_SIZE; i++) { - if(face->glyphs[i] == NULL) - continue; - for(int j = 0; j < GLYPH_CACHE_SIZE; j++) { - glyph_t* glyph = face->glyphs[i][j]; - if(glyph == NULL) - continue; - stbtt_FreeShape(&face->info, glyph->vertices); - free(glyph); - } - - free(face->glyphs[i]); - } - - if(face->destroy_func) - face->destroy_func(face->closure); - free(face); - } -} - -int plutovg_font_face_get_reference_count(const plutovg_font_face_t* face) -{ - if(face) - return face->ref_count; - return 0; -} - -static float plutovg_font_face_get_scale(const plutovg_font_face_t* face, float size) -{ - return stbtt_ScaleForMappingEmToPixels(&face->info, size); -} - -void plutovg_font_face_get_metrics(const plutovg_font_face_t* face, float size, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents) -{ - float scale = plutovg_font_face_get_scale(face, size); - if(ascent) *ascent = face->ascent * scale; - if(descent) *descent = face->descent * scale; - if(line_gap) *line_gap = face->line_gap * scale; - if(extents) { - extents->x = face->x1 * scale; - extents->y = face->y2 * -scale; - extents->w = (face->x2 - face->x1) * scale; - extents->h = (face->y1 - face->y2) * -scale; - } -} - -static glyph_t* plutovg_font_face_get_glyph(plutovg_font_face_t* face, plutovg_codepoint_t codepoint) -{ - unsigned int msb = (codepoint >> 8) & 0xFF; - if(face->glyphs[msb] == NULL) { - face->glyphs[msb] = calloc(GLYPH_CACHE_SIZE, sizeof(glyph_t*)); - } - - unsigned int lsb = codepoint & 0xFF; - if(face->glyphs[msb][lsb] == NULL) { - glyph_t* glyph = malloc(sizeof(glyph_t)); - glyph->index = stbtt_FindGlyphIndex(&face->info, codepoint); - glyph->nvertices = stbtt_GetGlyphShape(&face->info, glyph->index, &glyph->vertices); - stbtt_GetGlyphHMetrics(&face->info, glyph->index, &glyph->advance_width, &glyph->left_side_bearing); - if(!stbtt_GetGlyphBox(&face->info, glyph->index, &glyph->x1, &glyph->y1, &glyph->x2, &glyph->y2)) - glyph->x1 = glyph->y1 = glyph->x2 = glyph->y2 = 0; - face->glyphs[msb][lsb] = glyph; - } - - return face->glyphs[msb][lsb]; -} - -void plutovg_font_face_get_glyph_metrics(plutovg_font_face_t* face, float size, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents) -{ - float scale = plutovg_font_face_get_scale(face, size); - glyph_t* glyph = plutovg_font_face_get_glyph(face, codepoint); - if(advance_width) *advance_width = glyph->advance_width * scale; - if(left_side_bearing) *left_side_bearing = glyph->left_side_bearing * scale; - if(extents) { - extents->x = glyph->x1 * scale; - extents->y = glyph->y2 * -scale; - extents->w = (glyph->x2 - glyph->x1) * scale; - extents->h = (glyph->y1 - glyph->y2) * -scale; - } -} - -static void glyph_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) -{ - plutovg_path_t* path = (plutovg_path_t*)(closure); - switch(command) { - case PLUTOVG_PATH_COMMAND_MOVE_TO: - plutovg_path_move_to(path, points[0].x, points[0].y); - break; - case PLUTOVG_PATH_COMMAND_LINE_TO: - plutovg_path_line_to(path, points[0].x, points[0].y); - break; - case PLUTOVG_PATH_COMMAND_CUBIC_TO: - plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); - break; - case PLUTOVG_PATH_COMMAND_CLOSE: - assert(false); - } -} - -float plutovg_font_face_get_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_t* path) -{ - return plutovg_font_face_traverse_glyph_path(face, size, x, y, codepoint, glyph_traverse_func, path); -} - -float plutovg_font_face_traverse_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_traverse_func_t traverse_func, void* closure) -{ - float scale = plutovg_font_face_get_scale(face, size); - plutovg_matrix_t matrix; - plutovg_matrix_init_translate(&matrix, x, y); - plutovg_matrix_scale(&matrix, scale, -scale); - - plutovg_point_t points[3]; - plutovg_point_t current_point = {0, 0}; - glyph_t* glyph = plutovg_font_face_get_glyph(face, codepoint); - for(int i = 0; i < glyph->nvertices; i++) { - switch(glyph->vertices[i].type) { - case STBTT_vmove: - points[0].x = glyph->vertices[i].x; - points[0].y = glyph->vertices[i].y; - current_point = points[0]; - plutovg_matrix_map_points(&matrix, points, points, 1); - traverse_func(closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, 1); - break; - case STBTT_vline: - points[0].x = glyph->vertices[i].x; - points[0].y = glyph->vertices[i].y; - current_point = points[0]; - plutovg_matrix_map_points(&matrix, points, points, 1); - traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, points, 1); - break; - case STBTT_vcurve: - points[0].x = 2.f / 3.f * glyph->vertices[i].cx + 1.f / 3.f * current_point.x; - points[0].y = 2.f / 3.f * glyph->vertices[i].cy + 1.f / 3.f * current_point.y; - points[1].x = 2.f / 3.f * glyph->vertices[i].cx + 1.f / 3.f * glyph->vertices[i].x; - points[1].y = 2.f / 3.f * glyph->vertices[i].cy + 1.f / 3.f * glyph->vertices[i].y; - points[2].x = glyph->vertices[i].x; - points[2].y = glyph->vertices[i].y; - current_point = points[2]; - plutovg_matrix_map_points(&matrix, points, points, 3); - traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); - break; - case STBTT_vcubic: - points[0].x = glyph->vertices[i].cx; - points[0].y = glyph->vertices[i].cy; - points[1].x = glyph->vertices[i].cx1; - points[1].y = glyph->vertices[i].cy1; - points[2].x = glyph->vertices[i].x; - points[2].y = glyph->vertices[i].y; - current_point = points[2]; - plutovg_matrix_map_points(&matrix, points, points, 3); - traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); - break; - default: - assert(false); - } - } - - return glyph->advance_width * scale; -} - -float plutovg_font_face_text_extents(plutovg_font_face_t* face, float size, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents) -{ - plutovg_text_iterator_t it; - plutovg_text_iterator_init(&it, text, length, encoding); - plutovg_rect_t* text_extents = NULL; - float total_advance_width = 0.f; - while(plutovg_text_iterator_has_next(&it)) { - plutovg_codepoint_t codepoint = plutovg_text_iterator_next(&it); - - float advance_width; - if(extents == NULL) { - plutovg_font_face_get_glyph_metrics(face, size, codepoint, &advance_width, NULL, NULL); - total_advance_width += advance_width; - continue; - } - - plutovg_rect_t glyph_extents; - plutovg_font_face_get_glyph_metrics(face, size, codepoint, &advance_width, NULL, &glyph_extents); - - glyph_extents.x += total_advance_width; - total_advance_width += advance_width; - if(text_extents == NULL) { - text_extents = extents; - *text_extents = glyph_extents; - continue; - } - - float x1 = plutovg_min(text_extents->x, glyph_extents.x); - float y1 = plutovg_min(text_extents->y, glyph_extents.y); - float x2 = plutovg_max(text_extents->x + text_extents->w, glyph_extents.x + glyph_extents.w); - float y2 = plutovg_max(text_extents->y + text_extents->h, glyph_extents.y + glyph_extents.h); - - text_extents->x = x1; - text_extents->y = y1; - text_extents->w = x2 - x1; - text_extents->h = y2 - y1; - } - - if(extents && !text_extents) { - extents->x = 0; - extents->y = 0; - extents->w = 0; - extents->h = 0; - } - - return total_advance_width; -} diff --git a/vendor/lunasvg/LICENSE b/vendor/lunasvg/LICENSE index 0c17a4b39b..62a964e73f 100644 --- a/vendor/lunasvg/LICENSE +++ b/vendor/lunasvg/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Samuel Ugochukwu +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 diff --git a/vendor/lunasvg/README.md b/vendor/lunasvg/README.md index 0f941b4b05..ccfc96aef8 100644 --- a/vendor/lunasvg/README.md +++ b/vendor/lunasvg/README.md @@ -1,6 +1,9 @@ -[![Releases](https://img.shields.io/badge/Version-3.2.0-orange.svg)](https://github.com/sammycage/lunasvg/releases) -[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/sammycage/lunasvg/blob/master/LICENSE) -[![Build Status](https://github.com/sammycage/lunasvg/actions/workflows/main.yml/badge.svg)](https://github.com/sammycage/lunasvg/actions) +[![Actions](https://github.com/sammycage/lunasvg/actions/workflows/main.yml/badge.svg)](https://github.com/sammycage/lunasvg/actions) +[![License](https://img.shields.io/github/license/sammycage/lunasvg)](https://github.com/sammycage/lunasvg/blob/master/LICENSE) +[![Releases](https://img.shields.io/github/v/release/sammycage/lunasvg)](https://github.com/sammycage/lunasvg/releases) +[![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://www.paypal.me/sammycage) + +> Interested in HTML rendering? Explore a related library, [PlutoBook](https://github.com/plutoprint/plutobook), built for paged HTML rendering. # LunaSVG @@ -95,6 +98,69 @@ int main() | --- | --- | | ![summer.png](https://github.com/user-attachments/assets/c7f16780-23f8-4acd-906a-2242f2d0d33b) | ![winter.png](https://github.com/user-attachments/assets/fdd65288-11c7-4e16-bb5a-2bf28de57145) | +--- + +## Hit Testing + +This example demonstrates SVG element hit testing using `elementFromPoint(x, y)` in LunaSVG. It loads an SVG containing three shapes, performs point-based hit detection, and applies a skew transform with a black stroke to each matched element. The results are saved as `original.png` and `modified.png` for visual comparison. + +```cpp +#include + +#include +#include + +using namespace lunasvg; + +static const char kSVGContent[] = R"SVG( + + + + + +)SVG"; + +int main() +{ + auto document = Document::loadFromData(kSVGContent); + + document->renderToBitmap().writeToPng("original.png"); + + const std::pair points[] = { + {30, 30}, // inside red-rect + {200, 70}, // center of blue-circle + {310, 50}, // inside green-rect + {0, 0}, // outside all shapes + }; + + for(const auto& [x, y] : points) { + if(auto element = document->elementFromPoint(x, y)) { + std::cout << "Element at (" << x << ", " << y << "): " << element.getAttribute("id") << "\n"; + + element.setAttribute("stroke", "black"); + element.setAttribute("stroke-width", "3"); + element.setAttribute("transform", "skewX(9)"); + } else { + std::cout << "No element found at (" << x << ", " << y << ")\n"; + } + } + + document->renderToBitmap().writeToPng("modified.png"); + return 0; +} +``` + +| `original.png` | `modified.png` | +| --- | --- | +| ![original.png](https://github.com/user-attachments/assets/bbffbd84-6311-484b-bfe3-219d7aec055b) | ![modified.png](https://github.com/user-attachments/assets/a7f6e502-a64f-48d5-8a01-901ad15b108b) | + +```log +Element at (30, 30): red-rect +Element at (200, 70): blue-circle +Element at (310, 50): green-rect +No element found at (0, 0) +``` + ## Features LunaSVG supports nearly all graphical features outlined in the SVG 1.1 and SVG 1.2 Tiny specifications. The primary exceptions are animation, filters, and scripts. As LunaSVG is designed for static rendering, animation is unlikely to be supported in the future. However, support for filters may be added. It currently handles a wide variety of elements, including: @@ -108,7 +174,7 @@ Follow the steps below to install LunaSVG using either [CMake](https://cmake.org ### Using CMake ```bash -git clone --recursive https://github.com/sammycage/lunasvg.git +git clone https://github.com/sammycage/lunasvg.git cd lunasvg cmake -B build . cmake --build build @@ -141,6 +207,17 @@ target_link_libraries(your_target_name PRIVATE lunasvg::lunasvg) Replace `your_target_name` with the name of your executable or library target. +Build Options +LunaSVG provides several build options that can be configured using -D flags when running cmake. Below is a list of available options: + +USE_SYSTEM_PLUTOVG (default: OFF): Use the system-installed plutovg library (version 1.0.0 or higher) instead of the bundled submodule. If the system library is not found, the build will fall back to using the submodule. +Example: + +```bash +cmake -B build -DUSE_SYSTEM_PLUTOVG=ON . +cmake --build build +``` + ### Using Meson ```bash @@ -207,3 +284,8 @@ $ svg2png input.svg 512x512 0xff00ffff - [eScada Solutions](https://www.escadasolutions.com) - [CARLA Simulator](https://carla.org/) - [AUI Framework](https://github.com/aui-framework/aui) +- [Software Companions](http://www.softwarecompanions.com) + +## License + +LunaSVG is licensed under the MIT License, see [LICENSE](https://github.com/sammycage/lunasvg/blob/master/LICENSE) for more information. diff --git a/vendor/lunasvg/include/lunasvg.h b/vendor/lunasvg/include/lunasvg.h index bee1366d77..cf871adb9f 100644 --- a/vendor/lunasvg/include/lunasvg.h +++ b/vendor/lunasvg/include/lunasvg.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2024 Samuel Ugochukwu + * Copyright (c) 2020-2026 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 @@ -28,7 +28,10 @@ #include #include -#if !defined(LUNASVG_BUILD_STATIC) && (defined(_WIN32) || defined(__CYGWIN__)) +#if defined(LUNASVG_BUILD_STATIC) +#define LUNASVG_EXPORT +#define LUNASVG_IMPORT +#elif (defined(_WIN32) || defined(__CYGWIN__)) #define LUNASVG_EXPORT __declspec(dllexport) #define LUNASVG_IMPORT __declspec(dllimport) #elif defined(__GNUC__) && (__GNUC__ >= 4) @@ -46,7 +49,7 @@ #endif #define LUNASVG_VERSION_MAJOR 3 -#define LUNASVG_VERSION_MINOR 2 +#define LUNASVG_VERSION_MINOR 5 #define LUNASVG_VERSION_MICRO 0 #define LUNASVG_VERSION_ENCODE(major, minor, micro) (((major) * 10000) + ((minor) * 100) + ((micro) * 1)) @@ -514,6 +517,12 @@ class LUNASVG_API Node { */ bool isNull() const { return m_node == nullptr; } + /** + * @brief Checks if the node is not null. + * @return True if the node is not null, false otherwise. + */ + operator bool() const { return !isNull(); } + /** * @brief Checks if two nodes are equal. * @param element The node to compare. @@ -644,7 +653,7 @@ class LUNASVG_API Element : public Node { private: Element(SVGElement* element); - SVGElement* element(bool layout = false) const; + SVGElement* element(bool layoutIfNeeded = false) const; friend class Node; friend class Document; }; @@ -741,6 +750,14 @@ class LUNASVG_API Document { */ Bitmap renderToBitmap(int width = -1, int height = -1, uint32_t backgroundColor = 0x00000000) const; + /** + * @brief Returns the topmost element under the specified point. + * @param x The x-coordinate in viewport space. + * @param y The y-coordinate in viewport space. + * @return The topmost Element at the given point, or a null `Element` if no match is found. + */ + Element elementFromPoint(float x, float y) const; + /** * @brief Retrieves an element by its ID. * @param id The ID of the element to retrieve. @@ -754,11 +771,6 @@ class LUNASVG_API Document { */ Element documentElement() const; - /** - * @internal - */ - SVGRootElement* rootElement() const { return m_rootElement.get(); } - Document(Document&&); Document& operator=(Document&&); ~Document(); @@ -767,10 +779,13 @@ class LUNASVG_API Document { Document(); Document(const Document&) = delete; Document& operator=(const Document&) = delete; + SVGRootElement* rootElement(bool layoutIfNeeded = false) const; bool parse(const char* data, size_t length); std::unique_ptr m_rootElement; + friend class SVGURIReference; + friend class SVGNode; }; -} //namespace lunasvg +} // namespace lunasvg #endif // LUNASVG_H diff --git a/vendor/lunasvg/3rdparty/plutovg/LICENSE b/vendor/lunasvg/plutovg/LICENSE similarity index 100% rename from vendor/lunasvg/3rdparty/plutovg/LICENSE rename to vendor/lunasvg/plutovg/LICENSE diff --git a/vendor/lunasvg/plutovg/README.md b/vendor/lunasvg/plutovg/README.md new file mode 100644 index 0000000000..23b7faf059 --- /dev/null +++ b/vendor/lunasvg/plutovg/README.md @@ -0,0 +1,99 @@ +[![Actions](https://github.com/sammycage/plutovg/actions/workflows/main.yml/badge.svg)](https://github.com/sammycage/plutovg/actions) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/sammycage/plutovg/blob/main/LICENSE) +[![Releases](https://img.shields.io/github/v/release/sammycage/plutovg)](https://github.com/sammycage/plutovg/releases) +[![CodeFactor](https://www.codefactor.io/repository/github/sammycage/plutovg/badge)](https://www.codefactor.io/repository/github/sammycage/plutovg) + +# PlutoVG +PlutoVG is a standalone 2D vector graphics library in C. + +## Features +- Path Filling, Stroking and Dashing +- Solid, Gradient and Texture Paints +- Fonts and Texts +- Clipping and Compositing +- Transformations +- Images + +## Example +```c +#include + +int main(void) +{ + const int width = 150; + const int height = 150; + + const float center_x = width / 2.f; + const float center_y = height / 2.f; + const float face_radius = 70; + const float mouth_radius = 50; + const float eye_radius = 10; + const float eye_offset_x = 25; + const float eye_offset_y = 20; + const float eye_x = center_x - eye_offset_x; + const float eye_y = center_y - eye_offset_y; + + plutovg_surface_t* surface = plutovg_surface_create(width, height); + plutovg_canvas_t* canvas = plutovg_canvas_create(surface); + + plutovg_canvas_save(canvas); + plutovg_canvas_arc(canvas, center_x, center_y, face_radius, 0, PLUTOVG_TWO_PI, 0); + plutovg_canvas_set_rgb(canvas, 1, 1, 0); + plutovg_canvas_fill_preserve(canvas); + plutovg_canvas_set_rgb(canvas, 0, 0, 0); + plutovg_canvas_set_line_width(canvas, 5); + plutovg_canvas_stroke(canvas); + plutovg_canvas_restore(canvas); + + plutovg_canvas_save(canvas); + plutovg_canvas_arc(canvas, eye_x, eye_y, eye_radius, 0, PLUTOVG_TWO_PI, 0); + plutovg_canvas_arc(canvas, center_x + eye_offset_x, eye_y, eye_radius, 0, PLUTOVG_TWO_PI, 0); + plutovg_canvas_set_rgb(canvas, 0, 0, 0); + plutovg_canvas_fill(canvas); + plutovg_canvas_restore(canvas); + + plutovg_canvas_save(canvas); + plutovg_canvas_arc(canvas, center_x, center_y, mouth_radius, 0, PLUTOVG_PI, 0); + plutovg_canvas_set_rgb(canvas, 0, 0, 0); + plutovg_canvas_set_line_width(canvas, 5); + plutovg_canvas_stroke(canvas); + plutovg_canvas_restore(canvas); + + plutovg_surface_write_to_png(surface, "smiley.png"); + plutovg_canvas_destroy(canvas); + plutovg_surface_destroy(surface); + return 0; +} +``` + +output: + +![smiley.png](smiley.png) + +## Installation + +Follow the steps below to install PlutoVG using either [Meson](https://mesonbuild.com/) or [CMake](https://cmake.org/). + +### Using Meson + +```bash +git clone https://github.com/sammycage/plutovg.git +cd plutovg +meson setup build +meson compile -C build +meson install -C build +``` + +### Using CMake + +```bash +git clone https://github.com/sammycage/plutovg.git +cd plutovg +cmake -B build . +cmake --build build +cmake --install build +``` + +## Projects using PlutoVG +- [LunaSVG](https://github.com/sammycage/lunasvg) +- [PlutoSVG](https://github.com/sammycage/plutosvg) diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg.h b/vendor/lunasvg/plutovg/include/plutovg.h similarity index 88% rename from vendor/lunasvg/3rdparty/plutovg/plutovg.h rename to vendor/lunasvg/plutovg/include/plutovg.h index 07f6ff6773..c34d0c0894 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg.h +++ b/vendor/lunasvg/plutovg/include/plutovg.h @@ -29,7 +29,10 @@ extern "C" { #endif -#if !defined(PLUTOVG_BUILD_STATIC) && (defined(_WIN32) || defined(__CYGWIN__)) +#if defined(PLUTOVG_BUILD_STATIC) +#define PLUTOVG_EXPORT +#define PLUTOVG_IMPORT +#elif (defined(_WIN32) || defined(__CYGWIN__)) #define PLUTOVG_EXPORT __declspec(dllexport) #define PLUTOVG_IMPORT __declspec(dllimport) #elif defined(__GNUC__) && (__GNUC__ >= 4) @@ -46,9 +49,9 @@ extern "C" { #define PLUTOVG_API PLUTOVG_IMPORT #endif -#define PLUTOVG_VERSION_MAJOR 0 -#define PLUTOVG_VERSION_MINOR 0 -#define PLUTOVG_VERSION_MICRO 13 +#define PLUTOVG_VERSION_MAJOR 1 +#define PLUTOVG_VERSION_MINOR 3 +#define PLUTOVG_VERSION_MICRO 2 #define PLUTOVG_VERSION_ENCODE(major, minor, micro) (((major) * 10000) + ((minor) * 100) + ((micro) * 1)) #define PLUTOVG_VERSION PLUTOVG_VERSION_ENCODE(PLUTOVG_VERSION_MAJOR, PLUTOVG_VERSION_MINOR, PLUTOVG_VERSION_MICRO) @@ -101,6 +104,7 @@ typedef struct plutovg_point { } plutovg_point_t; #define PLUTOVG_MAKE_POINT(x, y) ((plutovg_point_t){x, y}) +#define PLUTOVG_EMPTY_POINT PLUTOVG_MAKE_POINT(0, 0) /** * @brief A structure representing a rectangle in 2D space. @@ -113,6 +117,7 @@ typedef struct plutovg_rect { } plutovg_rect_t; #define PLUTOVG_MAKE_RECT(x, y, w, h) ((plutovg_rect_t){x, y, w, h}) +#define PLUTOVG_EMPTY_RECT PLUTOVG_MAKE_RECT(0, 0, 0, 0) /** * @brief A structure representing a 2D transformation matrix. @@ -127,7 +132,6 @@ typedef struct plutovg_matrix { } plutovg_matrix_t; #define PLUTOVG_MAKE_MATRIX(a, b, c, d, e, f) ((plutovg_matrix_t){a, b, c, d, e, f}) - #define PLUTOVG_MAKE_SCALE(x, y) PLUTOVG_MAKE_MATRIX(x, 0, 0, y, 0, 0) #define PLUTOVG_MAKE_TRANSLATE(x, y) PLUTOVG_MAKE_MATRIX(1, 0, 0, 1, x, y) #define PLUTOVG_IDENTITY_MATRIX PLUTOVG_MAKE_MATRIX(1, 0, 0, 1, 0, 0) @@ -729,10 +733,10 @@ PLUTOVG_API bool plutovg_path_parse(plutovg_path_t* path, const char* data, int * @brief Text encodings used for converting text data to code points. */ typedef enum plutovg_text_encoding { + PLUTOVG_TEXT_ENCODING_LATIN1, ///< Latin-1 encoding PLUTOVG_TEXT_ENCODING_UTF8, ///< UTF-8 encoding PLUTOVG_TEXT_ENCODING_UTF16, ///< UTF-16 encoding - PLUTOVG_TEXT_ENCODING_UTF32, ///< UTF-32 encoding - PLUTOVG_TEXT_ENCODING_LATIN1 ///< Latin-1 encoding + PLUTOVG_TEXT_ENCODING_UTF32 ///< UTF-32 encoding } plutovg_text_encoding_t; /** @@ -889,6 +893,113 @@ PLUTOVG_API float plutovg_font_face_traverse_glyph_path(plutovg_font_face_t* fac */ PLUTOVG_API float plutovg_font_face_text_extents(plutovg_font_face_t* face, float size, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents); +/** + * @brief Represents a cache of loaded font faces. + */ +typedef struct plutovg_font_face_cache plutovg_font_face_cache_t; + +/** + * @brief Create a new, empty font‐face cache. + * + * @return Pointer to a newly allocated `plutovg_font_face_cache_t` object. + */ +PLUTOVG_API plutovg_font_face_cache_t* plutovg_font_face_cache_create(void); + +/** + * @brief Increments the reference count of a font‐face cache. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @return A pointer to the same `plutovg_font_face_cache_t` object with an incremented reference count. + */ +PLUTOVG_API plutovg_font_face_cache_t* plutovg_font_face_cache_reference(plutovg_font_face_cache_t* cache); + +/** + * @brief Decrement the reference count of a font‐face cache and destroy it when it reaches zero. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object to release. + */ +PLUTOVG_API void plutovg_font_face_cache_destroy(plutovg_font_face_cache_t* cache); + +/** + * @brief Retrieve the current reference count of a font‐face cache. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @return The current reference count, or 0 if cache is NULL. + */ +PLUTOVG_API int plutovg_font_face_cache_reference_count(const plutovg_font_face_cache_t* cache); + +/** + * @brief Remove all entries from a font‐face cache. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object to reset. + */ +PLUTOVG_API void plutovg_font_face_cache_reset(plutovg_font_face_cache_t* cache); + +/** + * @brief Add a font face to the cache with the specified family and style. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @param family The font family name. + * @param bold Whether the font is bold. + * @param italic Whether the font is italic. + * @param face A pointer to the `plutovg_font_face_t` to add. The cache increments its reference count. + */ +PLUTOVG_API void plutovg_font_face_cache_add(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, plutovg_font_face_t* face); + +/** + * @brief Load a font face from a file and add it to the cache with the specified family and style. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @param family The font family name to associate with the face. + * @param bold Whether the font is bold. + * @param italic Whether the font is italic. + * @param filename Path to the font file. + * @param ttcindex Index of the face in a TrueType collection (use 0 for non-TTC fonts). + * @return `true` on success, `false` if the file could not be loaded. + */ +PLUTOVG_API bool plutovg_font_face_cache_add_file(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, const char* filename, int ttcindex); + +/** + * @brief Retrieve a font face from the cache by family and style. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @param family The font family name. + * @param bold Whether the font is bold. + * @param italic Whether the font is italic. + * @return A pointer to the matching `plutovg_font_face_t` object, or NULL if not found. The returned face is owned by the cache and must not be destroyed by the caller. + */ +PLUTOVG_API plutovg_font_face_t* plutovg_font_face_cache_get(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic); + +/** + * @brief Load all font faces from a file and add them to the cache. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @param filename Path to the font file (TrueType, OpenType, or font collection). + * @return The number of faces successfully loaded, or `-1` if font face cache loading is disabled. + */ +PLUTOVG_API int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const char* filename); + +/** + * @brief Load all font faces from files in a directory recursively and add them to the cache. + * + * This scans the specified directory recursively and loads all supported font files. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @param dirname Path to the directory containing font files. + * @return The number of faces successfully loaded, or `-1` if font face cache loading is disabled. + */ +PLUTOVG_API int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname); + +/** + * @brief Load all available system font faces and add them to the cache. + * + * This scans standard system font directories recursively and loads all supported font files. + * + * @param cache A pointer to a `plutovg_font_face_cache_t` object. + * @return The number of faces successfully loaded, or `-1` if font face cache loading is disabled. + */ +PLUTOVG_API int plutovg_font_face_cache_load_sys(plutovg_font_face_cache_t* cache); + /** * @brief Represents a color with red, green, blue, and alpha components. */ @@ -1129,9 +1240,10 @@ PLUTOVG_API int plutovg_surface_get_height(const plutovg_surface_t* surface); PLUTOVG_API int plutovg_surface_get_stride(const plutovg_surface_t* surface); /** - * @brief plutovg_surface_clear - * @param surface - * @param color + * @brief Clears the entire surface with the specified color. + * + * @param surface Pointer to the target surface. + * @param color Pointer to the color used for clearing. */ PLUTOVG_API void plutovg_surface_clear(plutovg_surface_t* surface, const plutovg_color_t* color); @@ -1539,6 +1651,57 @@ PLUTOVG_API void plutovg_canvas_set_paint(plutovg_canvas_t* canvas, plutovg_pain */ PLUTOVG_API plutovg_paint_t* plutovg_canvas_get_paint(const plutovg_canvas_t* canvas, plutovg_color_t* color); +/** + * @brief Assigns a font-face cache to the canvas for font management. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param cache A pointer to a `plutovg_font_face_cache_t` object, or NULL to unset the current cache. + */ +PLUTOVG_API void plutovg_canvas_set_font_face_cache(plutovg_canvas_t* canvas, plutovg_font_face_cache_t* cache); + +/** + * @brief Returns the font-face cache associated with the canvas. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @return A pointer to the associated `plutovg_font_face_cache_t` object, or NULL if none is set. + */ +PLUTOVG_API plutovg_font_face_cache_t* plutovg_canvas_get_font_face_cache(const plutovg_canvas_t* canvas); + +/** + * @brief Add a font face to the canvas using the specified family and style. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param family The font family name to associate with the face. + * @param bold Whether the font is bold. + * @param italic Whether the font is italic. + * @param face A pointer to the `plutovg_font_face_t` object to add. + */ +PLUTOVG_API void plutovg_canvas_add_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, plutovg_font_face_t* face); + +/** + * @brief Load a font face from a file and add it to the canvas using the specified family and style. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param family The font family name to associate with the face. + * @param bold Whether the font is bold. + * @param italic Whether the font is italic. + * @param filename Path to the font file. + * @param ttcindex Index within a TrueType Collection (use 0 for regular font files). + * @return `true` on success, or `false` if the font could not be loaded. + */ +PLUTOVG_API bool plutovg_canvas_add_font_file(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, const char* filename, int ttcindex); + +/** + * @brief Selects and sets the current font face on the canvas. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param family The font family name to select. + * @param bold Whether to match a bold variant. + * @param italic Whether to match an italic variant. + * @return `true` if a matching font was found and set, `false` otherwise. + */ +PLUTOVG_API bool plutovg_canvas_select_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic); + /** * @brief Sets the font face and size for text rendering on the canvas. * @@ -2064,28 +2227,94 @@ PLUTOVG_API void plutovg_canvas_get_current_point(const plutovg_canvas_t* canvas PLUTOVG_API plutovg_path_t* plutovg_canvas_get_path(const plutovg_canvas_t* canvas); /** - * @brief Gets the bounding box of the filled region. + * @brief Tests whether a point lies within the current fill region. + * + * Determines whether the point at coordinates `(x, y)` falls within the area + * that would be filled by a `plutovg_canvas_fill()` operation, given the current path, + * fill rule, and transformation state. + * + * @note Clipping and surface dimensions are not considered in this test. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The X coordinate of the point, in user space. + * @param y The Y coordinate of the point, in user space. + * @return `true` if the point is within the fill region, `false` otherwise. + */ +PLUTOVG_API bool plutovg_canvas_fill_contains(plutovg_canvas_t* canvas, float x, float y); + +/** + * @brief Tests whether a point lies within the current stroke region. + * + * Determines whether the point at coordinates `(x, y)` falls within the area + * that would be stroked by a `plutovg_canvas_stroke()` operation, given the current path, + * stroke width, joins, caps, miter limit, dash pattern, and transformation state. + * + * @note Clipping and surface dimensions are not considered in this test. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param x The X coordinate of the point, in user space. + * @param y The Y coordinate of the point, in user space. + * @return `true` if the point is within the stroke region, `false` otherwise. + */ +PLUTOVG_API bool plutovg_canvas_stroke_contains(plutovg_canvas_t* canvas, float x, float y); + +/** + * @brief Tests whether a point lies within the current clipping region. + * + * Determines whether the point at coordinates `(x, y)` falls within the active clipping + * region on the canvas. + * + * If no clipping is applied, the default clipping region covers the entire canvas + * area starting at `(0, 0)` with width and height equal to the canvas dimensions. * * @param canvas A pointer to a `plutovg_canvas_t` object. - * @param extents The bounding box of the filled region. + * @param x The X coordinate of the point, in user space. + * @param y The Y coordinate of the point, in user space. + * @return `true` if the point is within the clipping region, `false` otherwise. */ -PLUTOVG_API void plutovg_canvas_fill_extents(const plutovg_canvas_t* canvas, plutovg_rect_t* extents); +PLUTOVG_API bool plutovg_canvas_clip_contains(plutovg_canvas_t* canvas, float x, float y); /** - * @brief Gets the bounding box of the stroked region. + * @brief Computes the bounding box of the area that would be affected by a fill operation. + * + * Computes an axis-aligned bounding box in user space that encloses the area + * which would be affected by a fill operation (`plutovg_canvas_fill()`) given the current path, + * fill rule, and transformation state. + * + * @note Clipping and surface dimensions are not considered in this calculation. * * @param canvas A pointer to a `plutovg_canvas_t` object. - * @param extents The bounding box of the stroked region. + * @param extents A pointer to a `plutovg_rect_t` structure that receives the bounding box. */ -PLUTOVG_API void plutovg_canvas_stroke_extents(const plutovg_canvas_t* canvas, plutovg_rect_t* extents); +PLUTOVG_API void plutovg_canvas_fill_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents); /** - * @brief Gets the bounding box of the clipped region. + * @brief Computes the bounding box of the area that would be affected by a stroke operation. + * + * Computes an axis-aligned bounding box in user space that encloses the area + * which would be affected by a stroke operation (`plutovg_canvas_stroke()`) given the current path, + * stroke width, joins, caps, miter limit, dash pattern, and transformation state. + * + * @note Clipping and surface dimensions are not considered in this calculation. + * + * @param canvas A pointer to a `plutovg_canvas_t` object. + * @param extents A pointer to a `plutovg_rect_t` structure that receives the bounding box. + */ +PLUTOVG_API void plutovg_canvas_stroke_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents); + +/** + * @brief Gets the bounding box of the current clipping region. + * + * Computes an axis-aligned bounding box in user space that encloses the currently active + * clipping region on the canvas. + * + * If no clip is applied, the returned rectangle covers the entire canvas area, + * starting at `(0, 0)` with width and height equal to the canvas dimensions. * * @param canvas A pointer to a `plutovg_canvas_t` object. - * @param extents The bounding box of the clipped region. + * @param extents A pointer to a `plutovg_rect_t` structure that receives the bounding box. */ -PLUTOVG_API void plutovg_canvas_clip_extents(const plutovg_canvas_t* canvas, plutovg_rect_t* extents); +PLUTOVG_API void plutovg_canvas_clip_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents); /** * @brief A drawing operator that fills the current path according to the current fill rule. diff --git a/vendor/lunasvg/plutovg/smiley.png b/vendor/lunasvg/plutovg/smiley.png new file mode 100644 index 0000000000..85e1d91d0b Binary files /dev/null and b/vendor/lunasvg/plutovg/smiley.png differ diff --git a/vendor/lunasvg/3rdparty/plutovg/FTL.TXT b/vendor/lunasvg/plutovg/source/FTL.TXT similarity index 100% rename from vendor/lunasvg/3rdparty/plutovg/FTL.TXT rename to vendor/lunasvg/plutovg/source/FTL.TXT diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-blend.c b/vendor/lunasvg/plutovg/source/plutovg-blend.c similarity index 91% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-blend.c rename to vendor/lunasvg/plutovg/source/plutovg-blend.c index e36b79dae7..892c0b8847 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-blend.c +++ b/vendor/lunasvg/plutovg/source/plutovg-blend.c @@ -55,11 +55,12 @@ static inline uint32_t premultiply_color_with_opacity(const plutovg_color_t* col return (alpha << 24) | (pr << 16) | (pg << 8) | (pb); } -static inline uint32_t INTERPOLATE_PIXEL(uint32_t x, uint32_t a, uint32_t y, uint32_t b) +static inline uint32_t INTERPOLATE_PIXEL_255(uint32_t x, uint32_t a, uint32_t y, uint32_t b) { uint32_t t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; t &= 0xff00ff; + x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; x = (x + ((x >> 8) & 0xff00ff) + 0x800080); x &= 0xff00ff00; @@ -67,11 +68,24 @@ static inline uint32_t INTERPOLATE_PIXEL(uint32_t x, uint32_t a, uint32_t y, uin return x; } +static inline uint32_t INTERPOLATE_PIXEL_256(uint32_t x, uint32_t a, uint32_t y, uint32_t b) +{ + uint32_t t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; + t >>= 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; + x &= 0xff00ff00; + x |= t; + return x; +} + static inline uint32_t BYTE_MUL(uint32_t x, uint32_t a) { uint32_t t = (x & 0xff00ff) * a; t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; t &= 0xff00ff; + x = ((x >> 8) & 0xff00ff) * a; x = (x + ((x >> 8) & 0xff00ff) + 0x800080); x &= 0xff00ff00; @@ -346,7 +360,7 @@ static void composition_solid_source_in(uint32_t* dest, int length, uint32_t col uint32_t cia = 255 - const_alpha; for(int i = 0; i < length; i++) { uint32_t d = dest[i]; - dest[i] = INTERPOLATE_PIXEL(color, plutovg_alpha(d), d, cia); + dest[i] = INTERPOLATE_PIXEL_255(color, plutovg_alpha(d), d, cia); } } } @@ -372,7 +386,7 @@ static void composition_solid_source_out(uint32_t* dest, int length, uint32_t co uint32_t cia = 255 - const_alpha; for(int i = 0; i < length; i++) { uint32_t d = dest[i]; - dest[i] = INTERPOLATE_PIXEL(color, plutovg_alpha(~d), d, cia); + dest[i] = INTERPOLATE_PIXEL_255(color, plutovg_alpha(~d), d, cia); } } } @@ -394,7 +408,7 @@ static void composition_solid_source_atop(uint32_t* dest, int length, uint32_t c uint32_t sia = plutovg_alpha(~color); for(int i = 0; i < length; i++) { uint32_t d = dest[i]; - dest[i] = INTERPOLATE_PIXEL(color, plutovg_alpha(d), d, sia); + dest[i] = INTERPOLATE_PIXEL_255(color, plutovg_alpha(d), d, sia); } } @@ -408,7 +422,7 @@ static void composition_solid_destination_atop(uint32_t* dest, int length, uint3 for(int i = 0; i < length; i++) { uint32_t d = dest[i]; - dest[i] = INTERPOLATE_PIXEL(d, a, color, plutovg_alpha(~d)); + dest[i] = INTERPOLATE_PIXEL_255(d, a, color, plutovg_alpha(~d)); } } @@ -419,7 +433,7 @@ static void composition_solid_xor(uint32_t* dest, int length, uint32_t color, ui uint32_t sia = plutovg_alpha(~color); for(int i = 0; i < length; i++) { uint32_t d = dest[i]; - dest[i] = INTERPOLATE_PIXEL(color, plutovg_alpha(~d), d, sia); + dest[i] = INTERPOLATE_PIXEL_255(color, plutovg_alpha(~d), d, sia); } } @@ -459,7 +473,7 @@ static void composition_source(uint32_t* dest, int length, const uint32_t* src, } else { uint32_t ialpha = 255 - const_alpha; for(int i = 0; i < length; i++) { - dest[i] = INTERPOLATE_PIXEL(src[i], const_alpha, dest[i], ialpha); + dest[i] = INTERPOLATE_PIXEL_255(src[i], const_alpha, dest[i], ialpha); } } } @@ -475,7 +489,7 @@ static void composition_source_over(uint32_t* dest, int length, const uint32_t* uint32_t s = src[i]; if(s >= 0xff000000) { dest[i] = s; - } else if (s != 0) { + } else if(s != 0) { dest[i] = s + BYTE_MUL(dest[i], plutovg_alpha(~s)); } } @@ -514,7 +528,7 @@ static void composition_source_in(uint32_t* dest, int length, const uint32_t* sr for(int i = 0; i < length; i++) { uint32_t d = dest[i]; uint32_t s = BYTE_MUL(src[i], const_alpha); - dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(d), d, cia); + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(d), d, cia); } } } @@ -545,7 +559,7 @@ static void composition_source_out(uint32_t* dest, int length, const uint32_t* s for(int i = 0; i < length; i++) { uint32_t s = BYTE_MUL(src[i], const_alpha); uint32_t d = dest[i]; - dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(~d), d, cia); + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(~d), d, cia); } } } @@ -571,13 +585,13 @@ static void composition_source_atop(uint32_t* dest, int length, const uint32_t* for(int i = 0; i < length; i++) { uint32_t s = src[i]; uint32_t d = dest[i]; - dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(d), d, plutovg_alpha(~s)); + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(d), d, plutovg_alpha(~s)); } } else { for(int i = 0; i < length; i++) { uint32_t s = BYTE_MUL(src[i], const_alpha); uint32_t d = dest[i]; - dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(d), d, plutovg_alpha(~s)); + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(d), d, plutovg_alpha(~s)); } } } @@ -588,7 +602,7 @@ static void composition_destination_atop(uint32_t* dest, int length, const uint3 for(int i = 0; i < length; i++) { uint32_t s = src[i]; uint32_t d = dest[i]; - dest[i] = INTERPOLATE_PIXEL(d, plutovg_alpha(s), s, plutovg_alpha(~d)); + dest[i] = INTERPOLATE_PIXEL_255(d, plutovg_alpha(s), s, plutovg_alpha(~d)); } } else { uint32_t cia = 255 - const_alpha; @@ -596,7 +610,7 @@ static void composition_destination_atop(uint32_t* dest, int length, const uint3 uint32_t s = BYTE_MUL(src[i], const_alpha); uint32_t d = dest[i]; uint32_t a = plutovg_alpha(s) + cia; - dest[i] = INTERPOLATE_PIXEL(d, a, s, plutovg_alpha(~d)); + dest[i] = INTERPOLATE_PIXEL_255(d, a, s, plutovg_alpha(~d)); } } } @@ -607,13 +621,13 @@ static void composition_xor(uint32_t* dest, int length, const uint32_t* src, uin for(int i = 0; i < length; i++) { uint32_t d = dest[i]; uint32_t s = src[i]; - dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(~d), d, plutovg_alpha(~s)); + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(~d), d, plutovg_alpha(~s)); } } else { for(int i = 0; i < length; i++) { uint32_t d = dest[i]; uint32_t s = BYTE_MUL(src[i], const_alpha); - dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(~d), d, plutovg_alpha(~s)); + dest[i] = INTERPOLATE_PIXEL_255(s, plutovg_alpha(~d), d, plutovg_alpha(~s)); } } } @@ -852,6 +866,16 @@ static void blend_untransformed_tiled_argb(plutovg_surface_t* surface, plutovg_o } } +static inline uint32_t interpolate_4_pixels(uint32_t tl, uint32_t tr, uint32_t bl, uint32_t br, uint32_t distx, uint32_t disty) +{ + uint32_t idistx = 256 - distx; + uint32_t idisty = 256 - disty; + uint32_t xtop = INTERPOLATE_PIXEL_256(tl, idistx, tr, distx); + uint32_t xbot = INTERPOLATE_PIXEL_256(bl, idistx, br, distx); + return INTERPOLATE_PIXEL_256(xtop, idisty, xbot, disty); +} + +#define HALF_POINT (1 << 15) static void blend_transformed_tiled_argb(plutovg_surface_t* surface, plutovg_operator_t op, const texture_data_t* texture, const plutovg_span_buffer_t* span_buffer) { composition_function_t func = composition_table[op]; @@ -859,7 +883,6 @@ static void blend_transformed_tiled_argb(plutovg_surface_t* surface, plutovg_ope int image_width = texture->width; int image_height = texture->height; - const int scanline_offset = texture->stride / 4; int fdx = (int)(texture->matrix.a * FIXED_SCALE); int fdy = (int)(texture->matrix.b * FIXED_SCALE); @@ -868,13 +891,15 @@ static void blend_transformed_tiled_argb(plutovg_surface_t* surface, plutovg_ope const plutovg_span_t* spans = span_buffer->spans.data; while(count--) { uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; - const uint32_t* image_bits = (const uint32_t*)texture->data; const float cx = spans->x + 0.5f; const float cy = spans->y + 0.5f; - int x = (int)((texture->matrix.c * cy + texture->matrix.a * cx + texture->matrix.e) * FIXED_SCALE); - int y = (int)((texture->matrix.d * cy + texture->matrix.b * cx + texture->matrix.f) * FIXED_SCALE); + int fx = (int)((texture->matrix.c * cy + texture->matrix.a * cx + texture->matrix.e) * FIXED_SCALE); + int fy = (int)((texture->matrix.d * cy + texture->matrix.b * cx + texture->matrix.f) * FIXED_SCALE); + + fx -= HALF_POINT; + fy -= HALF_POINT; const int coverage = (spans->coverage * texture->const_alpha) >> 8; int length = spans->len; @@ -882,21 +907,30 @@ static void blend_transformed_tiled_argb(plutovg_surface_t* surface, plutovg_ope int l = plutovg_min(length, BUFFER_SIZE); const uint32_t* end = buffer + l; uint32_t* b = buffer; - while(b < end) { - int px = x >> 16; - int py = y >> 16; - px %= image_width; - py %= image_height; - if(px < 0) px += image_width; - if(py < 0) py += image_height; - int y_offset = py * scanline_offset; + while (b < end) { + int x1 = (fx >> 16) % image_width; + int y1 = (fy >> 16) % image_height; - assert(px >= 0 && px < image_width); - assert(py >= 0 && py < image_height); + if(x1 < 0) x1 += image_width; + if(y1 < 0) y1 += image_height; - *b = image_bits[y_offset + px]; - x += fdx; - y += fdy; + int x2 = (x1 + 1) % image_width; + int y2 = (y1 + 1) % image_height; + + const uint32_t* s1 = (const uint32_t*)(texture->data + y1 * texture->stride); + const uint32_t* s2 = (const uint32_t*)(texture->data + y2 * texture->stride); + + uint32_t tl = s1[x1]; + uint32_t tr = s1[x2]; + uint32_t bl = s2[x1]; + uint32_t br = s2[x2]; + + int distx = (fx & 0x0000ffff) >> 8; + int disty = (fy & 0x0000ffff) >> 8; + *b = interpolate_4_pixels(tl, tr, bl, br, distx, disty); + + fx += fdx; + fy += fdy; ++b; } @@ -965,7 +999,7 @@ static void plutovg_blend_gradient(plutovg_canvas_t* canvas, const plutovg_gradi t = (fpos - curr->offset) * delta; dist = (uint32_t)(255 * t); idist = 255 - dist; - data.colortable[pos] = INTERPOLATE_PIXEL(curr_color, idist, next_color, dist); + data.colortable[pos] = INTERPOLATE_PIXEL_255(curr_color, idist, next_color, dist); ++pos; fpos += incr; } diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-canvas.c b/vendor/lunasvg/plutovg/source/plutovg-canvas.c similarity index 86% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-canvas.c rename to vendor/lunasvg/plutovg/source/plutovg-canvas.c index 187b2ef81e..908ac4e922 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-canvas.c +++ b/vendor/lunasvg/plutovg/source/plutovg-canvas.c @@ -82,11 +82,12 @@ static void plutovg_state_destroy(plutovg_state_t* state) plutovg_canvas_t* plutovg_canvas_create(plutovg_surface_t* surface) { plutovg_canvas_t* canvas = malloc(sizeof(plutovg_canvas_t)); - canvas->ref_count = 1; + plutovg_init_reference(canvas); canvas->surface = plutovg_surface_reference(surface); canvas->path = plutovg_path_create(); canvas->state = plutovg_state_create(); canvas->freed_state = NULL; + canvas->face_cache = NULL; canvas->clip_rect = PLUTOVG_MAKE_RECT(0, 0, surface->width, surface->height); plutovg_span_buffer_init(&canvas->clip_spans); plutovg_span_buffer_init(&canvas->fill_spans); @@ -95,17 +96,13 @@ plutovg_canvas_t* plutovg_canvas_create(plutovg_surface_t* surface) plutovg_canvas_t* plutovg_canvas_reference(plutovg_canvas_t* canvas) { - if(canvas == NULL) - return NULL; - ++canvas->ref_count; + plutovg_increment_reference(canvas); return canvas; } void plutovg_canvas_destroy(plutovg_canvas_t* canvas) { - if(canvas == NULL) - return; - if(--canvas->ref_count == 0) { + if(plutovg_destroy_reference(canvas)) { while(canvas->state) { plutovg_state_t* state = canvas->state; canvas->state = state->next; @@ -118,6 +115,7 @@ void plutovg_canvas_destroy(plutovg_canvas_t* canvas) plutovg_state_destroy(state); } + plutovg_font_face_cache_destroy(canvas->face_cache); plutovg_span_buffer_destroy(&canvas->fill_spans); plutovg_span_buffer_destroy(&canvas->clip_spans); plutovg_surface_destroy(canvas->surface); @@ -128,9 +126,7 @@ void plutovg_canvas_destroy(plutovg_canvas_t* canvas) int plutovg_canvas_get_reference_count(const plutovg_canvas_t* canvas) { - if(canvas == NULL) - return 0; - return canvas->ref_count; + return plutovg_get_reference_count(canvas); } plutovg_surface_t* plutovg_canvas_get_surface(const plutovg_canvas_t* canvas) @@ -212,6 +208,43 @@ plutovg_paint_t* plutovg_canvas_get_paint(const plutovg_canvas_t* canvas, plutov return canvas->state->paint; } +void plutovg_canvas_set_font_face_cache(plutovg_canvas_t* canvas, plutovg_font_face_cache_t* cache) +{ + cache = plutovg_font_face_cache_reference(cache); + plutovg_font_face_cache_destroy(canvas->face_cache); + canvas->face_cache = cache; +} + +plutovg_font_face_cache_t* plutovg_canvas_get_font_face_cache(const plutovg_canvas_t* canvas) +{ + return canvas->face_cache; +} + +void plutovg_canvas_add_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, plutovg_font_face_t* face) +{ + if(canvas->face_cache == NULL) + canvas->face_cache = plutovg_font_face_cache_create(); + plutovg_font_face_cache_add(canvas->face_cache, family, bold, italic, face); +} + +bool plutovg_canvas_add_font_file(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, const char* filename, int ttcindex) +{ + if(canvas->face_cache == NULL) + canvas->face_cache = plutovg_font_face_cache_create(); + return plutovg_font_face_cache_add_file(canvas->face_cache, family, bold, italic, filename, ttcindex); +} + +bool plutovg_canvas_select_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic) +{ + if(canvas->face_cache == NULL) + return false; + plutovg_font_face_t* face = plutovg_font_face_cache_get(canvas->face_cache, family, bold, italic); + if(face == NULL) + return false; + plutovg_canvas_set_font_face(canvas, face); + return true; +} + void plutovg_canvas_set_font(plutovg_canvas_t* canvas, plutovg_font_face_t* face, float size) { plutovg_canvas_set_font_face(canvas, face); @@ -469,33 +502,45 @@ plutovg_path_t* plutovg_canvas_get_path(const plutovg_canvas_t* canvas) return canvas->path; } -void plutovg_canvas_fill_extents(const plutovg_canvas_t* canvas, plutovg_rect_t* extents) +bool plutovg_canvas_fill_contains(plutovg_canvas_t* canvas, float x, float y) { - plutovg_path_extents(canvas->path, extents, true); - plutovg_canvas_map_rect(canvas, extents, extents); + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, NULL, canvas->state->winding); + return plutovg_span_buffer_contains(&canvas->fill_spans, x, y); } -void plutovg_canvas_stroke_extents(const plutovg_canvas_t* canvas, plutovg_rect_t* extents) +bool plutovg_canvas_stroke_contains(plutovg_canvas_t* canvas, float x, float y) { - plutovg_stroke_data_t* stroke = &canvas->state->stroke; - float cap_limit = stroke->style.width / 2.f; - if(stroke->style.cap == PLUTOVG_LINE_CAP_SQUARE) - cap_limit *= PLUTOVG_SQRT2; - float join_limit = stroke->style.width / 2.f; - if(stroke->style.join == PLUTOVG_LINE_JOIN_MITER) { - join_limit *= stroke->style.miter_limit; + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, NULL, canvas->state->winding); + return plutovg_span_buffer_contains(&canvas->fill_spans, x, y); +} + +bool plutovg_canvas_clip_contains(plutovg_canvas_t* canvas, float x, float y) +{ + if(canvas->state->clipping) { + return plutovg_span_buffer_contains(&canvas->state->clip_spans, x, y); } - float delta = plutovg_max(cap_limit, join_limit); - plutovg_path_extents(canvas->path, extents, true); - extents->x -= delta; - extents->y -= delta; - extents->w += delta * 2.f; - extents->h += delta * 2.f; - plutovg_canvas_map_rect(canvas, extents, extents); + float l = canvas->clip_rect.x; + float t = canvas->clip_rect.y; + float r = canvas->clip_rect.x + canvas->clip_rect.w; + float b = canvas->clip_rect.y + canvas->clip_rect.h; + + return x >= l && x <= r && y >= t && y <= b; +} + +void plutovg_canvas_fill_extents(plutovg_canvas_t *canvas, plutovg_rect_t* extents) +{ + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, NULL, canvas->state->winding); + plutovg_span_buffer_extents(&canvas->fill_spans, extents); +} + +void plutovg_canvas_stroke_extents(plutovg_canvas_t *canvas, plutovg_rect_t* extents) +{ + plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, &canvas->state->stroke, PLUTOVG_FILL_RULE_NON_ZERO); + plutovg_span_buffer_extents(&canvas->fill_spans, extents); } -void plutovg_canvas_clip_extents(const plutovg_canvas_t* canvas, plutovg_rect_t* extents) +void plutovg_canvas_clip_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents) { if(canvas->state->clipping) { plutovg_span_buffer_extents(&canvas->state->clip_spans, extents); diff --git a/vendor/lunasvg/plutovg/source/plutovg-font.c b/vendor/lunasvg/plutovg/source/plutovg-font.c new file mode 100644 index 0000000000..0ad89b1ac4 --- /dev/null +++ b/vendor/lunasvg/plutovg/source/plutovg-font.c @@ -0,0 +1,1065 @@ +#include "plutovg-private.h" +#include "plutovg-utils.h" + +#include +#include + +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#include "plutovg-stb-truetype.h" + +static int plutovg_text_iterator_length(const void* data, plutovg_text_encoding_t encoding) +{ + int length = 0; + switch(encoding) { + case PLUTOVG_TEXT_ENCODING_LATIN1: + case PLUTOVG_TEXT_ENCODING_UTF8: { + const uint8_t* text = data; + while(*text++) + length++; + break; + } case PLUTOVG_TEXT_ENCODING_UTF16: { + const uint16_t* text = data; + while(*text++) + length++; + break; + } case PLUTOVG_TEXT_ENCODING_UTF32: { + const uint32_t* text = data; + while(*text++) + length++; + break; + } default: + assert(false); + } + + return length; +} + +void plutovg_text_iterator_init(plutovg_text_iterator_t* it, const void* text, int length, plutovg_text_encoding_t encoding) +{ + if(length == -1) + length = plutovg_text_iterator_length(text, encoding); + it->text = text; + it->length = length; + it->encoding = encoding; + it->index = 0; +} + +bool plutovg_text_iterator_has_next(const plutovg_text_iterator_t* it) +{ + return it->index < it->length; +} + +plutovg_codepoint_t plutovg_text_iterator_next(plutovg_text_iterator_t* it) +{ + plutovg_codepoint_t codepoint = 0; + switch(it->encoding) { + case PLUTOVG_TEXT_ENCODING_LATIN1: { + const uint8_t* text = it->text; + codepoint = text[it->index++]; + break; + } case PLUTOVG_TEXT_ENCODING_UTF8: { + static const uint8_t trailing[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 + }; + + static const uint32_t offsets[6] = { + 0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080 + }; + + const uint8_t* text = it->text; + uint8_t trailing_offset = trailing[text[it->index]]; + uint32_t offset_value = offsets[trailing_offset]; + while(trailing_offset > 0 && it->index < it->length - 1) { + codepoint += text[it->index++]; + codepoint <<= 6; + trailing_offset--; + } + + codepoint += text[it->index++]; + codepoint -= offset_value; + break; + } case PLUTOVG_TEXT_ENCODING_UTF16: { + const uint16_t* text = it->text; + codepoint = text[it->index++]; + if(((codepoint) & 0xfffffc00) == 0xd800) { + if(it->index < it->length && (((codepoint) & 0xfffffc00) == 0xdc00)) { + uint16_t trail = text[it->index++]; + codepoint = (codepoint << 10) + trail - ((0xD800u << 10) - 0x10000u + 0xDC00u); + } + } + + break; + } case PLUTOVG_TEXT_ENCODING_UTF32: { + const uint32_t* text = it->text; + codepoint = text[it->index++]; + break; + } default: + assert(false); + } + + return codepoint; +} + +#if defined(_WIN32) + +#include + +typedef CRITICAL_SECTION plutovg_mutex_t; + +#define plutovg_mutex_init(mutex) InitializeCriticalSection(mutex) +#define plutovg_mutex_lock(mutex) EnterCriticalSection(mutex) +#define plutovg_mutex_unlock(mutex) LeaveCriticalSection(mutex) +#define plutovg_mutex_destroy(mutex) DeleteCriticalSection(mutex) + +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && defined(HAVE_THREADS_H) && !defined(__STDC_NO_THREADS__) + +#include + +typedef mtx_t plutovg_mutex_t; + +#define plutovg_mutex_init(mutex) mtx_init(mutex, mtx_plain | mtx_recursive) +#define plutovg_mutex_lock(mutex) mtx_lock(mutex) +#define plutovg_mutex_unlock(mutex) mtx_unlock(mutex) +#define plutovg_mutex_destroy(mutex) mtx_destroy(mutex) + +#else + +typedef int plutovg_mutex_t; + +#define plutovg_mutex_init(mutex) ((void)(mutex)) +#define plutovg_mutex_lock(mutex) ((void)(mutex)) +#define plutovg_mutex_unlock(mutex) ((void)(mutex)) +#define plutovg_mutex_destroy(mutex) ((void)(mutex)) + +#endif + +typedef struct plutovg_glyph { + plutovg_codepoint_t codepoint; + stbtt_vertex* vertices; + int nvertices; + int index; + int advance_width; + int left_side_bearing; + int x1; + int y1; + int x2; + int y2; + struct plutovg_glyph* next; +} plutovg_glyph_t; + +typedef struct { + plutovg_glyph_t** glyphs; + size_t size; + size_t capacity; +} plutovg_glyph_cache_t; + +struct plutovg_font_face { + plutovg_ref_count_t ref_count; + int ascent; + int descent; + int line_gap; + int x1; + int y1; + int x2; + int y2; + stbtt_fontinfo info; + plutovg_mutex_t mutex; + plutovg_glyph_cache_t cache; + plutovg_destroy_func_t destroy_func; + void* closure; +}; + +static void plutovg_glyph_cache_init(plutovg_glyph_cache_t* cache) +{ + cache->glyphs = NULL; + cache->size = 0; + cache->capacity = 0; +} + +static void plutovg_glyph_cache_finish(plutovg_glyph_cache_t* cache, plutovg_font_face_t* face) +{ + plutovg_mutex_lock(&face->mutex); + + if(cache->glyphs) { + for(size_t i = 0; i < cache->capacity; ++i) { + plutovg_glyph_t* glyph = cache->glyphs[i]; + while(glyph) { + plutovg_glyph_t* next = glyph->next; + stbtt_FreeShape(&face->info, glyph->vertices); + free(glyph); + glyph = next; + } + } + + free(cache->glyphs); + cache->glyphs = NULL; + cache->capacity = 0; + cache->size = 0; + } + + plutovg_mutex_unlock(&face->mutex); +} + +#define GLYPH_CACHE_INIT_CAPACITY 128 + +static plutovg_glyph_t* plutovg_glyph_cache_get(plutovg_glyph_cache_t* cache, plutovg_font_face_t* face, plutovg_codepoint_t codepoint) +{ + plutovg_mutex_lock(&face->mutex); + + if(cache->glyphs == NULL) { + assert(cache->size == 0); + cache->glyphs = calloc(GLYPH_CACHE_INIT_CAPACITY, sizeof(plutovg_glyph_t*)); + cache->capacity = GLYPH_CACHE_INIT_CAPACITY; + } + + size_t index = codepoint & (cache->capacity - 1); + plutovg_glyph_t* glyph = cache->glyphs[index]; + while(glyph && glyph->codepoint != codepoint) { + glyph = glyph->next; + } + + if(glyph == NULL) { + glyph = malloc(sizeof(plutovg_glyph_t)); + glyph->codepoint = codepoint; + glyph->index = stbtt_FindGlyphIndex(&face->info, codepoint); + glyph->nvertices = stbtt_GetGlyphShape(&face->info, glyph->index, &glyph->vertices); + stbtt_GetGlyphHMetrics(&face->info, glyph->index, &glyph->advance_width, &glyph->left_side_bearing); + if(!stbtt_GetGlyphBox(&face->info, glyph->index, &glyph->x1, &glyph->y1, &glyph->x2, &glyph->y2)) { + glyph->x1 = glyph->y1 = glyph->x2 = glyph->y2 = 0; + } + + glyph->next = cache->glyphs[index]; + cache->glyphs[index] = glyph; + cache->size += 1; + + if(cache->size > (cache->capacity * 3 / 4)) { + size_t newcapacity = cache->capacity << 1; + plutovg_glyph_t** newglyphs = calloc(newcapacity, sizeof(plutovg_glyph_t*)); + + for(size_t i = 0; i < cache->capacity; ++i) { + plutovg_glyph_t* entry = cache->glyphs[i]; + while(entry) { + plutovg_glyph_t* next = entry->next; + size_t newindex = entry->codepoint & (newcapacity - 1); + entry->next = newglyphs[newindex]; + newglyphs[newindex] = entry; + entry = next; + } + } + + free(cache->glyphs); + cache->glyphs = newglyphs; + cache->capacity = newcapacity; + } + } + + plutovg_mutex_unlock(&face->mutex); + return glyph; +} + +plutovg_font_face_t* plutovg_font_face_load_from_file(const char* filename, int ttcindex) +{ + FILE* fp = fopen(filename, "rb"); + if(fp == NULL) { + return NULL; + } + + fseek(fp, 0, SEEK_END); + long length = ftell(fp); + if(length == -1L) { + fclose(fp); + return NULL; + } + + void* data = malloc(length); + if(data == NULL) { + fclose(fp); + return NULL; + } + + fseek(fp, 0, SEEK_SET); + size_t nread = fread(data, 1, length, fp); + fclose(fp); + + if(nread != length) { + free(data); + return NULL; + } + + return plutovg_font_face_load_from_data(data, length, ttcindex, free, data); +} + +plutovg_font_face_t* plutovg_font_face_load_from_data(const void* data, unsigned int length, int ttcindex, plutovg_destroy_func_t destroy_func, void* closure) +{ + stbtt_fontinfo info; + int offset = stbtt_GetFontOffsetForIndex(data, ttcindex); + if(offset == -1 || !stbtt_InitFont(&info, data, offset)) { + if(destroy_func) + destroy_func(closure); + return NULL; + } + + plutovg_font_face_t* face = malloc(sizeof(plutovg_font_face_t)); + plutovg_init_reference(face); + face->info = info; + stbtt_GetFontVMetrics(&face->info, &face->ascent, &face->descent, &face->line_gap); + stbtt_GetFontBoundingBox(&face->info, &face->x1, &face->y1, &face->x2, &face->y2); + plutovg_mutex_init(&face->mutex); + plutovg_glyph_cache_init(&face->cache); + face->destroy_func = destroy_func; + face->closure = closure; + return face; +} + +plutovg_font_face_t* plutovg_font_face_reference(plutovg_font_face_t* face) +{ + plutovg_increment_reference(face); + return face; +} + +void plutovg_font_face_destroy(plutovg_font_face_t* face) +{ + if(plutovg_destroy_reference(face)) { + plutovg_glyph_cache_finish(&face->cache, face); + plutovg_mutex_destroy(&face->mutex); + if(face->destroy_func) + face->destroy_func(face->closure); + free(face); + } +} + +int plutovg_font_face_get_reference_count(const plutovg_font_face_t* face) +{ + return plutovg_get_reference_count(face); +} + +static float plutovg_font_face_get_scale(const plutovg_font_face_t* face, float size) +{ + return stbtt_ScaleForMappingEmToPixels(&face->info, size); +} + +void plutovg_font_face_get_metrics(const plutovg_font_face_t* face, float size, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents) +{ + float scale = plutovg_font_face_get_scale(face, size); + if(ascent) *ascent = face->ascent * scale; + if(descent) *descent = face->descent * scale; + if(line_gap) *line_gap = face->line_gap * scale; + if(extents) { + extents->x = face->x1 * scale; + extents->y = face->y2 * -scale; + extents->w = (face->x2 - face->x1) * scale; + extents->h = (face->y1 - face->y2) * -scale; + } +} + +static plutovg_glyph_t* plutovg_font_face_get_glyph(plutovg_font_face_t* face, plutovg_codepoint_t codepoint) +{ + return plutovg_glyph_cache_get(&face->cache, face, codepoint); +} + +void plutovg_font_face_get_glyph_metrics(plutovg_font_face_t* face, float size, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents) +{ + float scale = plutovg_font_face_get_scale(face, size); + plutovg_glyph_t* glyph = plutovg_font_face_get_glyph(face, codepoint); + if(advance_width) *advance_width = glyph->advance_width * scale; + if(left_side_bearing) *left_side_bearing = glyph->left_side_bearing * scale; + if(extents) { + extents->x = glyph->x1 * scale; + extents->y = glyph->y2 * -scale; + extents->w = (glyph->x2 - glyph->x1) * scale; + extents->h = (glyph->y1 - glyph->y2) * -scale; + } +} + +static void glyph_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) +{ + plutovg_path_t* path = (plutovg_path_t*)(closure); + switch(command) { + case PLUTOVG_PATH_COMMAND_MOVE_TO: + plutovg_path_move_to(path, points[0].x, points[0].y); + break; + case PLUTOVG_PATH_COMMAND_LINE_TO: + plutovg_path_line_to(path, points[0].x, points[0].y); + break; + case PLUTOVG_PATH_COMMAND_CUBIC_TO: + plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); + break; + case PLUTOVG_PATH_COMMAND_CLOSE: + assert(false); + } +} + +float plutovg_font_face_get_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_t* path) +{ + return plutovg_font_face_traverse_glyph_path(face, size, x, y, codepoint, glyph_traverse_func, path); +} + +float plutovg_font_face_traverse_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_traverse_func_t traverse_func, void* closure) +{ + float scale = plutovg_font_face_get_scale(face, size); + plutovg_matrix_t matrix; + plutovg_matrix_init_translate(&matrix, x, y); + plutovg_matrix_scale(&matrix, scale, -scale); + + plutovg_point_t points[3]; + plutovg_point_t current_point = {0, 0}; + plutovg_glyph_t* glyph = plutovg_font_face_get_glyph(face, codepoint); + for(int i = 0; i < glyph->nvertices; i++) { + switch(glyph->vertices[i].type) { + case STBTT_vmove: + points[0].x = glyph->vertices[i].x; + points[0].y = glyph->vertices[i].y; + current_point = points[0]; + plutovg_matrix_map_points(&matrix, points, points, 1); + traverse_func(closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, 1); + break; + case STBTT_vline: + points[0].x = glyph->vertices[i].x; + points[0].y = glyph->vertices[i].y; + current_point = points[0]; + plutovg_matrix_map_points(&matrix, points, points, 1); + traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, points, 1); + break; + case STBTT_vcurve: + points[0].x = 2.f / 3.f * glyph->vertices[i].cx + 1.f / 3.f * current_point.x; + points[0].y = 2.f / 3.f * glyph->vertices[i].cy + 1.f / 3.f * current_point.y; + points[1].x = 2.f / 3.f * glyph->vertices[i].cx + 1.f / 3.f * glyph->vertices[i].x; + points[1].y = 2.f / 3.f * glyph->vertices[i].cy + 1.f / 3.f * glyph->vertices[i].y; + points[2].x = glyph->vertices[i].x; + points[2].y = glyph->vertices[i].y; + current_point = points[2]; + plutovg_matrix_map_points(&matrix, points, points, 3); + traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); + break; + case STBTT_vcubic: + points[0].x = glyph->vertices[i].cx; + points[0].y = glyph->vertices[i].cy; + points[1].x = glyph->vertices[i].cx1; + points[1].y = glyph->vertices[i].cy1; + points[2].x = glyph->vertices[i].x; + points[2].y = glyph->vertices[i].y; + current_point = points[2]; + plutovg_matrix_map_points(&matrix, points, points, 3); + traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); + break; + default: + assert(false); + } + } + + return glyph->advance_width * scale; +} + +float plutovg_font_face_text_extents(plutovg_font_face_t* face, float size, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents) +{ + plutovg_text_iterator_t it; + plutovg_text_iterator_init(&it, text, length, encoding); + plutovg_rect_t* text_extents = NULL; + float total_advance_width = 0.f; + while(plutovg_text_iterator_has_next(&it)) { + plutovg_codepoint_t codepoint = plutovg_text_iterator_next(&it); + + float advance_width; + if(extents == NULL) { + plutovg_font_face_get_glyph_metrics(face, size, codepoint, &advance_width, NULL, NULL); + total_advance_width += advance_width; + continue; + } + + plutovg_rect_t glyph_extents; + plutovg_font_face_get_glyph_metrics(face, size, codepoint, &advance_width, NULL, &glyph_extents); + + glyph_extents.x += total_advance_width; + total_advance_width += advance_width; + if(text_extents == NULL) { + text_extents = extents; + *text_extents = glyph_extents; + continue; + } + + float x1 = plutovg_min(text_extents->x, glyph_extents.x); + float y1 = plutovg_min(text_extents->y, glyph_extents.y); + float x2 = plutovg_max(text_extents->x + text_extents->w, glyph_extents.x + glyph_extents.w); + float y2 = plutovg_max(text_extents->y + text_extents->h, glyph_extents.y + glyph_extents.h); + + text_extents->x = x1; + text_extents->y = y1; + text_extents->w = x2 - x1; + text_extents->h = y2 - y1; + } + + if(extents && !text_extents) { + extents->x = 0; + extents->y = 0; + extents->w = 0; + extents->h = 0; + } + + return total_advance_width; +} + +typedef struct plutovg_font_face_entry { + plutovg_font_face_t* face; + char* family; + char* filename; + int ttcindex; + bool bold; + bool italic; + struct plutovg_font_face_entry* next; +} plutovg_font_face_entry_t; + +struct plutovg_font_face_cache { + plutovg_ref_count_t ref_count; + plutovg_mutex_t mutex; + plutovg_font_face_entry_t** entries; + int size; + int capacity; + bool is_sorted; +}; + +plutovg_font_face_cache_t* plutovg_font_face_cache_create(void) +{ + plutovg_font_face_cache_t* cache = malloc(sizeof(plutovg_font_face_cache_t)); + plutovg_init_reference(cache); + plutovg_mutex_init(&cache->mutex); + cache->entries = NULL; + cache->size = 0; + cache->capacity = 0; + cache->is_sorted = false; + return cache; +} + +plutovg_font_face_cache_t* plutovg_font_face_cache_reference(plutovg_font_face_cache_t* cache) +{ + plutovg_increment_reference(cache); + return cache; +} + +void plutovg_font_face_cache_destroy(plutovg_font_face_cache_t* cache) +{ + if(plutovg_destroy_reference(cache)) { + plutovg_font_face_cache_reset(cache); + plutovg_mutex_destroy(&cache->mutex); + free(cache); + } +} + +int plutovg_font_face_cache_reference_count(const plutovg_font_face_cache_t* cache) +{ + return plutovg_get_reference_count(cache); +} + +void plutovg_font_face_cache_reset(plutovg_font_face_cache_t* cache) +{ + plutovg_mutex_lock(&cache->mutex); + + for(int i = 0; i < cache->size; ++i) { + plutovg_font_face_entry_t* entry = cache->entries[i]; + do { + plutovg_font_face_entry_t* next = entry->next; + plutovg_font_face_destroy(entry->face); + free(entry); + entry = next; + } while(entry); + } + + free(cache->entries); + cache->entries = NULL; + cache->size = 0; + cache->capacity = 0; + cache->is_sorted = false; + + plutovg_mutex_unlock(&cache->mutex); +} + +static void plutovg_font_face_cache_add_entry(plutovg_font_face_cache_t* cache, plutovg_font_face_entry_t* entry) +{ + plutovg_mutex_lock(&cache->mutex); + + for(int i = 0; i < cache->size; ++i) { + if(strcmp(entry->family, cache->entries[i]->family) == 0) { + entry->next = cache->entries[i]; + cache->entries[i] = entry; + goto unlock; + } + } + + if(cache->size >= cache->capacity) { + cache->capacity = cache->capacity == 0 ? 8 : cache->capacity << 2; + cache->entries = realloc(cache->entries, cache->capacity * sizeof(plutovg_font_face_entry_t*)); + } + + entry->next = NULL; + cache->entries[cache->size++] = entry; + cache->is_sorted = false; + +unlock: + plutovg_mutex_unlock(&cache->mutex); +} + +void plutovg_font_face_cache_add(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, plutovg_font_face_t* face) +{ + if(family == NULL) family = ""; + size_t family_length = strlen(family) + 1; + + plutovg_font_face_entry_t* entry = malloc(family_length + sizeof(plutovg_font_face_entry_t)); + entry->face = plutovg_font_face_reference(face); + entry->family = (char*)(entry + 1); + memcpy(entry->family, family, family_length); + + entry->filename = NULL; + entry->ttcindex = 0; + entry->bold = bold; + entry->italic = italic; + + plutovg_font_face_cache_add_entry(cache, entry); +} + +bool plutovg_font_face_cache_add_file(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, const char* filename, int ttcindex) +{ + plutovg_font_face_t* face = plutovg_font_face_load_from_file(filename, ttcindex); + if(face == NULL) + return false; + plutovg_font_face_cache_add(cache, family, bold, italic, face); + plutovg_font_face_destroy(face); + return true; +} + +static plutovg_font_face_entry_t* plutovg_font_face_entry_select(plutovg_font_face_entry_t* a, plutovg_font_face_entry_t* b, bool bold, bool italic) +{ + int a_score = (bold == a->bold) + (italic == a->italic); + int b_score = (bold == b->bold) + (italic == b->italic); + return a_score > b_score ? a : b; +} + +static int plutovg_font_face_entry_compare(const void* a, const void* b) +{ + const plutovg_font_face_entry_t* a_entry = *(const plutovg_font_face_entry_t**)a; + const plutovg_font_face_entry_t* b_entry = *(const plutovg_font_face_entry_t**)b; + return strcmp(a_entry->family, b_entry->family); +} + +plutovg_font_face_t* plutovg_font_face_cache_get(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic) +{ + plutovg_mutex_lock(&cache->mutex); + + if(!cache->is_sorted && cache->size > 0) { + qsort(cache->entries, cache->size, sizeof(cache->entries[0]), plutovg_font_face_entry_compare); + cache->is_sorted = true; + } + + plutovg_font_face_entry_t entry_key; + entry_key.family = (char*)(family); + + plutovg_font_face_entry_t* entry_key_ptr = &entry_key; + plutovg_font_face_entry_t** entry_result = bsearch( + &entry_key_ptr, + cache->entries, + cache->size, + sizeof(cache->entries[0]), + plutovg_font_face_entry_compare + ); + + plutovg_font_face_t* face = NULL; + if(entry_result) { + plutovg_font_face_entry_t* selected = *entry_result; + plutovg_font_face_entry_t* entry = selected->next; + while(entry) { + selected = plutovg_font_face_entry_select(entry, selected, bold, italic); + entry = entry->next; + } + + if(selected->filename && selected->face == NULL) + selected->face = plutovg_font_face_load_from_file(selected->filename, selected->ttcindex); + face = selected->face; + } + + plutovg_mutex_unlock(&cache->mutex); + return face; +} + +#ifndef PLUTOVG_DISABLE_FONT_FACE_CACHE_LOAD + +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include + +#ifdef __linux__ +#include +#else +#include +#endif + +#include +#include +#endif + +#ifdef _WIN32 + +static void* plutovg_mmap(const char* filename, long* length) +{ + HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if(file == INVALID_HANDLE_VALUE) + return NULL; + DWORD size = GetFileSize(file, NULL); + if(size == INVALID_FILE_SIZE) { + CloseHandle(file); + return NULL; + } + + HANDLE mapping = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL); + if(mapping == NULL) { + CloseHandle(file); + return NULL; + } + + void* data = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); + CloseHandle(mapping); + CloseHandle(file); + + if(data == NULL) + return NULL; + *length = size; + return data; +} + +static void plutovg_unmap(void* data, long length) +{ + UnmapViewOfFile(data); +} + +#else + +static void* plutovg_mmap(const char* filename, long* length) +{ + int fd = open(filename, O_RDONLY); + if(fd < 0) + return NULL; + struct stat st; + if(fstat(fd, &st) < 0) { + close(fd); + return NULL; + } + + if(st.st_size == 0) { + close(fd); + return NULL; + } + + void* data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + + if(data == MAP_FAILED) + return NULL; + *length = st.st_size; + return data; +} + +static void plutovg_unmap(void* data, long length) +{ + munmap(data, length); +} + +#endif // _WIN32 + +int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const char* filename) +{ + long length; + stbtt_uint8* data = plutovg_mmap(filename, &length); + if(data == NULL) { + return 0; + } + + int num_faces = 0; + + int num_fonts = stbtt_GetNumberOfFonts(data); + for(int index = 0; index < num_fonts; ++index) { + int offset = stbtt_GetFontOffsetForIndex(data, index); + if(offset == -1 || !stbtt__isfont(data + offset)) { + continue; + } + + stbtt_uint32 nm = stbtt__find_table(data, offset, "name"); + stbtt_uint16 nm_count = ttUSHORT(data + nm + 2); + + const stbtt_uint8* unicode_family_name = NULL; + const stbtt_uint8* roman_family_name = NULL; + + size_t family_length = 0; + for(stbtt_int32 i = 0; i < nm_count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_uint16 nm_id = ttUSHORT(data + loc + 6); + if(nm_id != 1) { + continue; + } + + stbtt_uint16 platform = ttUSHORT(data + loc + 0); + stbtt_uint16 encoding = ttUSHORT(data + loc + 2); + + const stbtt_uint8* family_name = data + nm + ttUSHORT(data + nm + 4) + ttUSHORT(data + loc + 10); + if(platform == 1 && encoding == 0) { + family_length = ttUSHORT(data + loc + 8); + roman_family_name = family_name; + continue; + } + + if(platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + family_length = ttUSHORT(data + loc + 8); + unicode_family_name = family_name; + break; + } + } + + if(unicode_family_name == NULL && roman_family_name == NULL) + continue; + size_t filename_length = strlen(filename) + 1; + size_t max_family_length = (unicode_family_name ? 3 * (family_length / 2) : family_length * 3) + 1; + + plutovg_font_face_entry_t* entry = malloc(max_family_length + filename_length + sizeof(plutovg_font_face_entry_t)); + entry->family = (char*)(entry + 1); + entry->filename = entry->family + max_family_length; + memcpy(entry->filename, filename, filename_length); + + size_t family_index = 0; + if(unicode_family_name) { + const stbtt_uint8* family_name = unicode_family_name; + while(family_length) { + stbtt_uint16 ch = family_name[0] * 256 + family_name[1]; + if(ch < 0x80) { + entry->family[family_index++] = ch; + } else if(ch < 0x800) { + entry->family[family_index++] = (0xc0 + (ch >> 6)); + entry->family[family_index++] = (0x80 + (ch & 0x3f)); + } else if(ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint16 ch2 = family_name[2] * 256 + family_name[3]; + stbtt_uint32 c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + + entry->family[family_index++] = (0xf0 + (c >> 18)); + entry->family[family_index++] = (0x80 + ((c >> 12) & 0x3f)); + entry->family[family_index++] = (0x80 + ((c >> 6) & 0x3f)); + entry->family[family_index++] = (0x80 + ((c) & 0x3f)); + + family_name += 2; + family_length -= 2; + } else { + entry->family[family_index++] = (0xe0 + (ch >> 12)); + entry->family[family_index++] = (0x80 + ((ch >> 6) & 0x3f)); + entry->family[family_index++] = (0x80 + ((ch) & 0x3f)); + } + + family_name += 2; + family_length -= 2; + } + + entry->family[family_index] = '\0'; + } else { + static const stbtt_uint16 MAC_ROMAN_TABLE[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + 0x0010, 0x2318, 0x21E7, 0x2325, 0x2303, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, + 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, + 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, + 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, + 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, + 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, + 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, + 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, + 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, + 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, + 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, + 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, + 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, + 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, + 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, + 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7, + }; + + const stbtt_uint8* family_name = roman_family_name; + while(family_length) { + stbtt_uint16 ch = MAC_ROMAN_TABLE[family_name[0]]; + if(ch < 0x80) { + entry->family[family_index++] = ch; + } else if(ch < 0x800) { + entry->family[family_index++] = (0xc0 + (ch >> 6)); + entry->family[family_index++] = (0x80 + (ch & 0x3f)); + } else { + entry->family[family_index++] = (0xe0 + (ch >> 12)); + entry->family[family_index++] = (0x80 + ((ch >> 6) & 0x3f)); + entry->family[family_index++] = (0x80 + ((ch) & 0x3f)); + } + + family_name += 1; + family_length -= 1; + } + + entry->family[family_index] = '\0'; + } + + entry->face = NULL; + entry->bold = false; + entry->italic = false; + entry->ttcindex = index; + + stbtt_uint32 hd = stbtt__find_table(data, offset, "head"); + stbtt_uint16 style = ttUSHORT(data + hd + 44); + if(style & 0x1) + entry->bold = true; + if(style & 0x2) { + entry->italic = true; + } + + plutovg_font_face_cache_add_entry(cache, entry); + num_faces++; + } + + plutovg_unmap(data, length); + return num_faces; +} + +static bool plutovg_font_face_supports_file(const char* filename) +{ + const char* extension = strrchr(filename, '.'); + if(extension) { + char ext[5]; + size_t length = strlen(extension); + if(length == 4) { + for(size_t i = 0; i < length; ++i) + ext[i] = tolower(extension[i]); + ext[length] = '\0'; + return strcmp(ext, ".ttf") == 0 + || strcmp(ext, ".otf") == 0 + || strcmp(ext, ".ttc") == 0 + || strcmp(ext, ".otc") == 0; + } + } + + return false; +} + +#ifdef _WIN32 + +int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname) +{ + char search_path[MAX_PATH]; + snprintf(search_path, sizeof(search_path), "%s\\*", dirname); + + WIN32_FIND_DATAA find_data; + HANDLE handle = FindFirstFileA(search_path, &find_data); + if(handle == INVALID_HANDLE_VALUE) { + return 0; + } + + int num_faces = 0; + + do { + const char* name = find_data.cFileName; + if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) + continue; + char path[MAX_PATH * 2]; + snprintf(path, sizeof(path), "%s\\%s", dirname, name); + + if(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + num_faces += plutovg_font_face_cache_load_dir(cache, path); + } else if(plutovg_font_face_supports_file(path)) { + num_faces += plutovg_font_face_cache_load_file(cache, path); + } + } while(FindNextFileA(handle, &find_data)); + + FindClose(handle); + return num_faces; +} + +#else + +int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname) +{ + DIR* dir = opendir(dirname); + if(dir == NULL) { + return 0; + } + + int num_faces = 0; + + struct dirent* entry; + while((entry = readdir(dir)) != NULL) { + if(strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name); + + struct stat st; + if(stat(path, &st) == -1) + continue; + if(S_ISDIR(st.st_mode)) { + num_faces += plutovg_font_face_cache_load_dir(cache, path); + } else if(S_ISREG(st.st_mode) && plutovg_font_face_supports_file(path)) { + num_faces += plutovg_font_face_cache_load_file(cache, path); + } + } + + closedir(dir); + return num_faces; +} + +#endif // _WIN32 + +int plutovg_font_face_cache_load_sys(plutovg_font_face_cache_t* cache) +{ + int num_faces = 0; +#if defined(_WIN32) + num_faces += plutovg_font_face_cache_load_dir(cache, "C:\\Windows\\Fonts"); +#elif defined(__APPLE__) + num_faces += plutovg_font_face_cache_load_dir(cache, "/Library/Fonts"); + num_faces += plutovg_font_face_cache_load_dir(cache, "/System/Library/Fonts"); +#elif defined(__linux__) + num_faces += plutovg_font_face_cache_load_dir(cache, "/usr/share/fonts"); + num_faces += plutovg_font_face_cache_load_dir(cache, "/usr/local/share/fonts"); +#endif + return num_faces; +} + +#else + +int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const char* filename) +{ + return -1; +} + +int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname) +{ + return -1; +} + +int plutovg_font_face_cache_load_sys(plutovg_font_face_cache_t* cache) +{ + return -1; +} + +#endif // PLUTOVG_DISABLE_FONT_FACE_CACHE_LOAD diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-ft-math.c b/vendor/lunasvg/plutovg/source/plutovg-ft-math.c similarity index 99% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-ft-math.c rename to vendor/lunasvg/plutovg/source/plutovg-ft-math.c index 5d6089075f..c4a1af8d79 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-ft-math.c +++ b/vendor/lunasvg/plutovg/source/plutovg-ft-math.c @@ -46,9 +46,6 @@ static inline int clz(unsigned int x) { #define PVG_FT_PAD_ROUND(x, n) PVG_FT_PAD_FLOOR((x) + ((n) / 2), n) #define PVG_FT_PAD_CEIL(x, n) PVG_FT_PAD_FLOOR((x) + ((n)-1), n) -#define PVG_FT_BEGIN_STMNT do { -#define PVG_FT_END_STMNT } while (0) - /* transfer sign leaving a positive number */ #define PVG_FT_MOVE_SIGN(x, s) \ PVG_FT_BEGIN_STMNT \ diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-ft-math.h b/vendor/lunasvg/plutovg/source/plutovg-ft-math.h similarity index 100% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-ft-math.h rename to vendor/lunasvg/plutovg/source/plutovg-ft-math.h diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-ft-raster.c b/vendor/lunasvg/plutovg/source/plutovg-ft-raster.c similarity index 87% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-ft-raster.c rename to vendor/lunasvg/plutovg/source/plutovg-ft-raster.c index b31633ce29..d746b789e9 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-ft-raster.c +++ b/vendor/lunasvg/plutovg/source/plutovg-ft-raster.c @@ -58,9 +58,6 @@ #include "plutovg-ft-raster.h" #include "plutovg-ft-math.h" -#define PVG_FT_BEGIN_STMNT do { -#define PVG_FT_END_STMNT } while ( 0 ) - #include #define pvg_ft_setjmp setjmp @@ -177,6 +174,9 @@ PVG_FT_END_STMNT PVG_FT_Outline outline; PVG_FT_BBox clip_box; + int clip_flags; + int clipping; + PVG_FT_Span gray_spans[PVG_FT_MAX_GRAY_SPANS]; int num_gray_spans; int skip_spans; @@ -492,39 +492,38 @@ PVG_FT_END_STMNT /* Render a given line as a series of scanlines. */ /* */ static void - gray_render_line( RAS_ARG_ TPos to_x, - TPos to_y ) + gray_render_line( RAS_ARG_ TPos from_x, TPos from_y, TPos to_x, TPos to_y ) { TCoord ey1, ey2, fy1, fy2, first, delta, mod; TPos p, dx, dy, x, x2; int incr; - ey1 = TRUNC( ras.y ); + ey1 = TRUNC( from_y ); ey2 = TRUNC( to_y ); /* if (ey2 >= ras.max_ey) ey2 = ras.max_ey-1; */ /* perform vertical clipping */ if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) || ( ey1 < ras.min_ey && ey2 < ras.min_ey ) ) - goto End; + return; - fy1 = FRACT( ras.y ); + fy1 = FRACT( from_y ); fy2 = FRACT( to_y ); /* everything is on a single scanline */ if ( ey1 == ey2 ) { - gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, to_x, fy2 ); - goto End; + gray_render_scanline( RAS_VAR_ ey1, from_x, fy1, to_x, fy2 ); + return; } - dx = to_x - ras.x; - dy = to_y - ras.y; + dx = to_x - from_x; + dy = to_y - from_y; /* vertical line - avoid calling gray_render_scanline */ if ( dx == 0 ) { - TCoord ex = TRUNC( ras.x ); - TCoord two_fx = FRACT( ras.x ) << 1; + TCoord ex = TRUNC( from_x ); + TCoord two_fx = FRACT( from_x ) << 1; TPos area, max_ey1; @@ -590,7 +589,7 @@ PVG_FT_END_STMNT ras.area += (TArea)two_fx * delta; ras.cover += delta; - goto End; + return; } /* ok, we have to render several scanlines */ @@ -612,8 +611,8 @@ PVG_FT_END_STMNT /* keep track of its accumulation for accurate rendering. */ PVG_FT_DIV_MOD( TCoord, p, dy, delta, mod ); - x = ras.x + delta; - gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, x, (TCoord)first ); + x = from_x + delta; + gray_render_scanline( RAS_VAR_ ey1, from_x, fy1, x, (TCoord)first ); ey1 += incr; gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 ); @@ -650,10 +649,6 @@ PVG_FT_END_STMNT gray_render_scanline( RAS_VAR_ ey1, x, ONE_PIXEL - first, to_x, fy2 ); - - End: - ras.x = to_x; - ras.y = to_y; } @@ -664,28 +659,27 @@ PVG_FT_END_STMNT /* Render a straight line across multiple cells in any direction. */ /* */ static void - gray_render_line( RAS_ARG_ TPos to_x, - TPos to_y ) + gray_render_line( RAS_ARG_ TPos from_x, TPos from_y, TPos to_x, TPos to_y ) { TPos dx, dy, fx1, fy1, fx2, fy2; TCoord ex1, ex2, ey1, ey2; - ex1 = TRUNC( ras.x ); + ex1 = TRUNC( from_x ); ex2 = TRUNC( to_x ); - ey1 = TRUNC( ras.y ); + ey1 = TRUNC( from_y ); ey2 = TRUNC( to_y ); /* perform vertical clipping */ if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) || ( ey1 < ras.min_ey && ey2 < ras.min_ey ) ) - goto End; + return; - dx = to_x - ras.x; - dy = to_y - ras.y; + dx = to_x - from_x; + dy = to_y - from_y; - fx1 = FRACT( ras.x ); - fy1 = FRACT( ras.y ); + fx1 = FRACT( from_x ); + fy1 = FRACT( from_y ); if ( ex1 == ex2 && ey1 == ey2 ) /* inside one cell */ ; @@ -787,14 +781,176 @@ PVG_FT_END_STMNT ras.cover += ( fy2 - fy1 ); ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); - - End: - ras.x = to_x; - ras.y = to_y; } #endif + static int + gray_clip_flags( RAS_ARG_ TPos x, TPos y ) + { + return ((x > ras.clip_box.xMax) << 0) | ((y > ras.clip_box.yMax) << 1) | + ((x < ras.clip_box.xMin) << 2) | ((y < ras.clip_box.yMin) << 3); + } + + static int + gray_clip_vflags( RAS_ARG_ TPos y ) + { + return ((y > ras.clip_box.yMax) << 1) | ((y < ras.clip_box.yMin) << 3); + } + + static void + gray_vline( RAS_ARG_ TPos x1, TPos y1, TPos x2, TPos y2, int f1, int f2 ) + { + f1 &= 10; + f2 &= 10; + if((f1 | f2) == 0) /* Fully visible */ + { + gray_render_line( RAS_VAR_ x1, y1, x2, y2 ); + } + else if(f1 == f2) /* Invisible by Y */ + { + return; + } + else + { + TPos tx1, ty1, tx2, ty2; + TPos clip_y1, clip_y2; + + tx1 = x1; + ty1 = y1; + tx2 = x2; + ty2 = y2; + + clip_y1 = ras.clip_box.yMin; + clip_y2 = ras.clip_box.yMax; + + if(f1 & 8) /* y1 < clip_y1 */ + { + tx1 = x1 + PVG_FT_MulDiv(clip_y1-y1, x2-x1, y2-y1); + ty1 = clip_y1; + } + + if(f1 & 2) /* y1 > clip_y2 */ + { + tx1 = x1 + PVG_FT_MulDiv(clip_y2-y1, x2-x1, y2-y1); + ty1 = clip_y2; + } + + if(f2 & 8) /* y2 < clip_y1 */ + { + tx2 = x1 + PVG_FT_MulDiv(clip_y1-y1, x2-x1, y2-y1); + ty2 = clip_y1; + } + + if(f2 & 2) /* y2 > clip_y2 */ + { + tx2 = x1 + PVG_FT_MulDiv(clip_y2-y1, x2-x1, y2-y1); + ty2 = clip_y2; + } + + gray_render_line( RAS_VAR_ tx1, ty1, tx2, ty2 ); + } + } + + static void + gray_line_to( RAS_ARG_ TPos x2, TPos y2 ) + { + if ( !ras.clipping ) + { + gray_render_line( RAS_VAR_ ras.x, ras.y, x2, y2 ); + } + else + { + TPos x1, y1, y3, y4; + TPos clip_x1, clip_x2; + int f1, f2, f3, f4; + + f1 = ras.clip_flags; + f2 = gray_clip_flags( RAS_VAR_ x2, y2 ); + + if((f1 & 10) == (f2 & 10) && (f1 & 10) != 0) /* Invisible by Y */ + { + ras.clip_flags = f2; + goto End; + } + + x1 = ras.x; + y1 = ras.y; + + clip_x1 = ras.clip_box.xMin; + clip_x2 = ras.clip_box.xMax; + + switch(((f1 & 5) << 1) | (f2 & 5)) + { + case 0: /* Visible by X */ + gray_vline( RAS_VAR_ x1, y1, x2, y2, f1, f2); + break; + + case 1: /* x2 > clip_x2 */ + y3 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + gray_vline( RAS_VAR_ x1, y1, clip_x2, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x2, y3, clip_x2, y2, f3, f2); + break; + + case 2: /* x1 > clip_x2 */ + y3 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + gray_vline( RAS_VAR_ clip_x2, y1, clip_x2, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x2, y3, x2, y2, f3, f2); + break; + + case 3: /* x1 > clip_x2 && x2 > clip_x2 */ + gray_vline( RAS_VAR_ clip_x2, y1, clip_x2, y2, f1, f2); + break; + + case 4: /* x2 < clip_x1 */ + y3 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + gray_vline( RAS_VAR_ x1, y1, clip_x1, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x1, y3, clip_x1, y2, f3, f2); + break; + + case 6: /* x1 > clip_x2 && x2 < clip_x1 */ + y3 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); + y4 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + f4 = gray_clip_vflags( RAS_VAR_ y4 ); + gray_vline( RAS_VAR_ clip_x2, y1, clip_x2, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x2, y3, clip_x1, y4, f3, f4); + gray_vline( RAS_VAR_ clip_x1, y4, clip_x1, y2, f4, f2); + break; + + case 8: /* x1 < clip_x1 */ + y3 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + gray_vline( RAS_VAR_ clip_x1, y1, clip_x1, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x1, y3, x2, y2, f3, f2); + break; + + case 9: /* x1 < clip_x1 && x2 > clip_x2 */ + y3 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); + y4 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); + f3 = gray_clip_vflags( RAS_VAR_ y3 ); + f4 = gray_clip_vflags( RAS_VAR_ y4 ); + gray_vline( RAS_VAR_ clip_x1, y1, clip_x1, y3, f1, f3); + gray_vline( RAS_VAR_ clip_x1, y3, clip_x2, y4, f3, f4); + gray_vline( RAS_VAR_ clip_x2, y4, clip_x2, y2, f4, f2); + break; + + case 12: /* x1 < clip_x1 && x2 < clip_x1 */ + gray_vline( RAS_VAR_ clip_x1, y1, clip_x1, y2, f1, f2); + break; + } + + ras.clip_flags = f2; + } + + End: + ras.x = x2; + ras.y = y2; + } + static void gray_split_conic( PVG_FT_Vector* base ) { @@ -840,6 +996,8 @@ PVG_FT_END_STMNT TRUNC( arc[1].y ) < ras.min_ey && TRUNC( arc[2].y ) < ras.min_ey ) ) { + if ( ras.clipping ) + ras.clip_flags = gray_clip_flags ( RAS_VAR_ arc[0].x, arc[0].y ); ras.x = arc[0].x; ras.y = arc[0].y; return; @@ -873,7 +1031,7 @@ PVG_FT_END_STMNT split <<= 1; } - gray_render_line( RAS_VAR_ arc[0].x, arc[0].y ); + gray_line_to( RAS_VAR_ arc[0].x, arc[0].y ); arc -= 2; } while ( --draw ); @@ -940,6 +1098,8 @@ PVG_FT_END_STMNT TRUNC( arc[2].y ) < ras.min_ey && TRUNC( arc[3].y ) < ras.min_ey ) ) { + if ( ras.clipping ) + ras.clip_flags = gray_clip_flags ( RAS_VAR_ arc[0].x, arc[0].y ); ras.x = arc[0].x; ras.y = arc[0].y; return; @@ -989,7 +1149,7 @@ PVG_FT_END_STMNT dx2 * ( dx2 - dx ) + dy2 * ( dy2 - dy ) > 0 ) goto Split; - gray_render_line( RAS_VAR_ arc[0].x, arc[0].y ); + gray_line_to( RAS_VAR_ arc[0].x, arc[0].y ); if ( arc == bez_stack ) return; @@ -1024,6 +1184,8 @@ PVG_FT_END_STMNT gray_start_cell( worker, TRUNC( x ), TRUNC( y ) ); + if ( ras.clipping ) + ras.clip_flags = gray_clip_flags( worker, x, y ); ras.x = x; ras.y = y; return 0; @@ -1361,7 +1523,7 @@ void PVG_FT_Outline_Get_CBox(const PVG_FT_Outline* outline, PVG_FT_BBox* acbox) vec.x = SCALED( point->x ); vec.y = SCALED( point->y ); - gray_render_line(user, UPSCALE(vec.x), UPSCALE(vec.y)); + gray_line_to(user, UPSCALE(vec.x), UPSCALE(vec.y)); continue; } @@ -1443,7 +1605,7 @@ void PVG_FT_Outline_Get_CBox(const PVG_FT_Outline* outline, PVG_FT_BBox* acbox) } /* close the contour with a line segment */ - gray_render_line(user, UPSCALE(v_start.x), UPSCALE(v_start.y)); + gray_line_to(user, UPSCALE(v_start.x), UPSCALE(v_start.y)); Close: first = last + 1; @@ -1506,11 +1668,32 @@ void PVG_FT_Outline_Get_CBox(const PVG_FT_Outline* outline, PVG_FT_BBox* acbox) ras.max_ey <= clip->yMin || ras.min_ey >= clip->yMax ) return 0; - if ( ras.min_ex < clip->xMin ) ras.min_ex = clip->xMin; - if ( ras.min_ey < clip->yMin ) ras.min_ey = clip->yMin; + ras.clip_flags = ras.clipping = 0; + + if ( ras.min_ex < clip->xMin ) { + ras.min_ex = clip->xMin; + ras.clipping = 1; + } + + if ( ras.min_ey < clip->yMin ) { + ras.min_ey = clip->yMin; + ras.clipping = 1; + } + + if ( ras.max_ex > clip->xMax ) { + ras.max_ex = clip->xMax; + ras.clipping = 1; + } + + if ( ras.max_ey > clip->yMax ) { + ras.max_ey = clip->yMax; + ras.clipping = 1; + } - if ( ras.max_ex > clip->xMax ) ras.max_ex = clip->xMax; - if ( ras.max_ey > clip->yMax ) ras.max_ey = clip->yMax; + clip->xMin = (ras.min_ex - 1) * ONE_PIXEL; + clip->yMin = (ras.min_ey - 1) * ONE_PIXEL; + clip->xMax = (ras.max_ex + 1) * ONE_PIXEL; + clip->yMax = (ras.max_ey + 1) * ONE_PIXEL; ras.count_ex = ras.max_ex - ras.min_ex; ras.count_ey = ras.max_ey - ras.min_ey; diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-ft-raster.h b/vendor/lunasvg/plutovg/source/plutovg-ft-raster.h similarity index 100% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-ft-raster.h rename to vendor/lunasvg/plutovg/source/plutovg-ft-raster.h diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.c b/vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c similarity index 100% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.c rename to vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.h b/vendor/lunasvg/plutovg/source/plutovg-ft-stroker.h similarity index 100% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.h rename to vendor/lunasvg/plutovg/source/plutovg-ft-stroker.h diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-ft-types.h b/vendor/lunasvg/plutovg/source/plutovg-ft-types.h similarity index 99% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-ft-types.h rename to vendor/lunasvg/plutovg/source/plutovg-ft-types.h index 06580d12ce..4b3db9003b 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-ft-types.h +++ b/vendor/lunasvg/plutovg/source/plutovg-ft-types.h @@ -170,4 +170,7 @@ typedef unsigned int PVG_FT_UInt32; #define FALSE 0 #endif +#define PVG_FT_BEGIN_STMNT do { +#define PVG_FT_END_STMNT } while (0) + #endif // PLUTOVG_FT_TYPES_H diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-matrix.c b/vendor/lunasvg/plutovg/source/plutovg-matrix.c similarity index 98% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-matrix.c rename to vendor/lunasvg/plutovg/source/plutovg-matrix.c index 312a7009cb..d784d7a47f 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-matrix.c +++ b/vendor/lunasvg/plutovg/source/plutovg-matrix.c @@ -10,9 +10,7 @@ void plutovg_matrix_init(plutovg_matrix_t* matrix, float a, float b, float c, fl void plutovg_matrix_init_identity(plutovg_matrix_t* matrix) { - matrix->a = 1; matrix->b = 0; - matrix->c = 0; matrix->d = 1; - matrix->e = 0; matrix->f = 0; + plutovg_matrix_init(matrix, 1, 0, 0, 1, 0, 0); } void plutovg_matrix_init_translate(plutovg_matrix_t* matrix, float tx, float ty) diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-paint.c b/vendor/lunasvg/plutovg/source/plutovg-paint.c similarity index 98% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-paint.c rename to vendor/lunasvg/plutovg/source/plutovg-paint.c index 44fe3a967d..43672ffdcd 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-paint.c +++ b/vendor/lunasvg/plutovg/source/plutovg-paint.c @@ -91,9 +91,7 @@ static inline uint8_t hex_digit(uint8_t c) return c - '0'; if(c >= 'a' && c <= 'f') return 10 + c - 'a'; - if(c >= 'A' && c <= 'F') - return 10 + c - 'A'; - return 0; + return 10 + c - 'A'; } static inline uint8_t hex_byte(uint8_t c1, uint8_t c2) @@ -387,7 +385,7 @@ int plutovg_color_parse(plutovg_color_t* color, const char* data, int length) static void* plutovg_paint_create(plutovg_paint_type_t type, size_t size) { plutovg_paint_t* paint = malloc(size); - paint->ref_count = 1; + plutovg_init_reference(paint); paint->type = type; return paint; } @@ -469,17 +467,13 @@ plutovg_paint_t* plutovg_paint_create_texture(plutovg_surface_t* surface, plutov plutovg_paint_t* plutovg_paint_reference(plutovg_paint_t* paint) { - if(paint == NULL) - return NULL; - ++paint->ref_count; + plutovg_increment_reference(paint); return paint; } void plutovg_paint_destroy(plutovg_paint_t* paint) { - if(paint == NULL) - return; - if(--paint->ref_count == 0) { + if(plutovg_destroy_reference(paint)) { if(paint->type == PLUTOVG_PAINT_TYPE_TEXTURE) { plutovg_texture_paint_t* texture = (plutovg_texture_paint_t*)(paint); plutovg_surface_destroy(texture->surface); @@ -491,7 +485,5 @@ void plutovg_paint_destroy(plutovg_paint_t* paint) int plutovg_paint_get_reference_count(const plutovg_paint_t* paint) { - if(paint) - return paint->ref_count; - return 0; + return plutovg_get_reference_count(paint); } diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-path.c b/vendor/lunasvg/plutovg/source/plutovg-path.c similarity index 98% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-path.c rename to vendor/lunasvg/plutovg/source/plutovg-path.c index cfcc0d7faa..2efaa6332d 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-path.c +++ b/vendor/lunasvg/plutovg/source/plutovg-path.c @@ -38,28 +38,24 @@ plutovg_path_command_t plutovg_path_iterator_next(plutovg_path_iterator_t* it, p plutovg_path_t* plutovg_path_create(void) { plutovg_path_t* path = malloc(sizeof(plutovg_path_t)); - path->ref_count = 1; + plutovg_init_reference(path); path->num_points = 0; path->num_contours = 0; path->num_curves = 0; - path->start_point = PLUTOVG_MAKE_POINT(0, 0); + path->start_point = PLUTOVG_EMPTY_POINT; plutovg_array_init(path->elements); return path; } plutovg_path_t* plutovg_path_reference(plutovg_path_t* path) { - if(path == NULL) - return NULL; - ++path->ref_count; + plutovg_increment_reference(path); return path; } void plutovg_path_destroy(plutovg_path_t* path) { - if(path == NULL) - return; - if(--path->ref_count == 0) { + if(plutovg_destroy_reference(path)) { plutovg_array_destroy(path->elements); free(path); } @@ -67,9 +63,7 @@ void plutovg_path_destroy(plutovg_path_t* path) int plutovg_path_get_reference_count(const plutovg_path_t* path) { - if(path) - return path->ref_count; - return 0; + return plutovg_get_reference_count(path); } int plutovg_path_get_elements(const plutovg_path_t* path, const plutovg_path_element_t** elements) @@ -244,7 +238,7 @@ void plutovg_path_reserve(plutovg_path_t* path, int count) void plutovg_path_reset(plutovg_path_t* path) { plutovg_array_clear(path->elements); - path->start_point = PLUTOVG_MAKE_POINT(0, 0); + path->start_point = PLUTOVG_EMPTY_POINT; path->num_points = 0; path->num_contours = 0; path->num_curves = 0; @@ -611,7 +605,7 @@ void plutovg_path_traverse_dashed(const plutovg_path_t* path, float offset, cons dasher.phase = dasher.start_phase; dasher.index = dasher.start_index; dasher.toggle = dasher.start_toggle; - dasher.current_point = PLUTOVG_MAKE_POINT(0, 0); + dasher.current_point = PLUTOVG_EMPTY_POINT; dasher.traverse_func = traverse_func; dasher.closure = closure; plutovg_path_traverse_flatten(path, dash_traverse_func, &dasher); diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-private.h b/vendor/lunasvg/plutovg/source/plutovg-private.h similarity index 70% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-private.h rename to vendor/lunasvg/plutovg/source/plutovg-private.h index 334e8b80d4..838cd437b6 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-private.h +++ b/vendor/lunasvg/plutovg/source/plutovg-private.h @@ -3,8 +3,41 @@ #include "plutovg.h" +#if defined(_WIN32) + +#include + +typedef LONG plutovg_ref_count_t; + +#define plutovg_init_reference(ob) ((ob)->ref_count = 1) +#define plutovg_increment_reference(ob) (void)(ob && InterlockedIncrement(&(ob)->ref_count)) +#define plutovg_destroy_reference(ob) (ob && InterlockedDecrement(&(ob)->ref_count) == 0) +#define plutovg_get_reference_count(ob) ((ob) ? InterlockedCompareExchange((LONG*)&(ob)->ref_count, 0, 0) : 0) + +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) + +#include + +typedef atomic_int plutovg_ref_count_t; + +#define plutovg_init_reference(ob) atomic_init(&(ob)->ref_count, 1) +#define plutovg_increment_reference(ob) (void)(ob && atomic_fetch_add(&(ob)->ref_count, 1)) +#define plutovg_destroy_reference(ob) (ob && atomic_fetch_sub(&(ob)->ref_count, 1) == 1) +#define plutovg_get_reference_count(ob) ((ob) ? atomic_load(&(ob)->ref_count) : 0) + +#else + +typedef int plutovg_ref_count_t; + +#define plutovg_init_reference(ob) ((ob)->ref_count = 1) +#define plutovg_increment_reference(ob) (void)(ob && ++(ob)->ref_count) +#define plutovg_destroy_reference(ob) (ob && --(ob)->ref_count == 0) +#define plutovg_get_reference_count(ob) ((ob) ? (ob)->ref_count : 0) + +#endif + struct plutovg_surface { - int ref_count; + plutovg_ref_count_t ref_count; int width; int height; int stride; @@ -12,7 +45,7 @@ struct plutovg_surface { }; struct plutovg_path { - int ref_count; + plutovg_ref_count_t ref_count; int num_points; int num_contours; int num_curves; @@ -31,7 +64,7 @@ typedef enum { } plutovg_paint_type_t; struct plutovg_paint { - int ref_count; + plutovg_ref_count_t ref_count; plutovg_paint_type_t type; }; @@ -120,11 +153,12 @@ typedef struct plutovg_state { } plutovg_state_t; struct plutovg_canvas { - int ref_count; + plutovg_ref_count_t ref_count; plutovg_surface_t* surface; plutovg_path_t* path; plutovg_state_t* state; plutovg_state_t* freed_state; + plutovg_font_face_cache_t* face_cache; plutovg_rect_t clip_rect; plutovg_span_buffer_t clip_spans; plutovg_span_buffer_t fill_spans; @@ -135,6 +169,7 @@ void plutovg_span_buffer_init_rect(plutovg_span_buffer_t* span_buffer, int x, in void plutovg_span_buffer_reset(plutovg_span_buffer_t* span_buffer); void plutovg_span_buffer_destroy(plutovg_span_buffer_t* span_buffer); void plutovg_span_buffer_copy(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* source); +bool plutovg_span_buffer_contains(const plutovg_span_buffer_t* span_buffer, float x, float y); void plutovg_span_buffer_extents(plutovg_span_buffer_t* span_buffer, plutovg_rect_t* extents); void plutovg_span_buffer_intersect(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* a, const plutovg_span_buffer_t* b); diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-rasterize.c b/vendor/lunasvg/plutovg/source/plutovg-rasterize.c similarity index 96% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-rasterize.c rename to vendor/lunasvg/plutovg/source/plutovg-rasterize.c index 2b6ea80553..559f0ee56e 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-rasterize.c +++ b/vendor/lunasvg/plutovg/source/plutovg-rasterize.c @@ -55,6 +55,23 @@ void plutovg_span_buffer_copy(plutovg_span_buffer_t* span_buffer, const plutovg_ span_buffer->h = source->h; } +bool plutovg_span_buffer_contains(const plutovg_span_buffer_t* span_buffer, float x, float y) +{ + const int ix = (int)floorf(x); + const int iy = (int)floorf(y); + + for(int i = 0; i < span_buffer->spans.size; i++) { + plutovg_span_t* span = &span_buffer->spans.data[i]; + if(span->y != iy) + continue; + if(ix >= span->x && ix < (span->x + span->len)) { + return true; + } + } + + return false; +} + static void plutovg_span_buffer_update_extents(plutovg_span_buffer_t* span_buffer) { if(span_buffer->w != -1 && span_buffer->h != -1) @@ -172,7 +189,7 @@ static void ft_outline_destroy(PVG_FT_Outline* outline) free(outline); } -#define FT_COORD(x) (PVG_FT_Pos)((x) * 64) +#define FT_COORD(x) (PVG_FT_Pos)(roundf(x * 64)) static void ft_outline_move_to(PVG_FT_Outline* ft, float x, float y) { ft->points[ft->n_points].x = FT_COORD(x); diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-stb-image-write.h b/vendor/lunasvg/plutovg/source/plutovg-stb-image-write.h similarity index 100% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-stb-image-write.h rename to vendor/lunasvg/plutovg/source/plutovg-stb-image-write.h diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-stb-image.h b/vendor/lunasvg/plutovg/source/plutovg-stb-image.h similarity index 100% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-stb-image.h rename to vendor/lunasvg/plutovg/source/plutovg-stb-image.h diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-stb-truetype.h b/vendor/lunasvg/plutovg/source/plutovg-stb-truetype.h similarity index 100% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-stb-truetype.h rename to vendor/lunasvg/plutovg/source/plutovg-stb-truetype.h diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-surface.c b/vendor/lunasvg/plutovg/source/plutovg-surface.c similarity index 96% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-surface.c rename to vendor/lunasvg/plutovg/source/plutovg-surface.c index 79263cec4d..f12cd38c4d 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-surface.c +++ b/vendor/lunasvg/plutovg/source/plutovg-surface.c @@ -11,13 +11,14 @@ static plutovg_surface_t* plutovg_surface_create_uninitialized(int width, int height) { - if(width > STBI_MAX_DIMENSIONS || height > STBI_MAX_DIMENSIONS) + static const int kMaxSize = 1 << 15; + if(width <= 0 || height <= 0 || width >= kMaxSize || height >= kMaxSize) return NULL; const size_t size = width * height * 4; plutovg_surface_t* surface = malloc(size + sizeof(plutovg_surface_t)); if(surface == NULL) return NULL; - surface->ref_count = 1; + plutovg_init_reference(surface); surface->width = width; surface->height = height; surface->stride = width * 4; @@ -36,7 +37,7 @@ plutovg_surface_t* plutovg_surface_create(int width, int height) plutovg_surface_t* plutovg_surface_create_for_data(unsigned char* data, int width, int height, int stride) { plutovg_surface_t* surface = malloc(sizeof(plutovg_surface_t)); - surface->ref_count = 1; + plutovg_init_reference(surface); surface->width = width; surface->height = height; surface->stride = stride; @@ -149,26 +150,20 @@ plutovg_surface_t* plutovg_surface_load_from_image_base64(const char* data, int plutovg_surface_t* plutovg_surface_reference(plutovg_surface_t* surface) { - if(surface == NULL) - return NULL; - ++surface->ref_count; + plutovg_increment_reference(surface); return surface; } void plutovg_surface_destroy(plutovg_surface_t* surface) { - if(surface == NULL) - return; - if(--surface->ref_count == 0) { + if(plutovg_destroy_reference(surface)) { free(surface); } } int plutovg_surface_get_reference_count(const plutovg_surface_t* surface) { - if(surface) - return surface->ref_count; - return 0; + return plutovg_get_reference_count(surface); } unsigned char* plutovg_surface_get_data(const plutovg_surface_t* surface) diff --git a/vendor/lunasvg/3rdparty/plutovg/plutovg-utils.h b/vendor/lunasvg/plutovg/source/plutovg-utils.h similarity index 98% rename from vendor/lunasvg/3rdparty/plutovg/plutovg-utils.h rename to vendor/lunasvg/plutovg/source/plutovg-utils.h index 585d8fb1a3..68c86bd841 100644 --- a/vendor/lunasvg/3rdparty/plutovg/plutovg-utils.h +++ b/vendor/lunasvg/plutovg/source/plutovg-utils.h @@ -16,7 +16,7 @@ #define plutovg_min(a, b) ((a) < (b) ? (a) : (b)) #define plutovg_max(a, b) ((a) > (b) ? (a) : (b)) -#define plutovg_clamp(v, lo, hi) ((v) < (lo) ? (lo) : (hi) < (v) ? (hi) : (v)) +#define plutovg_clamp(v, lo, hi) ((v) < (lo) ? (lo) : ((v) > (hi) ? (hi) : (v))) #define plutovg_alpha(c) (((c) >> 24) & 0xff) #define plutovg_red(c) (((c) >> 16) & 0xff) diff --git a/vendor/lunasvg/premake5.lua b/vendor/lunasvg/premake5.lua index 8d93a671c2..cbaf36958f 100644 --- a/vendor/lunasvg/premake5.lua +++ b/vendor/lunasvg/premake5.lua @@ -16,11 +16,11 @@ project "lunasvg" vpaths { ["Headers"] = "source/**.h", ["Headers/*"] = "include/**.h", - ["Headers/3rdparty/*"] = "3rdparty/**.h", + ["Headers/plutovg/*"] = "plutovg/**.h", ["Sources"] = "source/**.cpp", ["Sources/*"] = "source/**.c", - ["Sources/3rdparty"] = "3rdparty/**.cpp", - ["Sources/3rdparty/*"] = "3rdparty/**.c", + ["Sources/plutovg"] = "plutovg/**.cpp", + ["Sources/plutovg/*"] = "plutovg/**.c", ["*"] = "premake5.lua" } @@ -32,9 +32,9 @@ project "lunasvg" } includedirs { - "3rdparty/plutovg", - "source", - "include" + "plutovg/include", + "include", + "source" } filter "system:windows" diff --git a/vendor/lunasvg/source/graphics.cpp b/vendor/lunasvg/source/graphics.cpp index 38d00b2fa5..248c087bf2 100644 --- a/vendor/lunasvg/source/graphics.cpp +++ b/vendor/lunasvg/source/graphics.cpp @@ -360,10 +360,8 @@ void PathIterator::next() m_index += m_elements[m_index].header.length; } -const std::string emptyString; - FontFace::FontFace(plutovg_font_face_t* face) - : m_face(face) + : m_face(plutovg_font_face_reference(face)) { } @@ -417,108 +415,62 @@ plutovg_font_face_t* FontFace::release() bool FontFaceCache::addFontFace(const std::string& family, bool bold, bool italic, const FontFace& face) { if(!face.isNull()) - m_table[family].emplace_back(bold, italic, face); + plutovg_font_face_cache_add(m_cache, family.data(), bold, italic, face.get()); return !face.isNull(); } -FontFace FontFaceCache::getFontFace(const std::string_view& family, bool bold, bool italic) +FontFace FontFaceCache::getFontFace(const std::string& family, bool bold, bool italic) const { - auto it = m_table.find(family); - if(it == m_table.end()) { - return FontFace(); + if(auto face = plutovg_font_face_cache_get(m_cache, family.data(), bold, italic)) { + return FontFace(face); } - auto select = [bold, italic](const FontFaceEntry& a, const FontFaceEntry& b) { - if(std::get<2>(a).isNull()) - return b; - if(std::get<2>(b).isNull()) - return a; - int aScore = (bold == std::get<0>(a)) + (italic == std::get<1>(a)); - int bScore = (bold == std::get<0>(b)) + (italic == std::get<1>(b)); - return aScore > bScore ? a : b; + static const struct { + const char* generic; + const char* fallback; + } generic_fallbacks[] = { +#if defined(__linux__) + {"sans-serif", "DejaVu Sans"}, + {"serif", "DejaVu Serif"}, + {"monospace", "DejaVu Sans Mono"}, +#else + {"sans-serif", "Arial"}, + {"serif", "Times New Roman"}, + {"monospace", "Courier New"}, +#endif + {"cursive", "Comic Sans MS"}, + {"fantasy", "Impact"} }; - FontFaceEntry entry; - for(const auto& item : it->second) { - entry = select(entry, item); + for(auto value : generic_fallbacks) { + if(value.generic == family || family.empty()) { + return FontFace(plutovg_font_face_cache_get(m_cache, value.fallback, bold, italic)); + } } - return std::get<2>(entry); + return FontFace(); } FontFaceCache::FontFaceCache() + : m_cache(plutovg_font_face_cache_create()) { - static const struct { - const char* filename; - const bool bold; - const bool italic; - } entries[] = { -#if defined(_WIN32) - {"C:/Windows/Fonts/arial.ttf", false, false}, - {"C:/Windows/Fonts/arialbd.ttf", true, false}, - {"C:/Windows/Fonts/ariali.ttf", false, true}, - {"C:/Windows/Fonts/arialbi.ttf", true, true}, -#elif defined(__APPLE__) - {"/Library/Fonts/Arial.ttf", false, false}, - {"/Library/Fonts/Arial Bold.ttf", true, false}, - {"/Library/Fonts/Arial Italic.ttf", false, true}, - {"/Library/Fonts/Arial Bold Italic.ttf", true, true}, - - {"/System/Library/Fonts/Supplemental/Arial.ttf", false, false}, - {"/System/Library/Fonts/Supplemental/Arial Bold.ttf", true, false}, - {"/System/Library/Fonts/Supplemental/Arial Italic.ttf", false, true}, - {"/System/Library/Fonts/Supplemental/Arial Bold Italic.ttf", true, true}, -#elif defined(__linux__) - {"/usr/share/fonts/dejavu/DejaVuSans.ttf", false, false}, - {"/usr/share/fonts/dejavu/DejaVuSans-Bold.ttf", true, false}, - {"/usr/share/fonts/dejavu/DejaVuSans-Oblique.ttf", false, true}, - {"/usr/share/fonts/dejavu/DejaVuSans-BoldOblique.ttf", true, true}, - - {"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", false, false}, - {"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", true, false}, - {"/usr/share/fonts/truetype/dejavu/DejaVuSans-Oblique.ttf", false, true}, - {"/usr/share/fonts/truetype/dejavu/DejaVuSans-BoldOblique.ttf", true, true}, +#ifndef LUNASVG_DISABLE_LOAD_SYSTEM_FONTS + plutovg_font_face_cache_load_sys(m_cache); #endif - }; - - for(const auto& entry : entries) { - addFontFace(emptyString, entry.bold, entry.italic, FontFace(entry.filename)); - } } FontFaceCache* fontFaceCache() { - thread_local FontFaceCache cache; + static FontFaceCache cache; return &cache; } Font::Font(const FontFace& face, float size) : m_face(face), m_size(size) { -} - -float Font::ascent() const -{ - float ascent = 0; - if(m_size > 0.f && !m_face.isNull()) - plutovg_font_face_get_metrics(m_face.get(), m_size, &ascent, nullptr, nullptr, nullptr); - return ascent; -} - -float Font::descent() const -{ - float descent = 0; - if(m_size > 0.f && !m_face.isNull()) - plutovg_font_face_get_metrics(m_face.get(), m_size, nullptr, &descent, nullptr, nullptr); - return descent; -} - -float Font::height() const -{ - float ascent = 0, descent = 0; - if(m_size > 0.f && !m_face.isNull()) - plutovg_font_face_get_metrics(m_face.get(), m_size, &ascent, &descent, nullptr, nullptr); - return ascent + descent; + if(m_size > 0.f && !m_face.isNull()) { + plutovg_font_face_get_metrics(m_face.get(), m_size, &m_ascent, &m_descent, &m_lineGap, nullptr); + } } float Font::xHeight() const @@ -543,8 +495,8 @@ std::shared_ptr Canvas::create(const Bitmap& bitmap) std::shared_ptr Canvas::create(float x, float y, float width, float height) { - constexpr int kMaxSize = 1 << 24; - if(width <= 0 || height <= 0 || width > kMaxSize || height > kMaxSize) + constexpr int kMaxSize = 1 << 15; + if(width <= 0 || height <= 0 || width >= kMaxSize || height >= kMaxSize) return std::shared_ptr(new Canvas(0, 0, 1, 1)); auto l = static_cast(std::floor(x)); auto t = static_cast(std::floor(y)); @@ -585,8 +537,7 @@ void Canvas::setTexture(const Canvas& source, TextureType type, float opacity, c void Canvas::fillPath(const Path& path, FillRule fillRule, const Transform& transform) { - plutovg_canvas_reset_matrix(m_canvas); - plutovg_canvas_translate(m_canvas, -m_x, -m_y); + plutovg_canvas_set_matrix(m_canvas, &m_translation); plutovg_canvas_transform(m_canvas, &transform.matrix()); plutovg_canvas_set_fill_rule(m_canvas, static_cast(fillRule)); plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER); @@ -595,8 +546,7 @@ void Canvas::fillPath(const Path& path, FillRule fillRule, const Transform& tran void Canvas::strokePath(const Path& path, const StrokeData& strokeData, const Transform& transform) { - plutovg_canvas_reset_matrix(m_canvas); - plutovg_canvas_translate(m_canvas, -m_x, -m_y); + plutovg_canvas_set_matrix(m_canvas, &m_translation); plutovg_canvas_transform(m_canvas, &transform.matrix()); plutovg_canvas_set_line_width(m_canvas, strokeData.lineWidth()); plutovg_canvas_set_miter_limit(m_canvas, strokeData.miterLimit()); @@ -610,8 +560,7 @@ void Canvas::strokePath(const Path& path, const StrokeData& strokeData, const Tr void Canvas::fillText(const std::u32string_view& text, const Font& font, const Point& origin, const Transform& transform) { - plutovg_canvas_reset_matrix(m_canvas); - plutovg_canvas_translate(m_canvas, -m_x, -m_y); + plutovg_canvas_set_matrix(m_canvas, &m_translation); plutovg_canvas_transform(m_canvas, &transform.matrix()); plutovg_canvas_set_fill_rule(m_canvas, PLUTOVG_FILL_RULE_NON_ZERO); plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER); @@ -621,8 +570,7 @@ void Canvas::fillText(const std::u32string_view& text, const Font& font, const P void Canvas::strokeText(const std::u32string_view& text, float strokeWidth, const Font& font, const Point& origin, const Transform& transform) { - plutovg_canvas_reset_matrix(m_canvas); - plutovg_canvas_translate(m_canvas, -m_x, -m_y); + plutovg_canvas_set_matrix(m_canvas, &m_translation); plutovg_canvas_transform(m_canvas, &transform.matrix()); plutovg_canvas_set_line_width(m_canvas, strokeWidth); plutovg_canvas_set_miter_limit(m_canvas, 4.f); @@ -637,8 +585,7 @@ void Canvas::strokeText(const std::u32string_view& text, float strokeWidth, cons void Canvas::clipPath(const Path& path, FillRule clipRule, const Transform& transform) { - plutovg_canvas_reset_matrix(m_canvas); - plutovg_canvas_translate(m_canvas, -m_x, -m_y); + plutovg_canvas_set_matrix(m_canvas, &m_translation); plutovg_canvas_transform(m_canvas, &transform.matrix()); plutovg_canvas_set_fill_rule(m_canvas, static_cast(clipRule)); plutovg_canvas_clip_path(m_canvas, path.data()); @@ -646,8 +593,7 @@ void Canvas::clipPath(const Path& path, FillRule clipRule, const Transform& tran void Canvas::clipRect(const Rect& rect, FillRule clipRule, const Transform& transform) { - plutovg_canvas_reset_matrix(m_canvas); - plutovg_canvas_translate(m_canvas, -m_x, -m_y); + plutovg_canvas_set_matrix(m_canvas, &m_translation); plutovg_canvas_transform(m_canvas, &transform.matrix()); plutovg_canvas_set_fill_rule(m_canvas, static_cast(clipRule)); plutovg_canvas_clip_rect(m_canvas, rect.x, rect.y, rect.w, rect.h); @@ -658,8 +604,7 @@ void Canvas::drawImage(const Bitmap& image, const Rect& dstRect, const Rect& src auto xScale = dstRect.w / srcRect.w; auto yScale = dstRect.h / srcRect.h; plutovg_matrix_t matrix = { xScale, 0, 0, yScale, -srcRect.x * xScale, -srcRect.y * yScale }; - plutovg_canvas_reset_matrix(m_canvas); - plutovg_canvas_translate(m_canvas, -m_x, -m_y); + plutovg_canvas_set_matrix(m_canvas, &m_translation); plutovg_canvas_transform(m_canvas, &transform.matrix()); plutovg_canvas_translate(m_canvas, dstRect.x, dstRect.y); plutovg_canvas_set_fill_rule(m_canvas, PLUTOVG_FILL_RULE_NON_ZERO); @@ -671,8 +616,7 @@ void Canvas::drawImage(const Bitmap& image, const Rect& dstRect, const Rect& src void Canvas::blendCanvas(const Canvas& canvas, BlendMode blendMode, float opacity) { plutovg_matrix_t matrix = { 1, 0, 0, 1, static_cast(canvas.x()), static_cast(canvas.y()) }; - plutovg_canvas_reset_matrix(m_canvas); - plutovg_canvas_translate(m_canvas, -m_x, -m_y); + plutovg_canvas_set_matrix(m_canvas, &m_translation); plutovg_canvas_set_operator(m_canvas, static_cast(blendMode)); plutovg_canvas_set_texture(m_canvas, canvas.surface(), PLUTOVG_TEXTURE_TYPE_PLAIN, opacity, &matrix); plutovg_canvas_paint(m_canvas); @@ -708,11 +652,18 @@ void Canvas::convertToLuminanceMask() auto pixels = reinterpret_cast(data + stride * y); for(int x = 0; x < width; x++) { auto pixel = pixels[x]; + auto a = (pixel >> 24) & 0xFF; auto r = (pixel >> 16) & 0xFF; auto g = (pixel >> 8) & 0xFF; auto b = (pixel >> 0) & 0xFF; - auto l = (2*r + 3*g + b) / 6; - pixels[x] = l << 24; + if(a) { + r = (r * 255) / a; + g = (g * 255) / a; + b = (b * 255) / a; + } + + auto l = (r * 0.2125 + g * 0.7154 + b * 0.0721); + pixels[x] = static_cast(l * (a / 255.0)) << 24; } } } @@ -726,6 +677,7 @@ Canvas::~Canvas() Canvas::Canvas(const Bitmap& bitmap) : m_surface(plutovg_surface_reference(bitmap.surface())) , m_canvas(plutovg_canvas_create(m_surface)) + , m_translation({1, 0, 0, 1, 0, 0}) , m_x(0), m_y(0) { } @@ -733,6 +685,7 @@ Canvas::Canvas(const Bitmap& bitmap) Canvas::Canvas(int x, int y, int width, int height) : m_surface(plutovg_surface_create(width, height)) , m_canvas(plutovg_canvas_create(m_surface)) + , m_translation({1, 0, 0, 1, -static_cast(x), -static_cast(y)}) , m_x(x), m_y(y) { } diff --git a/vendor/lunasvg/source/graphics.h b/vendor/lunasvg/source/graphics.h index 737631ff4f..7e6bc121bd 100644 --- a/vendor/lunasvg/source/graphics.h +++ b/vendor/lunasvg/source/graphics.h @@ -10,7 +10,6 @@ #include #include #include -#include namespace lunasvg { @@ -199,6 +198,9 @@ class Rect { constexpr void inflate(float dx, float dy) { x -= dx; y -= dy; w += dx * 2.f; h += dy * 2.f; } constexpr void inflate(float d) { inflate(d, d); } + constexpr bool contains(float px, float py) const { return px >= x && px <= x + w && py >= y && py <= y + h; } + constexpr bool contains(const Point& p) const { return contains(p.x, p.y); } + constexpr Rect intersected(const Rect& rect) const; constexpr Rect united(const Rect& rect) const; @@ -392,12 +394,10 @@ class PathIterator { int m_index; }; -extern const std::string emptyString; - class FontFace { public: FontFace() = default; - FontFace(plutovg_font_face_t* face); + explicit FontFace(plutovg_font_face_t* face); FontFace(const void* data, size_t length, plutovg_destroy_func_t destroy_func, void* closure); FontFace(const char* filename); FontFace(const FontFace& face); @@ -420,12 +420,11 @@ class FontFace { class FontFaceCache { public: bool addFontFace(const std::string& family, bool bold, bool italic, const FontFace& face); - FontFace getFontFace(const std::string_view& family, bool bold, bool italic); + FontFace getFontFace(const std::string& family, bool bold, bool italic) const; private: FontFaceCache(); - using FontFaceEntry = std::tuple; - std::map, std::less<>> m_table; + plutovg_font_face_cache_t* m_cache; friend FontFaceCache* fontFaceCache(); }; @@ -436,9 +435,10 @@ class Font { Font() = default; Font(const FontFace& face, float size); - float ascent() const; - float descent() const; - float height() const; + float ascent() const { return m_ascent; } + float descent() const { return m_descent; } + float height() const { return m_ascent - m_descent; } + float lineGap() const { return m_lineGap; } float xHeight() const; float measureText(const std::u32string_view& text) const; @@ -451,6 +451,9 @@ class Font { private: FontFace m_face; float m_size = 0.f; + float m_ascent = 0.f; + float m_descent = 0.f; + float m_lineGap = 0.f; }; enum class TextureType { @@ -549,6 +552,7 @@ class Canvas { Canvas(int x, int y, int width, int height); plutovg_surface_t* m_surface; plutovg_canvas_t* m_canvas; + plutovg_matrix_t m_translation; const int m_x; const int m_y; }; diff --git a/vendor/lunasvg/source/lunasvg.cpp b/vendor/lunasvg/source/lunasvg.cpp index bcbe98e5d6..0a4399fbe5 100644 --- a/vendor/lunasvg/source/lunasvg.cpp +++ b/vendor/lunasvg/source/lunasvg.cpp @@ -359,7 +359,7 @@ Bitmap Element::renderToBitmap(int width, int height, uint32_t backgroundColor) Matrix matrix(xScale, 0, 0, yScale, -elementBounds.x * xScale, -elementBounds.y * yScale); Bitmap bitmap(width, height); - bitmap.clear(backgroundColor); + if(backgroundColor) bitmap.clear(backgroundColor); render(bitmap, matrix); return bitmap; } @@ -408,11 +408,11 @@ NodeList Element::children() const return children; } -SVGElement* Element::element(bool layout) const +SVGElement* Element::element(bool layoutIfNeeded) const { auto element = static_cast(m_node); - if(element && layout) - element->rootElement()->updateLayout(); + if(element && layoutIfNeeded) + element->rootElement()->layoutIfNeeded(); return element; } @@ -448,22 +448,22 @@ std::unique_ptr Document::loadFromData(const char* data, size_t length float Document::width() const { - return m_rootElement->updateLayout()->intrinsicWidth(); + return rootElement(true)->intrinsicWidth(); } float Document::height() const { - return m_rootElement->updateLayout()->intrinsicHeight(); + return rootElement(true)->intrinsicHeight(); } Box Document::boundingBox() const { - return m_rootElement->updateLayout()->localTransform().mapRect(m_rootElement->paintBoundingBox()); + return rootElement(true)->localTransform().mapRect(rootElement()->paintBoundingBox()); } void Document::updateLayout() { - m_rootElement->updateLayout(); + m_rootElement->layoutIfNeeded(); } void Document::forceLayout() @@ -477,13 +477,13 @@ void Document::render(Bitmap& bitmap, const Matrix& matrix) const return; auto canvas = Canvas::create(bitmap); SVGRenderState state(nullptr, nullptr, matrix, SVGRenderMode::Painting, canvas); - m_rootElement->updateLayout()->render(state); + rootElement(true)->render(state); } Bitmap Document::renderToBitmap(int width, int height, uint32_t backgroundColor) const { - auto intrinsicWidth = m_rootElement->updateLayout()->intrinsicWidth(); - auto intrinsicHeight = m_rootElement->intrinsicHeight(); + auto intrinsicWidth = rootElement(true)->intrinsicWidth(); + auto intrinsicHeight = rootElement()->intrinsicHeight(); if(intrinsicWidth == 0.f || intrinsicHeight == 0.f) return Bitmap(); if(width <= 0 && height <= 0) { @@ -500,11 +500,16 @@ Bitmap Document::renderToBitmap(int width, int height, uint32_t backgroundColor) Matrix matrix(xScale, 0, 0, yScale, 0, 0); Bitmap bitmap(width, height); - bitmap.clear(backgroundColor); + if(backgroundColor) bitmap.clear(backgroundColor); render(bitmap, matrix); return bitmap; } +Element Document::elementFromPoint(float x, float y) const +{ + return rootElement(true)->elementFromPoint(x, y); +} + Element Document::getElementById(const std::string& id) const { return m_rootElement->getElementById(id); @@ -515,6 +520,13 @@ Element Document::documentElement() const return m_rootElement.get(); } +SVGRootElement* Document::rootElement(bool layoutIfNeeded) const +{ + if(layoutIfNeeded) + m_rootElement->layoutIfNeeded(); + return m_rootElement.get(); +} + Document::Document(Document&&) = default; Document& Document::operator=(Document&&) = default; diff --git a/vendor/lunasvg/source/svgelement.cpp b/vendor/lunasvg/source/svgelement.cpp index dca432c134..942ce97e77 100644 --- a/vendor/lunasvg/source/svgelement.cpp +++ b/vendor/lunasvg/source/svgelement.cpp @@ -66,6 +66,8 @@ std::unique_ptr SVGTextNode::clone(bool deep) const return node; } +const std::string emptyString; + std::unique_ptr SVGElement::create(Document* document, ElementID id) { switch(id) { @@ -356,6 +358,32 @@ SVGPaintElement* SVGElement::getPainter(const std::string_view& id) const return nullptr; } +SVGElement* SVGElement::elementFromPoint(float x, float y) +{ + auto it = m_children.rbegin(); + auto end = m_children.rend(); + for(; it != end; ++it) { + auto child = toSVGElement(*it); + if(child && !child->isHiddenElement()) { + if(auto element = child->elementFromPoint(x, y)) { + return element; + } + } + } + + if(isPointableElement()) { + auto transform = localTransform(); + for(auto parent = parentElement(); parent; parent = parent->parentElement()) + transform.postMultiply(parent->localTransform()); + auto bbox = transform.mapRect(paintBoundingBox()); + if(bbox.contains(x, y)) { + return this; + } + } + + return nullptr; +} + void SVGElement::addProperty(SVGProperty& value) { m_properties.push_front(&value); @@ -432,6 +460,7 @@ void SVGElement::layoutElement(const SVGLayoutState& state) m_display = state.display(); m_overflow = state.overflow(); m_visibility = state.visibility(); + m_pointer_events = state.pointer_events(); } void SVGElement::layoutChildren(SVGLayoutState& state) @@ -483,6 +512,31 @@ bool SVGElement::isHiddenElement() const } } +bool SVGElement::isPointableElement() const +{ + if(m_pointer_events != PointerEvents::None + && m_visibility != Visibility::Hidden + && m_display != Display::None + && m_opacity != 0.f) { + switch(m_id) { + case ElementID::Line: + case ElementID::Rect: + case ElementID::Ellipse: + case ElementID::Circle: + case ElementID::Polyline: + case ElementID::Polygon: + case ElementID::Path: + case ElementID::Text: + case ElementID::Image: + return true; + default: + break; + } + } + + return false; +} + SVGStyleElement::SVGStyleElement(Document* document) : SVGElement(document, ElementID::Style) { @@ -622,7 +676,7 @@ SVGRootElement::SVGRootElement(Document* document) { } -SVGRootElement* SVGRootElement::updateLayout() +SVGRootElement* SVGRootElement::layoutIfNeeded() { if(needsLayout()) forceLayout(); diff --git a/vendor/lunasvg/source/svgelement.h b/vendor/lunasvg/source/svgelement.h index 6e898470fa..7a9c6b1ed6 100644 --- a/vendor/lunasvg/source/svgelement.h +++ b/vendor/lunasvg/source/svgelement.h @@ -120,6 +120,8 @@ class SVGPaintElement; class SVGLayoutState; class SVGRenderState; +extern const std::string emptyString; + class SVGElement : public SVGNode { public: static std::unique_ptr create(Document* document, ElementID id); @@ -162,6 +164,8 @@ class SVGElement : public SVGNode { SVGMaskElement* getMasker(const std::string_view& id) const; SVGPaintElement* getPainter(const std::string_view& id) const; + SVGElement* elementFromPoint(float x, float y); + template void transverse(T callback); @@ -187,6 +191,7 @@ class SVGElement : public SVGNode { bool isVisibilityHidden() const { return m_visibility != Visibility::Visible; } bool isHiddenElement() const; + bool isPointableElement() const; const SVGClipPathElement* clipper() const { return m_clipper; } const SVGMaskElement* masker() const { return m_masker; } @@ -204,6 +209,7 @@ class SVGElement : public SVGNode { Display m_display = Display::Inline; Overflow m_overflow = Overflow::Visible; Visibility m_visibility = Visibility::Visible; + PointerEvents m_pointer_events = PointerEvents::Auto; ElementID m_id; AttributeList m_attributes; @@ -338,7 +344,7 @@ class SVGRootElement final : public SVGSVGElement { void setNeedsLayout() { m_intrinsicWidth = -1.f; } bool needsLayout() const { return m_intrinsicWidth == -1.f; } - SVGRootElement* updateLayout(); + SVGRootElement* layoutIfNeeded(); SVGElement* getElementById(const std::string_view& id) const; void addElementById(const std::string& id, SVGElement* element); diff --git a/vendor/lunasvg/source/svglayoutstate.cpp b/vendor/lunasvg/source/svglayoutstate.cpp index cf9e81abc1..24d583b67f 100644 --- a/vendor/lunasvg/source/svglayoutstate.cpp +++ b/vendor/lunasvg/source/svglayoutstate.cpp @@ -147,6 +147,13 @@ static LengthList parseDashArray(std::string_view input) return values; } +static Length parseLengthOrNormal(std::string_view input) +{ + if(input.compare("normal") == 0) + return Length(0, LengthUnits::None); + return parseLength(input, LengthNegativeMode::Allow, Length(0, LengthUnits::None)); +} + static float parseFontSize(std::string_view input, const SVGLayoutState* state) { auto length = parseLength(input, LengthNegativeMode::Forbid, Length(12, LengthUnits::None)); @@ -202,11 +209,32 @@ static Overflow parseOverflow(const std::string_view& input) return parseEnumValue(input, entries, Overflow::Visible); } +static PointerEvents parsePointerEvents(const std::string_view& input) +{ + static const SVGEnumerationEntry entries[] = { + {PointerEvents::None,"none"}, + {PointerEvents::Auto,"auto"}, + {PointerEvents::Stroke,"stroke"}, + {PointerEvents::Fill,"fill"}, + {PointerEvents::Painted,"painted"}, + {PointerEvents::Visible,"visible"}, + {PointerEvents::VisibleStroke,"visibleStroke"}, + {PointerEvents::VisibleFill,"visibleFill"}, + {PointerEvents::VisiblePainted,"visiblePainted"}, + {PointerEvents::BoundingBox,"bounding-box"}, + {PointerEvents::All,"all"}, + }; + + return parseEnumValue(input, entries, PointerEvents::Auto); +} + static FontWeight parseFontWeight(const std::string_view& input) { static const SVGEnumerationEntry entries[] = { {FontWeight::Normal, "normal"}, {FontWeight::Bold, "bold"}, + {FontWeight::Bold, "bolder"}, + {FontWeight::Normal, "lighter"}, {FontWeight::Normal, "100"}, {FontWeight::Normal, "200"}, {FontWeight::Normal, "300"}, @@ -282,6 +310,33 @@ static Direction parseDirection(const std::string_view& input) return parseEnumValue(input, entries, Direction::Ltr); } +static WritingMode parseWritingMode(const std::string_view& input) +{ + static const SVGEnumerationEntry entries[] = { + {WritingMode::Horizontal, "horizontal-tb"}, + {WritingMode::Vertical, "vertical-rl"}, + {WritingMode::Vertical, "vertical-lr"}, + {WritingMode::Horizontal, "lr-tb"}, + {WritingMode::Horizontal, "lr"}, + {WritingMode::Horizontal, "rl-tb"}, + {WritingMode::Horizontal, "rl"}, + {WritingMode::Vertical, "tb-rl"}, + {WritingMode::Vertical, "tb"} + }; + + return parseEnumValue(input, entries, WritingMode::Horizontal); +} + +static TextOrientation parseTextOrientation(const std::string_view& input) +{ + static const SVGEnumerationEntry entries[] = { + {TextOrientation::Mixed, "mixed"}, + {TextOrientation::Upright, "upright"} + }; + + return parseEnumValue(input, entries, TextOrientation::Mixed); +} + static TextAnchor parseTextAnchor(const std::string_view& input) { static const SVGEnumerationEntry entries[] = { @@ -360,6 +415,8 @@ SVGLayoutState::SVGLayoutState(const SVGLayoutState& parent, const SVGElement* e , m_stroke_opacity(parent.stroke_opacity()) , m_stroke_miterlimit(parent.stroke_miterlimit()) , m_font_size(parent.font_size()) + , m_letter_spacing(parent.letter_spacing()) + , m_word_spacing(parent.word_spacing()) , m_stroke_width(parent.stroke_width()) , m_stroke_dashoffset(parent.stroke_dashoffset()) , m_stroke_dasharray(parent.stroke_dasharray()) @@ -372,9 +429,12 @@ SVGLayoutState::SVGLayoutState(const SVGLayoutState& parent, const SVGElement* e , m_dominant_baseline(parent.dominant_baseline()) , m_text_anchor(parent.text_anchor()) , m_white_space(parent.white_space()) + , m_writing_mode(parent.writing_mode()) + , m_text_orientation(parent.text_orientation()) , m_direction(parent.direction()) , m_visibility(parent.visibility()) , m_overflow(element->isRootElement() ? Overflow::Visible : Overflow::Hidden) + , m_pointer_events(parent.pointer_events()) , m_marker_start(parent.marker_start()) , m_marker_mid(parent.marker_mid()) , m_marker_end(parent.marker_end()) @@ -416,8 +476,14 @@ SVGLayoutState::SVGLayoutState(const SVGLayoutState& parent, const SVGElement* e case PropertyID::Font_Size: m_font_size = parseFontSize(input, this); break; + case PropertyID::Letter_Spacing: + m_letter_spacing = parseLengthOrNormal(input); + break; + case PropertyID::Word_Spacing: + m_word_spacing = parseLengthOrNormal(input); + break; case PropertyID::Baseline_Shift: - m_baseline_shit = parseBaselineShift(input); + m_baseline_shift = parseBaselineShift(input); break; case PropertyID::Stroke_Width: m_stroke_width = parseLength(input, LengthNegativeMode::Forbid, Length(1.f, LengthUnits::None)); @@ -458,9 +524,15 @@ SVGLayoutState::SVGLayoutState(const SVGLayoutState& parent, const SVGElement* e case PropertyID::Text_Anchor: m_text_anchor = parseTextAnchor(input); break; - case PropertyID::WhiteSpace: + case PropertyID::White_Space: m_white_space = parseWhiteSpace(input); break; + case PropertyID::Writing_Mode: + m_writing_mode = parseWritingMode(input); + break; + case PropertyID::Text_Orientation: + m_text_orientation = parseTextOrientation(input); + break; case PropertyID::Display: m_display = parseDisplay(input); break; @@ -470,6 +542,9 @@ SVGLayoutState::SVGLayoutState(const SVGLayoutState& parent, const SVGElement* e case PropertyID::Overflow: m_overflow = parseOverflow(input); break; + case PropertyID::Pointer_Events: + m_pointer_events = parsePointerEvents(input); + break; case PropertyID::Mask_Type: m_mask_type = parseMaskType(input); break; @@ -518,7 +593,10 @@ Font SVGLayoutState::font() const stripLeadingAndTrailingSpaces(family); } - face = fontFaceCache()->getFontFace(family, bold, italic); + std::string font_family(family); + if(!font_family.empty()) { + face = fontFaceCache()->getFontFace(font_family, bold, italic); + } } if(face.isNull()) diff --git a/vendor/lunasvg/source/svglayoutstate.h b/vendor/lunasvg/source/svglayoutstate.h index 34af91d9c6..4d08ad9ab9 100644 --- a/vendor/lunasvg/source/svglayoutstate.h +++ b/vendor/lunasvg/source/svglayoutstate.h @@ -26,7 +26,10 @@ class SVGLayoutState { float stroke_miterlimit() const { return m_stroke_miterlimit; } float font_size() const { return m_font_size; } - const BaselineShift& baseline_shit() const { return m_baseline_shit; } + const Length& letter_spacing() const { return m_letter_spacing; } + const Length& word_spacing() const { return m_word_spacing; } + + const BaselineShift& baseline_shift() const { return m_baseline_shift; } const Length& stroke_width() const { return m_stroke_width; } const Length& stroke_dashoffset() const { return m_stroke_dashoffset; } const LengthList& stroke_dasharray() const { return m_stroke_dasharray; } @@ -45,11 +48,14 @@ class SVGLayoutState { TextAnchor text_anchor() const { return m_text_anchor; } WhiteSpace white_space() const { return m_white_space; } + WritingMode writing_mode() const { return m_writing_mode; } + TextOrientation text_orientation() const { return m_text_orientation; } Direction direction() const { return m_direction; } Display display() const { return m_display; } Visibility visibility() const { return m_visibility; } Overflow overflow() const { return m_overflow; } + PointerEvents pointer_events() const { return m_pointer_events; } MaskType mask_type() const { return m_mask_type; } const std::string& mask() const { return m_mask; } @@ -78,7 +84,10 @@ class SVGLayoutState { float m_stroke_miterlimit = 4.f; float m_font_size = 12.f; - BaselineShift m_baseline_shit; + Length m_letter_spacing{0.f, LengthUnits::None}; + Length m_word_spacing{0.f, LengthUnits::None}; + + BaselineShift m_baseline_shift; Length m_stroke_width{1.f, LengthUnits::None}; Length m_stroke_dashoffset{0.f, LengthUnits::None}; LengthList m_stroke_dasharray; @@ -97,11 +106,14 @@ class SVGLayoutState { TextAnchor m_text_anchor = TextAnchor::Start; WhiteSpace m_white_space = WhiteSpace::Default; + WritingMode m_writing_mode = WritingMode::Horizontal; + TextOrientation m_text_orientation = TextOrientation::Mixed; Direction m_direction = Direction::Ltr; Display m_display = Display::Inline; Visibility m_visibility = Visibility::Visible; Overflow m_overflow = Overflow::Visible; + PointerEvents m_pointer_events = PointerEvents::Auto; MaskType m_mask_type = MaskType::Luminance; std::string m_mask; diff --git a/vendor/lunasvg/source/svgpaintelement.cpp b/vendor/lunasvg/source/svgpaintelement.cpp index 16c3e3db0b..9432d6edf5 100644 --- a/vendor/lunasvg/source/svgpaintelement.cpp +++ b/vendor/lunasvg/source/svgpaintelement.cpp @@ -2,6 +2,7 @@ #include "svglayoutstate.h" #include "svgrenderstate.h" +#include #include namespace lunasvg { @@ -115,9 +116,13 @@ SVGLinearGradientAttributes SVGLinearGradientElement::collectGradientAttributes( static GradientStops buildGradientStops(const SVGGradientElement* element, float opacity) { GradientStops gradientStops; - for(const auto& child : element->children()) { - if(auto element = toSVGElement(child); element && element->id() == ElementID::Stop) { - auto stopElement = static_cast(element); + + const auto& children = element->children(); + gradientStops.reserve(children.size()); + for(const auto& child : children) { + auto childElement = toSVGElement(child); + if(childElement && childElement->id() == ElementID::Stop) { + auto stopElement = static_cast(childElement); gradientStops.push_back(stopElement->gradientStop(opacity)); } } @@ -304,7 +309,7 @@ bool SVGPatternElement::applyPaint(SVGRenderState& state, float opacity) const auto patternTransform = attributes.patternTransform(); patternTransform.translate(patternRect.x, patternRect.y); - patternTransform.scale(1.f / xScale, 1.f / yScale); + patternTransform.scale(patternRect.w / patternImage->width(), patternRect.h / patternImage->height()); state->setTexture(*patternImage, TextureType::Tiled, opacity, patternTransform); return true; } diff --git a/vendor/lunasvg/source/svgparser.cpp b/vendor/lunasvg/source/svgparser.cpp index 399fc9b2e6..fa465887a9 100644 --- a/vendor/lunasvg/source/svgparser.cpp +++ b/vendor/lunasvg/source/svgparser.cpp @@ -214,7 +214,7 @@ static bool matchPseudoClassSelector(const PseudoClassSelector& selector, const while(sibling) { if(sibling->id() == element->id()) return false; - sibling = element->previousElement(); + sibling = sibling->previousElement(); } return true; @@ -225,7 +225,7 @@ static bool matchPseudoClassSelector(const PseudoClassSelector& selector, const while(sibling) { if(sibling->id() == element->id()) return false; - sibling = element->nextElement(); + sibling = sibling->nextElement(); } return true; @@ -571,6 +571,9 @@ static RuleDataList parseStyleSheet(std::string_view input) for(const auto& attributeSelector : simpleSelector.attributeSelectors) { specificity += (attributeSelector.id == PropertyID::Id) ? 0x10000 : 0x100; } + for(const auto& pseudoClassSelector : simpleSelector.pseudoClassSelectors) { + specificity += 0x100; + } } rules.emplace_back(selector, rule.declarations, specificity, rules.size()); @@ -738,6 +741,17 @@ bool Document::parse(const char* data, size_t length) }; std::string_view input(data, length); + if(length >= 3) { + auto buffer = (const uint8_t*)(data); + + const auto c1 = buffer[0]; + const auto c2 = buffer[1]; + const auto c3 = buffer[2]; + if(c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) { + input.remove_prefix(3); + } + } + while(!input.empty()) { if(currentElement) { auto text = input.substr(0, input.find('<')); diff --git a/vendor/lunasvg/source/svgproperty.cpp b/vendor/lunasvg/source/svgproperty.cpp index 8846d90647..e9b6ea659e 100644 --- a/vendor/lunasvg/source/svgproperty.cpp +++ b/vendor/lunasvg/source/svgproperty.cpp @@ -26,6 +26,7 @@ PropertyID propertyid(const std::string_view& name) {"height", PropertyID::Height}, {"href", PropertyID::Href}, {"id", PropertyID::Id}, + {"lengthAdjust", PropertyID::LengthAdjust}, {"markerHeight", PropertyID::MarkerHeight}, {"markerUnits", PropertyID::MarkerUnits}, {"markerWidth", PropertyID::MarkerWidth}, @@ -46,6 +47,7 @@ PropertyID propertyid(const std::string_view& name) {"ry", PropertyID::Ry}, {"spreadMethod", PropertyID::SpreadMethod}, {"style", PropertyID::Style}, + {"textLength", PropertyID::TextLength}, {"transform", PropertyID::Transform}, {"viewBox", PropertyID::ViewBox}, {"width", PropertyID::Width}, @@ -53,7 +55,7 @@ PropertyID propertyid(const std::string_view& name) {"x1", PropertyID::X1}, {"x2", PropertyID::X2}, {"xlink:href", PropertyID::Href}, - {"xml:space", PropertyID::WhiteSpace}, + {"xml:space", PropertyID::White_Space}, {"y", PropertyID::Y}, {"y1", PropertyID::Y1}, {"y2", PropertyID::Y2} @@ -86,6 +88,7 @@ PropertyID csspropertyid(const std::string_view& name) {"font-size", PropertyID::Font_Size}, {"font-style", PropertyID::Font_Style}, {"font-weight", PropertyID::Font_Weight}, + {"letter-spacing", PropertyID::Letter_Spacing}, {"marker-end", PropertyID::Marker_End}, {"marker-mid", PropertyID::Marker_Mid}, {"marker-start", PropertyID::Marker_Start}, @@ -93,6 +96,7 @@ PropertyID csspropertyid(const std::string_view& name) {"mask-type", PropertyID::Mask_Type}, {"opacity", PropertyID::Opacity}, {"overflow", PropertyID::Overflow}, + {"pointer-events", PropertyID::Pointer_Events}, {"stop-color", PropertyID::Stop_Color}, {"stop-opacity", PropertyID::Stop_Opacity}, {"stroke", PropertyID::Stroke}, @@ -104,8 +108,11 @@ PropertyID csspropertyid(const std::string_view& name) {"stroke-opacity", PropertyID::Stroke_Opacity}, {"stroke-width", PropertyID::Stroke_Width}, {"text-anchor", PropertyID::Text_Anchor}, + {"text-orientation", PropertyID::Text_Orientation}, {"visibility", PropertyID::Visibility}, - {"white-space", PropertyID::WhiteSpace} + {"white-space", PropertyID::White_Space}, + {"word-spacing", PropertyID::Word_Spacing}, + {"writing-mode", PropertyID::Writing_Mode} }; auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; }); @@ -160,6 +167,17 @@ bool SVGEnumeration::parse(std::string_view input) return parseEnum(input, entries); } +template<> +bool SVGEnumeration::parse(std::string_view input) +{ + static const SVGEnumerationEntry entries[] = { + {LengthAdjust::Spacing, "spacing"}, + {LengthAdjust::SpacingAndGlyphs, "spacingAndGlyphs"} + }; + + return parseEnum(input, entries); +} + template template bool SVGEnumeration::parseEnum(std::string_view input, const SVGEnumerationEntry(&entries)[N]) diff --git a/vendor/lunasvg/source/svgproperty.h b/vendor/lunasvg/source/svgproperty.h index 73163fd964..59ee12d0fe 100644 --- a/vendor/lunasvg/source/svgproperty.h +++ b/vendor/lunasvg/source/svgproperty.h @@ -12,9 +12,9 @@ enum class PropertyID : uint8_t { Alignment_Baseline, Baseline_Shift, Class, - ClipPathUnits, Clip_Path, Clip_Rule, + ClipPathUnits, Color, Cx, Cy, @@ -38,16 +38,18 @@ enum class PropertyID : uint8_t { Height, Href, Id, - MarkerHeight, - MarkerUnits, - MarkerWidth, + LengthAdjust, + Letter_Spacing, Marker_End, Marker_Mid, Marker_Start, + MarkerHeight, + MarkerUnits, + MarkerWidth, Mask, + Mask_Type, MaskContentUnits, MaskUnits, - Mask_Type, Offset, Opacity, Orient, @@ -55,6 +57,7 @@ enum class PropertyID : uint8_t { PatternContentUnits, PatternTransform, PatternUnits, + Pointer_Events, Points, PreserveAspectRatio, R, @@ -76,11 +79,15 @@ enum class PropertyID : uint8_t { Stroke_Width, Style, Text_Anchor, + Text_Orientation, + TextLength, Transform, ViewBox, Visibility, - WhiteSpace, + White_Space, Width, + Word_Spacing, + Writing_Mode, X, X1, X2, @@ -154,6 +161,20 @@ enum class Overflow : uint8_t { Hidden }; +enum class PointerEvents : uint8_t { + None, + Auto, + Stroke, + Fill, + Painted, + Visible, + VisibleStroke, + VisibleFill, + VisiblePainted, + BoundingBox, + All +}; + enum class FontStyle : uint8_t { Normal, Italic @@ -205,6 +226,16 @@ enum class WhiteSpace : uint8_t { Preserve }; +enum class WritingMode : uint8_t { + Horizontal, + Vertical +}; + +enum class TextOrientation : uint8_t { + Mixed, + Upright +}; + enum class Direction : uint8_t { Ltr, Rtl @@ -225,6 +256,11 @@ enum class MarkerUnits : uint8_t { UserSpaceOnUse }; +enum class LengthAdjust : uint8_t { + Spacing, + SpacingAndGlyphs +}; + template using SVGEnumerationEntry = std::pair; diff --git a/vendor/lunasvg/source/svgtextelement.cpp b/vendor/lunasvg/source/svgtextelement.cpp index 34d358c453..6d43e57dd7 100644 --- a/vendor/lunasvg/source/svgtextelement.cpp +++ b/vendor/lunasvg/source/svgtextelement.cpp @@ -22,6 +22,9 @@ static AlignmentBaseline resolveDominantBaseline(const SVGTextPositioningElement { switch(element->dominant_baseline()) { case DominantBaseline::Auto: + if(element->isVerticalWritingMode()) + return AlignmentBaseline::Central; + return AlignmentBaseline::Alphabetic; case DominantBaseline::UseScript: case DominantBaseline::NoChange: case DominantBaseline::ResetSize: @@ -131,6 +134,93 @@ static float calculateTextAnchorOffset(const SVGTextPositioningElement* element, return 0.f; } +using SVGTextFragmentIterator = SVGTextFragmentList::iterator; + +static float calculateTextChunkLength(SVGTextFragmentIterator begin, SVGTextFragmentIterator end, bool isVerticalText) +{ + float chunkLength = 0; + const SVGTextFragment* lastFragment = nullptr; + for(auto it = begin; it != end; ++it) { + const SVGTextFragment& fragment = *it; + chunkLength += isVerticalText ? fragment.height : fragment.width; + if(!lastFragment) { + lastFragment = &fragment; + continue; + } + + if(isVerticalText) { + chunkLength += fragment.y - (lastFragment->y + lastFragment->height); + } else { + chunkLength += fragment.x - (lastFragment->x + lastFragment->width); + } + + lastFragment = &fragment; + } + + return chunkLength; +} + +static void handleTextChunk(SVGTextFragmentIterator begin, SVGTextFragmentIterator end) +{ + const SVGTextFragment& firstFragment = *begin; + const auto isVerticalText = firstFragment.element->isVerticalWritingMode(); + if(firstFragment.element->hasAttribute(PropertyID::TextLength)) { + LengthContext lengthContext(firstFragment.element); + auto textLength = lengthContext.valueForLength(firstFragment.element->textLength()); + auto chunkLength = calculateTextChunkLength(begin, end, isVerticalText); + if(textLength > 0.f && chunkLength > 0.f) { + size_t numCharacters = 0; + for(auto it = begin; it != end; ++it) { + const SVGTextFragment& fragment = *it; + numCharacters += fragment.length; + } + + if(firstFragment.element->lengthAdjust() == LengthAdjust::SpacingAndGlyphs) { + auto textLengthScale = textLength / chunkLength; + auto lengthAdjustTransform = Transform::translated(firstFragment.x, firstFragment.y); + if(isVerticalText) { + lengthAdjustTransform.scale(1.f, textLengthScale); + } else { + lengthAdjustTransform.scale(textLengthScale, 1.f); + } + + lengthAdjustTransform.translate(-firstFragment.x, -firstFragment.y); + for(auto it = begin; it != end; ++it) { + SVGTextFragment& fragment = *it; + fragment.lengthAdjustTransform = lengthAdjustTransform; + } + } else if(numCharacters > 1) { + assert(firstFragment.element->lengthAdjust() == LengthAdjust::Spacing); + size_t characterOffset = 0; + auto textLengthShift = (textLength - chunkLength) / (numCharacters - 1); + for(auto it = begin; it != end; ++it) { + SVGTextFragment& fragment = *it; + if(isVerticalText) { + fragment.y += textLengthShift * characterOffset; + } else { + fragment.x += textLengthShift * characterOffset; + } + + characterOffset += fragment.length; + } + } + } + } + + if(needsTextAnchorAdjustment(firstFragment.element)) { + auto chunkLength = calculateTextChunkLength(begin, end, isVerticalText); + auto chunkOffset = calculateTextAnchorOffset(firstFragment.element, chunkLength); + for(auto it = begin; it != end; ++it) { + SVGTextFragment& fragment = *it; + if(isVerticalText) { + fragment.y += chunkOffset; + } else { + fragment.x += chunkOffset; + } + } + } +} + SVGTextFragmentsBuilder::SVGTextFragmentsBuilder(std::u32string& text, SVGTextFragmentList& fragments) : m_text(text), m_fragments(fragments) { @@ -150,34 +240,50 @@ void SVGTextFragmentsBuilder::build(const SVGTextElement* textElement) if(!textPosition.node->isTextNode()) continue; auto element = toSVGTextPositioningElement(textPosition.node->parentElement()); + const auto isVerticalText = element->isVerticalWritingMode(); + const auto isUprightText = element->isUprightTextOrientation(); + const auto& font = element->font(); + SVGTextFragment fragment(element); auto recordTextFragment = [&](auto startOffset, auto endOffset) { auto text = wholeText.substr(startOffset, endOffset - startOffset); fragment.offset = startOffset; fragment.length = text.length(); - fragment.width = element->font().measureText(text); + fragment.width = font.measureText(text); + fragment.height = font.height() + font.lineGap(); + if(isVerticalText) { + m_y += isUprightText ? fragment.height : fragment.width; + } else { + m_x += fragment.width; + } + m_fragments.push_back(fragment); - m_x += fragment.width; }; + auto needsTextLengthSpacing = element->lengthAdjust() == LengthAdjust::Spacing && element->hasAttribute(PropertyID::TextLength); auto baselineOffset = calculateBaselineOffset(element); auto startOffset = textPosition.startOffset; auto textOffset = textPosition.startOffset; auto didStartTextFragment = false; + auto applySpacingToNextCharacter = false; + auto lastCharacter = 0u; auto lastAngle = 0.f; while(textOffset < textPosition.endOffset) { SVGCharacterPosition characterPosition; - if(m_characterPositions.count(m_characterOffset) > 0) { - characterPosition = m_characterPositions.at(m_characterOffset); + if(auto it = m_characterPositions.find(m_characterOffset); it != m_characterPositions.end()) { + characterPosition = it->second; } + auto currentCharacter = wholeText.at(textOffset); auto angle = characterPosition.rotate.value_or(0); auto dx = characterPosition.dx.value_or(0); auto dy = characterPosition.dy.value_or(0); - auto shouldStartNewFragment = characterPosition.x || characterPosition.y || dx || dy || angle || angle != lastAngle; + auto shouldStartNewFragment = needsTextLengthSpacing || isVerticalText || applySpacingToNextCharacter + || characterPosition.x || characterPosition.y || dx || dy || angle || angle != lastAngle; if(shouldStartNewFragment && didStartTextFragment) { recordTextFragment(startOffset, textOffset); + applySpacingToNextCharacter = false; startOffset = textOffset; } @@ -185,14 +291,39 @@ void SVGTextFragmentsBuilder::build(const SVGTextElement* textElement) if(startsNewTextChunk || shouldStartNewFragment || !didStartTextFragment) { m_x = dx + characterPosition.x.value_or(m_x); m_y = dy + characterPosition.y.value_or(m_y); - fragment.x = m_x; - fragment.y = m_y - baselineOffset; + fragment.x = isVerticalText ? m_x + baselineOffset : m_x; + fragment.y = isVerticalText ? m_y : m_y - baselineOffset; fragment.angle = angle; + if(isVerticalText) { + if(isUprightText) { + fragment.y += font.height(); + } else { + fragment.angle += 90.f; + } + } + fragment.startsNewTextChunk = startsNewTextChunk; didStartTextFragment = true; } + auto spacing = element->letter_spacing(); + if(currentCharacter && lastCharacter && element->word_spacing()) { + if(currentCharacter == ' ' && lastCharacter != ' ') { + spacing += element->word_spacing(); + } + } + + if(spacing) { + applySpacingToNextCharacter = true; + if(isVerticalText) { + m_y += spacing; + } else { + m_x += spacing; + } + } + lastAngle = angle; + lastCharacter = currentCharacter; ++textOffset; ++m_characterOffset; } @@ -200,26 +331,6 @@ void SVGTextFragmentsBuilder::build(const SVGTextElement* textElement) recordTextFragment(startOffset, textOffset); } - auto handleTextChunk = [](auto begin, auto end) { - if(!needsTextAnchorAdjustment(begin->element)) - return; - float chunkWidth = 0.f; - const SVGTextFragment* lastFragment = nullptr; - for(auto it = begin; it != end; ++it) { - const SVGTextFragment& fragment = *it; - chunkWidth += fragment.width; - if(lastFragment) - chunkWidth += fragment.x - (lastFragment->x + lastFragment->width); - lastFragment = &fragment; - } - - auto chunkOffset = calculateTextAnchorOffset(begin->element, chunkWidth); - for(auto it = begin; it != end; ++it) { - SVGTextFragment& fragment = *it; - fragment.x += chunkOffset; - } - }; - if(m_fragments.empty()) return; auto it = m_fragments.begin(); @@ -267,6 +378,8 @@ void SVGTextFragmentsBuilder::handleText(const SVGTextNode* node) void SVGTextFragmentsBuilder::handleElement(const SVGTextPositioningElement* element) { + if(element->isDisplayNone()) + return; const auto itemIndex = m_textPositions.size(); m_textPositions.emplace_back(element, m_text.length(), m_text.length()); for(const auto& child : element->children()) { @@ -338,12 +451,16 @@ SVGTextPositioningElement::SVGTextPositioningElement(Document* document, Element , m_dx(PropertyID::Dx, LengthDirection::Horizontal, LengthNegativeMode::Allow) , m_dy(PropertyID::Dy, LengthDirection::Vertical, LengthNegativeMode::Allow) , m_rotate(PropertyID::Rotate) + , m_textLength(PropertyID::TextLength, LengthDirection::Horizontal, LengthNegativeMode::Forbid) + , m_lengthAdjust(PropertyID::LengthAdjust, LengthAdjust::Spacing) { addProperty(m_x); addProperty(m_y); addProperty(m_dx); addProperty(m_dy); addProperty(m_rotate); + addProperty(m_textLength); + addProperty(m_lengthAdjust); } void SVGTextPositioningElement::layoutElement(const SVGLayoutState& state) @@ -355,11 +472,16 @@ void SVGTextPositioningElement::layoutElement(const SVGLayoutState& state) LengthContext lengthContext(this); m_stroke_width = lengthContext.valueForLength(state.stroke_width(), LengthDirection::Diagonal); - m_baseline_offset = convertBaselineOffset(state.baseline_shit()); + m_letter_spacing = lengthContext.valueForLength(state.letter_spacing(), LengthDirection::Diagonal); + m_word_spacing = lengthContext.valueForLength(state.word_spacing(), LengthDirection::Diagonal); + + m_baseline_offset = convertBaselineOffset(state.baseline_shift()); m_alignment_baseline = state.alignment_baseline(); m_dominant_baseline = state.dominant_baseline(); m_text_anchor = state.text_anchor(); m_white_space = state.white_space(); + m_writing_mode = state.writing_mode(); + m_text_orientation = state.text_orientation(); m_direction = state.direction(); } @@ -412,7 +534,9 @@ void SVGTextElement::render(SVGRenderState& state) const std::u32string_view wholeText(m_text); for(const auto& fragment : m_fragments) { - auto transform = newState.currentTransform() * Transform::rotated(fragment.angle, fragment.x, fragment.y); + if(fragment.element->isVisibilityHidden()) + continue; + auto transform = newState.currentTransform() * Transform::rotated(fragment.angle, fragment.x, fragment.y) * fragment.lengthAdjustTransform; auto text = wholeText.substr(fragment.offset, fragment.length); auto origin = Point(fragment.x, fragment.y); @@ -440,8 +564,8 @@ Rect SVGTextElement::boundingBox(bool includeStroke) const for(const auto& fragment : m_fragments) { const auto& font = fragment.element->font(); const auto& stroke = fragment.element->stroke(); - auto fragmentTranform = Transform::rotated(fragment.angle, fragment.x, fragment.y); - auto fragmentRect = Rect(fragment.x, fragment.y - font.ascent(), fragment.width, fragment.element->font_size()); + auto fragmentTranform = Transform::rotated(fragment.angle, fragment.x, fragment.y) * fragment.lengthAdjustTransform; + auto fragmentRect = Rect(fragment.x, fragment.y - font.ascent(), fragment.width, fragment.height); if(includeStroke && stroke.isRenderable()) fragmentRect.inflate(fragment.element->stroke_width() / 2.f); boundingBox.unite(fragmentTranform.mapRect(fragmentRect)); diff --git a/vendor/lunasvg/source/svgtextelement.h b/vendor/lunasvg/source/svgtextelement.h index 8b0a0e24b8..71b0f1121d 100644 --- a/vendor/lunasvg/source/svgtextelement.h +++ b/vendor/lunasvg/source/svgtextelement.h @@ -35,13 +35,15 @@ using SVGTextPositionList = std::vector; struct SVGTextFragment { explicit SVGTextFragment(const SVGTextPositioningElement* element) : element(element) {} const SVGTextPositioningElement* element; + Transform lengthAdjustTransform; size_t offset = 0; size_t length = 0; + bool startsNewTextChunk = false; float x = 0; float y = 0; - float angle = 0; float width = 0; - bool startsNewTextChunk = false; + float height = 0; + float angle = 0; }; using SVGTextFragmentList = std::vector; @@ -77,11 +79,19 @@ class SVGTextPositioningElement : public SVGGraphicsElement { const LengthList& dy() const { return m_dy.values(); } const NumberList& rotate() const { return m_rotate.values(); } + const SVGLength& textLength() const { return m_textLength; } + LengthAdjust lengthAdjust() const { return m_lengthAdjust.value(); } + const Font& font() const { return m_font; } const SVGPaintServer& fill() const { return m_fill; } const SVGPaintServer& stroke() const { return m_stroke; } + bool isVerticalWritingMode() const { return m_writing_mode == WritingMode::Vertical; } + bool isUprightTextOrientation() const { return m_text_orientation == TextOrientation::Upright; } + float stroke_width() const { return m_stroke_width; } + float letter_spacing() const { return m_letter_spacing; } + float word_spacing() const { return m_word_spacing; } float baseline_offset() const { return m_baseline_offset; } AlignmentBaseline alignment_baseline() const { return m_alignment_baseline; } DominantBaseline dominant_baseline() const { return m_dominant_baseline; } @@ -99,16 +109,23 @@ class SVGTextPositioningElement : public SVGGraphicsElement { SVGLengthList m_dy; SVGNumberList m_rotate; + SVGLength m_textLength; + SVGEnumeration m_lengthAdjust; + Font m_font; SVGPaintServer m_fill; SVGPaintServer m_stroke; float m_stroke_width = 1.f; + float m_letter_spacing = 0.f; + float m_word_spacing = 0.f; float m_baseline_offset = 0.f; AlignmentBaseline m_alignment_baseline = AlignmentBaseline::Auto; DominantBaseline m_dominant_baseline = DominantBaseline::Auto; TextAnchor m_text_anchor = TextAnchor::Start; WhiteSpace m_white_space = WhiteSpace::Default; + WritingMode m_writing_mode = WritingMode::Horizontal; + TextOrientation m_text_orientation = TextOrientation::Mixed; Direction m_direction = Direction::Ltr; };