@@ -4,7 +4,9 @@
|
||||
"Bash(cd /c/Users/mta/projects/autosample && ./nob.exe debug 2>&1)",
|
||||
"Bash(cd /c/Users/mta/projects/autosample && rm -f build/*.pdb build/*.obj build/*.ilk && ./nob.exe debug 2>&1)",
|
||||
"Bash(cd /c/Users/mta/projects/autosample && ./nob.exe 2>&1)",
|
||||
"Bash(cd:*)"
|
||||
"Bash(cd:*)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(./build.exe:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
274
build.c
274
build.c
@@ -6,36 +6,6 @@
|
||||
#define NOB_IMPLEMENTATION
|
||||
#include "vendor/nob/nob.h"
|
||||
|
||||
////////////////////////////////
|
||||
// lunasvg/plutovg static library build helpers
|
||||
|
||||
static const char *plutovg_sources[] = {
|
||||
"vendor/lunasvg/plutovg/source/plutovg-blend.c",
|
||||
"vendor/lunasvg/plutovg/source/plutovg-canvas.c",
|
||||
"vendor/lunasvg/plutovg/source/plutovg-font.c",
|
||||
"vendor/lunasvg/plutovg/source/plutovg-ft-math.c",
|
||||
"vendor/lunasvg/plutovg/source/plutovg-ft-raster.c",
|
||||
"vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c",
|
||||
"vendor/lunasvg/plutovg/source/plutovg-matrix.c",
|
||||
"vendor/lunasvg/plutovg/source/plutovg-paint.c",
|
||||
"vendor/lunasvg/plutovg/source/plutovg-path.c",
|
||||
"vendor/lunasvg/plutovg/source/plutovg-rasterize.c",
|
||||
"vendor/lunasvg/plutovg/source/plutovg-surface.c",
|
||||
};
|
||||
|
||||
static const char *lunasvg_sources[] = {
|
||||
"vendor/lunasvg/source/graphics.cpp",
|
||||
"vendor/lunasvg/source/lunasvg.cpp",
|
||||
"vendor/lunasvg/source/svgelement.cpp",
|
||||
"vendor/lunasvg/source/svggeometryelement.cpp",
|
||||
"vendor/lunasvg/source/svglayoutstate.cpp",
|
||||
"vendor/lunasvg/source/svgpaintelement.cpp",
|
||||
"vendor/lunasvg/source/svgparser.cpp",
|
||||
"vendor/lunasvg/source/svgproperty.cpp",
|
||||
"vendor/lunasvg/source/svgrenderstate.cpp",
|
||||
"vendor/lunasvg/source/svgtextelement.cpp",
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// FreeType source files
|
||||
|
||||
@@ -95,7 +65,7 @@ static bool embed_font_file(const char *ttf_path, const char *header_path) {
|
||||
|
||||
#ifdef __APPLE__
|
||||
////////////////////////////////
|
||||
// macOS build (clang++ with Objective-C++)
|
||||
// macOS build (clang with Objective-C)
|
||||
|
||||
static const char *frameworks[] = {
|
||||
"-framework", "Metal",
|
||||
@@ -107,128 +77,6 @@ static const char *frameworks[] = {
|
||||
"-framework", "CoreFoundation",
|
||||
};
|
||||
|
||||
static bool build_lunasvg_lib(const char *build_dir, bool debug) {
|
||||
const char *obj_dir = nob_temp_sprintf("%s/lunasvg_obj", build_dir);
|
||||
const char *lib_path = "vendor/lunasvg/liblunasvg.a";
|
||||
|
||||
// Collect all source paths to check if rebuild is needed
|
||||
{
|
||||
const char *all_sources[NOB_ARRAY_LEN(plutovg_sources) + NOB_ARRAY_LEN(lunasvg_sources)];
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++)
|
||||
all_sources[n++] = plutovg_sources[i];
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++)
|
||||
all_sources[n++] = lunasvg_sources[i];
|
||||
|
||||
if (!nob_needs_rebuild(lib_path, all_sources, n)) {
|
||||
nob_log(NOB_INFO, "lunasvg is up to date");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nob_mkdir_if_not_exists(obj_dir)) return false;
|
||||
|
||||
// Compile plutovg C sources
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) {
|
||||
const char *src = plutovg_sources[i];
|
||||
// Extract filename for .o
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char obj_name[256];
|
||||
snprintf(obj_name, sizeof(obj_name), "%s", base);
|
||||
char *dot = strrchr(obj_name, '.');
|
||||
if (dot) { dot[1] = 'o'; dot[2] = '\0'; }
|
||||
const char *obj_path = nob_temp_sprintf("%s/%s", obj_dir, obj_name);
|
||||
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "clang");
|
||||
nob_cmd_append(&cmd, "-std=c11", "-c");
|
||||
nob_cmd_append(&cmd, "-DPLUTOVG_BUILD", "-DPLUTOVG_BUILD_STATIC");
|
||||
nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include");
|
||||
nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/source");
|
||||
nob_cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
|
||||
if (debug) {
|
||||
nob_cmd_append(&cmd, "-g", "-O0");
|
||||
} else {
|
||||
nob_cmd_append(&cmd, "-O2");
|
||||
}
|
||||
nob_cmd_append(&cmd, "-o", obj_path);
|
||||
nob_cmd_append(&cmd, src);
|
||||
Nob_Cmd_Opt copt = {0};
|
||||
if (!nob_cmd_run_opt(&cmd, copt)) return false;
|
||||
}
|
||||
|
||||
// Compile lunasvg C++ sources
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) {
|
||||
const char *src = lunasvg_sources[i];
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char obj_name[256];
|
||||
snprintf(obj_name, sizeof(obj_name), "%s", base);
|
||||
char *dot = strrchr(obj_name, '.');
|
||||
if (dot) { dot[1] = 'o'; dot[2] = '\0'; }
|
||||
const char *obj_path = nob_temp_sprintf("%s/%s", obj_dir, obj_name);
|
||||
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "clang++");
|
||||
nob_cmd_append(&cmd, "-std=c++17", "-c");
|
||||
nob_cmd_append(&cmd, "-fno-exceptions", "-fno-rtti");
|
||||
nob_cmd_append(&cmd, "-DLUNASVG_BUILD", "-DLUNASVG_BUILD_STATIC", "-DPLUTOVG_BUILD_STATIC");
|
||||
nob_cmd_append(&cmd, "-Ivendor/lunasvg/include");
|
||||
nob_cmd_append(&cmd, "-Ivendor/lunasvg/source");
|
||||
nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include");
|
||||
nob_cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
|
||||
if (debug) {
|
||||
nob_cmd_append(&cmd, "-g", "-O0");
|
||||
} else {
|
||||
nob_cmd_append(&cmd, "-O2");
|
||||
}
|
||||
nob_cmd_append(&cmd, "-o", obj_path);
|
||||
nob_cmd_append(&cmd, src);
|
||||
Nob_Cmd_Opt copt = {0};
|
||||
if (!nob_cmd_run_opt(&cmd, copt)) return false;
|
||||
}
|
||||
|
||||
// Archive into static library
|
||||
{
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "ar", "rcs", lib_path);
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) {
|
||||
const char *src = plutovg_sources[i];
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char obj_name[256];
|
||||
snprintf(obj_name, sizeof(obj_name), "%s", base);
|
||||
char *dot = strrchr(obj_name, '.');
|
||||
if (dot) { dot[1] = 'o'; dot[2] = '\0'; }
|
||||
nob_cmd_append(&cmd, nob_temp_sprintf("%s/%s", obj_dir, obj_name));
|
||||
}
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) {
|
||||
const char *src = lunasvg_sources[i];
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char obj_name[256];
|
||||
snprintf(obj_name, sizeof(obj_name), "%s", base);
|
||||
char *dot = strrchr(obj_name, '.');
|
||||
if (dot) { dot[1] = 'o'; dot[2] = '\0'; }
|
||||
nob_cmd_append(&cmd, nob_temp_sprintf("%s/%s", obj_dir, obj_name));
|
||||
}
|
||||
Nob_Cmd_Opt copt = {0};
|
||||
if (!nob_cmd_run_opt(&cmd, copt)) return false;
|
||||
}
|
||||
|
||||
// Clean up obj dir — only the .a is needed
|
||||
{
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "rm", "-rf", obj_dir);
|
||||
Nob_Cmd_Opt copt = {0};
|
||||
nob_cmd_run_opt(&cmd, copt);
|
||||
}
|
||||
|
||||
nob_log(NOB_INFO, "Built %s", lib_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_freetype_lib(const char *build_dir, bool debug) {
|
||||
const char *obj_dir = nob_temp_sprintf("%s/freetype_obj", build_dir);
|
||||
const char *lib_path = "vendor/freetype/libfreetype.a";
|
||||
@@ -320,7 +168,6 @@ int main(int argc, char **argv) {
|
||||
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); }
|
||||
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "rm", "-rf", "build_release");
|
||||
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); }
|
||||
remove("vendor/lunasvg/liblunasvg.a");
|
||||
remove("vendor/freetype/libfreetype.a");
|
||||
remove("src/renderer/font_inter.gen.h");
|
||||
return 0;
|
||||
@@ -340,25 +187,22 @@ int main(int argc, char **argv) {
|
||||
if (!nob_mkdir_if_not_exists(res_dir)) return 1;
|
||||
|
||||
// Build static libraries
|
||||
if (!build_lunasvg_lib(build_dir, debug)) return 1;
|
||||
if (!build_freetype_lib(build_dir, debug)) return 1;
|
||||
|
||||
// Generate embedded font header
|
||||
if (!embed_font_file("assets/fonts/Inter-Regular.ttf",
|
||||
"src/renderer/font_inter.gen.h")) return 1;
|
||||
|
||||
// Unity build: single clang++ invocation compiles main.cpp (which #includes everything)
|
||||
// Unity build: single clang invocation compiles main.c (which #includes everything)
|
||||
{
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "clang++");
|
||||
nob_cmd_append(&cmd, "-std=c++20", "-x", "objective-c++");
|
||||
nob_cmd_append(&cmd, "-fno-exceptions", "-fno-rtti");
|
||||
nob_cmd_append(&cmd, "clang");
|
||||
nob_cmd_append(&cmd, "-std=c11", "-x", "objective-c");
|
||||
nob_cmd_append(&cmd, "-Wall", "-Wextra", "-Wno-missing-field-initializers");
|
||||
nob_cmd_append(&cmd, "-Wno-deprecated-declarations");
|
||||
nob_cmd_append(&cmd, "-Isrc", "-Ivendor/clay");
|
||||
nob_cmd_append(&cmd, "-Ivendor/lunasvg/include");
|
||||
nob_cmd_append(&cmd, "-Ivendor/nanosvg");
|
||||
nob_cmd_append(&cmd, "-Ivendor/freetype/include");
|
||||
nob_cmd_append(&cmd, "-DLUNASVG_BUILD_STATIC");
|
||||
|
||||
if (debug) {
|
||||
nob_cmd_append(&cmd, "-g", "-O0", "-D_DEBUG");
|
||||
@@ -367,11 +211,10 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
nob_cmd_append(&cmd, "-o", binary_path);
|
||||
nob_cmd_append(&cmd, "src/main.cpp");
|
||||
nob_cmd_append(&cmd, "src/main.c");
|
||||
|
||||
// Reset language mode so .a is treated as a library, not source
|
||||
nob_cmd_append(&cmd, "-x", "none");
|
||||
nob_cmd_append(&cmd, "vendor/lunasvg/liblunasvg.a");
|
||||
nob_cmd_append(&cmd, "vendor/freetype/libfreetype.a");
|
||||
|
||||
{
|
||||
@@ -379,7 +222,7 @@ int main(int argc, char **argv) {
|
||||
for (i = 0; i < NOB_ARRAY_LEN(frameworks); i++)
|
||||
nob_cmd_append(&cmd, frameworks[i]);
|
||||
}
|
||||
nob_cmd_append(&cmd, "-lstdc++");
|
||||
nob_cmd_append(&cmd, "-lm");
|
||||
{ Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; }
|
||||
}
|
||||
|
||||
@@ -534,92 +377,6 @@ static bool compile_and_embed_shaders(const char *build_dir) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_lunasvg_lib(const char *build_dir, bool debug) {
|
||||
const char *obj_dir = nob_temp_sprintf("%s\\lunasvg_obj", build_dir);
|
||||
const char *lib_path = debug ? "vendor\\lunasvg\\lunasvg_d.lib" : "vendor\\lunasvg\\lunasvg.lib";
|
||||
|
||||
// Check if rebuild is needed
|
||||
{
|
||||
const char *all_sources[NOB_ARRAY_LEN(plutovg_sources) + NOB_ARRAY_LEN(lunasvg_sources)];
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++)
|
||||
all_sources[n++] = plutovg_sources[i];
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++)
|
||||
all_sources[n++] = lunasvg_sources[i];
|
||||
|
||||
if (!nob_needs_rebuild(lib_path, all_sources, n)) {
|
||||
nob_log(NOB_INFO, "lunasvg is up to date");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nob_mkdir_if_not_exists(obj_dir)) return false;
|
||||
|
||||
// Compile plutovg C sources
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) {
|
||||
const char *src = plutovg_sources[i];
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c");
|
||||
nob_cmd_append(&cmd, "/std:c11");
|
||||
nob_cmd_append(&cmd, "/DPLUTOVG_BUILD", "/DPLUTOVG_BUILD_STATIC");
|
||||
nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include");
|
||||
nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/source");
|
||||
nob_cmd_append(&cmd, "/W3");
|
||||
if (debug) {
|
||||
nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od");
|
||||
} else {
|
||||
nob_cmd_append(&cmd, "/MT", "/O2");
|
||||
}
|
||||
nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", obj_dir));
|
||||
nob_cmd_append(&cmd, src);
|
||||
Nob_Cmd_Opt copt = {0};
|
||||
if (!nob_cmd_run_opt(&cmd, copt)) return false;
|
||||
}
|
||||
|
||||
// Compile lunasvg C++ sources
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) {
|
||||
const char *src = lunasvg_sources[i];
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c");
|
||||
nob_cmd_append(&cmd, "/std:c++17", "/EHsc");
|
||||
nob_cmd_append(&cmd, "/DLUNASVG_BUILD", "/DLUNASVG_BUILD_STATIC", "/DPLUTOVG_BUILD_STATIC");
|
||||
nob_cmd_append(&cmd, "/Ivendor/lunasvg/include");
|
||||
nob_cmd_append(&cmd, "/Ivendor/lunasvg/source");
|
||||
nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include");
|
||||
nob_cmd_append(&cmd, "/W3");
|
||||
if (debug) {
|
||||
nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od");
|
||||
} else {
|
||||
nob_cmd_append(&cmd, "/MT", "/O2");
|
||||
}
|
||||
nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", obj_dir));
|
||||
nob_cmd_append(&cmd, src);
|
||||
Nob_Cmd_Opt copt = {0};
|
||||
if (!nob_cmd_run_opt(&cmd, copt)) return false;
|
||||
}
|
||||
|
||||
// Archive into static library
|
||||
{
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "lib.exe", "/nologo", nob_temp_sprintf("/OUT:%s", lib_path));
|
||||
nob_cmd_append(&cmd, nob_temp_sprintf("%s/*.obj", obj_dir));
|
||||
Nob_Cmd_Opt copt = {0};
|
||||
if (!nob_cmd_run_opt(&cmd, copt)) return false;
|
||||
}
|
||||
|
||||
// Clean up obj dir — only the .lib is needed
|
||||
{
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "cmd.exe", "/c",
|
||||
nob_temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir));
|
||||
Nob_Cmd_Opt copt = {0};
|
||||
nob_cmd_run_opt(&cmd, copt);
|
||||
}
|
||||
|
||||
nob_log(NOB_INFO, "Built %s", lib_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_freetype_lib(const char *build_dir, bool debug) {
|
||||
const char *obj_dir = nob_temp_sprintf("%s\\freetype_obj", build_dir);
|
||||
const char *lib_path = debug ? "vendor\\freetype\\freetype_d.lib" : "vendor\\freetype\\freetype.lib";
|
||||
@@ -640,7 +397,7 @@ static bool build_freetype_lib(const char *build_dir, bool debug) {
|
||||
nob_cmd_append(&cmd, "/Ivendor/freetype/include");
|
||||
nob_cmd_append(&cmd, "/W3");
|
||||
if (debug) {
|
||||
nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od");
|
||||
nob_cmd_append(&cmd, "/MTd", "/Zi");
|
||||
} else {
|
||||
nob_cmd_append(&cmd, "/MT", "/O2");
|
||||
}
|
||||
@@ -697,8 +454,6 @@ int main(int argc, char **argv) {
|
||||
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c",
|
||||
"if exist build_release rmdir /s /q build_release");
|
||||
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); }
|
||||
remove("vendor\\lunasvg\\lunasvg.lib");
|
||||
remove("vendor\\lunasvg\\lunasvg_d.lib");
|
||||
remove("vendor\\freetype\\freetype.lib");
|
||||
remove("vendor\\freetype\\freetype_d.lib");
|
||||
remove("src\\renderer\\font_inter.gen.h");
|
||||
@@ -710,7 +465,6 @@ int main(int argc, char **argv) {
|
||||
if (!nob_mkdir_if_not_exists(build_dir)) return 1;
|
||||
|
||||
// Build static libraries
|
||||
if (!build_lunasvg_lib(build_dir, debug)) return 1;
|
||||
if (!build_freetype_lib(build_dir, debug)) return 1;
|
||||
|
||||
// Generate embedded font header
|
||||
@@ -724,19 +478,18 @@ int main(int argc, char **argv) {
|
||||
const char *vk_include = nob_temp_sprintf("/I%s/Include", vk_sdk);
|
||||
const char *vk_lib = nob_temp_sprintf("%s/Lib/vulkan-1.lib", vk_sdk);
|
||||
|
||||
// Unity build: single cl.exe invocation compiles main.cpp (which #includes everything including the vulkan renderer)
|
||||
// Unity build: single cl.exe invocation compiles main.c (which #includes everything)
|
||||
{
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "cl.exe");
|
||||
nob_cmd_append(&cmd, "/nologo", "/std:c++20", "/EHsc", "/W3");
|
||||
nob_cmd_append(&cmd, "/nologo", "/std:c11", "/TC", "/W3");
|
||||
nob_cmd_append(&cmd, "/Isrc", "/Ivendor/clay");
|
||||
nob_cmd_append(&cmd, "/Ivendor/lunasvg/include");
|
||||
nob_cmd_append(&cmd, "/Ivendor/nanosvg");
|
||||
nob_cmd_append(&cmd, "/Ivendor/freetype/include");
|
||||
nob_cmd_append(&cmd, vk_include);
|
||||
nob_cmd_append(&cmd, "/DLUNASVG_BUILD_STATIC");
|
||||
|
||||
if (debug) {
|
||||
nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od", "/D_DEBUG");
|
||||
nob_cmd_append(&cmd, "/MTd", "/Zi", "/D_DEBUG");
|
||||
} else {
|
||||
nob_cmd_append(&cmd, "/MT", "/Zi", "/O2", "/DNDEBUG");
|
||||
}
|
||||
@@ -745,14 +498,13 @@ int main(int argc, char **argv) {
|
||||
nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", build_dir));
|
||||
nob_cmd_append(&cmd, nob_temp_sprintf("/Fd:%s/autosample.pdb", build_dir));
|
||||
|
||||
nob_cmd_append(&cmd, "src/main.cpp");
|
||||
nob_cmd_append(&cmd, "src/main.c");
|
||||
|
||||
nob_cmd_append(&cmd, "/link");
|
||||
nob_cmd_append(&cmd, "/MACHINE:X64");
|
||||
nob_cmd_append(&cmd, "/SUBSYSTEM:CONSOLE");
|
||||
nob_cmd_append(&cmd, nob_temp_sprintf("/PDB:%s/autosample.pdb", build_dir));
|
||||
nob_cmd_append(&cmd, "/DEBUG");
|
||||
nob_cmd_append(&cmd, debug ? "vendor/lunasvg/lunasvg_d.lib" : "vendor/lunasvg/lunasvg.lib");
|
||||
nob_cmd_append(&cmd, debug ? "vendor/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib");
|
||||
nob_cmd_append(&cmd, vk_lib);
|
||||
{
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
#include "base/base_core.h"
|
||||
|
||||
struct AudioEngine;
|
||||
typedef struct AudioEngine AudioEngine;
|
||||
|
||||
struct AudioDeviceInfo {
|
||||
typedef struct AudioDeviceInfo {
|
||||
char name[128];
|
||||
S32 id; // index into engine's device list
|
||||
};
|
||||
} AudioDeviceInfo;
|
||||
|
||||
AudioEngine *audio_create(void *hwnd);
|
||||
void audio_destroy(AudioEngine *engine);
|
||||
|
||||
@@ -30,7 +30,7 @@ enum {
|
||||
ASE_NoMemory = -994,
|
||||
};
|
||||
|
||||
enum ASIOSampleType {
|
||||
typedef enum ASIOSampleType {
|
||||
ASIOSTInt16MSB = 0,
|
||||
ASIOSTInt24MSB = 1,
|
||||
ASIOSTInt32MSB = 2,
|
||||
@@ -52,59 +52,59 @@ enum ASIOSampleType {
|
||||
ASIOSTInt32LSB18 = 25,
|
||||
ASIOSTInt32LSB20 = 26,
|
||||
ASIOSTInt32LSB24 = 27,
|
||||
};
|
||||
} ASIOSampleType;
|
||||
|
||||
struct ASIOClockSource {
|
||||
typedef struct ASIOClockSource {
|
||||
long index;
|
||||
long channel;
|
||||
long group;
|
||||
ASIOBool isCurrentSource;
|
||||
char name[32];
|
||||
};
|
||||
} ASIOClockSource;
|
||||
|
||||
struct ASIOChannelInfo {
|
||||
typedef struct ASIOChannelInfo {
|
||||
long channel;
|
||||
ASIOBool isInput;
|
||||
ASIOBool isActive;
|
||||
long channelGroup;
|
||||
ASIOSampleType type;
|
||||
char name[32];
|
||||
};
|
||||
} ASIOChannelInfo;
|
||||
|
||||
struct ASIOBufferInfo {
|
||||
typedef struct ASIOBufferInfo {
|
||||
ASIOBool isInput;
|
||||
long channelNum;
|
||||
void *buffers[2]; // F64 buffer
|
||||
};
|
||||
} ASIOBufferInfo;
|
||||
|
||||
struct ASIOTimeCode {
|
||||
typedef struct ASIOTimeCode {
|
||||
F64 speed;
|
||||
ASIOSamples timeCodeSamples;
|
||||
unsigned long flags;
|
||||
char future[64];
|
||||
};
|
||||
} ASIOTimeCode;
|
||||
|
||||
struct AsioTimeInfo {
|
||||
typedef struct AsioTimeInfo {
|
||||
F64 speed;
|
||||
ASIOTimeStamp systemTime;
|
||||
ASIOSamples samplePosition;
|
||||
F64 sampleRate;
|
||||
unsigned long flags;
|
||||
char reserved[12];
|
||||
};
|
||||
} AsioTimeInfo;
|
||||
|
||||
struct ASIOTime {
|
||||
typedef struct ASIOTime {
|
||||
long reserved[4];
|
||||
AsioTimeInfo timeInfo;
|
||||
ASIOTimeCode timeCode;
|
||||
};
|
||||
} ASIOTime;
|
||||
|
||||
struct ASIOCallbacks {
|
||||
typedef struct ASIOCallbacks {
|
||||
void (*bufferSwitch)(long doubleBufferIndex, ASIOBool directProcess);
|
||||
void (*sampleRateDidChange)(F64 sRate);
|
||||
long (*asioMessage)(long selector, long value, void *message, F64 *opt);
|
||||
ASIOTime *(*bufferSwitchTimeInfo)(ASIOTime *params, long doubleBufferIndex, ASIOBool directProcess);
|
||||
};
|
||||
} ASIOCallbacks;
|
||||
|
||||
// ASIO message selectors
|
||||
enum {
|
||||
@@ -120,41 +120,49 @@ enum {
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// IASIO COM interface
|
||||
// IASIO COM interface (C vtable)
|
||||
// Standard ASIO vtable — inherits IUnknown
|
||||
|
||||
class IASIO : public IUnknown {
|
||||
public:
|
||||
virtual ASIOBool init(void *sysHandle) = 0;
|
||||
virtual void getDriverName(char *name) = 0;
|
||||
virtual long getDriverVersion() = 0;
|
||||
virtual void getErrorMessage(char *string) = 0;
|
||||
virtual ASIOError start() = 0;
|
||||
virtual ASIOError stop() = 0;
|
||||
virtual ASIOError getChannels(long *numInputChannels, long *numOutputChannels) = 0;
|
||||
virtual ASIOError getLatencies(long *inputLatency, long *outputLatency) = 0;
|
||||
virtual ASIOError getBufferSize(long *minSize, long *maxSize, long *preferredSize, long *granularity) = 0;
|
||||
virtual ASIOError canSampleRate(F64 sampleRate) = 0;
|
||||
virtual ASIOError getSampleRate(F64 *sampleRate) = 0;
|
||||
virtual ASIOError setSampleRate(F64 sampleRate) = 0;
|
||||
virtual ASIOError getClockSources(ASIOClockSource *clocks, long *numSources) = 0;
|
||||
virtual ASIOError setClockSource(long reference) = 0;
|
||||
virtual ASIOError getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp) = 0;
|
||||
virtual ASIOError getChannelInfo(ASIOChannelInfo *info) = 0;
|
||||
virtual ASIOError createBuffers(ASIOBufferInfo *bufferInfos, long numChannels, long bufferSize, ASIOCallbacks *callbacks) = 0;
|
||||
virtual ASIOError disposeBuffers() = 0;
|
||||
virtual ASIOError controlPanel() = 0;
|
||||
virtual ASIOError future(long selector, void *opt) = 0;
|
||||
virtual ASIOError outputReady() = 0;
|
||||
};
|
||||
typedef struct IASIOVtbl {
|
||||
// IUnknown
|
||||
HRESULT (STDMETHODCALLTYPE *QueryInterface)(void *this_, REFIID riid, void **ppvObject);
|
||||
ULONG (STDMETHODCALLTYPE *AddRef)(void *this_);
|
||||
ULONG (STDMETHODCALLTYPE *Release)(void *this_);
|
||||
// IASIO
|
||||
ASIOBool (*init)(void *this_, void *sysHandle);
|
||||
void (*getDriverName)(void *this_, char *name);
|
||||
long (*getDriverVersion)(void *this_);
|
||||
void (*getErrorMessage)(void *this_, char *string);
|
||||
ASIOError (*start)(void *this_);
|
||||
ASIOError (*stop)(void *this_);
|
||||
ASIOError (*getChannels)(void *this_, long *numInputChannels, long *numOutputChannels);
|
||||
ASIOError (*getLatencies)(void *this_, long *inputLatency, long *outputLatency);
|
||||
ASIOError (*getBufferSize)(void *this_, long *minSize, long *maxSize, long *preferredSize, long *granularity);
|
||||
ASIOError (*canSampleRate)(void *this_, F64 sampleRate);
|
||||
ASIOError (*getSampleRate)(void *this_, F64 *sampleRate);
|
||||
ASIOError (*setSampleRate)(void *this_, F64 sampleRate);
|
||||
ASIOError (*getClockSources)(void *this_, ASIOClockSource *clocks, long *numSources);
|
||||
ASIOError (*setClockSource)(void *this_, long reference);
|
||||
ASIOError (*getSamplePosition)(void *this_, ASIOSamples *sPos, ASIOTimeStamp *tStamp);
|
||||
ASIOError (*getChannelInfo)(void *this_, ASIOChannelInfo *info);
|
||||
ASIOError (*createBuffers)(void *this_, ASIOBufferInfo *bufferInfos, long numChannels, long bufferSize, ASIOCallbacks *callbacks);
|
||||
ASIOError (*disposeBuffers)(void *this_);
|
||||
ASIOError (*controlPanel)(void *this_);
|
||||
ASIOError (*future)(void *this_, long selector, void *opt);
|
||||
ASIOError (*outputReady)(void *this_);
|
||||
} IASIOVtbl;
|
||||
|
||||
typedef struct IASIO {
|
||||
IASIOVtbl *lpVtbl;
|
||||
} IASIO;
|
||||
|
||||
////////////////////////////////
|
||||
// Internal state
|
||||
|
||||
struct AsioDriverInfo {
|
||||
typedef struct AsioDriverInfo {
|
||||
char name[128];
|
||||
CLSID clsid;
|
||||
};
|
||||
} AsioDriverInfo;
|
||||
|
||||
struct AudioEngine {
|
||||
void *hwnd; // HWND for ASIO init
|
||||
@@ -186,7 +194,7 @@ struct AudioEngine {
|
||||
////////////////////////////////
|
||||
// Global engine pointer for ASIO callbacks (standard pattern — callbacks have no user-data param)
|
||||
|
||||
static AudioEngine *g_audio_engine = nullptr;
|
||||
static AudioEngine *g_audio_engine = NULL;
|
||||
|
||||
////////////////////////////////
|
||||
// Sample writing helper
|
||||
@@ -351,7 +359,7 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
|
||||
for (DWORD i = 0; engine->device_count < AUDIO_MAX_DEVICES; i++) {
|
||||
subkey_name_len = sizeof(subkey_name);
|
||||
if (RegEnumKeyExA(asio_key, i, subkey_name, &subkey_name_len, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
|
||||
if (RegEnumKeyExA(asio_key, i, subkey_name, &subkey_name_len, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
HKEY driver_key;
|
||||
@@ -359,10 +367,10 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
continue;
|
||||
|
||||
// Read CLSID string
|
||||
char clsid_str[64] = {};
|
||||
char clsid_str[64] = {0};
|
||||
DWORD clsid_len = sizeof(clsid_str);
|
||||
DWORD type = 0;
|
||||
if (RegQueryValueExA(driver_key, "CLSID", nullptr, &type, (LPBYTE)clsid_str, &clsid_len) != ERROR_SUCCESS ||
|
||||
if (RegQueryValueExA(driver_key, "CLSID", NULL, &type, (LPBYTE)clsid_str, &clsid_len) != ERROR_SUCCESS ||
|
||||
type != REG_SZ) {
|
||||
RegCloseKey(driver_key);
|
||||
continue;
|
||||
@@ -393,21 +401,20 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
// Public API
|
||||
|
||||
AudioEngine *audio_create(void *hwnd) {
|
||||
AudioEngine *engine = new AudioEngine();
|
||||
memset(engine, 0, sizeof(*engine));
|
||||
AudioEngine *engine = (AudioEngine *)calloc(1, sizeof(AudioEngine));
|
||||
engine->hwnd = hwnd;
|
||||
engine->active_device_index = -1;
|
||||
g_audio_engine = engine;
|
||||
|
||||
CoInitialize(nullptr);
|
||||
CoInitialize(NULL);
|
||||
enumerate_asio_drivers(engine);
|
||||
return engine;
|
||||
}
|
||||
|
||||
void audio_destroy(AudioEngine *engine) {
|
||||
audio_close_device(engine);
|
||||
if (g_audio_engine == engine) g_audio_engine = nullptr;
|
||||
delete engine;
|
||||
if (g_audio_engine == engine) g_audio_engine = NULL;
|
||||
free(engine);
|
||||
}
|
||||
|
||||
void audio_refresh_devices(AudioEngine *engine) {
|
||||
@@ -421,7 +428,7 @@ S32 audio_get_device_count(AudioEngine *engine) {
|
||||
|
||||
AudioDeviceInfo *audio_get_device(AudioEngine *engine, S32 index) {
|
||||
if (index < 0 || index >= engine->device_count)
|
||||
return nullptr;
|
||||
return NULL;
|
||||
return &engine->devices[index];
|
||||
}
|
||||
|
||||
@@ -433,52 +440,52 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
return false;
|
||||
|
||||
// Create COM instance of the ASIO driver
|
||||
IASIO *driver = nullptr;
|
||||
HRESULT hr = CoCreateInstance(engine->drivers[index].clsid,
|
||||
nullptr, CLSCTX_INPROC_SERVER,
|
||||
engine->drivers[index].clsid,
|
||||
IASIO *driver = NULL;
|
||||
HRESULT hr = CoCreateInstance(&engine->drivers[index].clsid,
|
||||
NULL, CLSCTX_INPROC_SERVER,
|
||||
&engine->drivers[index].clsid,
|
||||
(void **)&driver);
|
||||
if (FAILED(hr) || !driver)
|
||||
return false;
|
||||
|
||||
// Initialize the driver
|
||||
if (!driver->init(engine->hwnd)) {
|
||||
driver->Release();
|
||||
if (!driver->lpVtbl->init(driver, engine->hwnd)) {
|
||||
driver->lpVtbl->Release(driver);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Query channel counts
|
||||
long num_in = 0, num_out = 0;
|
||||
if (driver->getChannels(&num_in, &num_out) != ASE_OK || num_out <= 0) {
|
||||
driver->Release();
|
||||
if (driver->lpVtbl->getChannels(driver, &num_in, &num_out) != ASE_OK || num_out <= 0) {
|
||||
driver->lpVtbl->Release(driver);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Query buffer size
|
||||
long min_size = 0, max_size = 0, preferred_size = 0, granularity = 0;
|
||||
if (driver->getBufferSize(&min_size, &max_size, &preferred_size, &granularity) != ASE_OK) {
|
||||
driver->Release();
|
||||
if (driver->lpVtbl->getBufferSize(driver, &min_size, &max_size, &preferred_size, &granularity) != ASE_OK) {
|
||||
driver->lpVtbl->Release(driver);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Query sample rate
|
||||
F64 sample_rate = 0;
|
||||
if (driver->getSampleRate(&sample_rate) != ASE_OK || sample_rate <= 0) {
|
||||
if (driver->lpVtbl->getSampleRate(driver, &sample_rate) != ASE_OK || sample_rate <= 0) {
|
||||
// Try setting a common rate
|
||||
if (driver->setSampleRate(44100.0) == ASE_OK) {
|
||||
if (driver->lpVtbl->setSampleRate(driver, 44100.0) == ASE_OK) {
|
||||
sample_rate = 44100.0;
|
||||
} else {
|
||||
driver->Release();
|
||||
driver->lpVtbl->Release(driver);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Query output channel sample type
|
||||
ASIOChannelInfo chan_info = {};
|
||||
ASIOChannelInfo chan_info = {0};
|
||||
chan_info.channel = 0;
|
||||
chan_info.isInput = 0;
|
||||
if (driver->getChannelInfo(&chan_info) != ASE_OK) {
|
||||
driver->Release();
|
||||
if (driver->lpVtbl->getChannelInfo(driver, &chan_info) != ASE_OK) {
|
||||
driver->lpVtbl->Release(driver);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -490,8 +497,8 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
for (long ch = 0; ch < num_out; ch++) {
|
||||
engine->buffer_infos[ch].isInput = 0;
|
||||
engine->buffer_infos[ch].channelNum = ch;
|
||||
engine->buffer_infos[ch].buffers[0] = nullptr;
|
||||
engine->buffer_infos[ch].buffers[1] = nullptr;
|
||||
engine->buffer_infos[ch].buffers[0] = NULL;
|
||||
engine->buffer_infos[ch].buffers[1] = NULL;
|
||||
}
|
||||
|
||||
// Setup callbacks
|
||||
@@ -501,8 +508,8 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
engine->callbacks.bufferSwitchTimeInfo = asio_buffer_switch_time_info;
|
||||
|
||||
// Create buffers
|
||||
if (driver->createBuffers(engine->buffer_infos, num_out, preferred_size, &engine->callbacks) != ASE_OK) {
|
||||
driver->Release();
|
||||
if (driver->lpVtbl->createBuffers(driver, engine->buffer_infos, num_out, preferred_size, &engine->callbacks) != ASE_OK) {
|
||||
driver->lpVtbl->Release(driver);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -519,16 +526,16 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
engine->test_tone_phase = 0.0;
|
||||
|
||||
// Start the driver
|
||||
if (driver->start() != ASE_OK) {
|
||||
driver->disposeBuffers();
|
||||
driver->Release();
|
||||
engine->driver = nullptr;
|
||||
if (driver->lpVtbl->start(driver) != ASE_OK) {
|
||||
driver->lpVtbl->disposeBuffers(driver);
|
||||
driver->lpVtbl->Release(driver);
|
||||
engine->driver = NULL;
|
||||
engine->active_device_index = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Notify driver that output is ready (optional, some drivers benefit)
|
||||
driver->outputReady();
|
||||
driver->lpVtbl->outputReady(driver);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -539,10 +546,10 @@ void audio_close_device(AudioEngine *engine) {
|
||||
InterlockedExchange(&engine->test_tone_active, 0);
|
||||
InterlockedExchange(&engine->test_tone_samples_remaining, 0);
|
||||
|
||||
engine->driver->stop();
|
||||
engine->driver->disposeBuffers();
|
||||
engine->driver->Release();
|
||||
engine->driver = nullptr;
|
||||
engine->driver->lpVtbl->stop(engine->driver);
|
||||
engine->driver->lpVtbl->disposeBuffers(engine->driver);
|
||||
engine->driver->lpVtbl->Release(engine->driver);
|
||||
engine->driver = NULL;
|
||||
engine->active_device_index = -1;
|
||||
engine->test_tone_phase = 0.0;
|
||||
}
|
||||
@@ -13,9 +13,9 @@
|
||||
#define AUDIO_TEST_TONE_SEC 2.0
|
||||
#define AUDIO_PI 3.14159265358979323846
|
||||
|
||||
struct CoreAudioDeviceInfo {
|
||||
typedef struct CoreAudioDeviceInfo {
|
||||
AudioDeviceID device_id;
|
||||
};
|
||||
} CoreAudioDeviceInfo;
|
||||
|
||||
struct AudioEngine {
|
||||
AudioDeviceInfo devices[AUDIO_MAX_DEVICES];
|
||||
@@ -37,7 +37,7 @@ struct AudioEngine {
|
||||
////////////////////////////////
|
||||
// Audio render callback
|
||||
|
||||
static AudioEngine *g_audio_engine = nullptr;
|
||||
static AudioEngine *g_audio_engine = NULL;
|
||||
|
||||
static OSStatus audio_render_callback(void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
@@ -110,14 +110,14 @@ static void enumerate_output_devices(AudioEngine *engine) {
|
||||
};
|
||||
|
||||
UInt32 data_size = 0;
|
||||
if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, nullptr, &data_size) != noErr)
|
||||
if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &data_size) != noErr)
|
||||
return;
|
||||
|
||||
S32 device_count = (S32)(data_size / sizeof(AudioDeviceID));
|
||||
if (device_count <= 0) return;
|
||||
|
||||
AudioDeviceID *device_ids = (AudioDeviceID *)malloc(data_size);
|
||||
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, nullptr, &data_size, device_ids) != noErr) {
|
||||
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &data_size, device_ids) != noErr) {
|
||||
free(device_ids);
|
||||
return;
|
||||
}
|
||||
@@ -131,11 +131,11 @@ static void enumerate_output_devices(AudioEngine *engine) {
|
||||
};
|
||||
|
||||
UInt32 stream_size = 0;
|
||||
if (AudioObjectGetPropertyDataSize(device_ids[i], &stream_prop, 0, nullptr, &stream_size) != noErr)
|
||||
if (AudioObjectGetPropertyDataSize(device_ids[i], &stream_prop, 0, NULL, &stream_size) != noErr)
|
||||
continue;
|
||||
|
||||
AudioBufferList *buf_list = (AudioBufferList *)malloc(stream_size);
|
||||
if (AudioObjectGetPropertyData(device_ids[i], &stream_prop, 0, nullptr, &stream_size, buf_list) != noErr) {
|
||||
if (AudioObjectGetPropertyData(device_ids[i], &stream_prop, 0, NULL, &stream_size, buf_list) != noErr) {
|
||||
free(buf_list);
|
||||
continue;
|
||||
}
|
||||
@@ -154,9 +154,9 @@ static void enumerate_output_devices(AudioEngine *engine) {
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
|
||||
CFStringRef name_ref = nullptr;
|
||||
CFStringRef name_ref = NULL;
|
||||
UInt32 name_size = sizeof(name_ref);
|
||||
if (AudioObjectGetPropertyData(device_ids[i], &name_prop, 0, nullptr, &name_size, &name_ref) != noErr)
|
||||
if (AudioObjectGetPropertyData(device_ids[i], &name_prop, 0, NULL, &name_size, &name_ref) != noErr)
|
||||
continue;
|
||||
|
||||
S32 idx = engine->device_count++;
|
||||
@@ -176,8 +176,7 @@ static void enumerate_output_devices(AudioEngine *engine) {
|
||||
AudioEngine *audio_create(void *hwnd) {
|
||||
(void)hwnd;
|
||||
|
||||
AudioEngine *engine = new AudioEngine();
|
||||
memset(engine, 0, sizeof(*engine));
|
||||
AudioEngine *engine = (AudioEngine *)calloc(1, sizeof(AudioEngine));
|
||||
engine->active_device_index = -1;
|
||||
atomic_store(&engine->test_tone_active, 0);
|
||||
atomic_store(&engine->test_tone_samples_remaining, 0);
|
||||
@@ -189,8 +188,8 @@ AudioEngine *audio_create(void *hwnd) {
|
||||
|
||||
void audio_destroy(AudioEngine *engine) {
|
||||
audio_close_device(engine);
|
||||
if (g_audio_engine == engine) g_audio_engine = nullptr;
|
||||
delete engine;
|
||||
if (g_audio_engine == engine) g_audio_engine = NULL;
|
||||
free(engine);
|
||||
}
|
||||
|
||||
void audio_refresh_devices(AudioEngine *engine) {
|
||||
@@ -203,7 +202,7 @@ S32 audio_get_device_count(AudioEngine *engine) {
|
||||
}
|
||||
|
||||
AudioDeviceInfo *audio_get_device(AudioEngine *engine, S32 index) {
|
||||
if (index < 0 || index >= engine->device_count) return nullptr;
|
||||
if (index < 0 || index >= engine->device_count) return NULL;
|
||||
return &engine->devices[index];
|
||||
}
|
||||
|
||||
@@ -218,7 +217,7 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
if (NewAUGraph(&engine->graph) != noErr) return false;
|
||||
|
||||
// Add HAL output node
|
||||
AudioComponentDescription output_desc = {};
|
||||
AudioComponentDescription output_desc = {0};
|
||||
output_desc.componentType = kAudioUnitType_Output;
|
||||
output_desc.componentSubType = kAudioUnitSubType_HALOutput;
|
||||
output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
@@ -226,19 +225,19 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
AUNode output_node;
|
||||
if (AUGraphAddNode(engine->graph, &output_desc, &output_node) != noErr) {
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
engine->graph = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AUGraphOpen(engine->graph) != noErr) {
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
engine->graph = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AUGraphNodeInfo(engine->graph, output_node, nullptr, &engine->output_unit) != noErr) {
|
||||
if (AUGraphNodeInfo(engine->graph, output_node, NULL, &engine->output_unit) != noErr) {
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
engine->graph = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -246,7 +245,7 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
if (AudioUnitSetProperty(engine->output_unit, kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0, &device_id, sizeof(device_id)) != noErr) {
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
engine->graph = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -258,11 +257,11 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
};
|
||||
Float64 sample_rate = 44100.0;
|
||||
UInt32 rate_size = sizeof(sample_rate);
|
||||
AudioObjectGetPropertyData(device_id, &rate_prop, 0, nullptr, &rate_size, &sample_rate);
|
||||
AudioObjectGetPropertyData(device_id, &rate_prop, 0, NULL, &rate_size, &sample_rate);
|
||||
engine->sample_rate = sample_rate;
|
||||
|
||||
// Set stream format: Float32, non-interleaved
|
||||
AudioStreamBasicDescription fmt = {};
|
||||
AudioStreamBasicDescription fmt = {0};
|
||||
fmt.mSampleRate = sample_rate;
|
||||
fmt.mFormatID = kAudioFormatLinearPCM;
|
||||
fmt.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsPacked;
|
||||
@@ -277,27 +276,27 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
kAudioUnitScope_Input, 0, &fmt, sizeof(fmt));
|
||||
|
||||
// Set render callback
|
||||
AURenderCallbackStruct cb = {};
|
||||
AURenderCallbackStruct cb = {0};
|
||||
cb.inputProc = audio_render_callback;
|
||||
cb.inputProcRefCon = engine;
|
||||
|
||||
if (AudioUnitSetProperty(engine->output_unit, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &cb, sizeof(cb)) != noErr) {
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
engine->graph = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize and start
|
||||
if (AUGraphInitialize(engine->graph) != noErr) {
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
engine->graph = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AUGraphStart(engine->graph) != noErr) {
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
engine->graph = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -318,8 +317,8 @@ void audio_close_device(AudioEngine *engine) {
|
||||
AUGraphStop(engine->graph);
|
||||
AUGraphUninitialize(engine->graph);
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
engine->output_unit = nullptr;
|
||||
engine->graph = NULL;
|
||||
engine->output_unit = NULL;
|
||||
engine->active_device_index = -1;
|
||||
engine->test_tone_phase = 0.0;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
Arena *arena_alloc(U64 cap) {
|
||||
U8 *mem = (U8 *)malloc(sizeof(Arena) + cap);
|
||||
if (!mem) return nullptr;
|
||||
if (!mem) return NULL;
|
||||
Arena *arena = (Arena *)mem;
|
||||
arena->base = mem + sizeof(Arena);
|
||||
arena->pos = 0;
|
||||
@@ -19,7 +19,7 @@ void *arena_push(Arena *arena, U64 size) {
|
||||
U64 aligned = AlignPow2(size, 8);
|
||||
if (arena->pos + aligned > arena->cap) {
|
||||
Assert(!"Arena overflow");
|
||||
return nullptr;
|
||||
return NULL;
|
||||
}
|
||||
void *result = arena->base + arena->pos;
|
||||
arena->pos += aligned;
|
||||
@@ -31,7 +31,7 @@ void *arena_push_no_zero(Arena *arena, U64 size) {
|
||||
U64 aligned = AlignPow2(size, 8);
|
||||
if (arena->pos + aligned > arena->cap) {
|
||||
Assert(!"Arena overflow");
|
||||
return nullptr;
|
||||
return NULL;
|
||||
}
|
||||
void *result = arena->base + arena->pos;
|
||||
arena->pos += aligned;
|
||||
@@ -8,17 +8,17 @@
|
||||
////////////////////////////////
|
||||
// Arena type
|
||||
|
||||
struct Arena {
|
||||
typedef struct Arena {
|
||||
U8 *base;
|
||||
U64 pos;
|
||||
U64 cap;
|
||||
};
|
||||
} Arena;
|
||||
|
||||
// Temporary scope (save/restore position)
|
||||
struct Temp {
|
||||
typedef struct Temp {
|
||||
Arena *arena;
|
||||
U64 pos;
|
||||
};
|
||||
} Temp;
|
||||
|
||||
////////////////////////////////
|
||||
// Arena functions
|
||||
@@ -34,8 +34,8 @@ void arena_clear(Arena *arena);
|
||||
////////////////////////////////
|
||||
// Temporary scope helpers
|
||||
|
||||
inline Temp temp_begin(Arena *arena) { return {arena, arena->pos}; }
|
||||
inline void temp_end(Temp temp) { arena_pop_to(temp.arena, temp.pos); }
|
||||
static inline Temp temp_begin(Arena *arena) { Temp t = {arena, arena->pos}; return t; }
|
||||
static inline void temp_end(Temp temp) { arena_pop_to(temp.arena, temp.pos); }
|
||||
|
||||
////////////////////////////////
|
||||
// Push helper macros
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
////////////////////////////////
|
||||
// Codebase keywords
|
||||
@@ -17,7 +18,7 @@
|
||||
#endif
|
||||
#define local_persist static
|
||||
|
||||
#define trvke true
|
||||
#define trvke 1
|
||||
|
||||
////////////////////////////////
|
||||
// Base types
|
||||
|
||||
3
src/base/base_inc.c
Normal file
3
src/base/base_inc.c
Normal file
@@ -0,0 +1,3 @@
|
||||
// base_inc.c - Unity build for the base layer
|
||||
#include "base/base_arena.c"
|
||||
#include "base/base_strings.c"
|
||||
@@ -1,3 +0,0 @@
|
||||
// base_inc.cpp - Unity build for the base layer
|
||||
#include "base/base_arena.cpp"
|
||||
#include "base/base_strings.cpp"
|
||||
@@ -7,60 +7,60 @@
|
||||
////////////////////////////////
|
||||
// Axis enum
|
||||
|
||||
enum Axis2 {
|
||||
typedef enum Axis2 {
|
||||
Axis2_X = 0,
|
||||
Axis2_Y = 1,
|
||||
Axis2_COUNT,
|
||||
};
|
||||
} Axis2;
|
||||
|
||||
enum Side {
|
||||
typedef enum Side {
|
||||
Side_Min = 0,
|
||||
Side_Max = 1,
|
||||
Side_COUNT,
|
||||
};
|
||||
} Side;
|
||||
|
||||
enum Corner {
|
||||
typedef enum Corner {
|
||||
Corner_00 = 0, // top-left
|
||||
Corner_01 = 1, // top-right
|
||||
Corner_10 = 2, // bottom-left
|
||||
Corner_11 = 3, // bottom-right
|
||||
Corner_COUNT,
|
||||
};
|
||||
} Corner;
|
||||
|
||||
////////////////////////////////
|
||||
// Vector types
|
||||
|
||||
struct Vec2F32 { F32 x, y; };
|
||||
struct Vec2S32 { S32 x, y; };
|
||||
struct Vec3F32 { F32 x, y, z; };
|
||||
struct Vec4F32 { F32 x, y, z, w; };
|
||||
typedef struct Vec2F32 { F32 x, y; } Vec2F32;
|
||||
typedef struct Vec2S32 { S32 x, y; } Vec2S32;
|
||||
typedef struct Vec3F32 { F32 x, y, z; } Vec3F32;
|
||||
typedef struct Vec4F32 { F32 x, y, z, w; } Vec4F32;
|
||||
|
||||
////////////////////////////////
|
||||
// Range types
|
||||
|
||||
struct Rng1F32 { F32 min, max; };
|
||||
struct Rng1S64 { S64 min, max; };
|
||||
struct Rng2F32 { Vec2F32 p0, p1; };
|
||||
typedef struct Rng1F32 { F32 min, max; } Rng1F32;
|
||||
typedef struct Rng1S64 { S64 min, max; } Rng1S64;
|
||||
typedef struct Rng2F32 { Vec2F32 p0, p1; } Rng2F32;
|
||||
|
||||
////////////////////////////////
|
||||
// Constructors
|
||||
|
||||
static inline Vec2F32 v2f32(F32 x, F32 y) { return {x, y}; }
|
||||
static inline Vec2S32 v2s32(S32 x, S32 y) { return {x, y}; }
|
||||
static inline Vec3F32 v3f32(F32 x, F32 y, F32 z) { return {x, y, z}; }
|
||||
static inline Vec4F32 v4f32(F32 x, F32 y, F32 z, F32 w) { return {x, y, z, w}; }
|
||||
static inline Rng1F32 rng1f32(F32 min, F32 max) { return {min, max}; }
|
||||
static inline Rng1S64 rng1s64(S64 min, S64 max) { return {min, max}; }
|
||||
static inline Rng2F32 rng2f32(Vec2F32 p0, Vec2F32 p1) { return {p0, p1}; }
|
||||
static inline Rng2F32 rng2f32p(F32 x0, F32 y0, F32 x1, F32 y1) { return {{x0, y0}, {x1, y1}}; }
|
||||
static inline Vec2F32 v2f32(F32 x, F32 y) { return (Vec2F32){x, y}; }
|
||||
static inline Vec2S32 v2s32(S32 x, S32 y) { return (Vec2S32){x, y}; }
|
||||
static inline Vec3F32 v3f32(F32 x, F32 y, F32 z) { return (Vec3F32){x, y, z}; }
|
||||
static inline Vec4F32 v4f32(F32 x, F32 y, F32 z, F32 w) { return (Vec4F32){x, y, z, w}; }
|
||||
static inline Rng1F32 rng1f32(F32 min, F32 max) { return (Rng1F32){min, max}; }
|
||||
static inline Rng1S64 rng1s64(S64 min, S64 max) { return (Rng1S64){min, max}; }
|
||||
static inline Rng2F32 rng2f32(Vec2F32 p0, Vec2F32 p1) { return (Rng2F32){p0, p1}; }
|
||||
static inline Rng2F32 rng2f32p(F32 x0, F32 y0, F32 x1, F32 y1) { return (Rng2F32){{x0, y0}, {x1, y1}}; }
|
||||
|
||||
////////////////////////////////
|
||||
// Vec2F32 operations
|
||||
|
||||
static inline Vec2F32 add_2f32(Vec2F32 a, Vec2F32 b) { return {a.x + b.x, a.y + b.y}; }
|
||||
static inline Vec2F32 sub_2f32(Vec2F32 a, Vec2F32 b) { return {a.x - b.x, a.y - b.y}; }
|
||||
static inline Vec2F32 mul_2f32(Vec2F32 a, Vec2F32 b) { return {a.x * b.x, a.y * b.y}; }
|
||||
static inline Vec2F32 scale_2f32(Vec2F32 v, F32 s) { return {v.x * s, v.y * s}; }
|
||||
static inline Vec2F32 add_2f32(Vec2F32 a, Vec2F32 b) { return (Vec2F32){a.x + b.x, a.y + b.y}; }
|
||||
static inline Vec2F32 sub_2f32(Vec2F32 a, Vec2F32 b) { return (Vec2F32){a.x - b.x, a.y - b.y}; }
|
||||
static inline Vec2F32 mul_2f32(Vec2F32 a, Vec2F32 b) { return (Vec2F32){a.x * b.x, a.y * b.y}; }
|
||||
static inline Vec2F32 scale_2f32(Vec2F32 v, F32 s) { return (Vec2F32){v.x * s, v.y * s}; }
|
||||
|
||||
// Axis-indexed access
|
||||
static inline F32 v2f32_axis(Vec2F32 v, Axis2 a) { return a == Axis2_X ? v.x : v.y; }
|
||||
@@ -71,10 +71,10 @@ static inline void v2f32_set_axis(Vec2F32 *v, Axis2 a, F32 val) {
|
||||
////////////////////////////////
|
||||
// Vec4F32 operations
|
||||
|
||||
static inline Vec4F32 add_4f32(Vec4F32 a, Vec4F32 b) { return {a.x+b.x, a.y+b.y, a.z+b.z, a.w+b.w}; }
|
||||
static inline Vec4F32 scale_4f32(Vec4F32 v, F32 s) { return {v.x*s, v.y*s, v.z*s, v.w*s}; }
|
||||
static inline Vec4F32 add_4f32(Vec4F32 a, Vec4F32 b) { return (Vec4F32){a.x+b.x, a.y+b.y, a.z+b.z, a.w+b.w}; }
|
||||
static inline Vec4F32 scale_4f32(Vec4F32 v, F32 s) { return (Vec4F32){v.x*s, v.y*s, v.z*s, v.w*s}; }
|
||||
static inline Vec4F32 lerp_4f32(Vec4F32 a, Vec4F32 b, F32 t) {
|
||||
return {a.x + (b.x - a.x)*t, a.y + (b.y - a.y)*t, a.z + (b.z - a.z)*t, a.w + (b.w - a.w)*t};
|
||||
return (Vec4F32){a.x + (b.x - a.x)*t, a.y + (b.y - a.y)*t, a.z + (b.z - a.z)*t, a.w + (b.w - a.w)*t};
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
@@ -82,19 +82,19 @@ static inline Vec4F32 lerp_4f32(Vec4F32 a, Vec4F32 b, F32 t) {
|
||||
|
||||
static inline F32 rng2f32_width(Rng2F32 r) { return r.p1.x - r.p0.x; }
|
||||
static inline F32 rng2f32_height(Rng2F32 r) { return r.p1.y - r.p0.y; }
|
||||
static inline Vec2F32 rng2f32_dim(Rng2F32 r) { return {r.p1.x - r.p0.x, r.p1.y - r.p0.y}; }
|
||||
static inline Vec2F32 rng2f32_center(Rng2F32 r) { return {(r.p0.x + r.p1.x)*0.5f, (r.p0.y + r.p1.y)*0.5f}; }
|
||||
static inline Vec2F32 rng2f32_dim(Rng2F32 r) { return (Vec2F32){r.p1.x - r.p0.x, r.p1.y - r.p0.y}; }
|
||||
static inline Vec2F32 rng2f32_center(Rng2F32 r) { return (Vec2F32){(r.p0.x + r.p1.x)*0.5f, (r.p0.y + r.p1.y)*0.5f}; }
|
||||
static inline B32 rng2f32_contains(Rng2F32 r, Vec2F32 p) {
|
||||
return p.x >= r.p0.x && p.x <= r.p1.x && p.y >= r.p0.y && p.y <= r.p1.y;
|
||||
}
|
||||
static inline Rng2F32 rng2f32_pad(Rng2F32 r, F32 p) {
|
||||
return {{r.p0.x - p, r.p0.y - p}, {r.p1.x + p, r.p1.y + p}};
|
||||
return (Rng2F32){{r.p0.x - p, r.p0.y - p}, {r.p1.x + p, r.p1.y + p}};
|
||||
}
|
||||
static inline Rng2F32 rng2f32_shift(Rng2F32 r, Vec2F32 v) {
|
||||
return {{r.p0.x + v.x, r.p0.y + v.y}, {r.p1.x + v.x, r.p1.y + v.y}};
|
||||
return (Rng2F32){{r.p0.x + v.x, r.p0.y + v.y}, {r.p1.x + v.x, r.p1.y + v.y}};
|
||||
}
|
||||
static inline Rng2F32 rng2f32_intersect(Rng2F32 a, Rng2F32 b) {
|
||||
return {{Max(a.p0.x, b.p0.x), Max(a.p0.y, b.p0.y)},
|
||||
return (Rng2F32){{Max(a.p0.x, b.p0.x), Max(a.p0.y, b.p0.y)},
|
||||
{Min(a.p1.x, b.p1.x), Min(a.p1.y, b.p1.y)}};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,22 +5,24 @@ Str8 str8_pushf(Arena *arena, const char *fmt, ...) {
|
||||
va_list args, args2;
|
||||
va_start(args, fmt);
|
||||
va_copy(args2, args);
|
||||
S32 len = vsnprintf(nullptr, 0, fmt, args);
|
||||
S32 len = vsnprintf(NULL, 0, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
char *buf = push_array(arena, char, len + 1);
|
||||
vsnprintf(buf, len + 1, fmt, args2);
|
||||
va_end(args2);
|
||||
|
||||
return {buf, (U64)len};
|
||||
Str8 r = {buf, (U64)len};
|
||||
return r;
|
||||
}
|
||||
|
||||
Str8 str8_push_copy(Arena *arena, Str8 s) {
|
||||
if (s.size == 0 || !s.str) return {nullptr, 0};
|
||||
if (s.size == 0 || !s.str) { Str8 r = {NULL, 0}; return r; }
|
||||
char *buf = push_array_no_zero(arena, char, s.size + 1);
|
||||
MemoryCopy(buf, s.str, s.size);
|
||||
buf[s.size] = 0;
|
||||
return {buf, s.size};
|
||||
Str8 r = {buf, s.size};
|
||||
return r;
|
||||
}
|
||||
|
||||
void str8_list_push(Arena *arena, Str8List *list, Str8 s) {
|
||||
@@ -7,38 +7,38 @@
|
||||
////////////////////////////////
|
||||
// String types
|
||||
|
||||
struct Str8 {
|
||||
typedef struct Str8 {
|
||||
const char *str;
|
||||
U64 size;
|
||||
};
|
||||
} Str8;
|
||||
|
||||
struct Str8Node {
|
||||
Str8Node *next;
|
||||
Str8 string;
|
||||
};
|
||||
typedef struct Str8Node {
|
||||
struct Str8Node *next;
|
||||
Str8 string;
|
||||
} Str8Node;
|
||||
|
||||
struct Str8List {
|
||||
typedef struct Str8List {
|
||||
Str8Node *first;
|
||||
Str8Node *last;
|
||||
U64 count;
|
||||
U64 total_size;
|
||||
};
|
||||
} Str8List;
|
||||
|
||||
////////////////////////////////
|
||||
// Forward declaration for Arena
|
||||
struct Arena;
|
||||
typedef struct Arena Arena;
|
||||
|
||||
////////////////////////////////
|
||||
// Constructors
|
||||
|
||||
static inline Str8 str8(const char *s, U64 len) { return {s, len}; }
|
||||
static inline Str8 str8_cstr(const char *s) { return {s, s ? (U64)strlen(s) : 0}; }
|
||||
static inline Str8 str8_lit(const char *s) { return {s, s ? (U64)strlen(s) : 0}; }
|
||||
static inline Str8 str8(const char *s, U64 len) { Str8 r = {s, len}; return r; }
|
||||
static inline Str8 str8_cstr(const char *s) { Str8 r = {s, s ? (U64)strlen(s) : 0}; return r; }
|
||||
static inline Str8 str8_lit(const char *s) { Str8 r = {s, s ? (U64)strlen(s) : 0}; return r; }
|
||||
static inline B32 str8_match(Str8 a, Str8 b) {
|
||||
if (a.size != b.size) return 0;
|
||||
return MemoryCompare(a.str, b.str, a.size) == 0;
|
||||
}
|
||||
static inline B32 str8_is_empty(Str8 s) { return s.size == 0 || s.str == nullptr; }
|
||||
static inline B32 str8_is_empty(Str8 s) { return s.size == 0 || s.str == NULL; }
|
||||
|
||||
////////////////////////////////
|
||||
// String operations (require arena)
|
||||
|
||||
@@ -15,25 +15,25 @@
|
||||
#include "ui/ui_popups.h"
|
||||
#include "ui/ui_piano.h"
|
||||
|
||||
// [cpp]
|
||||
#include "base/base_inc.cpp"
|
||||
#include "ui/ui_core.cpp"
|
||||
#include "ui/ui_icons.cpp"
|
||||
#include "ui/ui_widgets.cpp"
|
||||
#include "ui/ui_popups.cpp"
|
||||
#include "ui/ui_piano.cpp"
|
||||
// [c]
|
||||
#include "base/base_inc.c"
|
||||
#include "ui/ui_core.c"
|
||||
#include "ui/ui_icons.c"
|
||||
#include "ui/ui_widgets.c"
|
||||
#include "ui/ui_popups.c"
|
||||
#include "ui/ui_piano.c"
|
||||
#ifdef __APPLE__
|
||||
#include "platform/platform_macos.mm"
|
||||
#include "renderer/renderer_metal.mm"
|
||||
#include "midi/midi_coremidi.cpp"
|
||||
#include "audio/audio_coreaudio.cpp"
|
||||
#include "platform/platform_macos.m"
|
||||
#include "renderer/renderer_metal.m"
|
||||
#include "midi/midi_coremidi.c"
|
||||
#include "audio/audio_coreaudio.c"
|
||||
#else
|
||||
#include "platform/platform_win32.cpp"
|
||||
#include "renderer/renderer_vulkan.cpp"
|
||||
#include "midi/midi_win32.cpp"
|
||||
#include "audio/audio_asio.cpp"
|
||||
#include "platform/platform_win32.c"
|
||||
#include "renderer/renderer_vulkan.c"
|
||||
#include "midi/midi_win32.c"
|
||||
#include "audio/audio_asio.c"
|
||||
#endif
|
||||
#include "menus.cpp"
|
||||
#include "menus.c"
|
||||
|
||||
////////////////////////////////
|
||||
// Clay text config helpers
|
||||
@@ -43,17 +43,17 @@ static Clay_TextElementConfig g_text_config_title;
|
||||
static Clay_TextElementConfig g_text_config_dim;
|
||||
|
||||
static void init_text_configs() {
|
||||
g_text_config_normal = {};
|
||||
memset(&g_text_config_normal, 0, sizeof(g_text_config_normal));
|
||||
g_text_config_normal.textColor = g_theme.text;
|
||||
g_text_config_normal.fontSize = 15;
|
||||
g_text_config_normal.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
g_text_config_title = {};
|
||||
memset(&g_text_config_title, 0, sizeof(g_text_config_title));
|
||||
g_text_config_title.textColor = g_theme.text;
|
||||
g_text_config_title.fontSize = 15;
|
||||
g_text_config_title.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
g_text_config_dim = {};
|
||||
memset(&g_text_config_dim, 0, sizeof(g_text_config_dim));
|
||||
g_text_config_dim.textColor = g_theme.text_dim;
|
||||
g_text_config_dim.fontSize = 15;
|
||||
g_text_config_dim.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -62,7 +62,7 @@ static void init_text_configs() {
|
||||
////////////////////////////////
|
||||
// App state — all mutable state the frame function needs
|
||||
|
||||
struct AppState {
|
||||
typedef struct AppState {
|
||||
PlatformWindow *window;
|
||||
Renderer *renderer;
|
||||
MidiEngine *midi;
|
||||
@@ -164,7 +164,7 @@ struct AppState {
|
||||
S32 patch_tab; // 0 = Matrix, 1 = Graph
|
||||
B32 patch_matrix[32][33]; // internal routing: 32 inputs x (Master + 32 outputs)
|
||||
B32 hw_matrix[32][32]; // hardware routing: 32 channel outputs x 32 hw outputs
|
||||
};
|
||||
} AppState;
|
||||
|
||||
////////////////////////////////
|
||||
////////////////////////////////
|
||||
@@ -466,7 +466,7 @@ static void build_main_panel(AppState *app) {
|
||||
// Thumb color: highlight on hover or drag
|
||||
Clay_Color thumb_color = g_theme.scrollbar_grab;
|
||||
if (app->scrollbar_dragging || thumb_hovered) {
|
||||
thumb_color = Clay_Color{
|
||||
thumb_color = (Clay_Color){
|
||||
(F32)Min((S32)thumb_color.r + 30, 255),
|
||||
(F32)Min((S32)thumb_color.g + 30, 255),
|
||||
(F32)Min((S32)thumb_color.b + 30, 255),
|
||||
@@ -582,8 +582,8 @@ static void build_right_panel(AppState *app) {
|
||||
static char note_bufs[64][8];
|
||||
static char vel_bufs[64][8];
|
||||
static Clay_TextElementConfig box_text_config;
|
||||
box_text_config = {};
|
||||
box_text_config.textColor = Clay_Color{255, 255, 255, 255};
|
||||
memset(&box_text_config, 0, sizeof(box_text_config));
|
||||
box_text_config.textColor = (Clay_Color){255, 255, 255, 255};
|
||||
box_text_config.fontSize = FONT_SIZE_SMALL;
|
||||
box_text_config.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
@@ -602,9 +602,9 @@ static void build_right_panel(AppState *app) {
|
||||
if (dev->active) {
|
||||
box_color = piano_velocity_color(dev->velocity);
|
||||
} else if (dev->releasing) {
|
||||
box_color = Clay_Color{255, 255, 255, 255};
|
||||
box_color = (Clay_Color){255, 255, 255, 255};
|
||||
} else {
|
||||
box_color = Clay_Color{60, 60, 60, 255};
|
||||
box_color = (Clay_Color){60, 60, 60, 255};
|
||||
}
|
||||
|
||||
// Box text: note name when held, "OFF" when releasing, "---" when idle
|
||||
@@ -624,7 +624,7 @@ static void build_right_panel(AppState *app) {
|
||||
Clay_TextElementConfig *box_txt = &box_text_config;
|
||||
static Clay_TextElementConfig box_text_dark;
|
||||
box_text_dark = box_text_config;
|
||||
box_text_dark.textColor = Clay_Color{30, 30, 30, 255};
|
||||
box_text_dark.textColor = (Clay_Color){30, 30, 30, 255};
|
||||
if (dev->releasing) box_txt = &box_text_dark;
|
||||
|
||||
// Velocity text
|
||||
@@ -746,7 +746,7 @@ static void build_log_panel(AppState *app) {
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Corner radius presets (indexed by AppState::radius_sel)
|
||||
// Corner radius presets
|
||||
static const F32 radius_values[] = { 0.0f, 4.0f, 6.0f, 10.0f };
|
||||
|
||||
// Window content callbacks
|
||||
@@ -818,7 +818,7 @@ static void settings_window_content(void *user_data) {
|
||||
.backgroundColor = g_theme.disabled_bg,
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS)
|
||||
) {
|
||||
static Clay_TextElementConfig disabled_text = {};
|
||||
static Clay_TextElementConfig disabled_text = {0};
|
||||
disabled_text.textColor = g_theme.disabled_text;
|
||||
disabled_text.fontSize = FONT_SIZE_NORMAL;
|
||||
disabled_text.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -976,22 +976,22 @@ static void build_header_bar(AppState *app) {
|
||||
Clay_Color bar_bg = g_theme.header_bg;
|
||||
Clay_Color border_bot = {(F32)Max((S32)bar_bg.r - 12, 0), (F32)Max((S32)bar_bg.g - 12, 0), (F32)Max((S32)bar_bg.b - 12, 0), 255};
|
||||
|
||||
static Clay_TextElementConfig header_btn_active_text = {};
|
||||
header_btn_active_text.textColor = Clay_Color{255, 255, 255, 255};
|
||||
static Clay_TextElementConfig header_btn_active_text = {0};
|
||||
header_btn_active_text.textColor = (Clay_Color){255, 255, 255, 255};
|
||||
header_btn_active_text.fontSize = FONT_SIZE_NORMAL;
|
||||
header_btn_active_text.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
static Clay_TextElementConfig header_clock_text = {};
|
||||
static Clay_TextElementConfig header_clock_text = {0};
|
||||
header_clock_text.textColor = g_theme.text;
|
||||
header_clock_text.fontSize = uifs(22);
|
||||
header_clock_text.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
static Clay_TextElementConfig header_indicator_text = {};
|
||||
static Clay_TextElementConfig header_indicator_text = {0};
|
||||
header_indicator_text.textColor = g_theme.text;
|
||||
header_indicator_text.fontSize = FONT_SIZE_NORMAL;
|
||||
header_indicator_text.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
static Clay_TextElementConfig header_indicator_label = {};
|
||||
static Clay_TextElementConfig header_indicator_label = {0};
|
||||
header_indicator_label.textColor = g_theme.text_dim;
|
||||
header_indicator_label.fontSize = FONT_SIZE_SMALL;
|
||||
header_indicator_label.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -1056,7 +1056,7 @@ static void build_header_bar(AppState *app) {
|
||||
},
|
||||
.backgroundColor = g_theme.bg_lighter,
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
|
||||
) { ui_icon(UI_ICON_TRANSPORT_RECORD, uis(14), Clay_Color{200, 60, 60, 255}); }
|
||||
) { ui_icon(UI_ICON_TRANSPORT_RECORD, uis(14), (Clay_Color){200, 60, 60, 255}); }
|
||||
}
|
||||
|
||||
// Spacer
|
||||
@@ -1410,28 +1410,28 @@ static void build_patch_view(AppState *app) {
|
||||
#define MATRIX_OUTPUTS 33 // Master + Out 1..32
|
||||
#define HW_OUTPUTS 32
|
||||
|
||||
static Clay_TextElementConfig matrix_hdr_text = {};
|
||||
static Clay_TextElementConfig matrix_hdr_text = {0};
|
||||
matrix_hdr_text.textColor = g_theme.text_dim;
|
||||
matrix_hdr_text.fontSize = FONT_SIZE_SMALL;
|
||||
matrix_hdr_text.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
static Clay_TextElementConfig matrix_axis_text = {};
|
||||
static Clay_TextElementConfig matrix_axis_text = {0};
|
||||
matrix_axis_text.textColor = g_theme.text;
|
||||
matrix_axis_text.fontSize = FONT_SIZE_SMALL;
|
||||
matrix_axis_text.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
static Clay_TextElementConfig matrix_title_text = {};
|
||||
static Clay_TextElementConfig matrix_title_text = {0};
|
||||
matrix_title_text.textColor = g_theme.text;
|
||||
matrix_title_text.fontSize = FONT_SIZE_NORMAL;
|
||||
matrix_title_text.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
static Clay_TextElementConfig cell_x_text = {};
|
||||
cell_x_text.textColor = Clay_Color{255, 255, 255, 255};
|
||||
static Clay_TextElementConfig cell_x_text = {0};
|
||||
cell_x_text.textColor = (Clay_Color){255, 255, 255, 255};
|
||||
cell_x_text.fontSize = FONT_SIZE_SMALL;
|
||||
cell_x_text.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
static Clay_TextElementConfig fb_text = {};
|
||||
fb_text.textColor = Clay_Color{80, 80, 80, 255};
|
||||
static Clay_TextElementConfig fb_text = {0};
|
||||
fb_text.textColor = (Clay_Color){80, 80, 80, 255};
|
||||
fb_text.fontSize = FONT_SIZE_SMALL;
|
||||
fb_text.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
@@ -1549,7 +1549,7 @@ static void build_patch_view(AppState *app) {
|
||||
|
||||
Clay_Color cell_bg;
|
||||
if (is_feedback) {
|
||||
cell_bg = Clay_Color{
|
||||
cell_bg = (Clay_Color){
|
||||
(F32)Max((S32)g_theme.bg_dark.r - 10, 0),
|
||||
(F32)Max((S32)g_theme.bg_dark.g - 10, 0),
|
||||
(F32)Max((S32)g_theme.bg_dark.b - 10, 0), 255
|
||||
@@ -1561,7 +1561,7 @@ static void build_patch_view(AppState *app) {
|
||||
} else if ((s + d) % 2 == 0) {
|
||||
cell_bg = g_theme.bg_dark;
|
||||
} else {
|
||||
cell_bg = Clay_Color{
|
||||
cell_bg = (Clay_Color){
|
||||
(F32)Min((S32)g_theme.bg_dark.r + 6, 255),
|
||||
(F32)Min((S32)g_theme.bg_dark.g + 6, 255),
|
||||
(F32)Min((S32)g_theme.bg_dark.b + 6, 255), 255
|
||||
@@ -1569,7 +1569,7 @@ static void build_patch_view(AppState *app) {
|
||||
}
|
||||
|
||||
if (d == 0 && !active && !cell_hovered && !is_feedback) {
|
||||
cell_bg = Clay_Color{
|
||||
cell_bg = (Clay_Color){
|
||||
(F32)Min((S32)cell_bg.r + 10, 255),
|
||||
(F32)Min((S32)cell_bg.g + 10, 255),
|
||||
(F32)Min((S32)cell_bg.b + 12, 255), 255
|
||||
@@ -1699,7 +1699,7 @@ static void build_patch_view(AppState *app) {
|
||||
} else if ((s + d) % 2 == 0) {
|
||||
cell_bg = g_theme.bg_dark;
|
||||
} else {
|
||||
cell_bg = Clay_Color{
|
||||
cell_bg = (Clay_Color){
|
||||
(F32)Min((S32)g_theme.bg_dark.r + 6, 255),
|
||||
(F32)Min((S32)g_theme.bg_dark.g + 6, 255),
|
||||
(F32)Min((S32)g_theme.bg_dark.b + 6, 255), 255
|
||||
@@ -1933,7 +1933,7 @@ static void do_frame(AppState *app) {
|
||||
Clay_RenderCommandArray render_commands = ui_end_frame(app->ui);
|
||||
|
||||
// Render
|
||||
renderer_end_frame(app->renderer, render_commands);
|
||||
renderer_end_frame(app->renderer, &render_commands);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
@@ -1950,7 +1950,7 @@ int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
PlatformWindowDesc window_desc = {};
|
||||
PlatformWindowDesc window_desc = platform_window_desc_default();
|
||||
PlatformWindow *window = platform_create_window(&window_desc);
|
||||
if (!window)
|
||||
return 1;
|
||||
@@ -1958,7 +1958,7 @@ int main(int argc, char **argv) {
|
||||
S32 w, h;
|
||||
platform_get_size(window, &w, &h);
|
||||
|
||||
RendererDesc renderer_desc = {};
|
||||
RendererDesc renderer_desc = {0};
|
||||
renderer_desc.window_handle = platform_get_native_handle(window);
|
||||
renderer_desc.width = w;
|
||||
renderer_desc.height = h;
|
||||
@@ -1990,7 +1990,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
AppState app = {};
|
||||
AppState app = {0};
|
||||
app.window = window;
|
||||
app.renderer = renderer;
|
||||
app.midi = midi;
|
||||
@@ -2060,9 +2060,9 @@ int main(int argc, char **argv) {
|
||||
|
||||
// Open popup windows when flags transition to 1
|
||||
if (app.show_settings_window && !popup_find_by_flag(&app.show_settings_window))
|
||||
popup_open(window, renderer, "Preferences", &app.show_settings_window, 480, 400, settings_window_content, &app);
|
||||
popup_open(window, renderer, "Preferences", &app.show_settings_window, 480, 400, settings_window_content, &app, PLATFORM_WINDOW_STYLE_POPUP, 0);
|
||||
if (app.show_about_window && !popup_find_by_flag(&app.show_about_window))
|
||||
popup_open(window, renderer, "About", &app.show_about_window, 260, 200, about_window_content, nullptr);
|
||||
popup_open(window, renderer, "About", &app.show_about_window, 260, 200, about_window_content, NULL, PLATFORM_WINDOW_STYLE_POPUP, 0);
|
||||
if (app.show_mix_popout && !popup_find_by_flag(&app.show_mix_popout))
|
||||
popup_open(window, renderer, "Mix", &app.show_mix_popout, 900, 600, mix_popout_content, &app, PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE, 1);
|
||||
if (app.show_patch_popout && !popup_find_by_flag(&app.show_patch_popout))
|
||||
@@ -2082,7 +2082,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
popup_close_all();
|
||||
platform_set_frame_callback(window, nullptr, nullptr);
|
||||
platform_set_frame_callback(window, NULL, NULL);
|
||||
audio_destroy(audio);
|
||||
midi_destroy(midi);
|
||||
ui_destroy(ui);
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "platform/platform.h"
|
||||
|
||||
enum MenuCmd {
|
||||
typedef enum MenuCmd {
|
||||
MENU_NONE = 0,
|
||||
MENU_FILE_NEW,
|
||||
MENU_FILE_OPEN,
|
||||
@@ -15,7 +15,7 @@ enum MenuCmd {
|
||||
MENU_VIEW_DEMO,
|
||||
MENU_VIEW_MIDI_DEVICES,
|
||||
MENU_PREFERENCES_EDIT,
|
||||
};
|
||||
} MenuCmd;
|
||||
|
||||
static void setup_menus(PlatformWindow *window) {
|
||||
PlatformMenuItem file_items[] = {
|
||||
@@ -23,7 +23,7 @@ static void setup_menus(PlatformWindow *window) {
|
||||
{ "Open...", MENU_FILE_OPEN },
|
||||
{ "Save", MENU_FILE_SAVE },
|
||||
{ "Save As...", MENU_FILE_SAVE_AS },
|
||||
{ nullptr, 0 },
|
||||
{ NULL, 0 },
|
||||
{ "Exit", MENU_FILE_EXIT },
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ static void setup_menus(PlatformWindow *window) {
|
||||
{ "Browser", MENU_VIEW_BROWSER },
|
||||
{ "Properties", MENU_VIEW_PROPERTIES },
|
||||
{ "Log", MENU_VIEW_LOG },
|
||||
{ nullptr, 0 },
|
||||
{ NULL, 0 },
|
||||
{ "Demo", MENU_VIEW_DEMO },
|
||||
{ "MIDI Devices", MENU_VIEW_MIDI_DEVICES },
|
||||
};
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
#include "base/base_core.h"
|
||||
|
||||
struct MidiEngine;
|
||||
typedef struct MidiEngine MidiEngine;
|
||||
|
||||
struct MidiDeviceInfo {
|
||||
typedef struct MidiDeviceInfo {
|
||||
char name[64];
|
||||
S32 id;
|
||||
B32 is_input;
|
||||
@@ -12,9 +12,9 @@ struct MidiDeviceInfo {
|
||||
B32 releasing; // true during release flash
|
||||
S32 velocity; // last note-on velocity (0-127)
|
||||
S32 note; // last MIDI note number (0-127)
|
||||
};
|
||||
} MidiDeviceInfo;
|
||||
|
||||
MidiEngine *midi_create();
|
||||
MidiEngine *midi_create(void);
|
||||
void midi_destroy(MidiEngine *engine);
|
||||
void midi_refresh_devices(MidiEngine *engine);
|
||||
S32 midi_get_device_count(MidiEngine *engine);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MIDI_MAX_DEVICES 64
|
||||
#define MIDI_RELEASE_FLASH_DURATION 0.15f
|
||||
@@ -113,7 +114,7 @@ static void enumerate_midi_devices(MidiEngine *engine) {
|
||||
for (ItemCount i = 0; i < num_sources && engine->device_count < MIDI_MAX_DEVICES; i++) {
|
||||
MIDIEndpointRef endpoint = MIDIGetSource((ItemCount)i);
|
||||
|
||||
CFStringRef name_ref = nullptr;
|
||||
CFStringRef name_ref = NULL;
|
||||
MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref);
|
||||
|
||||
MidiDeviceInfo *dev = &engine->devices[engine->device_count];
|
||||
@@ -139,7 +140,7 @@ static void enumerate_midi_devices(MidiEngine *engine) {
|
||||
for (ItemCount i = 0; i < num_dests && engine->device_count < MIDI_MAX_DEVICES; i++) {
|
||||
MIDIEndpointRef endpoint = MIDIGetDestination((ItemCount)i);
|
||||
|
||||
CFStringRef name_ref = nullptr;
|
||||
CFStringRef name_ref = NULL;
|
||||
MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref);
|
||||
|
||||
MidiDeviceInfo *dev = &engine->devices[engine->device_count];
|
||||
@@ -163,11 +164,10 @@ static void enumerate_midi_devices(MidiEngine *engine) {
|
||||
////////////////////////////////
|
||||
// Public API
|
||||
|
||||
MidiEngine *midi_create() {
|
||||
MidiEngine *engine = new MidiEngine();
|
||||
memset(engine, 0, sizeof(*engine));
|
||||
MidiEngine *midi_create(void) {
|
||||
MidiEngine *engine = (MidiEngine *)calloc(1, sizeof(MidiEngine));
|
||||
|
||||
MIDIClientCreate(CFSTR("autosample"), nullptr, nullptr, &engine->client);
|
||||
MIDIClientCreate(CFSTR("autosample"), NULL, NULL, &engine->client);
|
||||
MIDIInputPortCreate(engine->client, CFSTR("Input"), midi_read_callback, engine, &engine->input_port);
|
||||
|
||||
midi_refresh_devices(engine);
|
||||
@@ -178,7 +178,7 @@ void midi_destroy(MidiEngine *engine) {
|
||||
midi_close_all_inputs(engine);
|
||||
if (engine->input_port) MIDIPortDispose(engine->input_port);
|
||||
if (engine->client) MIDIClientDispose(engine->client);
|
||||
delete engine;
|
||||
free(engine);
|
||||
}
|
||||
|
||||
void midi_open_all_inputs(MidiEngine *engine) {
|
||||
@@ -276,6 +276,6 @@ S32 midi_get_device_count(MidiEngine *engine) {
|
||||
}
|
||||
|
||||
MidiDeviceInfo *midi_get_device(MidiEngine *engine, S32 index) {
|
||||
if (index < 0 || index >= engine->device_count) return nullptr;
|
||||
if (index < 0 || index >= engine->device_count) return NULL;
|
||||
return &engine->devices[index];
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <windows.h>
|
||||
#include <mmeapi.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MIDI_MAX_DEVICES 64
|
||||
#define MIDI_RELEASE_FLASH_DURATION 0.15f
|
||||
@@ -31,7 +32,7 @@ struct MidiEngine {
|
||||
////////////////////////////////
|
||||
// MIDI input callback — called from Win32 MIDI driver thread
|
||||
|
||||
static MidiEngine *g_midi_engine = nullptr;
|
||||
static MidiEngine *g_midi_engine = NULL;
|
||||
|
||||
static void CALLBACK midi_in_callback(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
@@ -71,9 +72,8 @@ static void CALLBACK midi_in_callback(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwIn
|
||||
}
|
||||
}
|
||||
|
||||
MidiEngine *midi_create() {
|
||||
MidiEngine *engine = new MidiEngine();
|
||||
memset(engine, 0, sizeof(*engine));
|
||||
MidiEngine *midi_create(void) {
|
||||
MidiEngine *engine = (MidiEngine *)calloc(1, sizeof(MidiEngine));
|
||||
g_midi_engine = engine;
|
||||
midi_refresh_devices(engine);
|
||||
return engine;
|
||||
@@ -81,8 +81,8 @@ MidiEngine *midi_create() {
|
||||
|
||||
void midi_destroy(MidiEngine *engine) {
|
||||
midi_close_all_inputs(engine);
|
||||
if (g_midi_engine == engine) g_midi_engine = nullptr;
|
||||
delete engine;
|
||||
if (g_midi_engine == engine) g_midi_engine = NULL;
|
||||
free(engine);
|
||||
}
|
||||
|
||||
void midi_open_all_inputs(MidiEngine *engine) {
|
||||
@@ -91,7 +91,7 @@ void midi_open_all_inputs(MidiEngine *engine) {
|
||||
if (!dev->is_input) continue;
|
||||
if (engine->input_handles[i]) continue; // already open
|
||||
|
||||
HMIDIIN handle = nullptr;
|
||||
HMIDIIN handle = NULL;
|
||||
MMRESULT res = midiInOpen(&handle, (UINT)dev->id,
|
||||
(DWORD_PTR)midi_in_callback,
|
||||
(DWORD_PTR)i, CALLBACK_FUNCTION);
|
||||
@@ -107,7 +107,7 @@ void midi_close_all_inputs(MidiEngine *engine) {
|
||||
if (engine->input_handles[i]) {
|
||||
midiInStop(engine->input_handles[i]);
|
||||
midiInClose(engine->input_handles[i]);
|
||||
engine->input_handles[i] = nullptr;
|
||||
engine->input_handles[i] = NULL;
|
||||
}
|
||||
engine->pending_note_on_vel[i] = 0;
|
||||
engine->pending_note_num[i] = 0;
|
||||
@@ -193,7 +193,7 @@ void midi_refresh_devices(MidiEngine *engine) {
|
||||
|
||||
UINT num_in = midiInGetNumDevs();
|
||||
for (UINT i = 0; i < num_in && engine->device_count < MIDI_MAX_DEVICES; i++) {
|
||||
MIDIINCAPSA caps = {};
|
||||
MIDIINCAPSA caps = {0};
|
||||
if (midiInGetDevCapsA(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR) {
|
||||
MidiDeviceInfo *dev = &engine->devices[engine->device_count++];
|
||||
strncpy_s(dev->name, sizeof(dev->name), caps.szPname, _TRUNCATE);
|
||||
@@ -205,7 +205,7 @@ void midi_refresh_devices(MidiEngine *engine) {
|
||||
|
||||
UINT num_out = midiOutGetNumDevs();
|
||||
for (UINT i = 0; i < num_out && engine->device_count < MIDI_MAX_DEVICES; i++) {
|
||||
MIDIOUTCAPSA caps = {};
|
||||
MIDIOUTCAPSA caps = {0};
|
||||
if (midiOutGetDevCapsA(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR) {
|
||||
MidiDeviceInfo *dev = &engine->devices[engine->device_count++];
|
||||
strncpy_s(dev->name, sizeof(dev->name), caps.szPname, _TRUNCATE);
|
||||
@@ -224,6 +224,6 @@ S32 midi_get_device_count(MidiEngine *engine) {
|
||||
|
||||
MidiDeviceInfo *midi_get_device(MidiEngine *engine, S32 index) {
|
||||
if (index < 0 || index >= engine->device_count)
|
||||
return nullptr;
|
||||
return NULL;
|
||||
return &engine->devices[index];
|
||||
}
|
||||
@@ -31,7 +31,7 @@ enum {
|
||||
PKEY_MINUS = 0xBD, // '-'/'_' (VK_OEM_MINUS)
|
||||
};
|
||||
|
||||
struct PlatformInput {
|
||||
typedef struct PlatformInput {
|
||||
// Typed characters (UTF-16 code units, printable only)
|
||||
U16 chars[PLATFORM_MAX_CHARS_PER_FRAME];
|
||||
S32 char_count;
|
||||
@@ -49,41 +49,50 @@ struct PlatformInput {
|
||||
Vec2F32 scroll_delta;
|
||||
B32 mouse_down;
|
||||
B32 was_mouse_down;
|
||||
};
|
||||
} PlatformInput;
|
||||
|
||||
struct PlatformWindow;
|
||||
typedef struct PlatformWindow PlatformWindow;
|
||||
|
||||
enum PlatformWindowStyle {
|
||||
typedef enum PlatformWindowStyle {
|
||||
PLATFORM_WINDOW_STYLE_NORMAL = 0,
|
||||
PLATFORM_WINDOW_STYLE_POPUP = 1, // utility panel, owned by parent, fixed size
|
||||
PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE = 2, // utility panel, owned by parent, resizable
|
||||
};
|
||||
} PlatformWindowStyle;
|
||||
|
||||
struct PlatformWindowDesc {
|
||||
const char *title = "autosample";
|
||||
S32 width = 1280;
|
||||
S32 height = 720;
|
||||
PlatformWindowStyle style = PLATFORM_WINDOW_STYLE_NORMAL;
|
||||
PlatformWindow *parent = nullptr;
|
||||
B32 independent = 0; // if true, don't attach as child (independent top-level window)
|
||||
};
|
||||
typedef struct PlatformWindowDesc {
|
||||
const char *title;
|
||||
S32 width;
|
||||
S32 height;
|
||||
PlatformWindowStyle style;
|
||||
PlatformWindow *parent;
|
||||
B32 independent; // if true, don't attach as child (independent top-level window)
|
||||
} PlatformWindowDesc;
|
||||
|
||||
enum PlatformMsgBoxType {
|
||||
// Helper to create a default PlatformWindowDesc
|
||||
static inline PlatformWindowDesc platform_window_desc_default(void) {
|
||||
PlatformWindowDesc d = {0};
|
||||
d.title = "autosample";
|
||||
d.width = 1280;
|
||||
d.height = 720;
|
||||
return d;
|
||||
}
|
||||
|
||||
typedef enum PlatformMsgBoxType {
|
||||
PLATFORM_MSGBOX_OK = 0,
|
||||
PLATFORM_MSGBOX_OK_CANCEL = 1,
|
||||
PLATFORM_MSGBOX_YES_NO = 2,
|
||||
};
|
||||
} PlatformMsgBoxType;
|
||||
|
||||
struct PlatformMenuItem {
|
||||
const char *label; // nullptr = separator
|
||||
typedef struct PlatformMenuItem {
|
||||
const char *label; // NULL = separator
|
||||
S32 id; // command ID (ignored for separators)
|
||||
};
|
||||
} PlatformMenuItem;
|
||||
|
||||
struct PlatformMenu {
|
||||
typedef struct PlatformMenu {
|
||||
const char *label;
|
||||
PlatformMenuItem *items;
|
||||
S32 item_count;
|
||||
};
|
||||
} PlatformMenu;
|
||||
|
||||
// Called by the platform layer when the window needs a frame rendered
|
||||
// (e.g., during a live resize). user_data is the pointer passed to
|
||||
@@ -114,11 +123,11 @@ S32 platform_message_box(PlatformWindow *parent, const char *title,
|
||||
const char *message, PlatformMsgBoxType type);
|
||||
|
||||
// Cursor shapes for resize handles
|
||||
enum PlatformCursor {
|
||||
typedef enum PlatformCursor {
|
||||
PLATFORM_CURSOR_ARROW = 0,
|
||||
PLATFORM_CURSOR_SIZE_WE = 1, // horizontal resize
|
||||
PLATFORM_CURSOR_SIZE_NS = 2, // vertical resize
|
||||
};
|
||||
} PlatformCursor;
|
||||
|
||||
void platform_set_cursor(PlatformCursor cursor);
|
||||
|
||||
@@ -128,6 +137,6 @@ F32 platform_get_dpi_scale(PlatformWindow *window);
|
||||
|
||||
// Clipboard operations (null-terminated UTF-8 strings).
|
||||
// platform_clipboard_set copies text to the system clipboard.
|
||||
// platform_clipboard_get returns a pointer to a static buffer (valid until next call), or nullptr.
|
||||
// platform_clipboard_get returns a pointer to a static buffer (valid until next call), or NULL.
|
||||
void platform_clipboard_set(const char *text);
|
||||
const char *platform_clipboard_get();
|
||||
const char *platform_clipboard_get(void);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "platform/platform.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// macOS virtual key codes (avoids Carbon.h include)
|
||||
enum {
|
||||
@@ -46,10 +48,8 @@ static U8 macos_keycode_to_pkey(U16 keycode) {
|
||||
////////////////////////////////
|
||||
// Forward declarations
|
||||
|
||||
struct PlatformWindow;
|
||||
|
||||
// Main window receives menu commands
|
||||
static PlatformWindow *g_main_window = nullptr;
|
||||
static PlatformWindow *g_main_window = NULL;
|
||||
|
||||
////////////////////////////////
|
||||
// Objective-C helper classes
|
||||
@@ -77,7 +77,7 @@ static PlatformWindow *g_main_window = nullptr;
|
||||
////////////////////////////////
|
||||
// PlatformWindow struct
|
||||
|
||||
struct PlatformWindow {
|
||||
typedef struct PlatformWindow {
|
||||
NSWindow *ns_window;
|
||||
ASmplView *view;
|
||||
ASmplWindowDelegate *delegate;
|
||||
@@ -91,7 +91,7 @@ struct PlatformWindow {
|
||||
B32 prev_mouse_down;
|
||||
B32 mouse_down_state;
|
||||
F32 backing_scale;
|
||||
};
|
||||
} PlatformWindow;
|
||||
|
||||
////////////////////////////////
|
||||
// C callback helpers (called from ObjC via PlatformWindow pointer)
|
||||
@@ -288,8 +288,7 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
ASmplWindowDelegate *delegate = [[ASmplWindowDelegate alloc] init];
|
||||
[ns_window setDelegate:delegate];
|
||||
|
||||
PlatformWindow *window = new PlatformWindow();
|
||||
memset(window, 0, sizeof(*window));
|
||||
PlatformWindow *window = (PlatformWindow *)calloc(1, sizeof(PlatformWindow));
|
||||
window->ns_window = ns_window;
|
||||
window->view = view;
|
||||
window->delegate = delegate;
|
||||
@@ -329,8 +328,8 @@ void platform_destroy_window(PlatformWindow *window) {
|
||||
|
||||
[window->ns_window close];
|
||||
if (g_main_window == window)
|
||||
g_main_window = nullptr;
|
||||
delete window;
|
||||
g_main_window = NULL;
|
||||
free(window);
|
||||
}
|
||||
|
||||
B32 platform_poll_events(PlatformWindow *window) {
|
||||
@@ -442,7 +441,7 @@ PlatformInput platform_get_input(PlatformWindow *window) {
|
||||
result.shift_held = (mods & NSEventModifierFlagShift) != 0;
|
||||
|
||||
// Clear accumulated events for next frame
|
||||
window->input = {};
|
||||
memset(&window->input, 0, sizeof(window->input));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -491,7 +490,7 @@ const char *platform_clipboard_get() {
|
||||
}
|
||||
}
|
||||
|
||||
return buf[0] ? buf : nullptr;
|
||||
return buf[0] ? buf : NULL;
|
||||
}
|
||||
|
||||
S32 platform_message_box(PlatformWindow *parent, const char *title,
|
||||
@@ -5,8 +5,10 @@
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <malloc.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct PlatformWindow {
|
||||
typedef struct PlatformWindow {
|
||||
HWND hwnd;
|
||||
B32 should_close;
|
||||
S32 width;
|
||||
@@ -16,11 +18,11 @@ struct PlatformWindow {
|
||||
void *frame_callback_user_data;
|
||||
PlatformInput input;
|
||||
B32 prev_mouse_down;
|
||||
};
|
||||
} PlatformWindow;
|
||||
|
||||
// Main window receives menu commands
|
||||
static PlatformWindow *g_main_window = nullptr;
|
||||
static HCURSOR g_current_cursor = nullptr;
|
||||
static PlatformWindow *g_main_window = NULL;
|
||||
static HCURSOR g_current_cursor = NULL;
|
||||
static B32 g_wndclass_registered = false;
|
||||
|
||||
static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
||||
@@ -69,14 +71,14 @@ static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM
|
||||
case WM_SETCURSOR:
|
||||
// When the cursor is in our client area, use the app-set cursor.
|
||||
if (LOWORD(lparam) == HTCLIENT) {
|
||||
SetCursor(g_current_cursor ? g_current_cursor : LoadCursor(nullptr, IDC_ARROW));
|
||||
SetCursor(g_current_cursor ? g_current_cursor : LoadCursor(NULL, IDC_ARROW));
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
case WM_DPICHANGED:
|
||||
if (pw) {
|
||||
RECT *suggested = (RECT *)lparam;
|
||||
SetWindowPos(hwnd, nullptr, suggested->left, suggested->top,
|
||||
SetWindowPos(hwnd, NULL, suggested->left, suggested->top,
|
||||
suggested->right - suggested->left, suggested->bottom - suggested->top,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
@@ -101,12 +103,12 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
|
||||
if (!g_wndclass_registered) {
|
||||
WNDCLASSEXW wc = {};
|
||||
WNDCLASSEXW wc = {0};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.style = CS_CLASSDC;
|
||||
wc.lpfnWndProc = win32_wndproc;
|
||||
wc.hInstance = GetModuleHandleW(nullptr);
|
||||
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wc.hInstance = GetModuleHandleW(NULL);
|
||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wc.lpszClassName = L"autosample_wc";
|
||||
RegisterClassExW(&wc);
|
||||
g_wndclass_registered = true;
|
||||
@@ -119,7 +121,7 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
int y = (screen_h - desc->height) / 2;
|
||||
|
||||
DWORD style;
|
||||
HWND parent_hwnd = nullptr;
|
||||
HWND parent_hwnd = NULL;
|
||||
if (desc->style == PLATFORM_WINDOW_STYLE_POPUP) {
|
||||
style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
|
||||
if (desc->parent && !desc->independent) parent_hwnd = desc->parent->hwnd;
|
||||
@@ -133,7 +135,7 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
RECT rect = { 0, 0, (LONG)desc->width, (LONG)desc->height };
|
||||
AdjustWindowRectExForDpi(&rect, style, FALSE, 0, dpi);
|
||||
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, nullptr, 0);
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, NULL, 0);
|
||||
wchar_t *wtitle = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, wtitle, wchar_count);
|
||||
|
||||
@@ -143,15 +145,15 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
x, y,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top,
|
||||
parent_hwnd, nullptr, GetModuleHandleW(nullptr), nullptr
|
||||
parent_hwnd, NULL, GetModuleHandleW(NULL), NULL
|
||||
);
|
||||
|
||||
_freea(wtitle);
|
||||
|
||||
if (!hwnd)
|
||||
return nullptr;
|
||||
return NULL;
|
||||
|
||||
PlatformWindow *window = new PlatformWindow();
|
||||
PlatformWindow *window = (PlatformWindow *)calloc(1, sizeof(PlatformWindow));
|
||||
window->hwnd = hwnd;
|
||||
window->should_close = false;
|
||||
window->width = desc->width;
|
||||
@@ -179,14 +181,14 @@ void platform_destroy_window(PlatformWindow *window) {
|
||||
}
|
||||
|
||||
if (g_main_window == window)
|
||||
g_main_window = nullptr;
|
||||
g_main_window = NULL;
|
||||
|
||||
delete window;
|
||||
free(window);
|
||||
}
|
||||
|
||||
B32 platform_poll_events(PlatformWindow *window) {
|
||||
MSG msg;
|
||||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
if (msg.message == WM_QUIT) {
|
||||
@@ -219,9 +221,9 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
for (S32 j = 0; j < menus[i].item_count; j++) {
|
||||
PlatformMenuItem *item = &menus[i].items[j];
|
||||
if (!item->label) {
|
||||
AppendMenuW(submenu, MF_SEPARATOR, 0, nullptr);
|
||||
AppendMenuW(submenu, MF_SEPARATOR, 0, NULL);
|
||||
} else {
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, item->label, -1, nullptr, 0);
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, item->label, -1, NULL, 0);
|
||||
wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, item->label, -1, wlabel, wchar_count);
|
||||
AppendMenuW(submenu, MF_STRING, (UINT_PTR)item->id, wlabel);
|
||||
@@ -229,7 +231,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
}
|
||||
}
|
||||
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, nullptr, 0);
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, NULL, 0);
|
||||
wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, wlabel, wchar_count);
|
||||
AppendMenuW(menu_bar, MF_POPUP, (UINT_PTR)submenu, wlabel);
|
||||
@@ -260,7 +262,7 @@ PlatformInput platform_get_input(PlatformWindow *window) {
|
||||
window->prev_mouse_down = result.mouse_down;
|
||||
|
||||
// Clear accumulated events for next frame
|
||||
window->input = {};
|
||||
memset(&window->input, 0, sizeof(window->input));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -281,9 +283,9 @@ F32 platform_get_dpi_scale(PlatformWindow *window) {
|
||||
|
||||
void platform_set_cursor(PlatformCursor cursor) {
|
||||
switch (cursor) {
|
||||
case PLATFORM_CURSOR_SIZE_WE: g_current_cursor = LoadCursor(nullptr, IDC_SIZEWE); break;
|
||||
case PLATFORM_CURSOR_SIZE_NS: g_current_cursor = LoadCursor(nullptr, IDC_SIZENS); break;
|
||||
default: g_current_cursor = LoadCursor(nullptr, IDC_ARROW); break;
|
||||
case PLATFORM_CURSOR_SIZE_WE: g_current_cursor = LoadCursor(NULL, IDC_SIZEWE); break;
|
||||
case PLATFORM_CURSOR_SIZE_NS: g_current_cursor = LoadCursor(NULL, IDC_SIZENS); break;
|
||||
default: g_current_cursor = LoadCursor(NULL, IDC_ARROW); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +295,7 @@ void platform_clipboard_set(const char *text) {
|
||||
if (len == 0) return;
|
||||
|
||||
// Convert UTF-8 to wide string for Windows clipboard
|
||||
int wlen = MultiByteToWideChar(CP_UTF8, 0, text, len, nullptr, 0);
|
||||
int wlen = MultiByteToWideChar(CP_UTF8, 0, text, len, NULL, 0);
|
||||
if (wlen == 0) return;
|
||||
|
||||
HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE, (wlen + 1) * sizeof(wchar_t));
|
||||
@@ -304,7 +306,7 @@ void platform_clipboard_set(const char *text) {
|
||||
wbuf[wlen] = L'\0';
|
||||
GlobalUnlock(hmem);
|
||||
|
||||
HWND hwnd = g_main_window ? g_main_window->hwnd : nullptr;
|
||||
HWND hwnd = g_main_window ? g_main_window->hwnd : NULL;
|
||||
if (OpenClipboard(hwnd)) {
|
||||
EmptyClipboard();
|
||||
SetClipboardData(CF_UNICODETEXT, hmem);
|
||||
@@ -318,21 +320,21 @@ const char *platform_clipboard_get() {
|
||||
static char buf[4096];
|
||||
buf[0] = '\0';
|
||||
|
||||
HWND hwnd = g_main_window ? g_main_window->hwnd : nullptr;
|
||||
if (!OpenClipboard(hwnd)) return nullptr;
|
||||
HWND hwnd = g_main_window ? g_main_window->hwnd : NULL;
|
||||
if (!OpenClipboard(hwnd)) return NULL;
|
||||
|
||||
HGLOBAL hmem = GetClipboardData(CF_UNICODETEXT);
|
||||
if (hmem) {
|
||||
wchar_t *wbuf = (wchar_t *)GlobalLock(hmem);
|
||||
if (wbuf) {
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf) - 1, nullptr, nullptr);
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf) - 1, NULL, NULL);
|
||||
buf[len > 0 ? len - 1 : 0] = '\0'; // WideCharToMultiByte includes null in count
|
||||
GlobalUnlock(hmem);
|
||||
}
|
||||
}
|
||||
|
||||
CloseClipboard();
|
||||
return buf[0] ? buf : nullptr;
|
||||
return buf[0] ? buf : NULL;
|
||||
}
|
||||
|
||||
S32 platform_message_box(PlatformWindow *parent, const char *title,
|
||||
@@ -346,15 +348,15 @@ S32 platform_message_box(PlatformWindow *parent, const char *title,
|
||||
}
|
||||
|
||||
// Convert UTF-8 to wide strings
|
||||
int title_wlen = MultiByteToWideChar(CP_UTF8, 0, title, -1, nullptr, 0);
|
||||
int title_wlen = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0);
|
||||
wchar_t *wtitle = (wchar_t *)_malloca(title_wlen * sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, title, -1, wtitle, title_wlen);
|
||||
|
||||
int msg_wlen = MultiByteToWideChar(CP_UTF8, 0, message, -1, nullptr, 0);
|
||||
int msg_wlen = MultiByteToWideChar(CP_UTF8, 0, message, -1, NULL, 0);
|
||||
wchar_t *wmsg = (wchar_t *)_malloca(msg_wlen * sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, message, -1, wmsg, msg_wlen);
|
||||
|
||||
HWND hwnd = parent ? parent->hwnd : nullptr;
|
||||
HWND hwnd = parent ? parent->hwnd : NULL;
|
||||
int result = MessageBoxW(hwnd, wmsg, wtitle, mb_type);
|
||||
|
||||
_freea(wmsg);
|
||||
@@ -1,13 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "base/base_core.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include "base/base_math.h"
|
||||
#include "clay.h"
|
||||
|
||||
typedef struct Renderer Renderer;
|
||||
struct Clay_RenderCommandArray;
|
||||
|
||||
typedef struct RendererDesc {
|
||||
void *window_handle;
|
||||
@@ -26,7 +23,7 @@ Renderer *renderer_create_shared(Renderer *parent, RendererDesc *desc);
|
||||
|
||||
void renderer_destroy(Renderer *renderer);
|
||||
B32 renderer_begin_frame(Renderer *renderer);
|
||||
void renderer_end_frame(Renderer *renderer, Clay_RenderCommandArray render_commands);
|
||||
void renderer_end_frame(Renderer *renderer, Clay_RenderCommandArray *render_commands);
|
||||
void renderer_resize(Renderer *renderer, S32 width, S32 height);
|
||||
void renderer_set_font_scale(Renderer *renderer, F32 scale);
|
||||
void renderer_sync_from_parent(Renderer *renderer); // sync shared font atlas from parent
|
||||
@@ -39,7 +36,3 @@ Vec2F32 renderer_measure_text(const char *text, S32 length, F32 font_size, void
|
||||
|
||||
// Upload an RGBA8 icon atlas texture for icon rendering (4 bytes per pixel)
|
||||
void renderer_create_icon_atlas(Renderer *renderer, const U8 *data, S32 w, S32 h);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// FreeType headers — temporarily undefine `internal` macro (base_core.h: #define internal static)
|
||||
// because FreeType uses `internal` as a struct field name.
|
||||
@@ -30,7 +32,7 @@
|
||||
////////////////////////////////
|
||||
// Vertex format — matches DX12 UIVertex exactly
|
||||
|
||||
struct UIVertex {
|
||||
typedef struct UIVertex {
|
||||
float pos[2];
|
||||
float uv[2];
|
||||
float col[4];
|
||||
@@ -40,120 +42,119 @@ struct UIVertex {
|
||||
float border_thickness;
|
||||
float softness;
|
||||
float mode; // 0 = rect SDF, 1 = textured
|
||||
};
|
||||
} UIVertex;
|
||||
|
||||
////////////////////////////////
|
||||
// Glyph info
|
||||
|
||||
struct GlyphInfo {
|
||||
typedef struct GlyphInfo {
|
||||
F32 u0, v0, u1, v1;
|
||||
F32 w, h;
|
||||
F32 x_advance;
|
||||
};
|
||||
} GlyphInfo;
|
||||
|
||||
////////////////////////////////
|
||||
// Metal shader (MSL) — port of HLSL SDF shader
|
||||
|
||||
static const char *g_shader_msl = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct Vertex {
|
||||
float2 pos [[attribute(0)]];
|
||||
float2 uv [[attribute(1)]];
|
||||
float4 col [[attribute(2)]];
|
||||
float2 rect_min [[attribute(3)]];
|
||||
float2 rect_max [[attribute(4)]];
|
||||
float4 corner_radii [[attribute(5)]];
|
||||
float border_thickness [[attribute(6)]];
|
||||
float softness [[attribute(7)]];
|
||||
float mode [[attribute(8)]];
|
||||
};
|
||||
|
||||
struct Fragment {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
float4 col;
|
||||
float2 rect_min;
|
||||
float2 rect_max;
|
||||
float4 corner_radii;
|
||||
float border_thickness;
|
||||
float softness;
|
||||
float mode;
|
||||
};
|
||||
|
||||
struct Constants {
|
||||
float2 viewport_size;
|
||||
};
|
||||
|
||||
vertex Fragment vertex_main(Vertex in [[stage_in]],
|
||||
constant Constants &cb [[buffer(1)]]) {
|
||||
Fragment out;
|
||||
float2 ndc;
|
||||
ndc.x = (in.pos.x / cb.viewport_size.x) * 2.0 - 1.0;
|
||||
ndc.y = 1.0 - (in.pos.y / cb.viewport_size.y) * 2.0;
|
||||
out.pos = float4(ndc, 0.0, 1.0);
|
||||
out.uv = in.uv;
|
||||
out.col = in.col;
|
||||
out.rect_min = in.rect_min;
|
||||
out.rect_max = in.rect_max;
|
||||
out.corner_radii = in.corner_radii;
|
||||
out.border_thickness = in.border_thickness;
|
||||
out.softness = in.softness;
|
||||
out.mode = in.mode;
|
||||
return out;
|
||||
}
|
||||
|
||||
float rounded_rect_sdf(float2 sample_pos, float2 rect_center, float2 rect_half_size, float radius) {
|
||||
float2 d = abs(sample_pos - rect_center) - rect_half_size + float2(radius, radius);
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;
|
||||
}
|
||||
|
||||
fragment float4 fragment_main(Fragment in [[stage_in]],
|
||||
texture2d<float> font_tex [[texture(0)]],
|
||||
sampler font_smp [[sampler(0)]]) {
|
||||
float4 col = in.col;
|
||||
|
||||
if (in.mode > 1.5) {
|
||||
float4 tex = font_tex.sample(font_smp, in.uv);
|
||||
col *= tex;
|
||||
} else if (in.mode > 0.5) {
|
||||
float alpha = font_tex.sample(font_smp, in.uv).r;
|
||||
col.a *= alpha;
|
||||
} else {
|
||||
float2 pixel_pos = in.pos.xy;
|
||||
float2 rect_center = (in.rect_min + in.rect_max) * 0.5;
|
||||
float2 rect_half_size = (in.rect_max - in.rect_min) * 0.5;
|
||||
float radius = (pixel_pos.x < rect_center.x)
|
||||
? ((pixel_pos.y < rect_center.y) ? in.corner_radii.x : in.corner_radii.w)
|
||||
: ((pixel_pos.y < rect_center.y) ? in.corner_radii.y : in.corner_radii.z);
|
||||
float softness = max(in.softness, 0.5);
|
||||
float dist = rounded_rect_sdf(pixel_pos, rect_center, rect_half_size, radius);
|
||||
|
||||
if (in.border_thickness > 0) {
|
||||
float inner_dist = dist + in.border_thickness;
|
||||
float outer_alpha = 1.0 - smoothstep(-softness, softness, dist);
|
||||
float inner_alpha = smoothstep(-softness, softness, inner_dist);
|
||||
col.a *= outer_alpha * inner_alpha;
|
||||
} else {
|
||||
col.a *= 1.0 - smoothstep(-softness, softness, dist);
|
||||
}
|
||||
}
|
||||
|
||||
// Dither to reduce gradient banding (interleaved gradient noise)
|
||||
float dither = fract(52.9829189 * fract(dot(in.pos.xy, float2(0.06711056, 0.00583715)))) - 0.5;
|
||||
col.rgb += dither / 255.0;
|
||||
|
||||
if (col.a < 0.002) discard_fragment();
|
||||
return col;
|
||||
}
|
||||
)";
|
||||
static const char *g_shader_msl =
|
||||
"#include <metal_stdlib>\n"
|
||||
"using namespace metal;\n"
|
||||
"\n"
|
||||
"struct Vertex {\n"
|
||||
" float2 pos [[attribute(0)]];\n"
|
||||
" float2 uv [[attribute(1)]];\n"
|
||||
" float4 col [[attribute(2)]];\n"
|
||||
" float2 rect_min [[attribute(3)]];\n"
|
||||
" float2 rect_max [[attribute(4)]];\n"
|
||||
" float4 corner_radii [[attribute(5)]];\n"
|
||||
" float border_thickness [[attribute(6)]];\n"
|
||||
" float softness [[attribute(7)]];\n"
|
||||
" float mode [[attribute(8)]];\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"struct Fragment {\n"
|
||||
" float4 pos [[position]];\n"
|
||||
" float2 uv;\n"
|
||||
" float4 col;\n"
|
||||
" float2 rect_min;\n"
|
||||
" float2 rect_max;\n"
|
||||
" float4 corner_radii;\n"
|
||||
" float border_thickness;\n"
|
||||
" float softness;\n"
|
||||
" float mode;\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"struct Constants {\n"
|
||||
" float2 viewport_size;\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"vertex Fragment vertex_main(Vertex in [[stage_in]],\n"
|
||||
" constant Constants &cb [[buffer(1)]]) {\n"
|
||||
" Fragment out;\n"
|
||||
" float2 ndc;\n"
|
||||
" ndc.x = (in.pos.x / cb.viewport_size.x) * 2.0 - 1.0;\n"
|
||||
" ndc.y = 1.0 - (in.pos.y / cb.viewport_size.y) * 2.0;\n"
|
||||
" out.pos = float4(ndc, 0.0, 1.0);\n"
|
||||
" out.uv = in.uv;\n"
|
||||
" out.col = in.col;\n"
|
||||
" out.rect_min = in.rect_min;\n"
|
||||
" out.rect_max = in.rect_max;\n"
|
||||
" out.corner_radii = in.corner_radii;\n"
|
||||
" out.border_thickness = in.border_thickness;\n"
|
||||
" out.softness = in.softness;\n"
|
||||
" out.mode = in.mode;\n"
|
||||
" return out;\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"float rounded_rect_sdf(float2 sample_pos, float2 rect_center, float2 rect_half_size, float radius) {\n"
|
||||
" float2 d = abs(sample_pos - rect_center) - rect_half_size + float2(radius, radius);\n"
|
||||
" return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"fragment float4 fragment_main(Fragment in [[stage_in]],\n"
|
||||
" texture2d<float> font_tex [[texture(0)]],\n"
|
||||
" sampler font_smp [[sampler(0)]]) {\n"
|
||||
" float4 col = in.col;\n"
|
||||
"\n"
|
||||
" if (in.mode > 1.5) {\n"
|
||||
" float4 tex = font_tex.sample(font_smp, in.uv);\n"
|
||||
" col *= tex;\n"
|
||||
" } else if (in.mode > 0.5) {\n"
|
||||
" float alpha = font_tex.sample(font_smp, in.uv).r;\n"
|
||||
" col.a *= alpha;\n"
|
||||
" } else {\n"
|
||||
" float2 pixel_pos = in.pos.xy;\n"
|
||||
" float2 rect_center = (in.rect_min + in.rect_max) * 0.5;\n"
|
||||
" float2 rect_half_size = (in.rect_max - in.rect_min) * 0.5;\n"
|
||||
" float radius = (pixel_pos.x < rect_center.x)\n"
|
||||
" ? ((pixel_pos.y < rect_center.y) ? in.corner_radii.x : in.corner_radii.w)\n"
|
||||
" : ((pixel_pos.y < rect_center.y) ? in.corner_radii.y : in.corner_radii.z);\n"
|
||||
" float softness = max(in.softness, 0.5);\n"
|
||||
" float dist = rounded_rect_sdf(pixel_pos, rect_center, rect_half_size, radius);\n"
|
||||
"\n"
|
||||
" if (in.border_thickness > 0) {\n"
|
||||
" float inner_dist = dist + in.border_thickness;\n"
|
||||
" float outer_alpha = 1.0 - smoothstep(-softness, softness, dist);\n"
|
||||
" float inner_alpha = smoothstep(-softness, softness, inner_dist);\n"
|
||||
" col.a *= outer_alpha * inner_alpha;\n"
|
||||
" } else {\n"
|
||||
" col.a *= 1.0 - smoothstep(-softness, softness, dist);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" // Dither to reduce gradient banding (interleaved gradient noise)\n"
|
||||
" float dither = fract(52.9829189 * fract(dot(in.pos.xy, float2(0.06711056, 0.00583715)))) - 0.5;\n"
|
||||
" col.rgb += dither / 255.0;\n"
|
||||
"\n"
|
||||
" if (col.a < 0.002) discard_fragment();\n"
|
||||
" return col;\n"
|
||||
"}\n";
|
||||
|
||||
////////////////////////////////
|
||||
// Renderer struct
|
||||
|
||||
struct Renderer {
|
||||
Renderer *parent; // non-null for shared renderers
|
||||
typedef struct Renderer {
|
||||
struct Renderer *parent; // non-null for shared renderers
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 frame_count;
|
||||
@@ -190,7 +191,7 @@ struct Renderer {
|
||||
|
||||
// Clear color
|
||||
F32 clear_r, clear_g, clear_b;
|
||||
};
|
||||
} Renderer;
|
||||
|
||||
////////////////////////////////
|
||||
// Font atlas (FreeType)
|
||||
@@ -302,12 +303,12 @@ Vec2F32 renderer_measure_text(const char *text, S32 length, F32 font_size, void
|
||||
////////////////////////////////
|
||||
// Draw batch and quad emission (same logic as DX12)
|
||||
|
||||
struct DrawBatch {
|
||||
typedef struct DrawBatch {
|
||||
UIVertex *vertices;
|
||||
U32 *indices;
|
||||
U32 vertex_count;
|
||||
U32 index_count;
|
||||
};
|
||||
} DrawBatch;
|
||||
|
||||
static void emit_quad(DrawBatch *batch,
|
||||
F32 x0, F32 y0, F32 x1, F32 y1,
|
||||
@@ -519,8 +520,7 @@ static void emit_text_glyphs(DrawBatch *batch, Renderer *r,
|
||||
// Public API
|
||||
|
||||
Renderer *renderer_create(RendererDesc *desc) {
|
||||
Renderer *r = new Renderer();
|
||||
memset(r, 0, sizeof(*r));
|
||||
Renderer *r = (Renderer *)calloc(1, sizeof(Renderer));
|
||||
|
||||
r->width = desc->width;
|
||||
r->height = desc->height;
|
||||
@@ -532,7 +532,7 @@ Renderer *renderer_create(RendererDesc *desc) {
|
||||
[view setWantsLayer:YES];
|
||||
|
||||
r->device = MTLCreateSystemDefaultDevice();
|
||||
if (!r->device) { delete r; return nullptr; }
|
||||
if (!r->device) { free(r); return NULL; }
|
||||
|
||||
r->command_queue = [r->device newCommandQueue];
|
||||
|
||||
@@ -559,8 +559,8 @@ Renderer *renderer_create(RendererDesc *desc) {
|
||||
[NSString stringWithUTF8String:g_shader_msl] options:nil error:&error];
|
||||
if (!library) {
|
||||
NSLog(@"Metal shader compile error: %@", error);
|
||||
delete r;
|
||||
return nullptr;
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
id<MTLFunction> vert_fn = [library newFunctionWithName:@"vertex_main"];
|
||||
@@ -615,8 +615,8 @@ Renderer *renderer_create(RendererDesc *desc) {
|
||||
r->pipeline_state = [r->device newRenderPipelineStateWithDescriptor:pipe_desc error:&error];
|
||||
if (!r->pipeline_state) {
|
||||
NSLog(@"Metal pipeline error: %@", error);
|
||||
delete r;
|
||||
return nullptr;
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create F64-buffered vertex/index buffers
|
||||
@@ -630,8 +630,8 @@ Renderer *renderer_create(RendererDesc *desc) {
|
||||
// FreeType + font atlas
|
||||
init_freetype(r);
|
||||
if (!create_font_atlas(r, 22.0f)) {
|
||||
delete r;
|
||||
return nullptr;
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Default clear color (dark theme bg_dark)
|
||||
@@ -643,10 +643,9 @@ Renderer *renderer_create(RendererDesc *desc) {
|
||||
}
|
||||
|
||||
Renderer *renderer_create_shared(Renderer *parent, RendererDesc *desc) {
|
||||
if (!parent) return nullptr;
|
||||
if (!parent) return NULL;
|
||||
|
||||
Renderer *r = new Renderer();
|
||||
memset(r, 0, sizeof(*r));
|
||||
Renderer *r = (Renderer *)calloc(1, sizeof(Renderer));
|
||||
|
||||
r->parent = parent;
|
||||
r->width = desc->width;
|
||||
@@ -711,7 +710,7 @@ void renderer_destroy(Renderer *r) {
|
||||
if (r->ft_face) FT_Done_Face(r->ft_face);
|
||||
if (r->ft_lib) FT_Done_FreeType(r->ft_lib);
|
||||
}
|
||||
delete r;
|
||||
free(r);
|
||||
}
|
||||
|
||||
B32 renderer_begin_frame(Renderer *r) {
|
||||
@@ -741,7 +740,47 @@ B32 renderer_begin_frame(Renderer *r) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
// Helper context for batch flushing (replaces C++ lambdas)
|
||||
typedef struct FlushCtx {
|
||||
DrawBatch *batch;
|
||||
U32 *flush_index_start;
|
||||
S32 *bound_texture;
|
||||
Renderer *r;
|
||||
U32 buf_idx;
|
||||
id<MTLRenderCommandEncoder> encoder;
|
||||
} FlushCtx;
|
||||
|
||||
static void flush_batch(FlushCtx *ctx) {
|
||||
U32 index_count = ctx->batch->index_count - *ctx->flush_index_start;
|
||||
if (index_count == 0) return;
|
||||
|
||||
[ctx->encoder setVertexBuffer:ctx->r->vertex_buffers[ctx->buf_idx] offset:0 atIndex:0];
|
||||
[ctx->encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
|
||||
indexCount:index_count
|
||||
indexType:MTLIndexTypeUInt32
|
||||
indexBuffer:ctx->r->index_buffers[ctx->buf_idx]
|
||||
indexBufferOffset:*ctx->flush_index_start * sizeof(U32)];
|
||||
|
||||
*ctx->flush_index_start = ctx->batch->index_count;
|
||||
}
|
||||
|
||||
static void bind_font_texture(FlushCtx *ctx) {
|
||||
if (*ctx->bound_texture != 0) {
|
||||
flush_batch(ctx);
|
||||
[ctx->encoder setFragmentTexture:ctx->r->font_texture atIndex:0];
|
||||
*ctx->bound_texture = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void bind_icon_texture(FlushCtx *ctx) {
|
||||
if (*ctx->bound_texture != 1 && ctx->r->icon_texture) {
|
||||
flush_batch(ctx);
|
||||
[ctx->encoder setFragmentTexture:ctx->r->icon_texture atIndex:0];
|
||||
*ctx->bound_texture = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void renderer_end_frame(Renderer *r, Clay_RenderCommandArray *render_commands) {
|
||||
@autoreleasepool {
|
||||
id<CAMetalDrawable> drawable = r->current_drawable;
|
||||
r->current_drawable = nil;
|
||||
@@ -762,7 +801,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
[encoder setFragmentSamplerState:r->font_sampler atIndex:0];
|
||||
|
||||
// Viewport
|
||||
MTLViewport viewport = {};
|
||||
MTLViewport viewport = {0};
|
||||
viewport.width = (F64)r->width;
|
||||
viewport.height = (F64)r->height;
|
||||
viewport.zfar = 1.0;
|
||||
@@ -777,8 +816,8 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
[encoder setVertexBytes:constants length:sizeof(constants) atIndex:1];
|
||||
|
||||
// Process Clay render commands
|
||||
if (render_commands.length > 0) {
|
||||
DrawBatch batch = {};
|
||||
if (render_commands->length > 0) {
|
||||
DrawBatch batch = {0};
|
||||
batch.vertices = (UIVertex *)[r->vertex_buffers[buf_idx] contents];
|
||||
batch.indices = (U32 *)[r->index_buffers[buf_idx] contents];
|
||||
batch.vertex_count = 0;
|
||||
@@ -788,37 +827,15 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
S32 bound_texture = 0;
|
||||
U32 flush_index_start = 0;
|
||||
|
||||
auto flush_batch = [&]() {
|
||||
U32 index_count = batch.index_count - flush_index_start;
|
||||
if (index_count == 0) return;
|
||||
FlushCtx fctx;
|
||||
fctx.batch = &batch;
|
||||
fctx.flush_index_start = &flush_index_start;
|
||||
fctx.bound_texture = &bound_texture;
|
||||
fctx.r = r;
|
||||
fctx.buf_idx = buf_idx;
|
||||
fctx.encoder = encoder;
|
||||
|
||||
[encoder setVertexBuffer:r->vertex_buffers[buf_idx] offset:0 atIndex:0];
|
||||
[encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
|
||||
indexCount:index_count
|
||||
indexType:MTLIndexTypeUInt32
|
||||
indexBuffer:r->index_buffers[buf_idx]
|
||||
indexBufferOffset:flush_index_start * sizeof(U32)];
|
||||
|
||||
flush_index_start = batch.index_count;
|
||||
};
|
||||
|
||||
auto bind_font_texture = [&]() {
|
||||
if (bound_texture != 0) {
|
||||
flush_batch();
|
||||
[encoder setFragmentTexture:r->font_texture atIndex:0];
|
||||
bound_texture = 0;
|
||||
}
|
||||
};
|
||||
|
||||
auto bind_icon_texture = [&]() {
|
||||
if (bound_texture != 1 && r->icon_texture) {
|
||||
flush_batch();
|
||||
[encoder setFragmentTexture:r->icon_texture atIndex:0];
|
||||
bound_texture = 1;
|
||||
}
|
||||
};
|
||||
|
||||
for (S32 i = 0; i < render_commands.length; i++) {
|
||||
for (S32 i = 0; i < render_commands->length; i++) {
|
||||
Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&render_commands, i);
|
||||
Clay_BoundingBox bb = cmd->boundingBox;
|
||||
|
||||
@@ -877,7 +894,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
} break;
|
||||
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||
bind_font_texture();
|
||||
bind_font_texture(&fctx);
|
||||
Clay_TextRenderData *text = &cmd->renderData.text;
|
||||
emit_text_glyphs(&batch, r, bb, text->textColor,
|
||||
text->stringContents.chars, text->stringContents.length,
|
||||
@@ -885,7 +902,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
} break;
|
||||
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
|
||||
flush_batch();
|
||||
flush_batch(&fctx);
|
||||
NSUInteger sx = (NSUInteger)Max(bb.x, 0.f);
|
||||
NSUInteger sy = (NSUInteger)Max(bb.y, 0.f);
|
||||
NSUInteger sw = (NSUInteger)Min(bb.width, (F32)r->width - (F32)sx);
|
||||
@@ -897,7 +914,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
} break;
|
||||
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
|
||||
flush_batch();
|
||||
flush_batch(&fctx);
|
||||
[encoder setScissorRect:full_scissor];
|
||||
} break;
|
||||
|
||||
@@ -906,7 +923,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
if (custom->customData) {
|
||||
CustomRenderType type = *(CustomRenderType *)custom->customData;
|
||||
if (type == CUSTOM_RENDER_VGRADIENT) {
|
||||
bind_font_texture();
|
||||
bind_font_texture(&fctx);
|
||||
CustomGradientData *grad = (CustomGradientData *)custom->customData;
|
||||
Clay_Color tc = grad->top_color;
|
||||
Clay_Color bc = grad->bottom_color;
|
||||
@@ -918,7 +935,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
custom->cornerRadius.bottomRight, custom->cornerRadius.bottomLeft,
|
||||
1.0f);
|
||||
} else if (type == CUSTOM_RENDER_ICON) {
|
||||
bind_icon_texture();
|
||||
bind_icon_texture(&fctx);
|
||||
CustomIconData *icon = (CustomIconData *)custom->customData;
|
||||
Clay_Color c = icon->color;
|
||||
F32 cr = c.r / 255.f, cg = c.g / 255.f;
|
||||
@@ -932,7 +949,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
0, 0, 0, 0,
|
||||
0, 0, 2.0f);
|
||||
} else if (type == CUSTOM_RENDER_ROTATED_ICON) {
|
||||
bind_icon_texture();
|
||||
bind_icon_texture(&fctx);
|
||||
CustomRotatedIconData *ri = (CustomRotatedIconData *)custom->customData;
|
||||
Clay_Color c = ri->color;
|
||||
F32 cr = c.r / 255.f, cg = c.g / 255.f;
|
||||
@@ -953,7 +970,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
}
|
||||
}
|
||||
|
||||
flush_batch();
|
||||
flush_batch(&fctx);
|
||||
}
|
||||
|
||||
[encoder endEncoding];
|
||||
@@ -14,12 +14,6 @@
|
||||
#define VK_USE_PLATFORM_WIN32_KHR
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
// VMA — single-file implementation in this translation unit
|
||||
#define VMA_IMPLEMENTATION
|
||||
#define VMA_STATIC_VULKAN_FUNCTIONS 1
|
||||
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0
|
||||
#include <vma/vk_mem_alloc.h>
|
||||
|
||||
// FreeType headers — temporarily undefine `internal` macro (base_core.h: #define internal static)
|
||||
// because FreeType uses `internal` as a struct field name.
|
||||
#undef internal
|
||||
@@ -33,6 +27,13 @@
|
||||
#include "renderer/ui_vert.spv.h"
|
||||
#include "renderer/ui_frag.spv.h"
|
||||
|
||||
// MSVC ICE workaround: prevent inlining of large Vulkan setup functions
|
||||
#ifdef _MSC_VER
|
||||
#define VK_NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define VK_NOINLINE
|
||||
#endif
|
||||
|
||||
#define NUM_BACK_BUFFERS 2
|
||||
#define MAX_VERTICES (64 * 1024)
|
||||
#define MAX_INDICES (MAX_VERTICES * 3)
|
||||
@@ -95,8 +96,6 @@ struct Renderer {
|
||||
VkDevice device;
|
||||
VkQueue graphics_queue;
|
||||
U32 queue_family;
|
||||
VmaAllocator allocator;
|
||||
|
||||
// Surface & swap chain
|
||||
VkSurfaceKHR surface;
|
||||
VkSwapchainKHR swap_chain;
|
||||
@@ -130,16 +129,16 @@ struct Renderer {
|
||||
|
||||
// Per-frame vertex/index buffers (double-buffered, persistently mapped)
|
||||
VkBuffer vertex_buffers[NUM_BACK_BUFFERS];
|
||||
VmaAllocation vertex_allocs[NUM_BACK_BUFFERS];
|
||||
VkDeviceMemory vertex_memory[NUM_BACK_BUFFERS];
|
||||
void *vb_mapped[NUM_BACK_BUFFERS];
|
||||
|
||||
VkBuffer index_buffers[NUM_BACK_BUFFERS];
|
||||
VmaAllocation index_allocs[NUM_BACK_BUFFERS];
|
||||
VkDeviceMemory index_memory[NUM_BACK_BUFFERS];
|
||||
void *ib_mapped[NUM_BACK_BUFFERS];
|
||||
|
||||
// Font atlas
|
||||
VkImage font_image;
|
||||
VmaAllocation font_alloc;
|
||||
VkDeviceMemory font_memory;
|
||||
VkImageView font_view;
|
||||
GlyphInfo glyphs[GLYPH_COUNT];
|
||||
F32 font_atlas_size;
|
||||
@@ -147,7 +146,7 @@ struct Renderer {
|
||||
|
||||
// Icon atlas
|
||||
VkImage icon_image;
|
||||
VmaAllocation icon_alloc;
|
||||
VkDeviceMemory icon_memory;
|
||||
VkImageView icon_view;
|
||||
|
||||
// FreeType
|
||||
@@ -242,7 +241,7 @@ static void transition_image(VkCommandBuffer cb, VkImage image,
|
||||
////////////////////////////////
|
||||
// Vulkan infrastructure
|
||||
|
||||
static B32 create_instance(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_instance(Renderer *r) {
|
||||
VkApplicationInfo app_info = {0};
|
||||
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
app_info.pApplicationName = "autosample";
|
||||
@@ -296,7 +295,7 @@ static B32 create_instance(Renderer *r) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static B32 create_surface(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_surface(Renderer *r) {
|
||||
VkWin32SurfaceCreateInfoKHR ci = {0};
|
||||
ci.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
||||
ci.hinstance = GetModuleHandle(NULL);
|
||||
@@ -304,7 +303,7 @@ static B32 create_surface(Renderer *r) {
|
||||
return vkCreateWin32SurfaceKHR(r->instance, &ci, NULL, &r->surface) == VK_SUCCESS;
|
||||
}
|
||||
|
||||
static B32 pick_physical_device(Renderer *r) {
|
||||
static VK_NOINLINE B32 pick_physical_device(Renderer *r) {
|
||||
U32 count = 0;
|
||||
vkEnumeratePhysicalDevices(r->instance, &count, NULL);
|
||||
if (count == 0) return 0;
|
||||
@@ -326,7 +325,7 @@ static B32 pick_physical_device(Renderer *r) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static B32 find_queue_family(Renderer *r) {
|
||||
static VK_NOINLINE B32 find_queue_family(Renderer *r) {
|
||||
U32 count = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(r->physical_device, &count, NULL);
|
||||
VkQueueFamilyProperties props[64];
|
||||
@@ -344,7 +343,7 @@ static B32 find_queue_family(Renderer *r) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static B32 create_device(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_device(Renderer *r) {
|
||||
float priority = 1.0f;
|
||||
VkDeviceQueueCreateInfo queue_ci = {0};
|
||||
queue_ci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
@@ -368,16 +367,17 @@ static B32 create_device(Renderer *r) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static B32 create_vma(Renderer *r) {
|
||||
VmaAllocatorCreateInfo ci = {0};
|
||||
ci.physicalDevice = r->physical_device;
|
||||
ci.device = r->device;
|
||||
ci.instance = r->instance;
|
||||
ci.vulkanApiVersion = VK_API_VERSION_1_2;
|
||||
return vmaCreateAllocator(&ci, &r->allocator) == VK_SUCCESS;
|
||||
static U32 find_memory_type(VkPhysicalDevice phys_dev, U32 type_filter, VkMemoryPropertyFlags props) {
|
||||
VkPhysicalDeviceMemoryProperties mem_props;
|
||||
vkGetPhysicalDeviceMemoryProperties(phys_dev, &mem_props);
|
||||
for (U32 i = 0; i < mem_props.memoryTypeCount; i++) {
|
||||
if ((type_filter & (1 << i)) && (mem_props.memoryTypes[i].propertyFlags & props) == props)
|
||||
return i;
|
||||
}
|
||||
return 0; // fallback
|
||||
}
|
||||
|
||||
static B32 create_swap_chain(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_swap_chain(Renderer *r) {
|
||||
VkSurfaceCapabilitiesKHR caps;
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(r->physical_device, r->surface, &caps);
|
||||
|
||||
@@ -468,7 +468,7 @@ static B32 create_swap_chain(Renderer *r) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static B32 create_render_pass(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_render_pass(Renderer *r) {
|
||||
VkAttachmentDescription color_attach = {0};
|
||||
color_attach.format = r->swap_chain_format;
|
||||
color_attach.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
@@ -508,7 +508,7 @@ static B32 create_render_pass(Renderer *r) {
|
||||
return vkCreateRenderPass(r->device, &ci, NULL, &r->render_pass) == VK_SUCCESS;
|
||||
}
|
||||
|
||||
static B32 create_framebuffers(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_framebuffers(Renderer *r) {
|
||||
for (U32 i = 0; i < NUM_BACK_BUFFERS; i++) {
|
||||
VkFramebufferCreateInfo ci = {0};
|
||||
ci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
@@ -524,7 +524,7 @@ static B32 create_framebuffers(Renderer *r) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static B32 create_command_resources(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_command_resources(Renderer *r) {
|
||||
VkCommandPoolCreateInfo pool_ci = {0};
|
||||
pool_ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
pool_ci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
@@ -564,7 +564,7 @@ static B32 create_command_resources(Renderer *r) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static B32 create_sampler(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_sampler(Renderer *r) {
|
||||
VkSamplerCreateInfo ci = {0};
|
||||
ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
ci.magFilter = VK_FILTER_LINEAR;
|
||||
@@ -576,7 +576,7 @@ static B32 create_sampler(Renderer *r) {
|
||||
return vkCreateSampler(r->device, &ci, NULL, &r->sampler) == VK_SUCCESS;
|
||||
}
|
||||
|
||||
static B32 create_descriptor_resources(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_descriptor_resources(Renderer *r) {
|
||||
// Descriptor set layout: single combined image sampler at binding 0
|
||||
VkDescriptorSetLayoutBinding binding = {0};
|
||||
binding.binding = 0;
|
||||
@@ -626,7 +626,7 @@ static VkShaderModule create_shader_module(Renderer *r, const U32 *code, size_t
|
||||
return module;
|
||||
}
|
||||
|
||||
static B32 create_pipeline(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_pipeline(Renderer *r) {
|
||||
VkShaderModule vert_module = create_shader_module(r, ui_vert_spv, sizeof(ui_vert_spv));
|
||||
VkShaderModule frag_module = create_shader_module(r, ui_frag_spv, sizeof(ui_frag_spv));
|
||||
if (!vert_module || !frag_module) return 0;
|
||||
@@ -647,7 +647,7 @@ static B32 create_pipeline(Renderer *r) {
|
||||
binding.stride = sizeof(UIVertex);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription attrs[9] = {};
|
||||
VkVertexInputAttributeDescription attrs[9] = {0};
|
||||
attrs[0].location = 0; attrs[0].binding = 0; attrs[0].format = VK_FORMAT_R32G32_SFLOAT; attrs[0].offset = offsetof(UIVertex, pos);
|
||||
attrs[1].location = 1; attrs[1].binding = 0; attrs[1].format = VK_FORMAT_R32G32_SFLOAT; attrs[1].offset = offsetof(UIVertex, uv);
|
||||
attrs[2].location = 2; attrs[2].binding = 0; attrs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT; attrs[2].offset = offsetof(UIVertex, col);
|
||||
@@ -753,29 +753,42 @@ static B32 create_pipeline(Renderer *r) {
|
||||
return result == VK_SUCCESS;
|
||||
}
|
||||
|
||||
static B32 create_ui_buffers(Renderer *r) {
|
||||
static VK_NOINLINE B32 create_ui_buffers(Renderer *r) {
|
||||
for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
|
||||
// Vertex buffer
|
||||
VkBufferCreateInfo buf_ci = {0};
|
||||
buf_ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
buf_ci.size = MAX_VERTICES * sizeof(UIVertex);
|
||||
buf_ci.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
|
||||
|
||||
VmaAllocationCreateInfo alloc_ci = {0};
|
||||
alloc_ci.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||
alloc_ci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
|
||||
VmaAllocationInfo alloc_info;
|
||||
if (vmaCreateBuffer(r->allocator, &buf_ci, &alloc_ci,
|
||||
&r->vertex_buffers[i], &r->vertex_allocs[i], &alloc_info) != VK_SUCCESS)
|
||||
if (vkCreateBuffer(r->device, &buf_ci, NULL, &r->vertex_buffers[i]) != VK_SUCCESS)
|
||||
return 0;
|
||||
r->vb_mapped[i] = alloc_info.pMappedData;
|
||||
VkMemoryRequirements mem_req;
|
||||
vkGetBufferMemoryRequirements(r->device, r->vertex_buffers[i], &mem_req);
|
||||
VkMemoryAllocateInfo alloc_info = {0};
|
||||
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
alloc_info.allocationSize = mem_req.size;
|
||||
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
if (vkAllocateMemory(r->device, &alloc_info, NULL, &r->vertex_memory[i]) != VK_SUCCESS)
|
||||
return 0;
|
||||
vkBindBufferMemory(r->device, r->vertex_buffers[i], r->vertex_memory[i], 0);
|
||||
vkMapMemory(r->device, r->vertex_memory[i], 0, buf_ci.size, 0, &r->vb_mapped[i]);
|
||||
|
||||
// Index buffer
|
||||
buf_ci.size = MAX_INDICES * sizeof(U32);
|
||||
buf_ci.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
||||
if (vmaCreateBuffer(r->allocator, &buf_ci, &alloc_ci,
|
||||
&r->index_buffers[i], &r->index_allocs[i], &alloc_info) != VK_SUCCESS)
|
||||
|
||||
if (vkCreateBuffer(r->device, &buf_ci, NULL, &r->index_buffers[i]) != VK_SUCCESS)
|
||||
return 0;
|
||||
r->ib_mapped[i] = alloc_info.pMappedData;
|
||||
vkGetBufferMemoryRequirements(r->device, r->index_buffers[i], &mem_req);
|
||||
alloc_info.allocationSize = mem_req.size;
|
||||
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
if (vkAllocateMemory(r->device, &alloc_info, NULL, &r->index_memory[i]) != VK_SUCCESS)
|
||||
return 0;
|
||||
vkBindBufferMemory(r->device, r->index_buffers[i], r->index_memory[i], 0);
|
||||
vkMapMemory(r->device, r->index_memory[i], 0, buf_ci.size, 0, &r->ib_mapped[i]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -807,7 +820,7 @@ static void init_freetype(Renderer *r) {
|
||||
FT_New_Memory_Face(r->ft_lib, font_inter_data, font_inter_size, 0, &r->ft_face);
|
||||
}
|
||||
|
||||
static B32 create_font_atlas(Renderer *r, F32 font_size) {
|
||||
static VK_NOINLINE B32 create_font_atlas(Renderer *r, F32 font_size) {
|
||||
S32 pixel_size = (S32)(font_size + 0.5f);
|
||||
r->font_atlas_size = font_size;
|
||||
|
||||
@@ -875,13 +888,24 @@ static B32 create_font_atlas(Renderer *r, F32 font_size) {
|
||||
img_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
img_ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
|
||||
VmaAllocationCreateInfo alloc_ci = {0};
|
||||
alloc_ci.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
|
||||
if (vmaCreateImage(r->allocator, &img_ci, &alloc_ci, &r->font_image, &r->font_alloc, NULL) != VK_SUCCESS) {
|
||||
if (vkCreateImage(r->device, &img_ci, NULL, &r->font_image) != VK_SUCCESS) {
|
||||
free(atlas_data);
|
||||
return 0;
|
||||
}
|
||||
{
|
||||
VkMemoryRequirements mem_req;
|
||||
vkGetImageMemoryRequirements(r->device, r->font_image, &mem_req);
|
||||
VkMemoryAllocateInfo alloc_info = {0};
|
||||
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
alloc_info.allocationSize = mem_req.size;
|
||||
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
if (vkAllocateMemory(r->device, &alloc_info, NULL, &r->font_memory) != VK_SUCCESS) {
|
||||
free(atlas_data);
|
||||
return 0;
|
||||
}
|
||||
vkBindImageMemory(r->device, r->font_image, r->font_memory, 0);
|
||||
}
|
||||
|
||||
// Staging buffer
|
||||
VkBufferCreateInfo staging_ci = {0};
|
||||
@@ -889,15 +913,23 @@ static B32 create_font_atlas(Renderer *r, F32 font_size) {
|
||||
staging_ci.size = FONT_ATLAS_W * FONT_ATLAS_H;
|
||||
staging_ci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
|
||||
VmaAllocationCreateInfo staging_alloc_ci = {0};
|
||||
staging_alloc_ci.usage = VMA_MEMORY_USAGE_CPU_ONLY;
|
||||
staging_alloc_ci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
|
||||
VkBuffer staging_buf;
|
||||
VmaAllocation staging_alloc;
|
||||
VmaAllocationInfo staging_info;
|
||||
vmaCreateBuffer(r->allocator, &staging_ci, &staging_alloc_ci, &staging_buf, &staging_alloc, &staging_info);
|
||||
memcpy(staging_info.pMappedData, atlas_data, FONT_ATLAS_W * FONT_ATLAS_H);
|
||||
VkDeviceMemory staging_memory;
|
||||
void *staging_mapped;
|
||||
vkCreateBuffer(r->device, &staging_ci, NULL, &staging_buf);
|
||||
{
|
||||
VkMemoryRequirements mem_req;
|
||||
vkGetBufferMemoryRequirements(r->device, staging_buf, &mem_req);
|
||||
VkMemoryAllocateInfo alloc_info = {0};
|
||||
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
alloc_info.allocationSize = mem_req.size;
|
||||
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
vkAllocateMemory(r->device, &alloc_info, NULL, &staging_memory);
|
||||
vkBindBufferMemory(r->device, staging_buf, staging_memory, 0);
|
||||
vkMapMemory(r->device, staging_memory, 0, staging_ci.size, 0, &staging_mapped);
|
||||
}
|
||||
memcpy(staging_mapped, atlas_data, FONT_ATLAS_W * FONT_ATLAS_H);
|
||||
free(atlas_data);
|
||||
|
||||
// Copy via command buffer
|
||||
@@ -923,7 +955,8 @@ static B32 create_font_atlas(Renderer *r, F32 font_size) {
|
||||
|
||||
end_one_shot(r, cb);
|
||||
|
||||
vmaDestroyBuffer(r->allocator, staging_buf, staging_alloc);
|
||||
vkDestroyBuffer(r->device, staging_buf, NULL);
|
||||
vkFreeMemory(r->device, staging_memory, NULL);
|
||||
|
||||
// Image view
|
||||
VkImageViewCreateInfo view_ci = {0};
|
||||
@@ -1223,7 +1256,6 @@ Renderer *renderer_create(RendererDesc *desc) {
|
||||
if (!pick_physical_device(r)) goto fail;
|
||||
if (!find_queue_family(r)) goto fail;
|
||||
if (!create_device(r)) goto fail;
|
||||
if (!create_vma(r)) goto fail;
|
||||
if (!create_command_resources(r)) goto fail;
|
||||
if (!create_sampler(r)) goto fail;
|
||||
if (!create_descriptor_resources(r)) goto fail;
|
||||
@@ -1261,18 +1293,17 @@ Renderer *renderer_create_shared(Renderer *parent, RendererDesc *desc) {
|
||||
r->device = parent->device;
|
||||
r->graphics_queue = parent->graphics_queue;
|
||||
r->queue_family = parent->queue_family;
|
||||
r->allocator = parent->allocator;
|
||||
r->render_pass = parent->render_pass;
|
||||
r->pipeline_layout = parent->pipeline_layout;
|
||||
r->pipeline = parent->pipeline;
|
||||
r->descriptor_set_layout = parent->descriptor_set_layout;
|
||||
r->sampler = parent->sampler;
|
||||
r->font_image = parent->font_image;
|
||||
r->font_alloc = parent->font_alloc;
|
||||
r->font_memory = parent->font_memory;
|
||||
r->font_view = parent->font_view;
|
||||
r->font_descriptor_set = parent->font_descriptor_set;
|
||||
r->icon_image = parent->icon_image;
|
||||
r->icon_alloc = parent->icon_alloc;
|
||||
r->icon_memory = parent->icon_memory;
|
||||
r->icon_view = parent->icon_view;
|
||||
r->icon_descriptor_set = parent->icon_descriptor_set;
|
||||
r->descriptor_pool = parent->descriptor_pool;
|
||||
@@ -1308,10 +1339,14 @@ void renderer_destroy(Renderer *r) {
|
||||
|
||||
// Per-window resources
|
||||
for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
|
||||
if (r->vertex_buffers[i])
|
||||
vmaDestroyBuffer(r->allocator, r->vertex_buffers[i], r->vertex_allocs[i]);
|
||||
if (r->index_buffers[i])
|
||||
vmaDestroyBuffer(r->allocator, r->index_buffers[i], r->index_allocs[i]);
|
||||
if (r->vertex_buffers[i]) {
|
||||
vkDestroyBuffer(r->device, r->vertex_buffers[i], NULL);
|
||||
vkFreeMemory(r->device, r->vertex_memory[i], NULL);
|
||||
}
|
||||
if (r->index_buffers[i]) {
|
||||
vkDestroyBuffer(r->device, r->index_buffers[i], NULL);
|
||||
vkFreeMemory(r->device, r->index_memory[i], NULL);
|
||||
}
|
||||
}
|
||||
|
||||
for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
|
||||
@@ -1333,9 +1368,9 @@ void renderer_destroy(Renderer *r) {
|
||||
// Shared resources only freed by root renderer
|
||||
if (!r->parent) {
|
||||
if (r->font_view) vkDestroyImageView(r->device, r->font_view, NULL);
|
||||
if (r->font_image) vmaDestroyImage(r->allocator, r->font_image, r->font_alloc);
|
||||
if (r->font_image) { vkDestroyImage(r->device, r->font_image, NULL); vkFreeMemory(r->device, r->font_memory, NULL); }
|
||||
if (r->icon_view) vkDestroyImageView(r->device, r->icon_view, NULL);
|
||||
if (r->icon_image) vmaDestroyImage(r->allocator, r->icon_image, r->icon_alloc);
|
||||
if (r->icon_image) { vkDestroyImage(r->device, r->icon_image, NULL); vkFreeMemory(r->device, r->icon_memory, NULL); }
|
||||
|
||||
if (r->pipeline) vkDestroyPipeline(r->device, r->pipeline, NULL);
|
||||
if (r->pipeline_layout) vkDestroyPipelineLayout(r->device, r->pipeline_layout, NULL);
|
||||
@@ -1347,7 +1382,6 @@ void renderer_destroy(Renderer *r) {
|
||||
if (r->ft_face) FT_Done_Face(r->ft_face);
|
||||
if (r->ft_lib) FT_Done_FreeType(r->ft_lib);
|
||||
|
||||
if (r->allocator) vmaDestroyAllocator(r->allocator);
|
||||
if (r->device) vkDestroyDevice(r->device, NULL);
|
||||
|
||||
#ifdef _DEBUG
|
||||
@@ -1385,7 +1419,7 @@ B32 renderer_begin_frame(Renderer *r) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
void renderer_end_frame(Renderer *r, Clay_RenderCommandArray *render_commands) {
|
||||
U32 frame_idx = r->frame_index % NUM_BACK_BUFFERS;
|
||||
FrameContext *fc = &r->frames[frame_idx];
|
||||
|
||||
@@ -1440,7 +1474,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
vkCmdSetScissor(cb, 0, 1, &scissor);
|
||||
|
||||
// Process Clay render commands
|
||||
if (render_commands.length > 0) {
|
||||
if (render_commands->length > 0) {
|
||||
DrawBatch batch = {0};
|
||||
batch.vertices = (UIVertex *)r->vb_mapped[frame_idx];
|
||||
batch.indices = (U32 *)r->ib_mapped[frame_idx];
|
||||
@@ -1448,8 +1482,8 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
|
||||
S32 bound_texture = 0; // 0 = font, 1 = icon
|
||||
U32 flush_index_start = 0;
|
||||
|
||||
for (S32 i = 0; i < render_commands.length; i++) {
|
||||
Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&render_commands, i);
|
||||
for (S32 i = 0; i < render_commands->length; i++) {
|
||||
Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(render_commands, i);
|
||||
Clay_BoundingBox bb = cmd->boundingBox;
|
||||
|
||||
switch (cmd->commandType) {
|
||||
@@ -1655,10 +1689,18 @@ void renderer_create_icon_atlas(Renderer *r, const U8 *data, S32 w, S32 h) {
|
||||
img_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
img_ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
|
||||
VmaAllocationCreateInfo alloc_ci = {0};
|
||||
alloc_ci.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
|
||||
vmaCreateImage(r->allocator, &img_ci, &alloc_ci, &r->icon_image, &r->icon_alloc, NULL);
|
||||
vkCreateImage(r->device, &img_ci, NULL, &r->icon_image);
|
||||
{
|
||||
VkMemoryRequirements mem_req;
|
||||
vkGetImageMemoryRequirements(r->device, r->icon_image, &mem_req);
|
||||
VkMemoryAllocateInfo alloc_info = {0};
|
||||
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
alloc_info.allocationSize = mem_req.size;
|
||||
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
vkAllocateMemory(r->device, &alloc_info, NULL, &r->icon_memory);
|
||||
vkBindImageMemory(r->device, r->icon_image, r->icon_memory, 0);
|
||||
}
|
||||
|
||||
// Staging buffer
|
||||
VkBufferCreateInfo staging_ci = {0};
|
||||
@@ -1666,15 +1708,23 @@ void renderer_create_icon_atlas(Renderer *r, const U8 *data, S32 w, S32 h) {
|
||||
staging_ci.size = (VkDeviceSize)w * h * 4;
|
||||
staging_ci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
|
||||
VmaAllocationCreateInfo staging_alloc_ci = {0};
|
||||
staging_alloc_ci.usage = VMA_MEMORY_USAGE_CPU_ONLY;
|
||||
staging_alloc_ci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
|
||||
VkBuffer staging_buf;
|
||||
VmaAllocation staging_alloc;
|
||||
VmaAllocationInfo staging_info;
|
||||
vmaCreateBuffer(r->allocator, &staging_ci, &staging_alloc_ci, &staging_buf, &staging_alloc, &staging_info);
|
||||
memcpy(staging_info.pMappedData, data, (size_t)w * h * 4);
|
||||
VkDeviceMemory staging_memory;
|
||||
void *staging_mapped;
|
||||
vkCreateBuffer(r->device, &staging_ci, NULL, &staging_buf);
|
||||
{
|
||||
VkMemoryRequirements mem_req;
|
||||
vkGetBufferMemoryRequirements(r->device, staging_buf, &mem_req);
|
||||
VkMemoryAllocateInfo alloc_info = {0};
|
||||
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
alloc_info.allocationSize = mem_req.size;
|
||||
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
vkAllocateMemory(r->device, &alloc_info, NULL, &staging_memory);
|
||||
vkBindBufferMemory(r->device, staging_buf, staging_memory, 0);
|
||||
vkMapMemory(r->device, staging_memory, 0, staging_ci.size, 0, &staging_mapped);
|
||||
}
|
||||
memcpy(staging_mapped, data, (size_t)w * h * 4);
|
||||
|
||||
VkCommandBuffer cb = begin_one_shot(r);
|
||||
|
||||
@@ -1697,7 +1747,8 @@ void renderer_create_icon_atlas(Renderer *r, const U8 *data, S32 w, S32 h) {
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||
|
||||
end_one_shot(r, cb);
|
||||
vmaDestroyBuffer(r->allocator, staging_buf, staging_alloc);
|
||||
vkDestroyBuffer(r->device, staging_buf, NULL);
|
||||
vkFreeMemory(r->device, staging_memory, NULL);
|
||||
|
||||
// Image view
|
||||
VkImageViewCreateInfo view_ci = {0};
|
||||
@@ -1756,7 +1807,7 @@ void renderer_set_font_scale(Renderer *r, F32 scale) {
|
||||
if (fabsf(target_size - r->font_atlas_size) < 0.1f) return;
|
||||
vkDeviceWaitIdle(r->device);
|
||||
if (r->font_view) { vkDestroyImageView(r->device, r->font_view, NULL); r->font_view = VK_NULL_HANDLE; }
|
||||
if (r->font_image) { vmaDestroyImage(r->allocator, r->font_image, r->font_alloc); r->font_image = VK_NULL_HANDLE; }
|
||||
if (r->font_image) { vkDestroyImage(r->device, r->font_image, NULL); vkFreeMemory(r->device, r->font_memory, NULL); r->font_image = VK_NULL_HANDLE; }
|
||||
create_font_atlas(r, target_size);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
// ui_core.cpp - Clay wrapper implementation
|
||||
// ui_core.c - Clay wrapper implementation
|
||||
// CLAY_IMPLEMENTATION must be defined exactly once before including clay.h.
|
||||
// In the unity build, ui_core.h (which includes clay.h) has #pragma once,
|
||||
// so we include clay.h directly here first to get the implementation compiled.
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// MSVC: stub out Clay's debug view (uses CLAY() macros MSVC can't compile in C)
|
||||
#ifdef _MSC_VER
|
||||
#define CLAY_DISABLE_DEBUG_VIEW
|
||||
#endif
|
||||
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#include "clay.h"
|
||||
|
||||
// MSVC C mode: bypass Clay's wrapper struct pattern for designated initializers.
|
||||
// Must come after clay.h so CLAY__CONFIG_WRAPPER is defined, then we override it.
|
||||
#ifdef _MSC_VER
|
||||
#undef CLAY__CONFIG_WRAPPER
|
||||
#define CLAY__CONFIG_WRAPPER(type, ...) ((type) { __VA_ARGS__ })
|
||||
#endif
|
||||
|
||||
#include "ui/ui_core.h"
|
||||
|
||||
////////////////////////////////
|
||||
@@ -18,7 +30,7 @@ F32 g_ui_scale = 1.0f;
|
||||
////////////////////////////////
|
||||
// Theme global
|
||||
|
||||
UI_Theme g_theme = {};
|
||||
UI_Theme g_theme = {0};
|
||||
S32 g_theme_id = 0;
|
||||
|
||||
void ui_set_theme(S32 theme_id) {
|
||||
@@ -27,54 +39,54 @@ void ui_set_theme(S32 theme_id) {
|
||||
switch (theme_id) {
|
||||
default:
|
||||
case 0: // Dark
|
||||
g_theme.bg_dark = Clay_Color{ 26, 26, 26, 255};
|
||||
g_theme.bg_medium = Clay_Color{ 36, 36, 36, 255};
|
||||
g_theme.bg_light = Clay_Color{ 46, 46, 46, 255};
|
||||
g_theme.bg_lighter = Clay_Color{ 54, 54, 54, 255};
|
||||
g_theme.border = Clay_Color{ 52, 52, 52, 255};
|
||||
g_theme.text = Clay_Color{220, 220, 220, 255};
|
||||
g_theme.text_dim = Clay_Color{105, 105, 105, 255};
|
||||
g_theme.accent = Clay_Color{ 87, 138, 176, 255};
|
||||
g_theme.accent_hover = Clay_Color{102, 153, 191, 255};
|
||||
g_theme.button_text = Clay_Color{224, 224, 224, 255};
|
||||
g_theme.disabled_bg = Clay_Color{ 44, 44, 44, 255};
|
||||
g_theme.disabled_text = Clay_Color{ 90, 90, 90, 255};
|
||||
g_theme.header_bg = Clay_Color{ 46, 46, 46, 255};
|
||||
g_theme.title_bar = Clay_Color{ 22, 22, 22, 255};
|
||||
g_theme.scrollbar_bg = Clay_Color{ 22, 22, 22, 255};
|
||||
g_theme.scrollbar_grab = Clay_Color{ 58, 58, 58, 255};
|
||||
g_theme.shadow = Clay_Color{ 0, 0, 0, 30};
|
||||
g_theme.tab_active_top = Clay_Color{ 70, 120, 160, 255};
|
||||
g_theme.tab_active_bottom= Clay_Color{ 30, 55, 80, 255};
|
||||
g_theme.tab_inactive = Clay_Color{ 40, 40, 40, 255};
|
||||
g_theme.tab_inactive_hover= Clay_Color{ 50, 50, 50, 255};
|
||||
g_theme.tab_text = Clay_Color{240, 240, 240, 255};
|
||||
g_theme.bg_dark = (Clay_Color){ 26, 26, 26, 255};
|
||||
g_theme.bg_medium = (Clay_Color){ 36, 36, 36, 255};
|
||||
g_theme.bg_light = (Clay_Color){ 46, 46, 46, 255};
|
||||
g_theme.bg_lighter = (Clay_Color){ 54, 54, 54, 255};
|
||||
g_theme.border = (Clay_Color){ 52, 52, 52, 255};
|
||||
g_theme.text = (Clay_Color){220, 220, 220, 255};
|
||||
g_theme.text_dim = (Clay_Color){105, 105, 105, 255};
|
||||
g_theme.accent = (Clay_Color){ 87, 138, 176, 255};
|
||||
g_theme.accent_hover = (Clay_Color){102, 153, 191, 255};
|
||||
g_theme.button_text = (Clay_Color){224, 224, 224, 255};
|
||||
g_theme.disabled_bg = (Clay_Color){ 44, 44, 44, 255};
|
||||
g_theme.disabled_text = (Clay_Color){ 90, 90, 90, 255};
|
||||
g_theme.header_bg = (Clay_Color){ 46, 46, 46, 255};
|
||||
g_theme.title_bar = (Clay_Color){ 22, 22, 22, 255};
|
||||
g_theme.scrollbar_bg = (Clay_Color){ 22, 22, 22, 255};
|
||||
g_theme.scrollbar_grab = (Clay_Color){ 58, 58, 58, 255};
|
||||
g_theme.shadow = (Clay_Color){ 0, 0, 0, 30};
|
||||
g_theme.tab_active_top = (Clay_Color){ 70, 120, 160, 255};
|
||||
g_theme.tab_active_bottom= (Clay_Color){ 30, 55, 80, 255};
|
||||
g_theme.tab_inactive = (Clay_Color){ 40, 40, 40, 255};
|
||||
g_theme.tab_inactive_hover= (Clay_Color){ 50, 50, 50, 255};
|
||||
g_theme.tab_text = (Clay_Color){240, 240, 240, 255};
|
||||
g_theme.corner_radius = 4.0f;
|
||||
break;
|
||||
|
||||
case 1: // Light
|
||||
g_theme.bg_dark = Clay_Color{195, 193, 190, 255};
|
||||
g_theme.bg_medium = Clay_Color{212, 210, 207, 255};
|
||||
g_theme.bg_light = Clay_Color{225, 223, 220, 255};
|
||||
g_theme.bg_lighter = Clay_Color{232, 230, 227, 255};
|
||||
g_theme.border = Clay_Color{178, 176, 173, 255};
|
||||
g_theme.text = Clay_Color{ 35, 33, 30, 255};
|
||||
g_theme.text_dim = Clay_Color{115, 113, 108, 255};
|
||||
g_theme.accent = Clay_Color{ 50, 110, 170, 255};
|
||||
g_theme.accent_hover = Clay_Color{ 65, 125, 185, 255};
|
||||
g_theme.button_text = Clay_Color{255, 255, 255, 255};
|
||||
g_theme.disabled_bg = Clay_Color{185, 183, 180, 255};
|
||||
g_theme.disabled_text = Clay_Color{145, 143, 140, 255};
|
||||
g_theme.header_bg = Clay_Color{202, 200, 197, 255};
|
||||
g_theme.title_bar = Clay_Color{202, 200, 197, 255};
|
||||
g_theme.scrollbar_bg = Clay_Color{202, 200, 197, 255};
|
||||
g_theme.scrollbar_grab = Clay_Color{168, 166, 163, 255};
|
||||
g_theme.shadow = Clay_Color{ 0, 0, 0, 18};
|
||||
g_theme.tab_active_top = Clay_Color{ 70, 130, 180, 255};
|
||||
g_theme.tab_active_bottom= Clay_Color{ 50, 100, 150, 255};
|
||||
g_theme.tab_inactive = Clay_Color{205, 203, 200, 255};
|
||||
g_theme.tab_inactive_hover= Clay_Color{195, 193, 190, 255};
|
||||
g_theme.tab_text = Clay_Color{255, 255, 255, 255};
|
||||
g_theme.bg_dark = (Clay_Color){195, 193, 190, 255};
|
||||
g_theme.bg_medium = (Clay_Color){212, 210, 207, 255};
|
||||
g_theme.bg_light = (Clay_Color){225, 223, 220, 255};
|
||||
g_theme.bg_lighter = (Clay_Color){232, 230, 227, 255};
|
||||
g_theme.border = (Clay_Color){178, 176, 173, 255};
|
||||
g_theme.text = (Clay_Color){ 35, 33, 30, 255};
|
||||
g_theme.text_dim = (Clay_Color){115, 113, 108, 255};
|
||||
g_theme.accent = (Clay_Color){ 50, 110, 170, 255};
|
||||
g_theme.accent_hover = (Clay_Color){ 65, 125, 185, 255};
|
||||
g_theme.button_text = (Clay_Color){255, 255, 255, 255};
|
||||
g_theme.disabled_bg = (Clay_Color){185, 183, 180, 255};
|
||||
g_theme.disabled_text = (Clay_Color){145, 143, 140, 255};
|
||||
g_theme.header_bg = (Clay_Color){202, 200, 197, 255};
|
||||
g_theme.title_bar = (Clay_Color){202, 200, 197, 255};
|
||||
g_theme.scrollbar_bg = (Clay_Color){202, 200, 197, 255};
|
||||
g_theme.scrollbar_grab = (Clay_Color){168, 166, 163, 255};
|
||||
g_theme.shadow = (Clay_Color){ 0, 0, 0, 18};
|
||||
g_theme.tab_active_top = (Clay_Color){ 70, 130, 180, 255};
|
||||
g_theme.tab_active_bottom= (Clay_Color){ 50, 100, 150, 255};
|
||||
g_theme.tab_inactive = (Clay_Color){205, 203, 200, 255};
|
||||
g_theme.tab_inactive_hover= (Clay_Color){195, 193, 190, 255};
|
||||
g_theme.tab_text = (Clay_Color){255, 255, 255, 255};
|
||||
g_theme.corner_radius = 4.0f;
|
||||
break;
|
||||
}
|
||||
@@ -90,9 +102,9 @@ void ui_set_accent(S32 accent_id) {
|
||||
B32 dark = (g_theme_id == 0);
|
||||
|
||||
// Each palette: accent, accent_hover, tab_top, tab_bottom, button_text, tab_text
|
||||
struct AccentColors {
|
||||
typedef struct AccentColors {
|
||||
Clay_Color accent, accent_hover, tab_top, tab_bottom, button_text, tab_text;
|
||||
};
|
||||
} AccentColors;
|
||||
|
||||
// Dark-mode palettes
|
||||
static const AccentColors dark_palettes[] = {
|
||||
@@ -134,13 +146,13 @@ void ui_set_accent(S32 accent_id) {
|
||||
if (idx < 0) idx = 0;
|
||||
if (idx > 6) idx = 6;
|
||||
|
||||
const AccentColors &c = dark ? dark_palettes[idx] : light_palettes[idx];
|
||||
g_theme.accent = c.accent;
|
||||
g_theme.accent_hover = c.accent_hover;
|
||||
g_theme.tab_active_top = c.tab_top;
|
||||
g_theme.tab_active_bottom = c.tab_bottom;
|
||||
g_theme.button_text = c.button_text;
|
||||
g_theme.tab_text = c.tab_text;
|
||||
const AccentColors *c = dark ? &dark_palettes[idx] : &light_palettes[idx];
|
||||
g_theme.accent = c->accent;
|
||||
g_theme.accent_hover = c->accent_hover;
|
||||
g_theme.tab_active_top = c->tab_top;
|
||||
g_theme.tab_active_bottom = c->tab_bottom;
|
||||
g_theme.button_text = c->button_text;
|
||||
g_theme.tab_text = c->tab_text;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
@@ -158,15 +170,15 @@ static void clay_error_handler(Clay_ErrorData error) {
|
||||
// Clay text measurement bridge
|
||||
// Clay calls this; we forward to the app's UI_MeasureTextFn
|
||||
|
||||
static UI_Context *g_measure_ctx = nullptr;
|
||||
static UI_Context *g_measure_ctx = NULL;
|
||||
|
||||
static Clay_Dimensions clay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *user_data) {
|
||||
UI_Context *ctx = (UI_Context *)user_data;
|
||||
if (!ctx || !ctx->measure_text_fn || text.length == 0) {
|
||||
return Clay_Dimensions{0, (F32)config->fontSize};
|
||||
return (Clay_Dimensions){0, (F32)config->fontSize};
|
||||
}
|
||||
Vec2F32 result = ctx->measure_text_fn(text.chars, text.length, (F32)config->fontSize, ctx->measure_text_user_data);
|
||||
return Clay_Dimensions{result.x, result.y};
|
||||
return (Clay_Dimensions){result.x, result.y};
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
@@ -179,12 +191,12 @@ UI_Context *ui_create(F32 viewport_w, F32 viewport_h) {
|
||||
ctx->clay_memory = malloc(min_memory);
|
||||
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(min_memory, ctx->clay_memory);
|
||||
|
||||
Clay_ErrorHandler err_handler = {};
|
||||
Clay_ErrorHandler err_handler = {0};
|
||||
err_handler.errorHandlerFunction = clay_error_handler;
|
||||
err_handler.userData = ctx;
|
||||
|
||||
ctx->clay_ctx = Clay_Initialize(clay_arena,
|
||||
Clay_Dimensions{viewport_w, viewport_h},
|
||||
(Clay_Dimensions){viewport_w, viewport_h},
|
||||
err_handler);
|
||||
|
||||
Clay_SetMeasureTextFunction(clay_measure_text, ctx);
|
||||
@@ -204,9 +216,9 @@ void ui_begin_frame(UI_Context *ctx, F32 viewport_w, F32 viewport_h,
|
||||
{
|
||||
g_measure_ctx = ctx;
|
||||
Clay_SetCurrentContext(ctx->clay_ctx);
|
||||
Clay_SetLayoutDimensions(Clay_Dimensions{viewport_w, viewport_h});
|
||||
Clay_SetPointerState(Clay_Vector2{mouse_pos.x, mouse_pos.y}, mouse_down != 0);
|
||||
Clay_UpdateScrollContainers(false, Clay_Vector2{scroll_delta.x, scroll_delta.y}, dt);
|
||||
Clay_SetLayoutDimensions((Clay_Dimensions){viewport_w, viewport_h});
|
||||
Clay_SetPointerState((Clay_Vector2){mouse_pos.x, mouse_pos.y}, mouse_down != 0);
|
||||
Clay_UpdateScrollContainers(false, (Clay_Vector2){scroll_delta.x, scroll_delta.y}, dt);
|
||||
Clay_BeginLayout();
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ typedef Vec2F32 (*UI_MeasureTextFn)(const char *text, S32 length, F32 font_size,
|
||||
////////////////////////////////
|
||||
// UI Context
|
||||
|
||||
struct UI_Context {
|
||||
typedef struct UI_Context {
|
||||
Clay_Context *clay_ctx;
|
||||
void *clay_memory;
|
||||
|
||||
// Text measurement
|
||||
UI_MeasureTextFn measure_text_fn;
|
||||
void *measure_text_user_data;
|
||||
};
|
||||
} UI_Context;
|
||||
|
||||
////////////////////////////////
|
||||
// Lifecycle
|
||||
@@ -48,7 +48,7 @@ Vec2F32 ui_measure_text(const char *text, S32 length, F32 font_size);
|
||||
////////////////////////////////
|
||||
// Theme colors (convenience - 0-255 Clay_Color)
|
||||
|
||||
struct UI_Theme {
|
||||
typedef struct UI_Theme {
|
||||
Clay_Color bg_dark;
|
||||
Clay_Color bg_medium;
|
||||
Clay_Color bg_light;
|
||||
@@ -76,7 +76,7 @@ struct UI_Theme {
|
||||
|
||||
// Corner radius (unscaled pixels, applied via uis())
|
||||
F32 corner_radius;
|
||||
};
|
||||
} UI_Theme;
|
||||
|
||||
extern UI_Theme g_theme;
|
||||
extern S32 g_theme_id;
|
||||
@@ -116,30 +116,30 @@ static inline U16 uifs(F32 x) { return (U16)(x * g_ui_scale + 0.5f); }
|
||||
////////////////////////////////
|
||||
// Custom render types (for gradient rects via CLAY_RENDER_COMMAND_TYPE_CUSTOM)
|
||||
|
||||
enum CustomRenderType {
|
||||
typedef enum CustomRenderType {
|
||||
CUSTOM_RENDER_VGRADIENT = 1,
|
||||
CUSTOM_RENDER_ICON = 2,
|
||||
CUSTOM_RENDER_ROTATED_ICON = 3,
|
||||
};
|
||||
} CustomRenderType;
|
||||
|
||||
struct CustomGradientData {
|
||||
typedef struct CustomGradientData {
|
||||
CustomRenderType type;
|
||||
Clay_Color top_color;
|
||||
Clay_Color bottom_color;
|
||||
};
|
||||
} CustomGradientData;
|
||||
|
||||
struct CustomIconData {
|
||||
typedef struct CustomIconData {
|
||||
CustomRenderType type; // CUSTOM_RENDER_ICON
|
||||
S32 icon_id;
|
||||
Clay_Color color;
|
||||
};
|
||||
} CustomIconData;
|
||||
|
||||
struct CustomRotatedIconData {
|
||||
typedef struct CustomRotatedIconData {
|
||||
CustomRenderType type; // CUSTOM_RENDER_ROTATED_ICON
|
||||
S32 icon_id;
|
||||
Clay_Color color;
|
||||
F32 angle_rad;
|
||||
};
|
||||
} CustomRotatedIconData;
|
||||
|
||||
////////////////////////////////
|
||||
// Font sizes
|
||||
|
||||
219
src/ui/ui_icons.c
Normal file
219
src/ui/ui_icons.c
Normal file
@@ -0,0 +1,219 @@
|
||||
// ui_icons.cpp - SVG icon rasterization via nanosvg
|
||||
|
||||
#include "ui/ui_icons.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NANOSVG_IMPLEMENTATION
|
||||
#include "nanosvg.h"
|
||||
#define NANOSVGRAST_IMPLEMENTATION
|
||||
#include "nanosvgrast.h"
|
||||
|
||||
UI_IconInfo g_icons[UI_ICON_COUNT] = {0};
|
||||
|
||||
// Simple SVG icon sources (24x24 viewBox)
|
||||
static const char *g_icon_svgs[UI_ICON_COUNT] = {
|
||||
// UI_ICON_CLOSE - X mark
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<path d=\"M6 6 L18 18 M18 6 L6 18\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_CHECK - checkmark
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<path d=\"M5 12 L10 17 L19 7\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_CHEVRON_DOWN - downward arrow
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<path d=\"M7 10 L12 15 L17 10\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_KNOB - filled circle with indicator line pointing up (12 o'clock)
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"white\" opacity=\"0.25\"/>"
|
||||
"<line x1=\"12\" y1=\"12\" x2=\"12\" y2=\"3\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_SLIDER_THUMB - solid body with grip ridges
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<rect x=\"2\" y=\"1\" width=\"20\" height=\"22\" rx=\"4\" fill=\"white\"/>"
|
||||
"<line x1=\"6\" y1=\"8\" x2=\"18\" y2=\"8\" stroke=\"black\" stroke-width=\"1.2\" opacity=\"0.3\"/>"
|
||||
"<line x1=\"6\" y1=\"11\" x2=\"18\" y2=\"11\" stroke=\"black\" stroke-width=\"1.2\" opacity=\"0.3\"/>"
|
||||
"<line x1=\"6\" y1=\"14\" x2=\"18\" y2=\"14\" stroke=\"black\" stroke-width=\"1.2\" opacity=\"0.3\"/>"
|
||||
"<line x1=\"6\" y1=\"17\" x2=\"18\" y2=\"17\" stroke=\"black\" stroke-width=\"1.2\" opacity=\"0.3\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_FADER - exact asset fader cap from assets/fader.svg
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"847 488 62.2 129.3\">"
|
||||
" <defs>"
|
||||
" <linearGradient id=\"linearGradient7718\" y2=\"528.75\" gradientUnits=\"userSpaceOnUse\" x2=\"87.866\" gradientTransform=\"matrix(1.0278,0,0,1,787.52,-27.904)\" y1=\"516.83\" x1=\"87.866\">"
|
||||
" <stop style=\"stop-color:#999999\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#999999;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7720\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1.0235,0,0,1,242.38,-1008.6)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7722\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1,0,0,0.50643,256.46,265.42)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7724\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1,0,0,0.42746,256.46,317.38)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7726\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1,0,0,0.26952,256.46,405.1)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7728\" y2=\"521.42\" gradientUnits=\"userSpaceOnUse\" x2=\"87.866\" gradientTransform=\"matrix(1.0364,0,0,0.96441,786.64,-1114.5)\" y1=\"516.83\" x1=\"87.866\">"
|
||||
" <stop style=\"stop-color:#999999\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#333333\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7730\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1.0364,0,0,0.96441,234.39,-1076.7)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7732\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1.0274,0,0,0.48841,240.25,-833.5)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7734\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1.0213,0,0,0.41225,243.85,-783.64)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7736\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1.0122,0,0,0.25993,249.26,-698.66)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" </defs>"
|
||||
" <rect style=\"fill:#4d4d4d\" rx=\"3\" ry=\"3\" height=\"129.29\" width=\"62.143\" y=\"488.03\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:#e6e6e6\" height=\"3.5355\" width=\"60.419\" y=\"552.42\" x=\"847.97\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7718)\" rx=\"2.148\" ry=\"2\" height=\"21.071\" width=\"60.613\" y=\"488.74\" x=\"847.72\"/>"
|
||||
" <rect style=\"fill:#333333\" height=\"10.119\" width=\"58.811\" y=\"540.26\" x=\"847.92\"/>"
|
||||
" <rect style=\"fill:#333333\" height=\"8.793\" width=\"61.133\" y=\"530.89\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:#1a1a1a\" height=\"4.546\" width=\"61.133\" y=\"512.48\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:#1a1a1a\" height=\"11.364\" width=\"61.133\" y=\"518.25\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7720)\" rx=\"1.024\" ry=\"0.64\" transform=\"scale(1,-1)\" height=\"2.261\" width=\"60.012\" y=\"-511.76\" x=\"847.72\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7722)\" rx=\"1\" ry=\"0.324\" height=\"1.145\" width=\"58.633\" y=\"517.03\" x=\"847.89\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7724)\" rx=\"1\" ry=\"0.273\" height=\"0.967\" width=\"58.633\" y=\"529.76\" x=\"847.89\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7726)\" rx=\"1\" ry=\"0.172\" height=\"0.609\" width=\"58.633\" y=\"539.01\" x=\"847.89\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7728)\" transform=\"scale(1,-1)\" rx=\"2.166\" ry=\"1.929\" height=\"20.321\" width=\"61.118\" y=\"-616.24\" x=\"847.34\"/>"
|
||||
" <rect style=\"fill:#333333\" transform=\"scale(1,-1)\" height=\"9.759\" width=\"58.811\" y=\"-567.93\" x=\"847.92\"/>"
|
||||
" <rect style=\"fill:#666666\" transform=\"scale(1,-1)\" height=\"8.48\" width=\"61.133\" y=\"-576.96\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:#808080\" transform=\"scale(1,-1)\" height=\"4.384\" width=\"61.133\" y=\"-594.72\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:#808080\" transform=\"scale(1,-1)\" height=\"10.96\" width=\"61.133\" y=\"-589.16\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7730)\" transform=\"scale(1,-1)\" rx=\"1.036\" ry=\"0.617\" height=\"2.181\" width=\"60.767\" y=\"-597.6\" x=\"847.34\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7732)\" transform=\"scale(1,-1)\" rx=\"1.027\" ry=\"0.312\" height=\"1.104\" width=\"60.24\" y=\"-590.84\" x=\"847.89\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7734)\" transform=\"scale(1,-1)\" rx=\"1.021\" ry=\"0.264\" height=\"0.932\" width=\"59.883\" y=\"-578.82\" x=\"847.89\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7736)\" transform=\"scale(1,-1)\" rx=\"1.012\" ry=\"0.166\" height=\"0.588\" width=\"59.347\" y=\"-569.52\" x=\"847.89\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_TRANSPORT_REWIND - skip to start (|<<)
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<rect x=\"3\" y=\"5\" width=\"2.5\" height=\"14\" rx=\"0.5\" fill=\"white\"/>"
|
||||
"<path d=\"M11 5 L11 19 L4 12 Z\" fill=\"white\"/>"
|
||||
"<path d=\"M20 5 L20 19 L13 12 Z\" fill=\"white\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_TRANSPORT_STOP - filled square
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<rect x=\"5\" y=\"5\" width=\"14\" height=\"14\" rx=\"1.5\" fill=\"white\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_TRANSPORT_PLAY - right-pointing triangle
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<path d=\"M6 4 L6 20 L20 12 Z\" fill=\"white\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_TRANSPORT_RECORD - filled circle
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<circle cx=\"12\" cy=\"12\" r=\"8\" fill=\"white\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_POP_OUT - box with arrow pointing out (top-right)
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<rect x=\"3\" y=\"6\" width=\"14\" height=\"14\" rx=\"2\" stroke=\"white\" stroke-width=\"2\" fill=\"none\"/>"
|
||||
"<path d=\"M14 3 L21 3 L21 10\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>"
|
||||
"<path d=\"M21 3 L12 12\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_POP_IN - arrow pointing into a box (bottom-left)
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<rect x=\"3\" y=\"6\" width=\"14\" height=\"14\" rx=\"2\" stroke=\"white\" stroke-width=\"2\" fill=\"none\"/>"
|
||||
"<path d=\"M21 3 L12 12\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\"/>"
|
||||
"<path d=\"M12 5 L12 12 L19 12\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>"
|
||||
"</svg>",
|
||||
};
|
||||
|
||||
U8 *ui_icons_rasterize_atlas(S32 *out_w, S32 *out_h, S32 icon_size) {
|
||||
// Pack icons in a row
|
||||
S32 atlas_w = icon_size * UI_ICON_COUNT;
|
||||
S32 atlas_h = icon_size;
|
||||
|
||||
// Pad to power of 2 isn't necessary for correctness, but ensure minimum size
|
||||
if (atlas_w < 64) atlas_w = 64;
|
||||
if (atlas_h < 64) atlas_h = 64;
|
||||
|
||||
U8 *atlas = (U8 *)calloc(atlas_w * atlas_h * 4, 1);
|
||||
if (!atlas) return NULL;
|
||||
|
||||
NSVGrasterizer *rast = nsvgCreateRasterizer();
|
||||
if (!rast) { free(atlas); return NULL; }
|
||||
|
||||
S32 pen_x = 0;
|
||||
for (S32 i = 0; i < UI_ICON_COUNT; i++) {
|
||||
// nanosvg modifies the input string, so we need a mutable copy
|
||||
U64 svg_len = strlen(g_icon_svgs[i]);
|
||||
char *svg_copy = (char *)malloc(svg_len + 1);
|
||||
memcpy(svg_copy, g_icon_svgs[i], svg_len + 1);
|
||||
|
||||
NSVGimage *image = nsvgParse(svg_copy, "px", 96.0f);
|
||||
free(svg_copy);
|
||||
if (!image) continue;
|
||||
|
||||
// Calculate scale to fit icon_size
|
||||
F32 scale_x = (F32)icon_size / image->width;
|
||||
F32 scale_y = (F32)icon_size / image->height;
|
||||
F32 scale = scale_x < scale_y ? scale_x : scale_y;
|
||||
|
||||
// Rasterize to a temporary RGBA buffer
|
||||
U8 *tmp = (U8 *)calloc(icon_size * icon_size * 4, 1);
|
||||
if (tmp) {
|
||||
nsvgRasterize(rast, image, 0, 0, scale, tmp, icon_size, icon_size, icon_size * 4);
|
||||
|
||||
// Copy into atlas (nanosvg outputs RGBA, which is what we want)
|
||||
for (S32 y = 0; y < icon_size && y < atlas_h; y++) {
|
||||
for (S32 x = 0; x < icon_size && (pen_x + x) < atlas_w; x++) {
|
||||
S32 src_idx = (y * icon_size + x) * 4;
|
||||
S32 dst_idx = (y * atlas_w + pen_x + x) * 4;
|
||||
atlas[dst_idx + 0] = tmp[src_idx + 0];
|
||||
atlas[dst_idx + 1] = tmp[src_idx + 1];
|
||||
atlas[dst_idx + 2] = tmp[src_idx + 2];
|
||||
atlas[dst_idx + 3] = tmp[src_idx + 3];
|
||||
}
|
||||
}
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
S32 bmp_w = icon_size;
|
||||
S32 bmp_h = icon_size;
|
||||
|
||||
// Store UV and pixel info
|
||||
g_icons[i].u0 = (F32)pen_x / (F32)atlas_w;
|
||||
g_icons[i].v0 = 0.0f;
|
||||
g_icons[i].u1 = (F32)(pen_x + bmp_w) / (F32)atlas_w;
|
||||
g_icons[i].v1 = (F32)bmp_h / (F32)atlas_h;
|
||||
g_icons[i].w = (F32)bmp_w;
|
||||
g_icons[i].h = (F32)bmp_h;
|
||||
|
||||
pen_x += icon_size;
|
||||
nsvgDelete(image);
|
||||
}
|
||||
|
||||
nsvgDeleteRasterizer(rast);
|
||||
|
||||
*out_w = atlas_w;
|
||||
*out_h = atlas_h;
|
||||
return atlas;
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
// ui_icons.cpp - SVG icon rasterization via lunasvg
|
||||
|
||||
#include "ui/ui_icons.h"
|
||||
#include <lunasvg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
UI_IconInfo g_icons[UI_ICON_COUNT] = {};
|
||||
|
||||
// Simple SVG icon sources (24x24 viewBox)
|
||||
static const char *g_icon_svgs[UI_ICON_COUNT] = {
|
||||
// UI_ICON_CLOSE - X mark
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M6 6 L18 18 M18 6 L6 18" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_CHECK - checkmark
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M5 12 L10 17 L19 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_CHEVRON_DOWN - downward arrow
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M7 10 L12 15 L17 10" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_KNOB - filled circle with indicator line pointing up (12 o'clock)
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="10" fill="white" opacity="0.25"/>
|
||||
<line x1="12" y1="12" x2="12" y2="3" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_SLIDER_THUMB - solid body with grip ridges
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="2" y="1" width="20" height="22" rx="4" fill="white"/>
|
||||
<line x1="6" y1="8" x2="18" y2="8" stroke="black" stroke-width="1.2" opacity="0.3"/>
|
||||
<line x1="6" y1="11" x2="18" y2="11" stroke="black" stroke-width="1.2" opacity="0.3"/>
|
||||
<line x1="6" y1="14" x2="18" y2="14" stroke="black" stroke-width="1.2" opacity="0.3"/>
|
||||
<line x1="6" y1="17" x2="18" y2="17" stroke="black" stroke-width="1.2" opacity="0.3"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_FADER - exact asset fader cap from assets/fader.svg
|
||||
R"SVG(<svg xmlns="http://www.w3.org/2000/svg" viewBox="847 488 62.2 129.3">
|
||||
<defs>
|
||||
<linearGradient id="linearGradient7718" y2="528.75" gradientUnits="userSpaceOnUse" x2="87.866" gradientTransform="matrix(1.0278,0,0,1,787.52,-27.904)" y1="516.83" x1="87.866">
|
||||
<stop style="stop-color:#999999" offset="0"/>
|
||||
<stop style="stop-color:#999999;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7720" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1.0235,0,0,1,242.38,-1008.6)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7722" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1,0,0,0.50643,256.46,265.42)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7724" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1,0,0,0.42746,256.46,317.38)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7726" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1,0,0,0.26952,256.46,405.1)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7728" y2="521.42" gradientUnits="userSpaceOnUse" x2="87.866" gradientTransform="matrix(1.0364,0,0,0.96441,786.64,-1114.5)" y1="516.83" x1="87.866">
|
||||
<stop style="stop-color:#999999" offset="0"/>
|
||||
<stop style="stop-color:#333333" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7730" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1.0364,0,0,0.96441,234.39,-1076.7)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7732" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1.0274,0,0,0.48841,240.25,-833.5)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7734" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1.0213,0,0,0.41225,243.85,-783.64)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7736" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1.0122,0,0,0.25993,249.26,-698.66)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect style="fill:#4d4d4d" rx="3" ry="3" height="129.29" width="62.143" y="488.03" x="847.03"/>
|
||||
<rect style="fill:#e6e6e6" height="3.5355" width="60.419" y="552.42" x="847.97"/>
|
||||
<rect style="fill:url(#linearGradient7718)" rx="2.148" ry="2" height="21.071" width="60.613" y="488.74" x="847.72"/>
|
||||
<rect style="fill:#333333" height="10.119" width="58.811" y="540.26" x="847.92"/>
|
||||
<rect style="fill:#333333" height="8.793" width="61.133" y="530.89" x="847.03"/>
|
||||
<rect style="fill:#1a1a1a" height="4.546" width="61.133" y="512.48" x="847.03"/>
|
||||
<rect style="fill:#1a1a1a" height="11.364" width="61.133" y="518.25" x="847.03"/>
|
||||
<rect style="fill:url(#linearGradient7720)" rx="1.024" ry="0.64" transform="scale(1,-1)" height="2.261" width="60.012" y="-511.76" x="847.72"/>
|
||||
<rect style="fill:url(#linearGradient7722)" rx="1" ry="0.324" height="1.145" width="58.633" y="517.03" x="847.89"/>
|
||||
<rect style="fill:url(#linearGradient7724)" rx="1" ry="0.273" height="0.967" width="58.633" y="529.76" x="847.89"/>
|
||||
<rect style="fill:url(#linearGradient7726)" rx="1" ry="0.172" height="0.609" width="58.633" y="539.01" x="847.89"/>
|
||||
<rect style="fill:url(#linearGradient7728)" transform="scale(1,-1)" rx="2.166" ry="1.929" height="20.321" width="61.118" y="-616.24" x="847.34"/>
|
||||
<rect style="fill:#333333" transform="scale(1,-1)" height="9.759" width="58.811" y="-567.93" x="847.92"/>
|
||||
<rect style="fill:#666666" transform="scale(1,-1)" height="8.48" width="61.133" y="-576.96" x="847.03"/>
|
||||
<rect style="fill:#808080" transform="scale(1,-1)" height="4.384" width="61.133" y="-594.72" x="847.03"/>
|
||||
<rect style="fill:#808080" transform="scale(1,-1)" height="10.96" width="61.133" y="-589.16" x="847.03"/>
|
||||
<rect style="fill:url(#linearGradient7730)" transform="scale(1,-1)" rx="1.036" ry="0.617" height="2.181" width="60.767" y="-597.6" x="847.34"/>
|
||||
<rect style="fill:url(#linearGradient7732)" transform="scale(1,-1)" rx="1.027" ry="0.312" height="1.104" width="60.24" y="-590.84" x="847.89"/>
|
||||
<rect style="fill:url(#linearGradient7734)" transform="scale(1,-1)" rx="1.021" ry="0.264" height="0.932" width="59.883" y="-578.82" x="847.89"/>
|
||||
<rect style="fill:url(#linearGradient7736)" transform="scale(1,-1)" rx="1.012" ry="0.166" height="0.588" width="59.347" y="-569.52" x="847.89"/>
|
||||
</svg>)SVG",
|
||||
|
||||
// UI_ICON_TRANSPORT_REWIND - skip to start (|<<)
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="3" y="5" width="2.5" height="14" rx="0.5" fill="white"/>
|
||||
<path d="M11 5 L11 19 L4 12 Z" fill="white"/>
|
||||
<path d="M20 5 L20 19 L13 12 Z" fill="white"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_TRANSPORT_STOP - filled square
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="5" y="5" width="14" height="14" rx="1.5" fill="white"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_TRANSPORT_PLAY - right-pointing triangle
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M6 4 L6 20 L20 12 Z" fill="white"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_TRANSPORT_RECORD - filled circle
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="8" fill="white"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_POP_OUT - box with arrow pointing out (top-right)
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="3" y="6" width="14" height="14" rx="2" stroke="white" stroke-width="2" fill="none"/>
|
||||
<path d="M14 3 L21 3 L21 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
<path d="M21 3 L12 12" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_POP_IN - arrow pointing into a box (bottom-left)
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="3" y="6" width="14" height="14" rx="2" stroke="white" stroke-width="2" fill="none"/>
|
||||
<path d="M21 3 L12 12" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M12 5 L12 12 L19 12" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>)",
|
||||
};
|
||||
|
||||
U8 *ui_icons_rasterize_atlas(S32 *out_w, S32 *out_h, S32 icon_size) {
|
||||
// Pack icons in a row
|
||||
S32 atlas_w = icon_size * UI_ICON_COUNT;
|
||||
S32 atlas_h = icon_size;
|
||||
|
||||
// Pad to power of 2 isn't necessary for correctness, but ensure minimum size
|
||||
if (atlas_w < 64) atlas_w = 64;
|
||||
if (atlas_h < 64) atlas_h = 64;
|
||||
|
||||
U8 *atlas = (U8 *)calloc(atlas_w * atlas_h * 4, 1);
|
||||
if (!atlas) return nullptr;
|
||||
|
||||
S32 pen_x = 0;
|
||||
for (S32 i = 0; i < UI_ICON_COUNT; i++) {
|
||||
auto doc = lunasvg::Document::loadFromData(g_icon_svgs[i]);
|
||||
if (!doc) continue;
|
||||
|
||||
lunasvg::Bitmap bmp = doc->renderToBitmap(icon_size, icon_size);
|
||||
if (bmp.isNull()) continue;
|
||||
|
||||
// Copy BGRA premultiplied → RGBA straight (un-premultiply)
|
||||
U8 *src = bmp.data();
|
||||
S32 bmp_w = bmp.width();
|
||||
S32 bmp_h = bmp.height();
|
||||
S32 stride = bmp.stride();
|
||||
|
||||
for (S32 y = 0; y < bmp_h && y < atlas_h; y++) {
|
||||
for (S32 x = 0; x < bmp_w && (pen_x + x) < atlas_w; x++) {
|
||||
U8 *s = &src[y * stride + x * 4];
|
||||
S32 dst_idx = (y * atlas_w + pen_x + x) * 4;
|
||||
U8 b = s[0], g = s[1], r = s[2], a = s[3];
|
||||
if (a > 0 && a < 255) {
|
||||
r = (U8)((r * 255) / a);
|
||||
g = (U8)((g * 255) / a);
|
||||
b = (U8)((b * 255) / a);
|
||||
}
|
||||
atlas[dst_idx + 0] = r;
|
||||
atlas[dst_idx + 1] = g;
|
||||
atlas[dst_idx + 2] = b;
|
||||
atlas[dst_idx + 3] = a;
|
||||
}
|
||||
}
|
||||
|
||||
// Store UV and pixel info
|
||||
g_icons[i].u0 = (F32)pen_x / (F32)atlas_w;
|
||||
g_icons[i].v0 = 0.0f;
|
||||
g_icons[i].u1 = (F32)(pen_x + bmp_w) / (F32)atlas_w;
|
||||
g_icons[i].v1 = (F32)bmp_h / (F32)atlas_h;
|
||||
g_icons[i].w = (F32)bmp_w;
|
||||
g_icons[i].h = (F32)bmp_h;
|
||||
|
||||
pen_x += icon_size;
|
||||
}
|
||||
|
||||
*out_w = atlas_w;
|
||||
*out_h = atlas_h;
|
||||
return atlas;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
// ui_icons.h - SVG icon definitions and atlas rasterization via lunasvg
|
||||
// ui_icons.h - SVG icon definitions and atlas rasterization via nanosvg
|
||||
|
||||
#include "base/base_inc.h"
|
||||
|
||||
enum UI_IconID {
|
||||
typedef enum UI_IconID {
|
||||
UI_ICON_CLOSE,
|
||||
UI_ICON_CHECK,
|
||||
UI_ICON_CHEVRON_DOWN,
|
||||
@@ -17,12 +17,12 @@ enum UI_IconID {
|
||||
UI_ICON_POP_OUT,
|
||||
UI_ICON_POP_IN,
|
||||
UI_ICON_COUNT
|
||||
};
|
||||
} UI_IconID;
|
||||
|
||||
struct UI_IconInfo {
|
||||
typedef struct UI_IconInfo {
|
||||
F32 u0, v0, u1, v1; // UV coordinates in icon atlas
|
||||
F32 w, h; // pixel dimensions at rasterized size
|
||||
};
|
||||
} UI_IconInfo;
|
||||
|
||||
extern UI_IconInfo g_icons[UI_ICON_COUNT];
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ Clay_Color piano_velocity_color(S32 velocity) {
|
||||
g = 175.0f + s * (50.0f - 175.0f);
|
||||
b = 80.0f + s * (40.0f - 80.0f);
|
||||
}
|
||||
return Clay_Color{r, g, b, 255};
|
||||
return (Clay_Color){r, g, b, 255};
|
||||
}
|
||||
|
||||
void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h) {
|
||||
@@ -54,7 +54,7 @@ void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h)
|
||||
} else if (mouse_held) {
|
||||
bg = g_theme.accent;
|
||||
} else {
|
||||
bg = Clay_Color{240, 240, 240, 255};
|
||||
bg = (Clay_Color){240, 240, 240, 255};
|
||||
}
|
||||
|
||||
CLAY(CLAY_IDI("PKey", note),
|
||||
@@ -79,7 +79,7 @@ void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h)
|
||||
} else if (mouse_held) {
|
||||
bg = g_theme.accent;
|
||||
} else {
|
||||
bg = Clay_Color{25, 25, 30, 255};
|
||||
bg = (Clay_Color){25, 25, 30, 255};
|
||||
}
|
||||
|
||||
CLAY(CLAY_IDI("PKey", note),
|
||||
@@ -5,9 +5,9 @@
|
||||
#define PIANO_FIRST_NOTE 21 // A0
|
||||
#define PIANO_LAST_NOTE 108 // C8
|
||||
|
||||
struct UI_PianoState {
|
||||
typedef struct UI_PianoState {
|
||||
S32 mouse_note; // MIDI note held by mouse click (-1 = none)
|
||||
};
|
||||
} UI_PianoState;
|
||||
|
||||
B32 piano_is_black_key(S32 note);
|
||||
Clay_Color piano_velocity_color(S32 velocity);
|
||||
|
||||
@@ -12,7 +12,7 @@ PopupWindow *popup_find_by_flag(B32 *flag) {
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++)
|
||||
if (g_popups[i].alive && g_popups[i].open_flag == flag)
|
||||
return &g_popups[i];
|
||||
return nullptr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer,
|
||||
@@ -21,16 +21,16 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
UI_WindowContentFn content_fn, void *user_data,
|
||||
PlatformWindowStyle style, B32 independent) {
|
||||
// Find free slot
|
||||
PopupWindow *popup = nullptr;
|
||||
PopupWindow *popup = NULL;
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++) {
|
||||
if (!g_popups[i].alive) { popup = &g_popups[i]; break; }
|
||||
}
|
||||
if (!popup) return nullptr;
|
||||
if (!popup) return NULL;
|
||||
|
||||
memset(popup, 0, sizeof(*popup));
|
||||
|
||||
// Create native popup window
|
||||
PlatformWindowDesc desc = {};
|
||||
PlatformWindowDesc desc = {0};
|
||||
desc.title = title;
|
||||
desc.width = width;
|
||||
desc.height = height;
|
||||
@@ -38,20 +38,20 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
desc.parent = parent_window;
|
||||
desc.independent = independent;
|
||||
popup->platform_window = platform_create_window(&desc);
|
||||
if (!popup->platform_window) return nullptr;
|
||||
if (!popup->platform_window) return NULL;
|
||||
|
||||
// Create shared renderer
|
||||
S32 pw, ph;
|
||||
platform_get_size(popup->platform_window, &pw, &ph);
|
||||
|
||||
RendererDesc rdesc = {};
|
||||
RendererDesc rdesc = {0};
|
||||
rdesc.window_handle = platform_get_native_handle(popup->platform_window);
|
||||
rdesc.width = pw;
|
||||
rdesc.height = ph;
|
||||
popup->renderer = renderer_create_shared(parent_renderer, &rdesc);
|
||||
if (!popup->renderer) {
|
||||
platform_destroy_window(popup->platform_window);
|
||||
return nullptr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create UI context
|
||||
@@ -67,7 +67,7 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
popup->last_w = pw;
|
||||
popup->last_h = ph;
|
||||
popup->title = title;
|
||||
popup->wstate = {};
|
||||
memset(&popup->wstate, 0, sizeof(popup->wstate));
|
||||
|
||||
platform_set_frame_callback(popup->platform_window, popup_frame_callback, popup);
|
||||
|
||||
@@ -81,7 +81,7 @@ void popup_close(PopupWindow *popup) {
|
||||
*popup->open_flag = 0;
|
||||
|
||||
popup->alive = 0;
|
||||
platform_set_frame_callback(popup->platform_window, nullptr, nullptr);
|
||||
platform_set_frame_callback(popup->platform_window, NULL, NULL);
|
||||
|
||||
ui_destroy(popup->ui_ctx);
|
||||
renderer_destroy(popup->renderer);
|
||||
@@ -151,15 +151,15 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
g_wstate = saved_wstate;
|
||||
|
||||
// Render
|
||||
renderer_end_frame(popup->renderer, render_commands);
|
||||
renderer_end_frame(popup->renderer, &render_commands);
|
||||
}
|
||||
|
||||
void popup_close_all() {
|
||||
void popup_close_all(void) {
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++)
|
||||
if (g_popups[i].alive) popup_close(&g_popups[i]);
|
||||
}
|
||||
|
||||
void popup_close_check() {
|
||||
void popup_close_check(void) {
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++) {
|
||||
if (g_popups[i].alive && platform_window_should_close(g_popups[i].platform_window))
|
||||
popup_close(&g_popups[i]);
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#define MAX_POPUP_WINDOWS 4
|
||||
|
||||
struct PopupWindow {
|
||||
typedef struct PopupWindow {
|
||||
B32 alive;
|
||||
B32 *open_flag; // e.g. &app->show_settings_window
|
||||
PlatformWindow *platform_window;
|
||||
@@ -18,14 +18,14 @@ struct PopupWindow {
|
||||
S32 width, height;
|
||||
S32 last_w, last_h;
|
||||
const char *title;
|
||||
};
|
||||
} PopupWindow;
|
||||
|
||||
PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer,
|
||||
const char *title, B32 *open_flag,
|
||||
S32 width, S32 height,
|
||||
UI_WindowContentFn content_fn, void *user_data,
|
||||
PlatformWindowStyle style = PLATFORM_WINDOW_STYLE_POPUP,
|
||||
B32 independent = 0);
|
||||
PlatformWindowStyle style,
|
||||
B32 independent);
|
||||
void popup_close(PopupWindow *popup);
|
||||
PopupWindow *popup_find_by_flag(B32 *flag);
|
||||
void popup_do_frame(PopupWindow *popup, F32 dt);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
UI_WidgetState g_wstate = {};
|
||||
UI_WidgetState g_wstate = {0};
|
||||
|
||||
// Icon per-frame pool (forward declaration for begin_frame)
|
||||
#define UI_MAX_ICONS_PER_FRAME 32
|
||||
@@ -33,7 +33,7 @@ static char g_knob_text_bufs[UI_MAX_KNOB_TEXT_BUFS][32];
|
||||
static S32 g_knob_text_buf_count = 0;
|
||||
|
||||
static CustomGradientData *alloc_gradient(Clay_Color top, Clay_Color bottom) {
|
||||
if (g_grad_pool_count >= UI_MAX_GRADIENTS_PER_FRAME) return nullptr;
|
||||
if (g_grad_pool_count >= UI_MAX_GRADIENTS_PER_FRAME) return NULL;
|
||||
CustomGradientData *g = &g_grad_pool[g_grad_pool_count++];
|
||||
g->type = CUSTOM_RENDER_VGRADIENT;
|
||||
g->top_color = top;
|
||||
@@ -120,7 +120,7 @@ static void emit_shadow(Clay_BoundingBox bb, F32 ox, F32 oy, F32 radius,
|
||||
}
|
||||
|
||||
void ui_widgets_init() {
|
||||
g_wstate = {};
|
||||
memset(&g_wstate, 0, sizeof(g_wstate));
|
||||
}
|
||||
|
||||
void ui_widgets_begin_frame(PlatformInput input) {
|
||||
@@ -171,43 +171,43 @@ static void ensure_widget_text_configs() {
|
||||
if (g_widget_text_configs_scale == g_ui_scale) return;
|
||||
g_widget_text_configs_scale = g_ui_scale;
|
||||
|
||||
g_widget_text_config = {};
|
||||
memset(&g_widget_text_config, 0, sizeof(g_widget_text_config));
|
||||
g_widget_text_config.textColor = g_theme.text;
|
||||
g_widget_text_config.fontSize = FONT_SIZE_NORMAL;
|
||||
g_widget_text_config.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
g_widget_text_config_dim = {};
|
||||
memset(&g_widget_text_config_dim, 0, sizeof(g_widget_text_config_dim));
|
||||
g_widget_text_config_dim.textColor = g_theme.text_dim;
|
||||
g_widget_text_config_dim.fontSize = FONT_SIZE_NORMAL;
|
||||
g_widget_text_config_dim.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
// Selected text: white on accent background (background set on parent element)
|
||||
g_widget_text_config_sel = {};
|
||||
g_widget_text_config_sel.textColor = Clay_Color{255, 255, 255, 255};
|
||||
memset(&g_widget_text_config_sel, 0, sizeof(g_widget_text_config_sel));
|
||||
g_widget_text_config_sel.textColor = (Clay_Color){255, 255, 255, 255};
|
||||
g_widget_text_config_sel.fontSize = FONT_SIZE_NORMAL;
|
||||
g_widget_text_config_sel.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
// Button text (always readable on accent background)
|
||||
g_widget_text_config_btn = {};
|
||||
memset(&g_widget_text_config_btn, 0, sizeof(g_widget_text_config_btn));
|
||||
g_widget_text_config_btn.textColor = g_theme.button_text;
|
||||
g_widget_text_config_btn.fontSize = FONT_SIZE_NORMAL;
|
||||
g_widget_text_config_btn.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
// Tab text (always light — readable on colored tab gradient)
|
||||
g_widget_text_config_tab = {};
|
||||
memset(&g_widget_text_config_tab, 0, sizeof(g_widget_text_config_tab));
|
||||
g_widget_text_config_tab.textColor = g_theme.tab_text;
|
||||
g_widget_text_config_tab.fontSize = FONT_SIZE_TAB;
|
||||
g_widget_text_config_tab.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
// Inactive tab text (theme text color, smaller font)
|
||||
g_widget_text_config_tab_inactive = {};
|
||||
memset(&g_widget_text_config_tab_inactive, 0, sizeof(g_widget_text_config_tab_inactive));
|
||||
g_widget_text_config_tab_inactive.textColor = g_theme.text;
|
||||
g_widget_text_config_tab_inactive.fontSize = FONT_SIZE_TAB;
|
||||
g_widget_text_config_tab_inactive.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
}
|
||||
|
||||
static Clay_String clay_str(const char *s) {
|
||||
Clay_String r = {};
|
||||
Clay_String r = {0};
|
||||
r.isStaticallyAllocated = false;
|
||||
r.length = (S32)strlen(s);
|
||||
r.chars = s;
|
||||
@@ -926,7 +926,7 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected)
|
||||
Clay_TextElementConfig *item_text = (is_item_selected && !item_hovered)
|
||||
? &g_widget_text_config_btn : &g_widget_text_config;
|
||||
|
||||
Clay_CornerRadius item_radius = {};
|
||||
Clay_CornerRadius item_radius = {0};
|
||||
if (i == 0) { item_radius.topLeft = CORNER_RADIUS; item_radius.topRight = CORNER_RADIUS; }
|
||||
if (i == count - 1) { item_radius.bottomLeft = CORNER_RADIUS; item_radius.bottomRight = CORNER_RADIUS; }
|
||||
|
||||
@@ -1008,7 +1008,7 @@ S32 ui_tab_bar(const char *id, const char **labels, S32 count, S32 *selected) {
|
||||
CLAY_TEXT(lbl_str, &g_widget_text_config_tab);
|
||||
}
|
||||
} else {
|
||||
Clay_Color bg = hovered ? (Clay_Color)TAB_INACTIVE_HOVER : (Clay_Color)TAB_INACTIVE_BG;
|
||||
Clay_Color bg = hovered ? TAB_INACTIVE_HOVER : TAB_INACTIVE_BG;
|
||||
CLAY(tab_eid,
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(TAB_HEIGHT) },
|
||||
@@ -1033,6 +1033,18 @@ S32 ui_tab_bar(const char *id, const char **labels, S32 count, S32 *selected) {
|
||||
////////////////////////////////
|
||||
// Shared value-edit helpers (used by knob, sliders, fader)
|
||||
|
||||
// Helper: delete selected range from knob edit buffer.
|
||||
// Returns new string length.
|
||||
static S32 ke_delete_sel(char *ebuf, S32 elen, S32 *ecur, S32 *esel0, S32 *esel1) {
|
||||
S32 lo = (*esel0 < *esel1 ? *esel0 : *esel1);
|
||||
S32 hi = (*esel0 < *esel1 ? *esel1 : *esel0);
|
||||
if (lo == hi) return elen;
|
||||
memmove(&ebuf[lo], &ebuf[hi], elen - hi + 1);
|
||||
*ecur = lo;
|
||||
*esel0 = *ecur; *esel1 = *ecur;
|
||||
return elen - (hi - lo);
|
||||
}
|
||||
|
||||
// Process keyboard input for value text editing.
|
||||
// Returns 0 = still editing, 1 = committed, 2 = cancelled.
|
||||
static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *changed) {
|
||||
@@ -1054,15 +1066,6 @@ static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *
|
||||
#define KE_SEL_HI() (*esel0 < *esel1 ? *esel1 : *esel0)
|
||||
#define KE_CLEAR_SEL() do { *esel0 = *ecur; *esel1 = *ecur; } while(0)
|
||||
|
||||
auto ke_delete_sel = [&]() -> S32 {
|
||||
S32 lo = KE_SEL_LO(), hi = KE_SEL_HI();
|
||||
if (lo == hi) return elen;
|
||||
memmove(&ebuf[lo], &ebuf[hi], elen - hi + 1);
|
||||
*ecur = lo;
|
||||
KE_CLEAR_SEL();
|
||||
return elen - (hi - lo);
|
||||
};
|
||||
|
||||
for (S32 k = 0; k < g_wstate.input.key_count; k++) {
|
||||
U8 key = g_wstate.input.keys[k];
|
||||
|
||||
@@ -1085,14 +1088,14 @@ static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *
|
||||
char tmp[32]; S32 n = hi - lo; if (n > 31) n = 31;
|
||||
memcpy(tmp, &ebuf[lo], n); tmp[n] = '\0';
|
||||
platform_clipboard_set(tmp);
|
||||
elen = ke_delete_sel();
|
||||
elen = ke_delete_sel(ebuf, elen, ecur, esel0, esel1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key == PKEY_V) {
|
||||
const char *clip = platform_clipboard_get();
|
||||
if (clip) {
|
||||
if (KE_HAS_SEL()) elen = ke_delete_sel();
|
||||
if (KE_HAS_SEL()) elen = ke_delete_sel(ebuf, elen, ecur, esel0, esel1);
|
||||
S32 clip_len = (S32)strlen(clip);
|
||||
char filtered[32]; S32 flen = 0;
|
||||
for (S32 i = 0; i < clip_len && flen < 30; i++) {
|
||||
@@ -1117,14 +1120,14 @@ static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *
|
||||
if (key == PKEY_RETURN) { commit = 1; }
|
||||
else if (key == PKEY_ESCAPE) { cancel = 1; }
|
||||
else if (key == PKEY_BACKSPACE) {
|
||||
if (KE_HAS_SEL()) { elen = ke_delete_sel(); }
|
||||
if (KE_HAS_SEL()) { elen = ke_delete_sel(ebuf, elen, ecur, esel0, esel1); }
|
||||
else if (*ecur > 0) {
|
||||
memmove(&ebuf[*ecur - 1], &ebuf[*ecur], elen - *ecur + 1);
|
||||
(*ecur)--; elen--;
|
||||
}
|
||||
KE_CLEAR_SEL();
|
||||
} else if (key == PKEY_DELETE) {
|
||||
if (KE_HAS_SEL()) { elen = ke_delete_sel(); }
|
||||
if (KE_HAS_SEL()) { elen = ke_delete_sel(ebuf, elen, ecur, esel0, esel1); }
|
||||
else if (*ecur < elen) {
|
||||
memmove(&ebuf[*ecur], &ebuf[*ecur + 1], elen - *ecur);
|
||||
elen--;
|
||||
@@ -1146,7 +1149,7 @@ static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *
|
||||
U16 ch = g_wstate.input.chars[c];
|
||||
B32 valid = (ch >= '0' && ch <= '9') || ch == '.' || ch == '-';
|
||||
if (valid) {
|
||||
if (KE_HAS_SEL()) elen = ke_delete_sel();
|
||||
if (KE_HAS_SEL()) elen = ke_delete_sel(ebuf, elen, ecur, esel0, esel1);
|
||||
if (elen < 30) {
|
||||
memmove(&ebuf[*ecur + 1], &ebuf[*ecur], elen - *ecur + 1);
|
||||
ebuf[*ecur] = (char)ch; (*ecur)++; elen++;
|
||||
@@ -1162,7 +1165,7 @@ static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *
|
||||
#undef KE_CLEAR_SEL
|
||||
|
||||
if (commit) {
|
||||
char *end = nullptr;
|
||||
char *end = NULL;
|
||||
F32 parsed = strtof(ebuf, &end);
|
||||
if (end != ebuf) {
|
||||
F32 lo = is_signed ? -max_val : 0.0f;
|
||||
@@ -1199,13 +1202,13 @@ static void value_edit_render(U32 hash, F32 width) {
|
||||
static char ke_dbuf_sel[32];
|
||||
static char ke_dbuf_after[32];
|
||||
|
||||
static Clay_TextElementConfig edit_cfg = {};
|
||||
static Clay_TextElementConfig edit_cfg = {0};
|
||||
edit_cfg.textColor = g_theme.text;
|
||||
edit_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
edit_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
static Clay_TextElementConfig edit_sel_cfg = {};
|
||||
edit_sel_cfg.textColor = Clay_Color{255, 255, 255, 255};
|
||||
static Clay_TextElementConfig edit_sel_cfg = {0};
|
||||
edit_sel_cfg.textColor = (Clay_Color){255, 255, 255, 255};
|
||||
edit_sel_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
edit_sel_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
@@ -1266,7 +1269,7 @@ static Clay_ElementId value_edit_eid(U32 hash) {
|
||||
// Handle click-outside to commit the edit.
|
||||
static void value_edit_click_away(Clay_ElementId eid, F32 *value, F32 max_val, B32 is_signed, B32 *changed) {
|
||||
if (g_wstate.mouse_clicked && !Clay_PointerOver(eid)) {
|
||||
char *end = nullptr;
|
||||
char *end = NULL;
|
||||
F32 parsed = strtof(g_wstate.knob_edit_buf, &end);
|
||||
if (end != g_wstate.knob_edit_buf) {
|
||||
F32 lo = is_signed ? -max_val : 0.0f;
|
||||
@@ -1305,7 +1308,7 @@ static F32 value_normalize(F32 value, F32 max_val, B32 is_signed) {
|
||||
|
||||
// Format a value into a text buf from the pool. Returns null if pool exhausted.
|
||||
static char *value_format_text(F32 value, B32 is_signed, S32 *out_len) {
|
||||
if (g_knob_text_buf_count >= UI_MAX_KNOB_TEXT_BUFS) return nullptr;
|
||||
if (g_knob_text_buf_count >= UI_MAX_KNOB_TEXT_BUFS) return NULL;
|
||||
char *buf = g_knob_text_bufs[g_knob_text_buf_count++];
|
||||
if (is_signed) { *out_len = snprintf(buf, 32, "%+.1f", value); }
|
||||
else { *out_len = snprintf(buf, 32, "%.1f", value); }
|
||||
@@ -1364,7 +1367,7 @@ B32 ui_knob(const char *id, const char *label, F32 *value, F32 max_val, B32 is_s
|
||||
|
||||
// Format value text
|
||||
S32 val_len = 0;
|
||||
char *val_text = is_editing ? nullptr : value_format_text(*value, is_signed, &val_len);
|
||||
char *val_text = is_editing ? NULL : value_format_text(*value, is_signed, &val_len);
|
||||
|
||||
// Layout: vertical column (knob → value text → label)
|
||||
CLAY(WID(id),
|
||||
@@ -1432,7 +1435,7 @@ B32 ui_knob(const char *id, const char *label, F32 *value, F32 max_val, B32 is_s
|
||||
B32 val_hovered = Clay_PointerOver(val_eid);
|
||||
|
||||
Clay_String val_str = { .isStaticallyAllocated = false, .length = val_len, .chars = val_text };
|
||||
static Clay_TextElementConfig knob_val_cfg = {};
|
||||
static Clay_TextElementConfig knob_val_cfg = {0};
|
||||
knob_val_cfg.textColor = g_theme.text;
|
||||
knob_val_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
knob_val_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -1507,7 +1510,7 @@ B32 ui_slider_h(const char *id, const char *label, F32 *value, F32 max_val, B32
|
||||
|
||||
// Format value text
|
||||
S32 val_len = 0;
|
||||
char *val_text = is_editing ? nullptr : value_format_text(*value, is_signed, &val_len);
|
||||
char *val_text = is_editing ? NULL : value_format_text(*value, is_signed, &val_len);
|
||||
|
||||
// Dimmed accent for fill bar
|
||||
Clay_Color fill_color = g_theme.accent;
|
||||
@@ -1611,7 +1614,7 @@ B32 ui_slider_h(const char *id, const char *label, F32 *value, F32 max_val, B32
|
||||
B32 val_hovered = Clay_PointerOver(val_eid);
|
||||
|
||||
Clay_String val_str = { .isStaticallyAllocated = false, .length = val_len, .chars = val_text };
|
||||
static Clay_TextElementConfig sl_val_cfg = {};
|
||||
static Clay_TextElementConfig sl_val_cfg = {0};
|
||||
sl_val_cfg.textColor = g_theme.text;
|
||||
sl_val_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
sl_val_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -1683,7 +1686,7 @@ B32 ui_slider_v(const char *id, const char *label, F32 *value, F32 max_val, B32
|
||||
|
||||
// Format value text
|
||||
S32 val_len = 0;
|
||||
char *val_text = is_editing ? nullptr : value_format_text(*value, is_signed, &val_len);
|
||||
char *val_text = is_editing ? NULL : value_format_text(*value, is_signed, &val_len);
|
||||
|
||||
// Dimmed accent for fill bar
|
||||
Clay_Color fill_color = g_theme.accent;
|
||||
@@ -1788,7 +1791,7 @@ B32 ui_slider_v(const char *id, const char *label, F32 *value, F32 max_val, B32
|
||||
B32 val_hovered = Clay_PointerOver(val_eid);
|
||||
|
||||
Clay_String val_str = { .isStaticallyAllocated = false, .length = val_len, .chars = val_text };
|
||||
static Clay_TextElementConfig sl_val_cfg = {};
|
||||
static Clay_TextElementConfig sl_val_cfg = {0};
|
||||
sl_val_cfg.textColor = g_theme.text;
|
||||
sl_val_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
sl_val_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -1860,7 +1863,7 @@ B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_
|
||||
|
||||
// Format value text
|
||||
S32 val_len = 0;
|
||||
char *val_text = is_editing ? nullptr : value_format_text(*value, is_signed, &val_len);
|
||||
char *val_text = is_editing ? NULL : value_format_text(*value, is_signed, &val_len);
|
||||
|
||||
// Tick mark dimensions
|
||||
F32 tick_major_w = WIDGET_FADER_TICK_MAJOR_W;
|
||||
@@ -1954,7 +1957,7 @@ B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_
|
||||
CustomIconData *idata = &g_icon_pool[g_icon_pool_count++];
|
||||
idata->type = CUSTOM_RENDER_ICON;
|
||||
idata->icon_id = (S32)UI_ICON_FADER;
|
||||
idata->color = Clay_Color{255, 255, 255, 255};
|
||||
idata->color = (Clay_Color){255, 255, 255, 255};
|
||||
|
||||
F32 cap_y = (1.0f - normalized) * (track_h - cap_h);
|
||||
|
||||
@@ -2026,7 +2029,7 @@ B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_
|
||||
B32 val_hovered = Clay_PointerOver(val_eid);
|
||||
|
||||
Clay_String val_str = { .isStaticallyAllocated = false, .length = val_len, .chars = val_text };
|
||||
static Clay_TextElementConfig fdr_val_cfg = {};
|
||||
static Clay_TextElementConfig fdr_val_cfg = {0};
|
||||
fdr_val_cfg.textColor = g_theme.text;
|
||||
fdr_val_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
fdr_val_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -16,7 +16,7 @@
|
||||
#define UI_WIDGET_MAX_DROPDOWN_ITEMS 32
|
||||
#define UI_WIDGET_MAX_TEXT_INPUTS 16
|
||||
|
||||
struct UI_KnobDragState {
|
||||
typedef struct UI_KnobDragState {
|
||||
U32 dragging_id; // Hash of the knob being dragged (0 = none)
|
||||
F32 drag_start_y; // Mouse Y when drag started
|
||||
F32 drag_start_x; // Mouse X when drag started (for h-slider)
|
||||
@@ -24,9 +24,9 @@ struct UI_KnobDragState {
|
||||
B32 was_shift; // Shift state last frame (to re-anchor on change)
|
||||
U32 last_click_id; // Knob hash of last click (for F64-click detection)
|
||||
S32 last_click_frame; // Frame number of last click
|
||||
};
|
||||
} UI_KnobDragState;
|
||||
|
||||
struct UI_WidgetState {
|
||||
typedef struct UI_WidgetState {
|
||||
// Text input focus
|
||||
U32 focused_id; // Clay element ID hash of the focused text input (0 = none)
|
||||
S32 cursor_pos; // Cursor position in focused text input
|
||||
@@ -59,7 +59,7 @@ struct UI_WidgetState {
|
||||
S32 knob_edit_cursor; // Cursor position in edit buffer
|
||||
S32 knob_edit_sel_start; // Selection anchor
|
||||
S32 knob_edit_sel_end; // Selection extent
|
||||
};
|
||||
} UI_WidgetState;
|
||||
|
||||
extern UI_WidgetState g_wstate;
|
||||
|
||||
@@ -118,13 +118,13 @@ typedef void (*UI_WindowContentFn)(void *user_data);
|
||||
// editable: if true, clicking the value text opens a text input for direct entry.
|
||||
// Hold Shift while dragging for fine control.
|
||||
// Returns true if value changed this frame.
|
||||
B32 ui_knob(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable = 0);
|
||||
B32 ui_knob(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable);
|
||||
|
||||
// Horizontal slider. Drag left/right to change value.
|
||||
B32 ui_slider_h(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable = 0);
|
||||
B32 ui_slider_h(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable);
|
||||
|
||||
// Vertical slider. Drag up/down to change value.
|
||||
B32 ui_slider_v(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable = 0);
|
||||
B32 ui_slider_v(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable);
|
||||
|
||||
// DAW-style fader (vertical slider with fader cap icon).
|
||||
B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable = 0);
|
||||
B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable);
|
||||
|
||||
25
vendor/clay/clay.h
vendored
25
vendor/clay/clay.h
vendored
@@ -154,14 +154,7 @@ static inline void Clay__SuppressUnusedLatchDefinitionVariableWarning(void) { (v
|
||||
// CLAY(declarationStruct);
|
||||
#define CLAY__WRAPPER_TYPE(type) Clay__##type##Wrapper
|
||||
#define CLAY__WRAPPER_STRUCT(type) typedef struct { type wrapped; } CLAY__WRAPPER_TYPE(type)
|
||||
|
||||
// In C++, bypass the wrapper struct - MSVC doesn't support designated initializers
|
||||
// in function-style casts through wrapper structs. Direct construction works fine.
|
||||
#ifdef __cplusplus
|
||||
#define CLAY__CONFIG_WRAPPER(type, ...) (CLAY__INIT(type) { __VA_ARGS__ })
|
||||
#else
|
||||
#define CLAY__CONFIG_WRAPPER(type, ...) (CLAY__INIT(CLAY__WRAPPER_TYPE(type)) { __VA_ARGS__ }).wrapped
|
||||
#endif
|
||||
|
||||
#define CLAY_TEXT(text, textConfig) Clay__OpenTextElement(text, textConfig)
|
||||
|
||||
@@ -3152,14 +3145,6 @@ CLAY_DLL_EXPORT Clay_ElementIdArray Clay_GetPointerOverIds(void) {
|
||||
return Clay_GetCurrentContext()->pointerOverIds;
|
||||
}
|
||||
|
||||
// MSVC C++ cannot compile the debug view because it uses CLAY() macros with
|
||||
// designated initializers in function-style casts. Stub it out in C++ mode.
|
||||
#ifdef __cplusplus
|
||||
void Clay__RenderDebugViewElementConfigHeader(Clay_String elementId, Clay__ElementConfigType type) { (void)elementId; (void)type; }
|
||||
void Clay__RenderDebugViewColor(Clay_Color color, Clay_TextElementConfig *textConfig) { (void)color; (void)textConfig; }
|
||||
void Clay__RenderDebugViewCornerRadius(Clay_CornerRadius cornerRadius, Clay_TextElementConfig *textConfig) { (void)cornerRadius; (void)textConfig; }
|
||||
void Clay__RenderDebugView(void) {}
|
||||
#else
|
||||
#pragma region DebugTools
|
||||
Clay_Color CLAY__DEBUGVIEW_COLOR_1 = {58, 56, 52, 255};
|
||||
Clay_Color CLAY__DEBUGVIEW_COLOR_2 = {62, 60, 58, 255};
|
||||
@@ -3387,6 +3372,12 @@ void Clay__RenderDebugLayoutSizing(Clay_SizingAxis sizing, Clay_TextElementConfi
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CLAY_DISABLE_DEBUG_VIEW
|
||||
void Clay__RenderDebugViewElementConfigHeader(Clay_String elementId, Clay__ElementConfigType type) { (void)elementId; (void)type; }
|
||||
void Clay__RenderDebugViewColor(Clay_Color color, Clay_TextElementConfig *textConfig) { (void)color; (void)textConfig; }
|
||||
void Clay__RenderDebugViewCornerRadius(Clay_CornerRadius cornerRadius, Clay_TextElementConfig *textConfig) { (void)cornerRadius; (void)textConfig; }
|
||||
void Clay__RenderDebugView(void) {}
|
||||
#else
|
||||
void Clay__RenderDebugViewElementConfigHeader(Clay_String elementId, Clay__ElementConfigType type) {
|
||||
Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(type);
|
||||
Clay_Color backgroundColor = config.color;
|
||||
@@ -3857,7 +3848,7 @@ void Clay__RenderDebugView(void) {
|
||||
}
|
||||
}
|
||||
#pragma endregion
|
||||
#endif // __cplusplus (debug view)
|
||||
#endif // CLAY_DISABLE_DEBUG_VIEW
|
||||
|
||||
uint32_t Clay__debugViewWidth = 400;
|
||||
Clay_Color Clay__debugViewHighlightColor = { 168, 66, 28, 100 };
|
||||
@@ -4434,7 +4425,7 @@ void Clay_ResetMeasureTextCache(void) {
|
||||
context->measureTextHashMap.length = 0;
|
||||
context->measuredWords.length = 0;
|
||||
context->measuredWordsFreeList.length = 0;
|
||||
|
||||
|
||||
for (int32_t i = 0; i < context->measureTextHashMap.capacity; ++i) {
|
||||
context->measureTextHashMap.internalArray[i] = 0;
|
||||
}
|
||||
|
||||
21
vendor/lunasvg/LICENSE
vendored
21
vendor/lunasvg/LICENSE
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2026 Samuel Ugochukwu <sammycageagle@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
791
vendor/lunasvg/include/lunasvg.h
vendored
791
vendor/lunasvg/include/lunasvg.h
vendored
@@ -1,791 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2026 Samuel Ugochukwu <sammycageagle@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef LUNASVG_H
|
||||
#define LUNASVG_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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)
|
||||
#define LUNASVG_EXPORT __attribute__((__visibility__("default")))
|
||||
#define LUNASVG_IMPORT
|
||||
#else
|
||||
#define LUNASVG_EXPORT
|
||||
#define LUNASVG_IMPORT
|
||||
#endif
|
||||
|
||||
#ifdef LUNASVG_BUILD
|
||||
#define LUNASVG_API LUNASVG_EXPORT
|
||||
#else
|
||||
#define LUNASVG_API LUNASVG_IMPORT
|
||||
#endif
|
||||
|
||||
#define LUNASVG_VERSION_MAJOR 3
|
||||
#define LUNASVG_VERSION_MINOR 5
|
||||
#define LUNASVG_VERSION_MICRO 0
|
||||
|
||||
#define LUNASVG_VERSION_ENCODE(major, minor, micro) (((major) * 10000) + ((minor) * 100) + ((micro) * 1))
|
||||
#define LUNASVG_VERSION LUNASVG_VERSION_ENCODE(LUNASVG_VERSION_MAJOR, LUNASVG_VERSION_MINOR, LUNASVG_VERSION_MICRO)
|
||||
|
||||
#define LUNASVG_VERSION_XSTRINGIZE(major, minor, micro) #major"."#minor"."#micro
|
||||
#define LUNASVG_VERSION_STRINGIZE(major, minor, micro) LUNASVG_VERSION_XSTRINGIZE(major, minor, micro)
|
||||
#define LUNASVG_VERSION_STRING LUNASVG_VERSION_STRINGIZE(LUNASVG_VERSION_MAJOR, LUNASVG_VERSION_MINOR, LUNASVG_VERSION_MICRO)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct plutovg_surface plutovg_surface_t;
|
||||
typedef struct plutovg_matrix plutovg_matrix_t;
|
||||
|
||||
/**
|
||||
* @brief Callback for cleaning up resources.
|
||||
*
|
||||
* This function is called to release resources associated with a specific operation.
|
||||
*
|
||||
* @param closure A user-defined pointer to the resource or context to be freed.
|
||||
*/
|
||||
typedef void (*lunasvg_destroy_func_t)(void* closure);
|
||||
|
||||
/**
|
||||
* @brief A function pointer type for a write callback.
|
||||
* @param closure A pointer to user-defined data or context.
|
||||
* @param data A pointer to the data to be written.
|
||||
* @param size The size of the data in bytes.
|
||||
*/
|
||||
typedef void (*lunasvg_write_func_t)(void* closure, void* data, int size);
|
||||
|
||||
/**
|
||||
* @brief Returns the version of the lunasvg library encoded in a single integer.
|
||||
*
|
||||
* Encodes the version of the lunasvg library into a single integer for easier comparison.
|
||||
* The version is typically represented by combining major, minor, and patch numbers into one integer.
|
||||
*
|
||||
* @return The lunasvg library version as a single integer.
|
||||
*/
|
||||
LUNASVG_API int lunasvg_version(void);
|
||||
|
||||
/**
|
||||
* @brief Returns the lunasvg library version as a human-readable string in "X.Y.Z" format.
|
||||
*
|
||||
* Provides the version of the lunasvg library as a human-readable string in the format "X.Y.Z",
|
||||
* where X represents the major version, Y the minor version, and Z the patch version.
|
||||
*
|
||||
* @return A pointer to a string containing the version in "X.Y.Z" format.
|
||||
*/
|
||||
LUNASVG_API const char* lunasvg_version_string(void);
|
||||
|
||||
/**
|
||||
* @brief Add a font face from a file to the cache.
|
||||
* @param family The name of the font family. If an empty string is provided, the font will act as a fallback.
|
||||
* @param bold Use `true` for bold, `false` otherwise.
|
||||
* @param italic Use `true` for italic, `false` otherwise.
|
||||
* @param filename The path to the font file.
|
||||
* @return `true` if the font face was successfully added to the cache, `false` otherwise.
|
||||
*/
|
||||
LUNASVG_API bool lunasvg_add_font_face_from_file(const char* family, bool bold, bool italic, const char* filename);
|
||||
|
||||
/**
|
||||
* @brief Add a font face from memory to the cache.
|
||||
* @param family The name of the font family. If an empty string is provided, the font will act as a fallback.
|
||||
* @param bold Use `true` for bold, `false` otherwise.
|
||||
* @param italic Use `true` for italic, `false` otherwise.
|
||||
* @param data A pointer to the memory buffer containing the font data.
|
||||
* @param length The size of the memory buffer in bytes.
|
||||
* @param destroy_func Callback function to free the memory buffer when it is no longer needed.
|
||||
* @param closure User-defined pointer passed to the `destroy_func` callback.
|
||||
* @return `true` if the font face was successfully added to the cache, `false` otherwise.
|
||||
*/
|
||||
LUNASVG_API bool lunasvg_add_font_face_from_data(const char* family, bool bold, bool italic, const void* data, size_t length, lunasvg_destroy_func_t destroy_func, void* closure);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
/**
|
||||
* @note Bitmap pixel format is ARGB32_Premultiplied.
|
||||
*/
|
||||
class LUNASVG_API Bitmap {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a null bitmap.
|
||||
*/
|
||||
Bitmap() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a bitmap with the specified width and height.
|
||||
* @note A null bitmap will be returned if memory cannot be allocated.
|
||||
* @param width The width of the bitmap in pixels.
|
||||
* @param height The height of the bitmap in pixels.
|
||||
*/
|
||||
Bitmap(int width, int height);
|
||||
|
||||
/**
|
||||
* @brief Constructs a bitmap with the provided pixel data, width, height, and stride.
|
||||
*
|
||||
* @param data A pointer to the raw pixel data in ARGB32 Premultiplied format.
|
||||
* @param width The width of the bitmap in pixels.
|
||||
* @param height The height of the bitmap in pixels.
|
||||
* @param stride The number of bytes per row of pixel data (stride).
|
||||
*/
|
||||
Bitmap(uint8_t* data, int width, int height, int stride);
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
* @param bitmap The bitmap to copy.
|
||||
*/
|
||||
Bitmap(const Bitmap& bitmap);
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param bitmap The bitmap to move.
|
||||
*/
|
||||
Bitmap(Bitmap&& bitmap);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
Bitmap(plutovg_surface_t* surface) : m_surface(surface) {}
|
||||
|
||||
/**
|
||||
* @brief Cleans up any resources associated with the bitmap.
|
||||
*/
|
||||
~Bitmap();
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator.
|
||||
* @param bitmap The bitmap to copy.
|
||||
* @return A reference to this bitmap.
|
||||
*/
|
||||
Bitmap& operator=(const Bitmap& bitmap);
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param bitmap The bitmap to move.
|
||||
* @return A reference to this bitmap.
|
||||
*/
|
||||
Bitmap& operator=(Bitmap&& bitmap);
|
||||
|
||||
/**
|
||||
* @brief Swaps the content of this bitmap with another.
|
||||
* @param bitmap The bitmap to swap with.
|
||||
*/
|
||||
void swap(Bitmap& bitmap);
|
||||
|
||||
/**
|
||||
* @brief Gets the pointer to the raw pixel data.
|
||||
* @return A pointer to the raw pixel data.
|
||||
*/
|
||||
uint8_t* data() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the width of the bitmap.
|
||||
* @return The width of the bitmap in pixels.
|
||||
*/
|
||||
int width() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the height of the bitmap.
|
||||
* @return The height of the bitmap in pixels.
|
||||
*/
|
||||
int height() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the stride of the bitmap.
|
||||
* @return The number of bytes per row of pixel data (stride).
|
||||
*/
|
||||
int stride() const;
|
||||
|
||||
/**
|
||||
* @brief Clears the bitmap with the specified color.
|
||||
* @param The color value in 0xRRGGBBAA format.
|
||||
*/
|
||||
void clear(uint32_t value);
|
||||
|
||||
/**
|
||||
* @brief Converts the bitmap pixel data from ARGB32 Premultiplied to RGBA Plain format in place.
|
||||
*/
|
||||
void convertToRGBA();
|
||||
|
||||
/**
|
||||
* @brief Checks if the bitmap is null.
|
||||
* @return True if the bitmap is null, false otherwise.
|
||||
*/
|
||||
bool isNull() const { return m_surface == nullptr; }
|
||||
|
||||
/**
|
||||
* @brief Checks if the bitmap is valid.
|
||||
* @deprecated This function has been deprecated. Use `isNull()` instead to check whether the bitmap is null.
|
||||
* @return True if the bitmap is valid, false otherwise.
|
||||
*/
|
||||
bool valid() const { return !isNull(); }
|
||||
|
||||
/**
|
||||
* @brief Writes the bitmap to a PNG file.
|
||||
* @param filename The name of the file to write.
|
||||
* @return True if the file was written successfully, false otherwise.
|
||||
*/
|
||||
bool writeToPng(const std::string& filename) const;
|
||||
|
||||
/**
|
||||
* @brief Writes the bitmap to a PNG stream.
|
||||
* @param callback Callback function for writing data.
|
||||
* @param closure User-defined data passed to the callback.
|
||||
* @return True if successful, false otherwise.
|
||||
*/
|
||||
bool writeToPng(lunasvg_write_func_t callback, void* closure) const;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
plutovg_surface_t* surface() const { return m_surface; }
|
||||
|
||||
private:
|
||||
plutovg_surface_t* release();
|
||||
plutovg_surface_t* m_surface{nullptr};
|
||||
};
|
||||
|
||||
class Rect;
|
||||
class Matrix;
|
||||
|
||||
/**
|
||||
* @brief Represents a 2D axis-aligned bounding box.
|
||||
*/
|
||||
class LUNASVG_API Box {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a box with zero dimensions.
|
||||
*/
|
||||
Box() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a box with the specified position and size.
|
||||
* @param x The x-coordinate of the box's origin.
|
||||
* @param y The y-coordinate of the box's origin.
|
||||
* @param w The width of the box.
|
||||
* @param h The height of the box.
|
||||
*/
|
||||
Box(float x, float y, float w, float h);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
Box(const Rect& rect);
|
||||
|
||||
/**
|
||||
* @brief Transforms the box using the specified matrix.
|
||||
* @param matrix The transformation matrix.
|
||||
* @return A reference to this box, modified by the transformation.
|
||||
*/
|
||||
Box& transform(const Matrix& matrix);
|
||||
|
||||
/**
|
||||
* @brief Returns a new box transformed by the specified matrix.
|
||||
* @param matrix The transformation matrix.
|
||||
* @return A new box, transformed by the matrix.
|
||||
*/
|
||||
Box transformed(const Matrix& matrix) const;
|
||||
|
||||
float x{0}; ///< The x-coordinate of the box's origin.
|
||||
float y{0}; ///< The y-coordinate of the box's origin.
|
||||
float w{0}; ///< The width of the box.
|
||||
float h{0}; ///< The height of the box.
|
||||
};
|
||||
|
||||
class Transform;
|
||||
|
||||
/**
|
||||
* @brief Represents a 2D transformation matrix.
|
||||
*/
|
||||
class LUNASVG_API Matrix {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes the matrix to the identity matrix.
|
||||
*/
|
||||
Matrix() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a matrix with the specified values.
|
||||
* @param a The horizontal scaling factor.
|
||||
* @param b The vertical shearing factor.
|
||||
* @param c The horizontal shearing factor.
|
||||
* @param d The vertical scaling factor.
|
||||
* @param e The horizontal translation offset.
|
||||
* @param f The vertical translation offset.
|
||||
*/
|
||||
Matrix(float a, float b, float c, float d, float e, float f);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
Matrix(const plutovg_matrix_t& matrix);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
Matrix(const Transform& transform);
|
||||
|
||||
/**
|
||||
* @brief Multiplies this matrix with another matrix.
|
||||
* @param matrix The matrix to multiply with.
|
||||
* @return A new matrix that is the result of the multiplication.
|
||||
*/
|
||||
Matrix operator*(const Matrix& matrix) const;
|
||||
|
||||
/**
|
||||
* @brief Multiplies this matrix with another matrix in place.
|
||||
* @param matrix The matrix to multiply with.
|
||||
* @return A reference to this matrix after multiplication.
|
||||
*/
|
||||
Matrix& operator*=(const Matrix& matrix);
|
||||
|
||||
/**
|
||||
* @brief Multiplies this matrix with another matrix.
|
||||
* @param matrix The matrix to multiply with.
|
||||
* @return A reference to this matrix after multiplication.
|
||||
*/
|
||||
Matrix& multiply(const Matrix& matrix);
|
||||
|
||||
/**
|
||||
* @brief Translates this matrix by the specified offsets.
|
||||
* @param tx The horizontal translation offset.
|
||||
* @param ty The vertical translation offset.
|
||||
* @return A reference to this matrix after translation.
|
||||
*/
|
||||
Matrix& translate(float tx, float ty);
|
||||
|
||||
/**
|
||||
* @brief Scales this matrix by the specified factors.
|
||||
* @param sx The horizontal scaling factor.
|
||||
* @param sy The vertical scaling factor.
|
||||
* @return A reference to this matrix after scaling.
|
||||
*/
|
||||
Matrix& scale(float sx, float sy);
|
||||
|
||||
/**
|
||||
* @brief Rotates this matrix by the specified angle around a point.
|
||||
* @param angle The rotation angle in degrees.
|
||||
* @param cx The x-coordinate of the center of rotation.
|
||||
* @param cy The y-coordinate of the center of rotation.
|
||||
* @return A reference to this matrix after rotation.
|
||||
*/
|
||||
Matrix& rotate(float angle, float cx = 0.f, float cy = 0.f);
|
||||
|
||||
/**
|
||||
* @brief Shears this matrix by the specified factors.
|
||||
* @param shx The horizontal shearing factor.
|
||||
* @param shy The vertical shearing factor.
|
||||
* @return A reference to this matrix after shearing.
|
||||
*/
|
||||
Matrix& shear(float shx, float shy);
|
||||
|
||||
/**
|
||||
* @brief Inverts this matrix.
|
||||
* @return A reference to this matrix after inversion.
|
||||
*/
|
||||
Matrix& invert();
|
||||
|
||||
/**
|
||||
* @brief Returns the inverse of this matrix.
|
||||
* @return A new matrix that is the inverse of this matrix.
|
||||
*/
|
||||
Matrix inverse() const;
|
||||
|
||||
/**
|
||||
* @brief Resets this matrix to the identity matrix.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* @brief Creates a translation matrix with the specified offsets.
|
||||
* @param tx The horizontal translation offset.
|
||||
* @param ty The vertical translation offset.
|
||||
* @return A new translation matrix.
|
||||
*/
|
||||
static Matrix translated(float tx, float ty);
|
||||
|
||||
/**
|
||||
* @brief Creates a scaling matrix with the specified factors.
|
||||
* @param sx The horizontal scaling factor.
|
||||
* @param sy The vertical scaling factor.
|
||||
* @return A new scaling matrix.
|
||||
*/
|
||||
static Matrix scaled(float sx, float sy);
|
||||
|
||||
/**
|
||||
* @brief Creates a rotation matrix with the specified angle around a point.
|
||||
* @param angle The rotation angle in degrees.
|
||||
* @param cx The x-coordinate of the center of rotation.
|
||||
* @param cy The y-coordinate of the center of rotation.
|
||||
* @return A new rotation matrix.
|
||||
*/
|
||||
static Matrix rotated(float angle, float cx = 0.f, float cy = 0.f);
|
||||
|
||||
/**
|
||||
* @brief Creates a shearing matrix with the specified factors.
|
||||
* @param shx The horizontal shearing factor.
|
||||
* @param shy The vertical shearing factor.
|
||||
* @return A new shearing matrix.
|
||||
*/
|
||||
static Matrix sheared(float shx, float shy);
|
||||
|
||||
float a{1}; ///< The horizontal scaling factor.
|
||||
float b{0}; ///< The vertical shearing factor.
|
||||
float c{0}; ///< The horizontal shearing factor.
|
||||
float d{1}; ///< The vertical scaling factor.
|
||||
float e{0}; ///< The horizontal translation offset.
|
||||
float f{0}; ///< The vertical translation offset.
|
||||
};
|
||||
|
||||
class SVGNode;
|
||||
class SVGTextNode;
|
||||
class SVGElement;
|
||||
|
||||
class Element;
|
||||
class TextNode;
|
||||
|
||||
class LUNASVG_API Node {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a null node.
|
||||
*/
|
||||
Node() = default;
|
||||
|
||||
/**
|
||||
* @brief Checks if the node is a text node.
|
||||
* @return True if the node is a text node, false otherwise.
|
||||
*/
|
||||
bool isTextNode() const;
|
||||
|
||||
/**
|
||||
* @brief Checks if the node is an element node.
|
||||
* @return True if the node is an element node, false otherwise.
|
||||
*/
|
||||
bool isElement() const;
|
||||
|
||||
/**
|
||||
* @brief Converts the node to a TextNode.
|
||||
* @return A TextNode or a null node if conversion is not possible.
|
||||
*/
|
||||
TextNode toTextNode() const;
|
||||
|
||||
/**
|
||||
* @brief Converts the node to an Element.
|
||||
* @return An Element or a null node if conversion is not possible.
|
||||
*/
|
||||
Element toElement() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the parent element.
|
||||
* @return The parent element of this node. If this node has no parent, a null `Element` is returned.
|
||||
*/
|
||||
Element parentElement() const;
|
||||
|
||||
/**
|
||||
* @brief Checks if the node is null.
|
||||
* @return True if the node is null, false otherwise.
|
||||
*/
|
||||
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.
|
||||
* @return True if equal, otherwise false.
|
||||
*/
|
||||
bool operator==(const Node& node) const { return m_node == node.m_node; }
|
||||
|
||||
/**
|
||||
* @brief Checks if two nodes are not equal.
|
||||
* @param element The node to compare.
|
||||
* @return True if not equal, otherwise false.
|
||||
*/
|
||||
bool operator!=(const Node& node) const { return m_node != node.m_node; }
|
||||
|
||||
protected:
|
||||
Node(SVGNode* node);
|
||||
SVGNode* node() const { return m_node; }
|
||||
SVGNode* m_node{nullptr};
|
||||
friend class Element;
|
||||
};
|
||||
|
||||
using NodeList = std::vector<Node>;
|
||||
|
||||
class LUNASVG_API TextNode : public Node {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a null text node.
|
||||
*/
|
||||
TextNode() = default;
|
||||
|
||||
/**
|
||||
* @brief Returns the text content of the node.
|
||||
* @return A string representing the text content.
|
||||
*/
|
||||
const std::string& data() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the text content of the node.
|
||||
* @param data The new text content to set.
|
||||
*/
|
||||
void setData(const std::string& data);
|
||||
|
||||
private:
|
||||
TextNode(SVGTextNode* text);
|
||||
SVGTextNode* text() const;
|
||||
friend class Node;
|
||||
};
|
||||
|
||||
class LUNASVG_API Element : public Node {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a null element.
|
||||
*/
|
||||
Element() = default;
|
||||
|
||||
/**
|
||||
* @brief Checks if the element has a specific attribute.
|
||||
* @param name The name of the attribute to check.
|
||||
* @return True if the element has the specified attribute, false otherwise.
|
||||
*/
|
||||
bool hasAttribute(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the value of an attribute.
|
||||
* @param name The name of the attribute to retrieve.
|
||||
* @return The value of the attribute as a string.
|
||||
*/
|
||||
const std::string& getAttribute(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Sets the value of an attribute.
|
||||
* @param name The name of the attribute to set.
|
||||
* @param value The value to assign to the attribute.
|
||||
*/
|
||||
void setAttribute(const std::string& name, const std::string& value);
|
||||
|
||||
/**
|
||||
* @brief Renders the element onto a bitmap using a transformation matrix.
|
||||
* @param bitmap The bitmap to render onto.
|
||||
* @param The root transformation matrix.
|
||||
*/
|
||||
void render(Bitmap& bitmap, const Matrix& matrix = Matrix()) const;
|
||||
|
||||
/**
|
||||
* @brief Renders the element to a bitmap with specified dimensions.
|
||||
* @param width The desired width in pixels, or -1 to auto-scale based on the intrinsic size.
|
||||
* @param height The desired height in pixels, or -1 to auto-scale based on the intrinsic size.
|
||||
* @param backgroundColor The background color in 0xRRGGBBAA format.
|
||||
* @return A Bitmap containing the raster representation of the element.
|
||||
*/
|
||||
Bitmap renderToBitmap(int width = -1, int height = -1, uint32_t backgroundColor = 0x00000000) const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the local transformation matrix of the element.
|
||||
* @return The matrix that applies only to the element, relative to its parent.
|
||||
*/
|
||||
Matrix getLocalMatrix() const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the global transformation matrix of the element.
|
||||
* @return The matrix combining the element's local and all parent transformations.
|
||||
*/
|
||||
Matrix getGlobalMatrix() const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the local bounding box of the element.
|
||||
* @return A Box representing the bounding box after applying local transformations.
|
||||
*/
|
||||
Box getLocalBoundingBox() const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the global bounding box of the element.
|
||||
* @return A Box representing the bounding box after applying global transformations.
|
||||
*/
|
||||
Box getGlobalBoundingBox() const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the bounding box of the element without any transformations.
|
||||
* @return A Box representing the bounding box of the element without any transformations applied.
|
||||
*/
|
||||
Box getBoundingBox() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the child nodes of this node.
|
||||
* @return A NodeList containing the child nodes.
|
||||
*/
|
||||
NodeList children() const;
|
||||
|
||||
private:
|
||||
Element(SVGElement* element);
|
||||
SVGElement* element(bool layoutIfNeeded = false) const;
|
||||
friend class Node;
|
||||
friend class Document;
|
||||
};
|
||||
|
||||
using ElementList = std::vector<Element>;
|
||||
|
||||
class SVGRootElement;
|
||||
|
||||
class LUNASVG_API Document {
|
||||
public:
|
||||
/**
|
||||
* @brief Load an SVG document from a file.
|
||||
* @param filename The path to the SVG file.
|
||||
* @return A pointer to the loaded `Document`, or `nullptr` on failure.
|
||||
*/
|
||||
static std::unique_ptr<Document> loadFromFile(const std::string& filename);
|
||||
|
||||
/**
|
||||
* @brief Load an SVG document from a string.
|
||||
* @param string The SVG data as a string.
|
||||
* @return A pointer to the loaded `Document`, or `nullptr` on failure.
|
||||
*/
|
||||
static std::unique_ptr<Document> loadFromData(const std::string& string);
|
||||
|
||||
/**
|
||||
* @brief Load an SVG document from a null-terminated string.
|
||||
* @param data The string containing the SVG data.
|
||||
* @return A pointer to the loaded `Document`, or `nullptr` on failure.
|
||||
*/
|
||||
static std::unique_ptr<Document> loadFromData(const char* data);
|
||||
|
||||
/**
|
||||
* @brief Load an SVG document from a string with a specified length.
|
||||
* @param data The string containing the SVG data.
|
||||
* @param length The length of the string in bytes.
|
||||
* @return A pointer to the loaded `Document`, or `nullptr` on failure.
|
||||
*/
|
||||
static std::unique_ptr<Document> loadFromData(const char* data, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Applies a CSS stylesheet to the document.
|
||||
* @param content A string containing the CSS rules to apply, with comments removed.
|
||||
*/
|
||||
void applyStyleSheet(const std::string& content);
|
||||
|
||||
/**
|
||||
* @brief Selects all elements that match the given CSS selector(s).
|
||||
* @param content A string containing the CSS selector(s) to match elements.
|
||||
* @return A list of elements matching the selector(s).
|
||||
*/
|
||||
ElementList querySelectorAll(const std::string& content) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the intrinsic width of the document in pixels.
|
||||
* @return The width of the document.
|
||||
*/
|
||||
float width() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the intrinsic height of the document in pixels.
|
||||
* @return The height of the document.
|
||||
*/
|
||||
float height() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the smallest rectangle that encloses the document content.
|
||||
* @return A Box representing the bounding box of the document.
|
||||
*/
|
||||
Box boundingBox() const;
|
||||
|
||||
/**
|
||||
* @brief Updates the layout of the document if needed.
|
||||
*/
|
||||
void updateLayout();
|
||||
|
||||
/**
|
||||
* @brief Forces an immediate layout update.
|
||||
*/
|
||||
void forceLayout();
|
||||
|
||||
/**
|
||||
* @brief Renders the document onto a bitmap using a transformation matrix.
|
||||
* @param bitmap The bitmap to render onto.
|
||||
* @param The root transformation matrix.
|
||||
*/
|
||||
void render(Bitmap& bitmap, const Matrix& matrix = Matrix()) const;
|
||||
|
||||
/**
|
||||
* @brief Renders the document to a bitmap with specified dimensions.
|
||||
* @param width The desired width in pixels, or -1 to auto-scale based on the intrinsic size.
|
||||
* @param height The desired height in pixels, or -1 to auto-scale based on the intrinsic size.
|
||||
* @param backgroundColor The background color in 0xRRGGBBAA format.
|
||||
* @return A Bitmap containing the raster representation of the 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.
|
||||
* @return The Element with the specified ID, or a null `Element` if not found.
|
||||
*/
|
||||
Element getElementById(const std::string& id) const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the document element.
|
||||
* @return The root Element of the document.
|
||||
*/
|
||||
Element documentElement() const;
|
||||
|
||||
Document(Document&&);
|
||||
Document& operator=(Document&&);
|
||||
~Document();
|
||||
|
||||
private:
|
||||
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<SVGRootElement> m_rootElement;
|
||||
friend class SVGURIReference;
|
||||
friend class SVGNode;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_H
|
||||
2548
vendor/lunasvg/plutovg/include/plutovg.h
vendored
2548
vendor/lunasvg/plutovg/include/plutovg.h
vendored
File diff suppressed because it is too large
Load Diff
1144
vendor/lunasvg/plutovg/source/plutovg-blend.c
vendored
1144
vendor/lunasvg/plutovg/source/plutovg-blend.c
vendored
File diff suppressed because it is too large
Load Diff
759
vendor/lunasvg/plutovg/source/plutovg-canvas.c
vendored
759
vendor/lunasvg/plutovg/source/plutovg-canvas.c
vendored
@@ -1,759 +0,0 @@
|
||||
#include "plutovg-private.h"
|
||||
#include "plutovg-utils.h"
|
||||
|
||||
int plutovg_version(void)
|
||||
{
|
||||
return PLUTOVG_VERSION;
|
||||
}
|
||||
|
||||
const char* plutovg_version_string(void)
|
||||
{
|
||||
return PLUTOVG_VERSION_STRING;
|
||||
}
|
||||
|
||||
#define PLUTOVG_DEFAULT_STROKE_STYLE ((plutovg_stroke_style_t){1.f, PLUTOVG_LINE_CAP_BUTT, PLUTOVG_LINE_JOIN_MITER, 10.f})
|
||||
|
||||
static plutovg_state_t* plutovg_state_create(void)
|
||||
{
|
||||
plutovg_state_t* state = malloc(sizeof(plutovg_state_t));
|
||||
state->paint = NULL;
|
||||
state->font_face = NULL;
|
||||
state->color = PLUTOVG_BLACK_COLOR;
|
||||
state->matrix = PLUTOVG_IDENTITY_MATRIX;
|
||||
state->stroke.style = PLUTOVG_DEFAULT_STROKE_STYLE;
|
||||
state->stroke.dash.offset = 0.f;
|
||||
plutovg_array_init(state->stroke.dash.array);
|
||||
plutovg_span_buffer_init(&state->clip_spans);
|
||||
state->winding = PLUTOVG_FILL_RULE_NON_ZERO;
|
||||
state->op = PLUTOVG_OPERATOR_SRC_OVER;
|
||||
state->font_size = 12.f;
|
||||
state->opacity = 1.f;
|
||||
state->clipping = false;
|
||||
state->next = NULL;
|
||||
return state;
|
||||
}
|
||||
|
||||
static void plutovg_state_reset(plutovg_state_t* state)
|
||||
{
|
||||
plutovg_paint_destroy(state->paint);
|
||||
plutovg_font_face_destroy(state->font_face);
|
||||
state->paint = NULL;
|
||||
state->font_face = NULL;
|
||||
state->color = PLUTOVG_BLACK_COLOR;
|
||||
state->matrix = PLUTOVG_IDENTITY_MATRIX;
|
||||
state->stroke.style = PLUTOVG_DEFAULT_STROKE_STYLE;
|
||||
state->stroke.dash.offset = 0.f;
|
||||
plutovg_array_clear(state->stroke.dash.array);
|
||||
plutovg_span_buffer_reset(&state->clip_spans);
|
||||
state->winding = PLUTOVG_FILL_RULE_NON_ZERO;
|
||||
state->op = PLUTOVG_OPERATOR_SRC_OVER;
|
||||
state->font_size = 12.f;
|
||||
state->opacity = 1.f;
|
||||
state->clipping = false;
|
||||
}
|
||||
|
||||
static void plutovg_state_copy(plutovg_state_t* state, const plutovg_state_t* source)
|
||||
{
|
||||
state->paint = plutovg_paint_reference(source->paint);
|
||||
state->font_face = plutovg_font_face_reference(source->font_face);
|
||||
state->color = source->color;
|
||||
state->matrix = source->matrix;
|
||||
state->stroke.style = source->stroke.style;
|
||||
state->stroke.dash.offset = source->stroke.dash.offset;
|
||||
plutovg_array_clear(state->stroke.dash.array);
|
||||
plutovg_array_append(state->stroke.dash.array, source->stroke.dash.array);
|
||||
plutovg_span_buffer_copy(&state->clip_spans, &source->clip_spans);
|
||||
state->winding = source->winding;
|
||||
state->op = source->op;
|
||||
state->font_size = source->font_size;
|
||||
state->opacity = source->opacity;
|
||||
state->clipping = source->clipping;
|
||||
}
|
||||
|
||||
static void plutovg_state_destroy(plutovg_state_t* state)
|
||||
{
|
||||
plutovg_paint_destroy(state->paint);
|
||||
plutovg_font_face_destroy(state->font_face);
|
||||
plutovg_array_destroy(state->stroke.dash.array);
|
||||
plutovg_span_buffer_destroy(&state->clip_spans);
|
||||
free(state);
|
||||
}
|
||||
|
||||
plutovg_canvas_t* plutovg_canvas_create(plutovg_surface_t* surface)
|
||||
{
|
||||
plutovg_canvas_t* canvas = malloc(sizeof(plutovg_canvas_t));
|
||||
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);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
plutovg_canvas_t* plutovg_canvas_reference(plutovg_canvas_t* canvas)
|
||||
{
|
||||
plutovg_increment_reference(canvas);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
void plutovg_canvas_destroy(plutovg_canvas_t* canvas)
|
||||
{
|
||||
if(plutovg_destroy_reference(canvas)) {
|
||||
while(canvas->state) {
|
||||
plutovg_state_t* state = canvas->state;
|
||||
canvas->state = state->next;
|
||||
plutovg_state_destroy(state);
|
||||
}
|
||||
|
||||
while(canvas->freed_state) {
|
||||
plutovg_state_t* state = canvas->freed_state;
|
||||
canvas->freed_state = state->next;
|
||||
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);
|
||||
plutovg_path_destroy(canvas->path);
|
||||
free(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
int plutovg_canvas_get_reference_count(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return plutovg_get_reference_count(canvas);
|
||||
}
|
||||
|
||||
plutovg_surface_t* plutovg_canvas_get_surface(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->surface;
|
||||
}
|
||||
|
||||
void plutovg_canvas_save(plutovg_canvas_t* canvas)
|
||||
{
|
||||
plutovg_state_t* new_state = canvas->freed_state;
|
||||
if(new_state == NULL)
|
||||
new_state = plutovg_state_create();
|
||||
else
|
||||
canvas->freed_state = new_state->next;
|
||||
plutovg_state_copy(new_state, canvas->state);
|
||||
new_state->next = canvas->state;
|
||||
canvas->state = new_state;
|
||||
}
|
||||
|
||||
void plutovg_canvas_restore(plutovg_canvas_t* canvas)
|
||||
{
|
||||
if(canvas->state->next == NULL)
|
||||
return;
|
||||
plutovg_state_t* old_state = canvas->state;
|
||||
canvas->state = old_state->next;
|
||||
plutovg_state_reset(old_state);
|
||||
old_state->next = canvas->freed_state;
|
||||
canvas->freed_state = old_state;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_rgb(plutovg_canvas_t* canvas, float r, float g, float b)
|
||||
{
|
||||
plutovg_canvas_set_rgba(canvas, r, g, b, 1.f);
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_rgba(plutovg_canvas_t* canvas, float r, float g, float b, float a)
|
||||
{
|
||||
plutovg_color_init_rgba(&canvas->state->color, r, g, b, a);
|
||||
plutovg_canvas_set_paint(canvas, NULL);
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_color(plutovg_canvas_t* canvas, const plutovg_color_t* color)
|
||||
{
|
||||
plutovg_canvas_set_rgba(canvas, color->r, color->g, color->b, color->a);
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_linear_gradient(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
plutovg_paint_t* paint = plutovg_paint_create_linear_gradient(x1, y1, x2, y2, spread, stops, nstops, matrix);
|
||||
plutovg_canvas_set_paint(canvas, paint);
|
||||
plutovg_paint_destroy(paint);
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_radial_gradient(plutovg_canvas_t* canvas, float cx, float cy, float cr, float fx, float fy, float fr, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
plutovg_paint_t* paint = plutovg_paint_create_radial_gradient(cx, cy, cr, fx, fy, fr, spread, stops, nstops, matrix);
|
||||
plutovg_canvas_set_paint(canvas, paint);
|
||||
plutovg_paint_destroy(paint);
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_texture(plutovg_canvas_t* canvas, plutovg_surface_t* surface, plutovg_texture_type_t type, float opacity, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
plutovg_paint_t* paint = plutovg_paint_create_texture(surface, type, opacity, matrix);
|
||||
plutovg_canvas_set_paint(canvas, paint);
|
||||
plutovg_paint_destroy(paint);
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_paint(plutovg_canvas_t* canvas, plutovg_paint_t* paint)
|
||||
{
|
||||
paint = plutovg_paint_reference(paint);
|
||||
plutovg_paint_destroy(canvas->state->paint);
|
||||
canvas->state->paint = paint;
|
||||
}
|
||||
|
||||
plutovg_paint_t* plutovg_canvas_get_paint(const plutovg_canvas_t* canvas, plutovg_color_t* color)
|
||||
{
|
||||
if(color)
|
||||
*color = canvas->state->color;
|
||||
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);
|
||||
plutovg_canvas_set_font_size(canvas, size);
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_font_face(plutovg_canvas_t* canvas, plutovg_font_face_t* face)
|
||||
{
|
||||
face = plutovg_font_face_reference(face);
|
||||
plutovg_font_face_destroy(canvas->state->font_face);
|
||||
canvas->state->font_face = face;
|
||||
}
|
||||
|
||||
plutovg_font_face_t* plutovg_canvas_get_font_face(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->state->font_face;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_font_size(plutovg_canvas_t* canvas, float size)
|
||||
{
|
||||
canvas->state->font_size = size;
|
||||
}
|
||||
|
||||
float plutovg_canvas_get_font_size(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->state->font_size;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_fill_rule(plutovg_canvas_t* canvas, plutovg_fill_rule_t winding)
|
||||
{
|
||||
canvas->state->winding = winding;
|
||||
}
|
||||
|
||||
plutovg_fill_rule_t plutovg_canvas_get_fill_rule(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->state->winding;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_operator(plutovg_canvas_t* canvas, plutovg_operator_t op)
|
||||
{
|
||||
canvas->state->op = op;
|
||||
}
|
||||
|
||||
plutovg_operator_t plutovg_canvas_get_operator(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->state->op;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_opacity(plutovg_canvas_t* canvas, float opacity)
|
||||
{
|
||||
canvas->state->opacity = plutovg_clamp(opacity, 0.f, 1.f);
|
||||
}
|
||||
|
||||
float plutovg_canvas_get_opacity(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->state->opacity;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_line_width(plutovg_canvas_t* canvas, float line_width)
|
||||
{
|
||||
canvas->state->stroke.style.width = line_width;
|
||||
}
|
||||
|
||||
float plutovg_canvas_get_line_width(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->state->stroke.style.width;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_line_cap(plutovg_canvas_t* canvas, plutovg_line_cap_t line_cap)
|
||||
{
|
||||
canvas->state->stroke.style.cap = line_cap;
|
||||
}
|
||||
|
||||
plutovg_line_cap_t plutovg_canvas_get_line_cap(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->state->stroke.style.cap;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_line_join(plutovg_canvas_t* canvas, plutovg_line_join_t line_join)
|
||||
{
|
||||
canvas->state->stroke.style.join = line_join;
|
||||
}
|
||||
|
||||
plutovg_line_join_t plutovg_canvas_get_line_join(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->state->stroke.style.join;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_miter_limit(plutovg_canvas_t* canvas, float miter_limit)
|
||||
{
|
||||
canvas->state->stroke.style.miter_limit = miter_limit;
|
||||
}
|
||||
|
||||
float plutovg_canvas_get_miter_limit(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->state->stroke.style.miter_limit;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_dash(plutovg_canvas_t* canvas, float offset, const float* dashes, int ndashes)
|
||||
{
|
||||
plutovg_canvas_set_dash_offset(canvas, offset);
|
||||
plutovg_canvas_set_dash_array(canvas, dashes, ndashes);
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_dash_offset(plutovg_canvas_t* canvas, float offset)
|
||||
{
|
||||
canvas->state->stroke.dash.offset = offset;
|
||||
}
|
||||
|
||||
float plutovg_canvas_get_dash_offset(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->state->stroke.dash.offset;
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_dash_array(plutovg_canvas_t* canvas, const float* dashes, int ndashes)
|
||||
{
|
||||
plutovg_array_clear(canvas->state->stroke.dash.array);
|
||||
plutovg_array_append_data(canvas->state->stroke.dash.array, dashes, ndashes);
|
||||
}
|
||||
|
||||
int plutovg_canvas_get_dash_array(const plutovg_canvas_t* canvas, const float** dashes)
|
||||
{
|
||||
if(dashes)
|
||||
*dashes = canvas->state->stroke.dash.array.data;
|
||||
return canvas->state->stroke.dash.array.size;
|
||||
}
|
||||
|
||||
void plutovg_canvas_translate(plutovg_canvas_t* canvas, float tx, float ty)
|
||||
{
|
||||
plutovg_matrix_translate(&canvas->state->matrix, tx, ty);
|
||||
}
|
||||
|
||||
void plutovg_canvas_scale(plutovg_canvas_t* canvas, float sx, float sy)
|
||||
{
|
||||
plutovg_matrix_scale(&canvas->state->matrix, sx, sy);
|
||||
}
|
||||
|
||||
void plutovg_canvas_shear(plutovg_canvas_t* canvas, float shx, float shy)
|
||||
{
|
||||
plutovg_matrix_shear(&canvas->state->matrix, shx, shy);
|
||||
}
|
||||
|
||||
void plutovg_canvas_rotate(plutovg_canvas_t* canvas, float angle)
|
||||
{
|
||||
plutovg_matrix_rotate(&canvas->state->matrix, angle);
|
||||
}
|
||||
|
||||
void plutovg_canvas_transform(plutovg_canvas_t* canvas, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
plutovg_matrix_multiply(&canvas->state->matrix, matrix, &canvas->state->matrix);
|
||||
}
|
||||
|
||||
void plutovg_canvas_reset_matrix(plutovg_canvas_t* canvas)
|
||||
{
|
||||
plutovg_matrix_init_identity(&canvas->state->matrix);
|
||||
}
|
||||
|
||||
void plutovg_canvas_set_matrix(plutovg_canvas_t* canvas, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
canvas->state->matrix = matrix ? *matrix : PLUTOVG_IDENTITY_MATRIX;
|
||||
}
|
||||
|
||||
void plutovg_canvas_get_matrix(const plutovg_canvas_t* canvas, plutovg_matrix_t* matrix)
|
||||
{
|
||||
*matrix = canvas->state->matrix;
|
||||
}
|
||||
|
||||
void plutovg_canvas_map(const plutovg_canvas_t* canvas, float x, float y, float* xx, float* yy)
|
||||
{
|
||||
plutovg_matrix_map(&canvas->state->matrix, x, y, xx, yy);
|
||||
}
|
||||
|
||||
void plutovg_canvas_map_point(const plutovg_canvas_t* canvas, const plutovg_point_t* src, plutovg_point_t* dst)
|
||||
{
|
||||
plutovg_matrix_map_point(&canvas->state->matrix, src, dst);
|
||||
}
|
||||
|
||||
void plutovg_canvas_map_rect(const plutovg_canvas_t* canvas, const plutovg_rect_t* src, plutovg_rect_t* dst)
|
||||
{
|
||||
plutovg_matrix_map_rect(&canvas->state->matrix, src, dst);
|
||||
}
|
||||
|
||||
void plutovg_canvas_move_to(plutovg_canvas_t* canvas, float x, float y)
|
||||
{
|
||||
plutovg_path_move_to(canvas->path, x, y);
|
||||
}
|
||||
|
||||
void plutovg_canvas_line_to(plutovg_canvas_t* canvas, float x, float y)
|
||||
{
|
||||
plutovg_path_line_to(canvas->path, x, y);
|
||||
}
|
||||
|
||||
void plutovg_canvas_quad_to(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2)
|
||||
{
|
||||
plutovg_path_quad_to(canvas->path, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
void plutovg_canvas_cubic_to(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2, float x3, float y3)
|
||||
{
|
||||
plutovg_path_cubic_to(canvas->path, x1, y1, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
void plutovg_canvas_arc_to(plutovg_canvas_t* canvas, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y)
|
||||
{
|
||||
plutovg_path_arc_to(canvas->path, rx, ry, angle, large_arc_flag, sweep_flag, x, y);
|
||||
}
|
||||
|
||||
void plutovg_canvas_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h)
|
||||
{
|
||||
plutovg_path_add_rect(canvas->path, x, y, w, h);
|
||||
}
|
||||
|
||||
void plutovg_canvas_round_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h, float rx, float ry)
|
||||
{
|
||||
plutovg_path_add_round_rect(canvas->path, x, y, w, h, rx, ry);
|
||||
}
|
||||
|
||||
void plutovg_canvas_ellipse(plutovg_canvas_t* canvas, float cx, float cy, float rx, float ry)
|
||||
{
|
||||
plutovg_path_add_ellipse(canvas->path, cx, cy, rx, ry);
|
||||
}
|
||||
|
||||
void plutovg_canvas_circle(plutovg_canvas_t* canvas, float cx, float cy, float r)
|
||||
{
|
||||
plutovg_path_add_circle(canvas->path, cx, cy, r);
|
||||
}
|
||||
|
||||
void plutovg_canvas_arc(plutovg_canvas_t* canvas, float cx, float cy, float r, float a0, float a1, bool ccw)
|
||||
{
|
||||
plutovg_path_add_arc(canvas->path, cx, cy, r, a0, a1, ccw);
|
||||
}
|
||||
|
||||
void plutovg_canvas_add_path(plutovg_canvas_t* canvas, const plutovg_path_t* path)
|
||||
{
|
||||
plutovg_path_add_path(canvas->path, path, NULL);
|
||||
}
|
||||
|
||||
void plutovg_canvas_new_path(plutovg_canvas_t* canvas)
|
||||
{
|
||||
plutovg_path_reset(canvas->path);
|
||||
}
|
||||
|
||||
void plutovg_canvas_close_path(plutovg_canvas_t* canvas)
|
||||
{
|
||||
plutovg_path_close(canvas->path);
|
||||
}
|
||||
|
||||
void plutovg_canvas_get_current_point(const plutovg_canvas_t* canvas, float* x, float* y)
|
||||
{
|
||||
plutovg_path_get_current_point(canvas->path, x, y);
|
||||
}
|
||||
|
||||
plutovg_path_t* plutovg_canvas_get_path(const plutovg_canvas_t* canvas)
|
||||
{
|
||||
return canvas->path;
|
||||
}
|
||||
|
||||
bool plutovg_canvas_fill_contains(plutovg_canvas_t* canvas, float x, float y)
|
||||
{
|
||||
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_stroke_contains(plutovg_canvas_t* canvas, float x, float y)
|
||||
{
|
||||
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 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(plutovg_canvas_t* canvas, plutovg_rect_t* extents)
|
||||
{
|
||||
if(canvas->state->clipping) {
|
||||
plutovg_span_buffer_extents(&canvas->state->clip_spans, extents);
|
||||
} else {
|
||||
extents->x = canvas->clip_rect.x;
|
||||
extents->y = canvas->clip_rect.y;
|
||||
extents->w = canvas->clip_rect.w;
|
||||
extents->h = canvas->clip_rect.h;
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_canvas_fill(plutovg_canvas_t* canvas)
|
||||
{
|
||||
plutovg_canvas_fill_preserve(canvas);
|
||||
plutovg_canvas_new_path(canvas);
|
||||
}
|
||||
|
||||
void plutovg_canvas_stroke(plutovg_canvas_t* canvas)
|
||||
{
|
||||
plutovg_canvas_stroke_preserve(canvas);
|
||||
plutovg_canvas_new_path(canvas);
|
||||
}
|
||||
|
||||
void plutovg_canvas_clip(plutovg_canvas_t* canvas)
|
||||
{
|
||||
plutovg_canvas_clip_preserve(canvas);
|
||||
plutovg_canvas_new_path(canvas);
|
||||
}
|
||||
|
||||
void plutovg_canvas_paint(plutovg_canvas_t* canvas)
|
||||
{
|
||||
if(canvas->state->clipping) {
|
||||
plutovg_blend(canvas, &canvas->state->clip_spans);
|
||||
} else {
|
||||
plutovg_span_buffer_init_rect(&canvas->clip_spans, 0, 0, canvas->surface->width, canvas->surface->height);
|
||||
plutovg_blend(canvas, &canvas->clip_spans);
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_canvas_fill_preserve(plutovg_canvas_t* canvas)
|
||||
{
|
||||
plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, NULL, canvas->state->winding);
|
||||
if(canvas->state->clipping) {
|
||||
plutovg_span_buffer_intersect(&canvas->clip_spans, &canvas->fill_spans, &canvas->state->clip_spans);
|
||||
plutovg_blend(canvas, &canvas->clip_spans);
|
||||
} else {
|
||||
plutovg_blend(canvas, &canvas->fill_spans);
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_canvas_stroke_preserve(plutovg_canvas_t* canvas)
|
||||
{
|
||||
plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, &canvas->state->stroke, PLUTOVG_FILL_RULE_NON_ZERO);
|
||||
if(canvas->state->clipping) {
|
||||
plutovg_span_buffer_intersect(&canvas->clip_spans, &canvas->fill_spans, &canvas->state->clip_spans);
|
||||
plutovg_blend(canvas, &canvas->clip_spans);
|
||||
} else {
|
||||
plutovg_blend(canvas, &canvas->fill_spans);
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_canvas_clip_preserve(plutovg_canvas_t* canvas)
|
||||
{
|
||||
if(canvas->state->clipping) {
|
||||
plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, NULL, canvas->state->winding);
|
||||
plutovg_span_buffer_intersect(&canvas->clip_spans, &canvas->fill_spans, &canvas->state->clip_spans);
|
||||
plutovg_span_buffer_copy(&canvas->state->clip_spans, &canvas->clip_spans);
|
||||
} else {
|
||||
plutovg_rasterize(&canvas->state->clip_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, NULL, canvas->state->winding);
|
||||
canvas->state->clipping = true;
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_canvas_fill_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h)
|
||||
{
|
||||
plutovg_canvas_new_path(canvas);
|
||||
plutovg_canvas_rect(canvas, x, y, w, h);
|
||||
plutovg_canvas_fill(canvas);
|
||||
}
|
||||
|
||||
void plutovg_canvas_fill_path(plutovg_canvas_t* canvas, const plutovg_path_t* path)
|
||||
{
|
||||
plutovg_canvas_new_path(canvas);
|
||||
plutovg_canvas_add_path(canvas, path);
|
||||
plutovg_canvas_fill(canvas);
|
||||
}
|
||||
|
||||
void plutovg_canvas_stroke_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h)
|
||||
{
|
||||
plutovg_canvas_new_path(canvas);
|
||||
plutovg_canvas_rect(canvas, x, y, w, h);
|
||||
plutovg_canvas_stroke(canvas);
|
||||
}
|
||||
|
||||
void plutovg_canvas_stroke_path(plutovg_canvas_t* canvas, const plutovg_path_t* path)
|
||||
{
|
||||
plutovg_canvas_new_path(canvas);
|
||||
plutovg_canvas_add_path(canvas, path);
|
||||
plutovg_canvas_stroke(canvas);
|
||||
}
|
||||
|
||||
void plutovg_canvas_clip_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h)
|
||||
{
|
||||
plutovg_canvas_new_path(canvas);
|
||||
plutovg_canvas_rect(canvas, x, y, w, h);
|
||||
plutovg_canvas_clip(canvas);
|
||||
}
|
||||
|
||||
void plutovg_canvas_clip_path(plutovg_canvas_t* canvas, const plutovg_path_t* path)
|
||||
{
|
||||
plutovg_canvas_new_path(canvas);
|
||||
plutovg_canvas_add_path(canvas, path);
|
||||
plutovg_canvas_clip(canvas);
|
||||
}
|
||||
|
||||
float plutovg_canvas_add_glyph(plutovg_canvas_t* canvas, plutovg_codepoint_t codepoint, float x, float y)
|
||||
{
|
||||
plutovg_state_t* state = canvas->state;
|
||||
if(state->font_face && state->font_size > 0.f)
|
||||
return plutovg_font_face_get_glyph_path(state->font_face, state->font_size, x, y, codepoint, canvas->path);
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
float plutovg_canvas_add_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y)
|
||||
{
|
||||
plutovg_state_t* state = canvas->state;
|
||||
if(state->font_face == NULL || state->font_size <= 0.f)
|
||||
return 0.f;
|
||||
plutovg_text_iterator_t it;
|
||||
plutovg_text_iterator_init(&it, text, length, encoding);
|
||||
float advance_width = 0.f;
|
||||
while(plutovg_text_iterator_has_next(&it)) {
|
||||
plutovg_codepoint_t codepoint = plutovg_text_iterator_next(&it);
|
||||
advance_width += plutovg_font_face_get_glyph_path(state->font_face, state->font_size, x + advance_width, y, codepoint, canvas->path);
|
||||
}
|
||||
|
||||
return advance_width;
|
||||
}
|
||||
|
||||
float plutovg_canvas_fill_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y)
|
||||
{
|
||||
plutovg_canvas_new_path(canvas);
|
||||
float advance_width = plutovg_canvas_add_text(canvas, text, length, encoding, x, y);
|
||||
plutovg_canvas_fill(canvas);
|
||||
return advance_width;
|
||||
}
|
||||
|
||||
float plutovg_canvas_stroke_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y)
|
||||
{
|
||||
plutovg_canvas_new_path(canvas);
|
||||
float advance_width = plutovg_canvas_add_text(canvas, text, length, encoding, x, y);
|
||||
plutovg_canvas_stroke(canvas);
|
||||
return advance_width;
|
||||
}
|
||||
|
||||
float plutovg_canvas_clip_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y)
|
||||
{
|
||||
plutovg_canvas_new_path(canvas);
|
||||
float advance_width = plutovg_canvas_add_text(canvas, text, length, encoding, x, y);
|
||||
plutovg_canvas_clip(canvas);
|
||||
return advance_width;
|
||||
}
|
||||
|
||||
void plutovg_canvas_font_metrics(const plutovg_canvas_t* canvas, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents)
|
||||
{
|
||||
plutovg_state_t* state = canvas->state;
|
||||
if(state->font_face && state->font_size > 0.f) {
|
||||
plutovg_font_face_get_metrics(state->font_face, state->font_size, ascent, descent, line_gap, extents);
|
||||
return;
|
||||
}
|
||||
|
||||
if(ascent) *ascent = 0.f;
|
||||
if(descent) *descent = 0.f;
|
||||
if(line_gap) *line_gap = 0.f;
|
||||
if(extents) {
|
||||
extents->x = 0.f;
|
||||
extents->y = 0.f;
|
||||
extents->w = 0.f;
|
||||
extents->h = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_canvas_glyph_metrics(plutovg_canvas_t* canvas, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents)
|
||||
{
|
||||
plutovg_state_t* state = canvas->state;
|
||||
if(state->font_face && state->font_size > 0.f) {
|
||||
plutovg_font_face_get_glyph_metrics(state->font_face, state->font_size, codepoint, advance_width, left_side_bearing, extents);
|
||||
return;
|
||||
}
|
||||
|
||||
if(advance_width) *advance_width = 0.f;
|
||||
if(left_side_bearing) *left_side_bearing = 0.f;
|
||||
if(extents) {
|
||||
extents->x = 0.f;
|
||||
extents->y = 0.f;
|
||||
extents->w = 0.f;
|
||||
extents->h = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
float plutovg_canvas_text_extents(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents)
|
||||
{
|
||||
plutovg_state_t* state = canvas->state;
|
||||
if(state->font_face && state->font_size > 0.f) {
|
||||
return plutovg_font_face_text_extents(state->font_face, state->font_size, text, length, encoding, extents);
|
||||
}
|
||||
|
||||
if(extents) {
|
||||
extents->x = 0.f;
|
||||
extents->y = 0.f;
|
||||
extents->w = 0.f;
|
||||
extents->h = 0.f;
|
||||
}
|
||||
|
||||
return 0.f;
|
||||
}
|
||||
1065
vendor/lunasvg/plutovg/source/plutovg-font.c
vendored
1065
vendor/lunasvg/plutovg/source/plutovg-font.c
vendored
File diff suppressed because it is too large
Load Diff
441
vendor/lunasvg/plutovg/source/plutovg-ft-math.c
vendored
441
vendor/lunasvg/plutovg/source/plutovg-ft-math.c
vendored
@@ -1,441 +0,0 @@
|
||||
/***************************************************************************/
|
||||
/* */
|
||||
/* fttrigon.c */
|
||||
/* */
|
||||
/* FreeType trigonometric functions (body). */
|
||||
/* */
|
||||
/* Copyright 2001-2005, 2012-2013 by */
|
||||
/* David Turner, Robert Wilhelm, and Werner Lemberg. */
|
||||
/* */
|
||||
/* This file is part of the FreeType project, and may only be used, */
|
||||
/* modified, and distributed under the terms of the FreeType project */
|
||||
/* license, FTL.TXT. By continuing to use, modify, or distribute */
|
||||
/* this file you indicate that you have read the license and */
|
||||
/* understand and accept it fully. */
|
||||
/* */
|
||||
/***************************************************************************/
|
||||
|
||||
#include "plutovg-ft-math.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
static inline int clz(unsigned int x) {
|
||||
unsigned long r = 0;
|
||||
if (_BitScanReverse(&r, x))
|
||||
return 31 - r;
|
||||
return 32;
|
||||
}
|
||||
#define PVG_FT_MSB(x) (31 - clz(x))
|
||||
#elif defined(__GNUC__)
|
||||
#define PVG_FT_MSB(x) (31 - __builtin_clz(x))
|
||||
#else
|
||||
static inline int clz(unsigned int x) {
|
||||
int n = 0;
|
||||
if (x == 0) return 32;
|
||||
if (x <= 0x0000FFFFU) { n += 16; x <<= 16; }
|
||||
if (x <= 0x00FFFFFFU) { n += 8; x <<= 8; }
|
||||
if (x <= 0x0FFFFFFFU) { n += 4; x <<= 4; }
|
||||
if (x <= 0x3FFFFFFFU) { n += 2; x <<= 2; }
|
||||
if (x <= 0x7FFFFFFFU) { n += 1; }
|
||||
return n;
|
||||
}
|
||||
#define PVG_FT_MSB(x) (31 - clz(x))
|
||||
#endif
|
||||
|
||||
#define PVG_FT_PAD_FLOOR(x, n) ((x) & ~((n)-1))
|
||||
#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)
|
||||
|
||||
/* transfer sign leaving a positive number */
|
||||
#define PVG_FT_MOVE_SIGN(x, s) \
|
||||
PVG_FT_BEGIN_STMNT \
|
||||
if (x < 0) { \
|
||||
x = -x; \
|
||||
s = -s; \
|
||||
} \
|
||||
PVG_FT_END_STMNT
|
||||
|
||||
PVG_FT_Long PVG_FT_MulFix(PVG_FT_Long a, PVG_FT_Long b)
|
||||
{
|
||||
PVG_FT_Int s = 1;
|
||||
PVG_FT_Long c;
|
||||
|
||||
PVG_FT_MOVE_SIGN(a, s);
|
||||
PVG_FT_MOVE_SIGN(b, s);
|
||||
|
||||
c = (PVG_FT_Long)(((PVG_FT_Int64)a * b + 0x8000L) >> 16);
|
||||
|
||||
return (s > 0) ? c : -c;
|
||||
}
|
||||
|
||||
PVG_FT_Long PVG_FT_MulDiv(PVG_FT_Long a, PVG_FT_Long b, PVG_FT_Long c)
|
||||
{
|
||||
PVG_FT_Int s = 1;
|
||||
PVG_FT_Long d;
|
||||
|
||||
PVG_FT_MOVE_SIGN(a, s);
|
||||
PVG_FT_MOVE_SIGN(b, s);
|
||||
PVG_FT_MOVE_SIGN(c, s);
|
||||
|
||||
d = (PVG_FT_Long)(c > 0 ? ((PVG_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL);
|
||||
|
||||
return (s > 0) ? d : -d;
|
||||
}
|
||||
|
||||
PVG_FT_Long PVG_FT_DivFix(PVG_FT_Long a, PVG_FT_Long b)
|
||||
{
|
||||
PVG_FT_Int s = 1;
|
||||
PVG_FT_Long q;
|
||||
|
||||
PVG_FT_MOVE_SIGN(a, s);
|
||||
PVG_FT_MOVE_SIGN(b, s);
|
||||
|
||||
q = (PVG_FT_Long)(b > 0 ? (((PVG_FT_UInt64)a << 16) + (b >> 1)) / b
|
||||
: 0x7FFFFFFFL);
|
||||
|
||||
return (s < 0 ? -q : q);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* This is a fixed-point CORDIC implementation of trigonometric */
|
||||
/* functions as well as transformations between Cartesian and polar */
|
||||
/* coordinates. The angles are represented as 16.16 fixed-point values */
|
||||
/* in degrees, i.e., the angular resolution is 2^-16 degrees. Note that */
|
||||
/* only vectors longer than 2^16*180/pi (or at least 22 bits) on a */
|
||||
/* discrete Cartesian grid can have the same or better angular */
|
||||
/* resolution. Therefore, to maintain this precision, some functions */
|
||||
/* require an interim upscaling of the vectors, whereas others operate */
|
||||
/* with 24-bit long vectors directly. */
|
||||
/* */
|
||||
/*************************************************************************/
|
||||
|
||||
/* the Cordic shrink factor 0.858785336480436 * 2^32 */
|
||||
#define PVG_FT_TRIG_SCALE 0xDBD95B16UL
|
||||
|
||||
/* the highest bit in overflow-safe vector components, */
|
||||
/* MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */
|
||||
#define PVG_FT_TRIG_SAFE_MSB 29
|
||||
|
||||
/* this table was generated for PVG_FT_PI = 180L << 16, i.e. degrees */
|
||||
#define PVG_FT_TRIG_MAX_ITERS 23
|
||||
|
||||
static const PVG_FT_Fixed ft_trig_arctan_table[] = {
|
||||
1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, 14668L,
|
||||
7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, 57L,
|
||||
29L, 14L, 7L, 4L, 2L, 1L};
|
||||
|
||||
/* multiply a given value by the CORDIC shrink factor */
|
||||
static PVG_FT_Fixed ft_trig_downscale(PVG_FT_Fixed val)
|
||||
{
|
||||
PVG_FT_Fixed s;
|
||||
PVG_FT_Int64 v;
|
||||
|
||||
s = val;
|
||||
val = PVG_FT_ABS(val);
|
||||
|
||||
v = (val * (PVG_FT_Int64)PVG_FT_TRIG_SCALE) + 0x100000000UL;
|
||||
val = (PVG_FT_Fixed)(v >> 32);
|
||||
|
||||
return (s >= 0) ? val : -val;
|
||||
}
|
||||
|
||||
/* undefined and never called for zero vector */
|
||||
static PVG_FT_Int ft_trig_prenorm(PVG_FT_Vector* vec)
|
||||
{
|
||||
PVG_FT_Pos x, y;
|
||||
PVG_FT_Int shift;
|
||||
|
||||
x = vec->x;
|
||||
y = vec->y;
|
||||
|
||||
shift = PVG_FT_MSB(PVG_FT_ABS(x) | PVG_FT_ABS(y));
|
||||
|
||||
if (shift <= PVG_FT_TRIG_SAFE_MSB) {
|
||||
shift = PVG_FT_TRIG_SAFE_MSB - shift;
|
||||
vec->x = (PVG_FT_Pos)((PVG_FT_ULong)x << shift);
|
||||
vec->y = (PVG_FT_Pos)((PVG_FT_ULong)y << shift);
|
||||
} else {
|
||||
shift -= PVG_FT_TRIG_SAFE_MSB;
|
||||
vec->x = x >> shift;
|
||||
vec->y = y >> shift;
|
||||
shift = -shift;
|
||||
}
|
||||
|
||||
return shift;
|
||||
}
|
||||
|
||||
static void ft_trig_pseudo_rotate(PVG_FT_Vector* vec, PVG_FT_Angle theta)
|
||||
{
|
||||
PVG_FT_Int i;
|
||||
PVG_FT_Fixed x, y, xtemp, b;
|
||||
const PVG_FT_Fixed* arctanptr;
|
||||
|
||||
x = vec->x;
|
||||
y = vec->y;
|
||||
|
||||
/* Rotate inside [-PI/4,PI/4] sector */
|
||||
while (theta < -PVG_FT_ANGLE_PI4) {
|
||||
xtemp = y;
|
||||
y = -x;
|
||||
x = xtemp;
|
||||
theta += PVG_FT_ANGLE_PI2;
|
||||
}
|
||||
|
||||
while (theta > PVG_FT_ANGLE_PI4) {
|
||||
xtemp = -y;
|
||||
y = x;
|
||||
x = xtemp;
|
||||
theta -= PVG_FT_ANGLE_PI2;
|
||||
}
|
||||
|
||||
arctanptr = ft_trig_arctan_table;
|
||||
|
||||
/* Pseudorotations, with right shifts */
|
||||
for (i = 1, b = 1; i < PVG_FT_TRIG_MAX_ITERS; b <<= 1, i++) {
|
||||
PVG_FT_Fixed v1 = ((y + b) >> i);
|
||||
PVG_FT_Fixed v2 = ((x + b) >> i);
|
||||
if (theta < 0) {
|
||||
xtemp = x + v1;
|
||||
y = y - v2;
|
||||
x = xtemp;
|
||||
theta += *arctanptr++;
|
||||
} else {
|
||||
xtemp = x - v1;
|
||||
y = y + v2;
|
||||
x = xtemp;
|
||||
theta -= *arctanptr++;
|
||||
}
|
||||
}
|
||||
|
||||
vec->x = x;
|
||||
vec->y = y;
|
||||
}
|
||||
|
||||
static void ft_trig_pseudo_polarize(PVG_FT_Vector* vec)
|
||||
{
|
||||
PVG_FT_Angle theta;
|
||||
PVG_FT_Int i;
|
||||
PVG_FT_Fixed x, y, xtemp, b;
|
||||
const PVG_FT_Fixed* arctanptr;
|
||||
|
||||
x = vec->x;
|
||||
y = vec->y;
|
||||
|
||||
/* Get the vector into [-PI/4,PI/4] sector */
|
||||
if (y > x) {
|
||||
if (y > -x) {
|
||||
theta = PVG_FT_ANGLE_PI2;
|
||||
xtemp = y;
|
||||
y = -x;
|
||||
x = xtemp;
|
||||
} else {
|
||||
theta = y > 0 ? PVG_FT_ANGLE_PI : -PVG_FT_ANGLE_PI;
|
||||
x = -x;
|
||||
y = -y;
|
||||
}
|
||||
} else {
|
||||
if (y < -x) {
|
||||
theta = -PVG_FT_ANGLE_PI2;
|
||||
xtemp = -y;
|
||||
y = x;
|
||||
x = xtemp;
|
||||
} else {
|
||||
theta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
arctanptr = ft_trig_arctan_table;
|
||||
|
||||
/* Pseudorotations, with right shifts */
|
||||
for (i = 1, b = 1; i < PVG_FT_TRIG_MAX_ITERS; b <<= 1, i++) {
|
||||
PVG_FT_Fixed v1 = ((y + b) >> i);
|
||||
PVG_FT_Fixed v2 = ((x + b) >> i);
|
||||
if (y > 0) {
|
||||
xtemp = x + v1;
|
||||
y = y - v2;
|
||||
x = xtemp;
|
||||
theta += *arctanptr++;
|
||||
} else {
|
||||
xtemp = x - v1;
|
||||
y = y + v2;
|
||||
x = xtemp;
|
||||
theta -= *arctanptr++;
|
||||
}
|
||||
}
|
||||
|
||||
/* round theta */
|
||||
if (theta >= 0)
|
||||
theta = PVG_FT_PAD_ROUND(theta, 32);
|
||||
else
|
||||
theta = -PVG_FT_PAD_ROUND(-theta, 32);
|
||||
|
||||
vec->x = x;
|
||||
vec->y = theta;
|
||||
}
|
||||
|
||||
/* documentation is in fttrigon.h */
|
||||
|
||||
PVG_FT_Fixed PVG_FT_Cos(PVG_FT_Angle angle)
|
||||
{
|
||||
PVG_FT_Vector v;
|
||||
|
||||
v.x = PVG_FT_TRIG_SCALE >> 8;
|
||||
v.y = 0;
|
||||
ft_trig_pseudo_rotate(&v, angle);
|
||||
|
||||
return (v.x + 0x80L) >> 8;
|
||||
}
|
||||
|
||||
/* documentation is in fttrigon.h */
|
||||
|
||||
PVG_FT_Fixed PVG_FT_Sin(PVG_FT_Angle angle)
|
||||
{
|
||||
return PVG_FT_Cos(PVG_FT_ANGLE_PI2 - angle);
|
||||
}
|
||||
|
||||
/* documentation is in fttrigon.h */
|
||||
|
||||
PVG_FT_Fixed PVG_FT_Tan(PVG_FT_Angle angle)
|
||||
{
|
||||
PVG_FT_Vector v;
|
||||
|
||||
v.x = PVG_FT_TRIG_SCALE >> 8;
|
||||
v.y = 0;
|
||||
ft_trig_pseudo_rotate(&v, angle);
|
||||
|
||||
return PVG_FT_DivFix(v.y, v.x);
|
||||
}
|
||||
|
||||
/* documentation is in fttrigon.h */
|
||||
|
||||
PVG_FT_Angle PVG_FT_Atan2(PVG_FT_Fixed dx, PVG_FT_Fixed dy)
|
||||
{
|
||||
PVG_FT_Vector v;
|
||||
|
||||
if (dx == 0 && dy == 0) return 0;
|
||||
|
||||
v.x = dx;
|
||||
v.y = dy;
|
||||
ft_trig_prenorm(&v);
|
||||
ft_trig_pseudo_polarize(&v);
|
||||
|
||||
return v.y;
|
||||
}
|
||||
|
||||
/* documentation is in fttrigon.h */
|
||||
|
||||
void PVG_FT_Vector_Unit(PVG_FT_Vector* vec, PVG_FT_Angle angle)
|
||||
{
|
||||
vec->x = PVG_FT_TRIG_SCALE >> 8;
|
||||
vec->y = 0;
|
||||
ft_trig_pseudo_rotate(vec, angle);
|
||||
vec->x = (vec->x + 0x80L) >> 8;
|
||||
vec->y = (vec->y + 0x80L) >> 8;
|
||||
}
|
||||
|
||||
void PVG_FT_Vector_Rotate(PVG_FT_Vector* vec, PVG_FT_Angle angle)
|
||||
{
|
||||
PVG_FT_Int shift;
|
||||
PVG_FT_Vector v = *vec;
|
||||
|
||||
if ( v.x == 0 && v.y == 0 )
|
||||
return;
|
||||
|
||||
shift = ft_trig_prenorm( &v );
|
||||
ft_trig_pseudo_rotate( &v, angle );
|
||||
v.x = ft_trig_downscale( v.x );
|
||||
v.y = ft_trig_downscale( v.y );
|
||||
|
||||
if ( shift > 0 )
|
||||
{
|
||||
PVG_FT_Int32 half = (PVG_FT_Int32)1L << ( shift - 1 );
|
||||
|
||||
|
||||
vec->x = ( v.x + half - ( v.x < 0 ) ) >> shift;
|
||||
vec->y = ( v.y + half - ( v.y < 0 ) ) >> shift;
|
||||
}
|
||||
else
|
||||
{
|
||||
shift = -shift;
|
||||
vec->x = (PVG_FT_Pos)( (PVG_FT_ULong)v.x << shift );
|
||||
vec->y = (PVG_FT_Pos)( (PVG_FT_ULong)v.y << shift );
|
||||
}
|
||||
}
|
||||
|
||||
/* documentation is in fttrigon.h */
|
||||
|
||||
PVG_FT_Fixed PVG_FT_Vector_Length(PVG_FT_Vector* vec)
|
||||
{
|
||||
PVG_FT_Int shift;
|
||||
PVG_FT_Vector v;
|
||||
|
||||
v = *vec;
|
||||
|
||||
/* handle trivial cases */
|
||||
if (v.x == 0) {
|
||||
return PVG_FT_ABS(v.y);
|
||||
} else if (v.y == 0) {
|
||||
return PVG_FT_ABS(v.x);
|
||||
}
|
||||
|
||||
/* general case */
|
||||
shift = ft_trig_prenorm(&v);
|
||||
ft_trig_pseudo_polarize(&v);
|
||||
|
||||
v.x = ft_trig_downscale(v.x);
|
||||
|
||||
if (shift > 0) return (v.x + (1 << (shift - 1))) >> shift;
|
||||
|
||||
return (PVG_FT_Fixed)((PVG_FT_UInt32)v.x << -shift);
|
||||
}
|
||||
|
||||
/* documentation is in fttrigon.h */
|
||||
|
||||
void PVG_FT_Vector_Polarize(PVG_FT_Vector* vec, PVG_FT_Fixed* length,
|
||||
PVG_FT_Angle* angle)
|
||||
{
|
||||
PVG_FT_Int shift;
|
||||
PVG_FT_Vector v;
|
||||
|
||||
v = *vec;
|
||||
|
||||
if (v.x == 0 && v.y == 0) return;
|
||||
|
||||
shift = ft_trig_prenorm(&v);
|
||||
ft_trig_pseudo_polarize(&v);
|
||||
|
||||
v.x = ft_trig_downscale(v.x);
|
||||
|
||||
*length = (shift >= 0) ? (v.x >> shift)
|
||||
: (PVG_FT_Fixed)((PVG_FT_UInt32)v.x << -shift);
|
||||
*angle = v.y;
|
||||
}
|
||||
|
||||
/* documentation is in fttrigon.h */
|
||||
|
||||
void PVG_FT_Vector_From_Polar(PVG_FT_Vector* vec, PVG_FT_Fixed length,
|
||||
PVG_FT_Angle angle)
|
||||
{
|
||||
vec->x = length;
|
||||
vec->y = 0;
|
||||
|
||||
PVG_FT_Vector_Rotate(vec, angle);
|
||||
}
|
||||
|
||||
/* documentation is in fttrigon.h */
|
||||
|
||||
PVG_FT_Angle PVG_FT_Angle_Diff( PVG_FT_Angle angle1, PVG_FT_Angle angle2 )
|
||||
{
|
||||
PVG_FT_Angle delta = angle2 - angle1;
|
||||
|
||||
while ( delta <= -PVG_FT_ANGLE_PI )
|
||||
delta += PVG_FT_ANGLE_2PI;
|
||||
|
||||
while ( delta > PVG_FT_ANGLE_PI )
|
||||
delta -= PVG_FT_ANGLE_2PI;
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
/* END */
|
||||
436
vendor/lunasvg/plutovg/source/plutovg-ft-math.h
vendored
436
vendor/lunasvg/plutovg/source/plutovg-ft-math.h
vendored
@@ -1,436 +0,0 @@
|
||||
/***************************************************************************/
|
||||
/* */
|
||||
/* fttrigon.h */
|
||||
/* */
|
||||
/* FreeType trigonometric functions (specification). */
|
||||
/* */
|
||||
/* Copyright 2001, 2003, 2005, 2007, 2013 by */
|
||||
/* David Turner, Robert Wilhelm, and Werner Lemberg. */
|
||||
/* */
|
||||
/* This file is part of the FreeType project, and may only be used, */
|
||||
/* modified, and distributed under the terms of the FreeType project */
|
||||
/* license, FTL.TXT. By continuing to use, modify, or distribute */
|
||||
/* this file you indicate that you have read the license and */
|
||||
/* understand and accept it fully. */
|
||||
/* */
|
||||
/***************************************************************************/
|
||||
|
||||
#ifndef PLUTOVG_FT_MATH_H
|
||||
#define PLUTOVG_FT_MATH_H
|
||||
|
||||
#include "plutovg-ft-types.h"
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* The min and max functions missing in C. As usual, be careful not to */
|
||||
/* write things like PVG_FT_MIN( a++, b++ ) to avoid side effects. */
|
||||
/* */
|
||||
#define PVG_FT_MIN( a, b ) ( (a) < (b) ? (a) : (b) )
|
||||
#define PVG_FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) )
|
||||
|
||||
#define PVG_FT_ABS( a ) ( (a) < 0 ? -(a) : (a) )
|
||||
|
||||
/*
|
||||
* Approximate sqrt(x*x+y*y) using the `alpha max plus beta min'
|
||||
* algorithm. We use alpha = 1, beta = 3/8, giving us results with a
|
||||
* largest error less than 7% compared to the exact value.
|
||||
*/
|
||||
#define PVG_FT_HYPOT( x, y ) \
|
||||
( x = PVG_FT_ABS( x ), \
|
||||
y = PVG_FT_ABS( y ), \
|
||||
x > y ? x + ( 3 * y >> 3 ) \
|
||||
: y + ( 3 * x >> 3 ) )
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Function> */
|
||||
/* PVG_FT_MulFix */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A very simple function used to perform the computation */
|
||||
/* `(a*b)/0x10000' with maximum accuracy. Most of the time this is */
|
||||
/* used to multiply a given value by a 16.16 fixed-point factor. */
|
||||
/* */
|
||||
/* <Input> */
|
||||
/* a :: The first multiplier. */
|
||||
/* b :: The second multiplier. Use a 16.16 factor here whenever */
|
||||
/* possible (see note below). */
|
||||
/* */
|
||||
/* <Return> */
|
||||
/* The result of `(a*b)/0x10000'. */
|
||||
/* */
|
||||
/* <Note> */
|
||||
/* This function has been optimized for the case where the absolute */
|
||||
/* value of `a' is less than 2048, and `b' is a 16.16 scaling factor. */
|
||||
/* As this happens mainly when scaling from notional units to */
|
||||
/* fractional pixels in FreeType, it resulted in noticeable speed */
|
||||
/* improvements between versions 2.x and 1.x. */
|
||||
/* */
|
||||
/* As a conclusion, always try to place a 16.16 factor as the */
|
||||
/* _second_ argument of this function; this can make a great */
|
||||
/* difference. */
|
||||
/* */
|
||||
PVG_FT_Long
|
||||
PVG_FT_MulFix( PVG_FT_Long a,
|
||||
PVG_FT_Long b );
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Function> */
|
||||
/* PVG_FT_MulDiv */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A very simple function used to perform the computation `(a*b)/c' */
|
||||
/* with maximum accuracy (it uses a 64-bit intermediate integer */
|
||||
/* whenever necessary). */
|
||||
/* */
|
||||
/* This function isn't necessarily as fast as some processor specific */
|
||||
/* operations, but is at least completely portable. */
|
||||
/* */
|
||||
/* <Input> */
|
||||
/* a :: The first multiplier. */
|
||||
/* b :: The second multiplier. */
|
||||
/* c :: The divisor. */
|
||||
/* */
|
||||
/* <Return> */
|
||||
/* The result of `(a*b)/c'. This function never traps when trying to */
|
||||
/* divide by zero; it simply returns `MaxInt' or `MinInt' depending */
|
||||
/* on the signs of `a' and `b'. */
|
||||
/* */
|
||||
PVG_FT_Long
|
||||
PVG_FT_MulDiv( PVG_FT_Long a,
|
||||
PVG_FT_Long b,
|
||||
PVG_FT_Long c );
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Function> */
|
||||
/* PVG_FT_DivFix */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A very simple function used to perform the computation */
|
||||
/* `(a*0x10000)/b' with maximum accuracy. Most of the time, this is */
|
||||
/* used to divide a given value by a 16.16 fixed-point factor. */
|
||||
/* */
|
||||
/* <Input> */
|
||||
/* a :: The numerator. */
|
||||
/* b :: The denominator. Use a 16.16 factor here. */
|
||||
/* */
|
||||
/* <Return> */
|
||||
/* The result of `(a*0x10000)/b'. */
|
||||
/* */
|
||||
PVG_FT_Long
|
||||
PVG_FT_DivFix( PVG_FT_Long a,
|
||||
PVG_FT_Long b );
|
||||
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Section> */
|
||||
/* computations */
|
||||
/* */
|
||||
/*************************************************************************/
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @type:
|
||||
* PVG_FT_Angle
|
||||
*
|
||||
* @description:
|
||||
* This type is used to model angle values in FreeType. Note that the
|
||||
* angle is a 16.16 fixed-point value expressed in degrees.
|
||||
*
|
||||
*/
|
||||
typedef PVG_FT_Fixed PVG_FT_Angle;
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @macro:
|
||||
* PVG_FT_ANGLE_PI
|
||||
*
|
||||
* @description:
|
||||
* The angle pi expressed in @PVG_FT_Angle units.
|
||||
*
|
||||
*/
|
||||
#define PVG_FT_ANGLE_PI ( 180L << 16 )
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @macro:
|
||||
* PVG_FT_ANGLE_2PI
|
||||
*
|
||||
* @description:
|
||||
* The angle 2*pi expressed in @PVG_FT_Angle units.
|
||||
*
|
||||
*/
|
||||
#define PVG_FT_ANGLE_2PI ( PVG_FT_ANGLE_PI * 2 )
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @macro:
|
||||
* PVG_FT_ANGLE_PI2
|
||||
*
|
||||
* @description:
|
||||
* The angle pi/2 expressed in @PVG_FT_Angle units.
|
||||
*
|
||||
*/
|
||||
#define PVG_FT_ANGLE_PI2 ( PVG_FT_ANGLE_PI / 2 )
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @macro:
|
||||
* PVG_FT_ANGLE_PI4
|
||||
*
|
||||
* @description:
|
||||
* The angle pi/4 expressed in @PVG_FT_Angle units.
|
||||
*
|
||||
*/
|
||||
#define PVG_FT_ANGLE_PI4 ( PVG_FT_ANGLE_PI / 4 )
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Sin
|
||||
*
|
||||
* @description:
|
||||
* Return the sinus of a given angle in fixed-point format.
|
||||
*
|
||||
* @input:
|
||||
* angle ::
|
||||
* The input angle.
|
||||
*
|
||||
* @return:
|
||||
* The sinus value.
|
||||
*
|
||||
* @note:
|
||||
* If you need both the sinus and cosinus for a given angle, use the
|
||||
* function @PVG_FT_Vector_Unit.
|
||||
*
|
||||
*/
|
||||
PVG_FT_Fixed
|
||||
PVG_FT_Sin( PVG_FT_Angle angle );
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Cos
|
||||
*
|
||||
* @description:
|
||||
* Return the cosinus of a given angle in fixed-point format.
|
||||
*
|
||||
* @input:
|
||||
* angle ::
|
||||
* The input angle.
|
||||
*
|
||||
* @return:
|
||||
* The cosinus value.
|
||||
*
|
||||
* @note:
|
||||
* If you need both the sinus and cosinus for a given angle, use the
|
||||
* function @PVG_FT_Vector_Unit.
|
||||
*
|
||||
*/
|
||||
PVG_FT_Fixed
|
||||
PVG_FT_Cos( PVG_FT_Angle angle );
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Tan
|
||||
*
|
||||
* @description:
|
||||
* Return the tangent of a given angle in fixed-point format.
|
||||
*
|
||||
* @input:
|
||||
* angle ::
|
||||
* The input angle.
|
||||
*
|
||||
* @return:
|
||||
* The tangent value.
|
||||
*
|
||||
*/
|
||||
PVG_FT_Fixed
|
||||
PVG_FT_Tan( PVG_FT_Angle angle );
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Atan2
|
||||
*
|
||||
* @description:
|
||||
* Return the arc-tangent corresponding to a given vector (x,y) in
|
||||
* the 2d plane.
|
||||
*
|
||||
* @input:
|
||||
* x ::
|
||||
* The horizontal vector coordinate.
|
||||
*
|
||||
* y ::
|
||||
* The vertical vector coordinate.
|
||||
*
|
||||
* @return:
|
||||
* The arc-tangent value (i.e. angle).
|
||||
*
|
||||
*/
|
||||
PVG_FT_Angle
|
||||
PVG_FT_Atan2( PVG_FT_Fixed x,
|
||||
PVG_FT_Fixed y );
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Angle_Diff
|
||||
*
|
||||
* @description:
|
||||
* Return the difference between two angles. The result is always
|
||||
* constrained to the ]-PI..PI] interval.
|
||||
*
|
||||
* @input:
|
||||
* angle1 ::
|
||||
* First angle.
|
||||
*
|
||||
* angle2 ::
|
||||
* Second angle.
|
||||
*
|
||||
* @return:
|
||||
* Constrained value of `value2-value1'.
|
||||
*
|
||||
*/
|
||||
PVG_FT_Angle
|
||||
PVG_FT_Angle_Diff( PVG_FT_Angle angle1,
|
||||
PVG_FT_Angle angle2 );
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Vector_Unit
|
||||
*
|
||||
* @description:
|
||||
* Return the unit vector corresponding to a given angle. After the
|
||||
* call, the value of `vec.x' will be `sin(angle)', and the value of
|
||||
* `vec.y' will be `cos(angle)'.
|
||||
*
|
||||
* This function is useful to retrieve both the sinus and cosinus of a
|
||||
* given angle quickly.
|
||||
*
|
||||
* @output:
|
||||
* vec ::
|
||||
* The address of target vector.
|
||||
*
|
||||
* @input:
|
||||
* angle ::
|
||||
* The input angle.
|
||||
*
|
||||
*/
|
||||
void
|
||||
PVG_FT_Vector_Unit( PVG_FT_Vector* vec,
|
||||
PVG_FT_Angle angle );
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Vector_Rotate
|
||||
*
|
||||
* @description:
|
||||
* Rotate a vector by a given angle.
|
||||
*
|
||||
* @inout:
|
||||
* vec ::
|
||||
* The address of target vector.
|
||||
*
|
||||
* @input:
|
||||
* angle ::
|
||||
* The input angle.
|
||||
*
|
||||
*/
|
||||
void
|
||||
PVG_FT_Vector_Rotate( PVG_FT_Vector* vec,
|
||||
PVG_FT_Angle angle );
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Vector_Length
|
||||
*
|
||||
* @description:
|
||||
* Return the length of a given vector.
|
||||
*
|
||||
* @input:
|
||||
* vec ::
|
||||
* The address of target vector.
|
||||
*
|
||||
* @return:
|
||||
* The vector length, expressed in the same units that the original
|
||||
* vector coordinates.
|
||||
*
|
||||
*/
|
||||
PVG_FT_Fixed
|
||||
PVG_FT_Vector_Length( PVG_FT_Vector* vec );
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Vector_Polarize
|
||||
*
|
||||
* @description:
|
||||
* Compute both the length and angle of a given vector.
|
||||
*
|
||||
* @input:
|
||||
* vec ::
|
||||
* The address of source vector.
|
||||
*
|
||||
* @output:
|
||||
* length ::
|
||||
* The vector length.
|
||||
*
|
||||
* angle ::
|
||||
* The vector angle.
|
||||
*
|
||||
*/
|
||||
void
|
||||
PVG_FT_Vector_Polarize( PVG_FT_Vector* vec,
|
||||
PVG_FT_Fixed *length,
|
||||
PVG_FT_Angle *angle );
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Vector_From_Polar
|
||||
*
|
||||
* @description:
|
||||
* Compute vector coordinates from a length and angle.
|
||||
*
|
||||
* @output:
|
||||
* vec ::
|
||||
* The address of source vector.
|
||||
*
|
||||
* @input:
|
||||
* length ::
|
||||
* The vector length.
|
||||
*
|
||||
* angle ::
|
||||
* The vector angle.
|
||||
*
|
||||
*/
|
||||
void
|
||||
PVG_FT_Vector_From_Polar( PVG_FT_Vector* vec,
|
||||
PVG_FT_Fixed length,
|
||||
PVG_FT_Angle angle );
|
||||
|
||||
#endif /* PLUTOVG_FT_MATH_H */
|
||||
1889
vendor/lunasvg/plutovg/source/plutovg-ft-raster.c
vendored
1889
vendor/lunasvg/plutovg/source/plutovg-ft-raster.c
vendored
File diff suppressed because it is too large
Load Diff
420
vendor/lunasvg/plutovg/source/plutovg-ft-raster.h
vendored
420
vendor/lunasvg/plutovg/source/plutovg-ft-raster.h
vendored
@@ -1,420 +0,0 @@
|
||||
/***************************************************************************/
|
||||
/* */
|
||||
/* ftimage.h */
|
||||
/* */
|
||||
/* FreeType glyph image formats and default raster interface */
|
||||
/* (specification). */
|
||||
/* */
|
||||
/* Copyright 1996-2010, 2013 by */
|
||||
/* David Turner, Robert Wilhelm, and Werner Lemberg. */
|
||||
/* */
|
||||
/* This file is part of the FreeType project, and may only be used, */
|
||||
/* modified, and distributed under the terms of the FreeType project */
|
||||
/* license, FTL.TXT. By continuing to use, modify, or distribute */
|
||||
/* this file you indicate that you have read the license and */
|
||||
/* understand and accept it fully. */
|
||||
/* */
|
||||
/***************************************************************************/
|
||||
|
||||
#ifndef PLUTOVG_FT_RASTER_H
|
||||
#define PLUTOVG_FT_RASTER_H
|
||||
|
||||
#include "plutovg-ft-types.h"
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Struct> */
|
||||
/* FT_BBox */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A structure used to hold an outline's bounding box, i.e., the */
|
||||
/* coordinates of its extrema in the horizontal and vertical */
|
||||
/* directions. */
|
||||
/* */
|
||||
/* <Fields> */
|
||||
/* xMin :: The horizontal minimum (left-most). */
|
||||
/* */
|
||||
/* yMin :: The vertical minimum (bottom-most). */
|
||||
/* */
|
||||
/* xMax :: The horizontal maximum (right-most). */
|
||||
/* */
|
||||
/* yMax :: The vertical maximum (top-most). */
|
||||
/* */
|
||||
/* <Note> */
|
||||
/* The bounding box is specified with the coordinates of the lower */
|
||||
/* left and the upper right corner. In PostScript, those values are */
|
||||
/* often called (llx,lly) and (urx,ury), respectively. */
|
||||
/* */
|
||||
/* If `yMin' is negative, this value gives the glyph's descender. */
|
||||
/* Otherwise, the glyph doesn't descend below the baseline. */
|
||||
/* Similarly, if `ymax' is positive, this value gives the glyph's */
|
||||
/* ascender. */
|
||||
/* */
|
||||
/* `xMin' gives the horizontal distance from the glyph's origin to */
|
||||
/* the left edge of the glyph's bounding box. If `xMin' is negative, */
|
||||
/* the glyph extends to the left of the origin. */
|
||||
/* */
|
||||
typedef struct PVG_FT_BBox_
|
||||
{
|
||||
PVG_FT_Pos xMin, yMin;
|
||||
PVG_FT_Pos xMax, yMax;
|
||||
|
||||
} PVG_FT_BBox;
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Struct> */
|
||||
/* PVG_FT_Outline */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* This structure is used to describe an outline to the scan-line */
|
||||
/* converter. */
|
||||
/* */
|
||||
/* <Fields> */
|
||||
/* n_contours :: The number of contours in the outline. */
|
||||
/* */
|
||||
/* n_points :: The number of points in the outline. */
|
||||
/* */
|
||||
/* points :: A pointer to an array of `n_points' @PVG_FT_Vector */
|
||||
/* elements, giving the outline's point coordinates. */
|
||||
/* */
|
||||
/* tags :: A pointer to an array of `n_points' chars, giving */
|
||||
/* each outline point's type. */
|
||||
/* */
|
||||
/* If bit~0 is unset, the point is `off' the curve, */
|
||||
/* i.e., a Bézier control point, while it is `on' if */
|
||||
/* set. */
|
||||
/* */
|
||||
/* Bit~1 is meaningful for `off' points only. If set, */
|
||||
/* it indicates a third-order Bézier arc control point; */
|
||||
/* and a second-order control point if unset. */
|
||||
/* */
|
||||
/* If bit~2 is set, bits 5-7 contain the drop-out mode */
|
||||
/* (as defined in the OpenType specification; the value */
|
||||
/* is the same as the argument to the SCANMODE */
|
||||
/* instruction). */
|
||||
/* */
|
||||
/* Bits 3 and~4 are reserved for internal purposes. */
|
||||
/* */
|
||||
/* contours :: An array of `n_contours' shorts, giving the end */
|
||||
/* point of each contour within the outline. For */
|
||||
/* example, the first contour is defined by the points */
|
||||
/* `0' to `contours[0]', the second one is defined by */
|
||||
/* the points `contours[0]+1' to `contours[1]', etc. */
|
||||
/* */
|
||||
/* flags :: A set of bit flags used to characterize the outline */
|
||||
/* and give hints to the scan-converter and hinter on */
|
||||
/* how to convert/grid-fit it. See @PVG_FT_OUTLINE_FLAGS.*/
|
||||
/* */
|
||||
typedef struct PVG_FT_Outline_
|
||||
{
|
||||
int n_contours; /* number of contours in glyph */
|
||||
int n_points; /* number of points in the glyph */
|
||||
|
||||
PVG_FT_Vector* points; /* the outline's points */
|
||||
char* tags; /* the points flags */
|
||||
int* contours; /* the contour end points */
|
||||
char* contours_flag; /* the contour open flags */
|
||||
|
||||
int flags; /* outline masks */
|
||||
|
||||
} PVG_FT_Outline;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Enum> */
|
||||
/* PVG_FT_OUTLINE_FLAGS */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A list of bit-field constants use for the flags in an outline's */
|
||||
/* `flags' field. */
|
||||
/* */
|
||||
/* <Values> */
|
||||
/* PVG_FT_OUTLINE_NONE :: */
|
||||
/* Value~0 is reserved. */
|
||||
/* */
|
||||
/* PVG_FT_OUTLINE_OWNER :: */
|
||||
/* If set, this flag indicates that the outline's field arrays */
|
||||
/* (i.e., `points', `flags', and `contours') are `owned' by the */
|
||||
/* outline object, and should thus be freed when it is destroyed. */
|
||||
/* */
|
||||
/* PVG_FT_OUTLINE_EVEN_ODD_FILL :: */
|
||||
/* By default, outlines are filled using the non-zero winding rule. */
|
||||
/* If set to 1, the outline will be filled using the even-odd fill */
|
||||
/* rule (only works with the smooth rasterizer). */
|
||||
/* */
|
||||
/* PVG_FT_OUTLINE_REVERSE_FILL :: */
|
||||
/* By default, outside contours of an outline are oriented in */
|
||||
/* clock-wise direction, as defined in the TrueType specification. */
|
||||
/* This flag is set if the outline uses the opposite direction */
|
||||
/* (typically for Type~1 fonts). This flag is ignored by the scan */
|
||||
/* converter. */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* There exists a second mechanism to pass the drop-out mode to the */
|
||||
/* B/W rasterizer; see the `tags' field in @PVG_FT_Outline. */
|
||||
/* */
|
||||
/* Please refer to the description of the `SCANTYPE' instruction in */
|
||||
/* the OpenType specification (in file `ttinst1.doc') how simple */
|
||||
/* drop-outs, smart drop-outs, and stubs are defined. */
|
||||
/* */
|
||||
#define PVG_FT_OUTLINE_NONE 0x0
|
||||
#define PVG_FT_OUTLINE_OWNER 0x1
|
||||
#define PVG_FT_OUTLINE_EVEN_ODD_FILL 0x2
|
||||
#define PVG_FT_OUTLINE_REVERSE_FILL 0x4
|
||||
|
||||
/* */
|
||||
|
||||
#define PVG_FT_CURVE_TAG( flag ) ( flag & 3 )
|
||||
|
||||
#define PVG_FT_CURVE_TAG_ON 1
|
||||
#define PVG_FT_CURVE_TAG_CONIC 0
|
||||
#define PVG_FT_CURVE_TAG_CUBIC 2
|
||||
|
||||
|
||||
#define PVG_FT_Curve_Tag_On PVG_FT_CURVE_TAG_ON
|
||||
#define PVG_FT_Curve_Tag_Conic PVG_FT_CURVE_TAG_CONIC
|
||||
#define PVG_FT_Curve_Tag_Cubic PVG_FT_CURVE_TAG_CUBIC
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Function> */
|
||||
/* PVG_FT_Outline_Check */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* Check the contents of an outline descriptor. */
|
||||
/* */
|
||||
/* <Input> */
|
||||
/* outline :: A handle to a source outline. */
|
||||
/* */
|
||||
/* <Return> */
|
||||
/* FreeType error code. 0~means success. */
|
||||
/* */
|
||||
PVG_FT_Error
|
||||
PVG_FT_Outline_Check( PVG_FT_Outline* outline );
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Function> */
|
||||
/* PVG_FT_Outline_Get_CBox */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* Return an outline's `control box'. The control box encloses all */
|
||||
/* the outline's points, including Bézier control points. Though it */
|
||||
/* coincides with the exact bounding box for most glyphs, it can be */
|
||||
/* slightly larger in some situations (like when rotating an outline */
|
||||
/* that contains Bézier outside arcs). */
|
||||
/* */
|
||||
/* Computing the control box is very fast, while getting the bounding */
|
||||
/* box can take much more time as it needs to walk over all segments */
|
||||
/* and arcs in the outline. To get the latter, you can use the */
|
||||
/* `ftbbox' component, which is dedicated to this single task. */
|
||||
/* */
|
||||
/* <Input> */
|
||||
/* outline :: A pointer to the source outline descriptor. */
|
||||
/* */
|
||||
/* <Output> */
|
||||
/* acbox :: The outline's control box. */
|
||||
/* */
|
||||
/* <Note> */
|
||||
/* See @PVG_FT_Glyph_Get_CBox for a discussion of tricky fonts. */
|
||||
/* */
|
||||
void
|
||||
PVG_FT_Outline_Get_CBox( const PVG_FT_Outline* outline,
|
||||
PVG_FT_BBox *acbox );
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Struct> */
|
||||
/* PVG_FT_Span */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A structure used to model a single span of gray (or black) pixels */
|
||||
/* when rendering a monochrome or anti-aliased bitmap. */
|
||||
/* */
|
||||
/* <Fields> */
|
||||
/* x :: The span's horizontal start position. */
|
||||
/* */
|
||||
/* len :: The span's length in pixels. */
|
||||
/* */
|
||||
/* coverage :: The span color/coverage, ranging from 0 (background) */
|
||||
/* to 255 (foreground). Only used for anti-aliased */
|
||||
/* rendering. */
|
||||
/* */
|
||||
/* <Note> */
|
||||
/* This structure is used by the span drawing callback type named */
|
||||
/* @PVG_FT_SpanFunc that takes the y~coordinate of the span as a */
|
||||
/* parameter. */
|
||||
/* */
|
||||
/* The coverage value is always between 0 and 255. If you want less */
|
||||
/* gray values, the callback function has to reduce them. */
|
||||
/* */
|
||||
typedef struct PVG_FT_Span_
|
||||
{
|
||||
int x;
|
||||
int len;
|
||||
int y;
|
||||
unsigned char coverage;
|
||||
|
||||
} PVG_FT_Span;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <FuncType> */
|
||||
/* PVG_FT_SpanFunc */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A function used as a call-back by the anti-aliased renderer in */
|
||||
/* order to let client applications draw themselves the gray pixel */
|
||||
/* spans on each scan line. */
|
||||
/* */
|
||||
/* <Input> */
|
||||
/* y :: The scanline's y~coordinate. */
|
||||
/* */
|
||||
/* count :: The number of spans to draw on this scanline. */
|
||||
/* */
|
||||
/* spans :: A table of `count' spans to draw on the scanline. */
|
||||
/* */
|
||||
/* user :: User-supplied data that is passed to the callback. */
|
||||
/* */
|
||||
/* <Note> */
|
||||
/* This callback allows client applications to directly render the */
|
||||
/* gray spans of the anti-aliased bitmap to any kind of surfaces. */
|
||||
/* */
|
||||
/* This can be used to write anti-aliased outlines directly to a */
|
||||
/* given background bitmap, and even perform translucency. */
|
||||
/* */
|
||||
/* Note that the `count' field cannot be greater than a fixed value */
|
||||
/* defined by the `PVG_FT_MAX_GRAY_SPANS' configuration macro in */
|
||||
/* `ftoption.h'. By default, this value is set to~32, which means */
|
||||
/* that if there are more than 32~spans on a given scanline, the */
|
||||
/* callback is called several times with the same `y' parameter in */
|
||||
/* order to draw all callbacks. */
|
||||
/* */
|
||||
/* Otherwise, the callback is only called once per scan-line, and */
|
||||
/* only for those scanlines that do have `gray' pixels on them. */
|
||||
/* */
|
||||
typedef void
|
||||
(*PVG_FT_SpanFunc)( int count,
|
||||
const PVG_FT_Span* spans,
|
||||
void* user );
|
||||
|
||||
#define PVG_FT_Raster_Span_Func PVG_FT_SpanFunc
|
||||
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Enum> */
|
||||
/* PVG_FT_RASTER_FLAG_XXX */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A list of bit flag constants as used in the `flags' field of a */
|
||||
/* @PVG_FT_Raster_Params structure. */
|
||||
/* */
|
||||
/* <Values> */
|
||||
/* PVG_FT_RASTER_FLAG_DEFAULT :: This value is 0. */
|
||||
/* */
|
||||
/* PVG_FT_RASTER_FLAG_AA :: This flag is set to indicate that an */
|
||||
/* anti-aliased glyph image should be */
|
||||
/* generated. Otherwise, it will be */
|
||||
/* monochrome (1-bit). */
|
||||
/* */
|
||||
/* PVG_FT_RASTER_FLAG_DIRECT :: This flag is set to indicate direct */
|
||||
/* rendering. In this mode, client */
|
||||
/* applications must provide their own span */
|
||||
/* callback. This lets them directly */
|
||||
/* draw or compose over an existing bitmap. */
|
||||
/* If this bit is not set, the target */
|
||||
/* pixmap's buffer _must_ be zeroed before */
|
||||
/* rendering. */
|
||||
/* */
|
||||
/* Note that for now, direct rendering is */
|
||||
/* only possible with anti-aliased glyphs. */
|
||||
/* */
|
||||
/* PVG_FT_RASTER_FLAG_CLIP :: This flag is only used in direct */
|
||||
/* rendering mode. If set, the output will */
|
||||
/* be clipped to a box specified in the */
|
||||
/* `clip_box' field of the */
|
||||
/* @PVG_FT_Raster_Params structure. */
|
||||
/* */
|
||||
/* Note that by default, the glyph bitmap */
|
||||
/* is clipped to the target pixmap, except */
|
||||
/* in direct rendering mode where all spans */
|
||||
/* are generated if no clipping box is set. */
|
||||
/* */
|
||||
#define PVG_FT_RASTER_FLAG_DEFAULT 0x0
|
||||
#define PVG_FT_RASTER_FLAG_AA 0x1
|
||||
#define PVG_FT_RASTER_FLAG_DIRECT 0x2
|
||||
#define PVG_FT_RASTER_FLAG_CLIP 0x4
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Struct> */
|
||||
/* PVG_FT_Raster_Params */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A structure to hold the arguments used by a raster's render */
|
||||
/* function. */
|
||||
/* */
|
||||
/* <Fields> */
|
||||
/* target :: The target bitmap. */
|
||||
/* */
|
||||
/* source :: A pointer to the source glyph image (e.g., an */
|
||||
/* @PVG_FT_Outline). */
|
||||
/* */
|
||||
/* flags :: The rendering flags. */
|
||||
/* */
|
||||
/* gray_spans :: The gray span drawing callback. */
|
||||
/* */
|
||||
/* black_spans :: The black span drawing callback. UNIMPLEMENTED! */
|
||||
/* */
|
||||
/* bit_test :: The bit test callback. UNIMPLEMENTED! */
|
||||
/* */
|
||||
/* bit_set :: The bit set callback. UNIMPLEMENTED! */
|
||||
/* */
|
||||
/* user :: User-supplied data that is passed to each drawing */
|
||||
/* callback. */
|
||||
/* */
|
||||
/* clip_box :: An optional clipping box. It is only used in */
|
||||
/* direct rendering mode. Note that coordinates here */
|
||||
/* should be expressed in _integer_ pixels (and not in */
|
||||
/* 26.6 fixed-point units). */
|
||||
/* */
|
||||
/* <Note> */
|
||||
/* An anti-aliased glyph bitmap is drawn if the @PVG_FT_RASTER_FLAG_AA */
|
||||
/* bit flag is set in the `flags' field, otherwise a monochrome */
|
||||
/* bitmap is generated. */
|
||||
/* */
|
||||
/* If the @PVG_FT_RASTER_FLAG_DIRECT bit flag is set in `flags', the */
|
||||
/* raster will call the `gray_spans' callback to draw gray pixel */
|
||||
/* spans, in the case of an aa glyph bitmap, it will call */
|
||||
/* `black_spans', and `bit_test' and `bit_set' in the case of a */
|
||||
/* monochrome bitmap. This allows direct composition over a */
|
||||
/* pre-existing bitmap through user-provided callbacks to perform the */
|
||||
/* span drawing/composition. */
|
||||
/* */
|
||||
/* Note that the `bit_test' and `bit_set' callbacks are required when */
|
||||
/* rendering a monochrome bitmap, as they are crucial to implement */
|
||||
/* correct drop-out control as defined in the TrueType specification. */
|
||||
/* */
|
||||
typedef struct PVG_FT_Raster_Params_
|
||||
{
|
||||
const void* source;
|
||||
int flags;
|
||||
PVG_FT_SpanFunc gray_spans;
|
||||
void* user;
|
||||
PVG_FT_BBox clip_box;
|
||||
|
||||
} PVG_FT_Raster_Params;
|
||||
|
||||
|
||||
void
|
||||
PVG_FT_Raster_Render(const PVG_FT_Raster_Params *params);
|
||||
|
||||
#endif // PLUTOVG_FT_RASTER_H
|
||||
1873
vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c
vendored
1873
vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c
vendored
File diff suppressed because it is too large
Load Diff
320
vendor/lunasvg/plutovg/source/plutovg-ft-stroker.h
vendored
320
vendor/lunasvg/plutovg/source/plutovg-ft-stroker.h
vendored
@@ -1,320 +0,0 @@
|
||||
/***************************************************************************/
|
||||
/* */
|
||||
/* ftstroke.h */
|
||||
/* */
|
||||
/* FreeType path stroker (specification). */
|
||||
/* */
|
||||
/* Copyright 2002-2006, 2008, 2009, 2011-2012 by */
|
||||
/* David Turner, Robert Wilhelm, and Werner Lemberg. */
|
||||
/* */
|
||||
/* This file is part of the FreeType project, and may only be used, */
|
||||
/* modified, and distributed under the terms of the FreeType project */
|
||||
/* license, FTL.TXT. By continuing to use, modify, or distribute */
|
||||
/* this file you indicate that you have read the license and */
|
||||
/* understand and accept it fully. */
|
||||
/* */
|
||||
/***************************************************************************/
|
||||
|
||||
#ifndef PLUTOVG_FT_STROKER_H
|
||||
#define PLUTOVG_FT_STROKER_H
|
||||
|
||||
#include "plutovg-ft-raster.h"
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* @type:
|
||||
* PVG_FT_Stroker
|
||||
*
|
||||
* @description:
|
||||
* Opaque handler to a path stroker object.
|
||||
*/
|
||||
typedef struct PVG_FT_StrokerRec_* PVG_FT_Stroker;
|
||||
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* @enum:
|
||||
* PVG_FT_Stroker_LineJoin
|
||||
*
|
||||
* @description:
|
||||
* These values determine how two joining lines are rendered
|
||||
* in a stroker.
|
||||
*
|
||||
* @values:
|
||||
* PVG_FT_STROKER_LINEJOIN_ROUND ::
|
||||
* Used to render rounded line joins. Circular arcs are used
|
||||
* to join two lines smoothly.
|
||||
*
|
||||
* PVG_FT_STROKER_LINEJOIN_BEVEL ::
|
||||
* Used to render beveled line joins. The outer corner of
|
||||
* the joined lines is filled by enclosing the triangular
|
||||
* region of the corner with a straight line between the
|
||||
* outer corners of each stroke.
|
||||
*
|
||||
* PVG_FT_STROKER_LINEJOIN_MITER_FIXED ::
|
||||
* Used to render mitered line joins, with fixed bevels if the
|
||||
* miter limit is exceeded. The outer edges of the strokes
|
||||
* for the two segments are extended until they meet at an
|
||||
* angle. If the segments meet at too sharp an angle (such
|
||||
* that the miter would extend from the intersection of the
|
||||
* segments a distance greater than the product of the miter
|
||||
* limit value and the border radius), then a bevel join (see
|
||||
* above) is used instead. This prevents long spikes being
|
||||
* created. PVG_FT_STROKER_LINEJOIN_MITER_FIXED generates a miter
|
||||
* line join as used in PostScript and PDF.
|
||||
*
|
||||
* PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE ::
|
||||
* PVG_FT_STROKER_LINEJOIN_MITER ::
|
||||
* Used to render mitered line joins, with variable bevels if
|
||||
* the miter limit is exceeded. The intersection of the
|
||||
* strokes is clipped at a line perpendicular to the bisector
|
||||
* of the angle between the strokes, at the distance from the
|
||||
* intersection of the segments equal to the product of the
|
||||
* miter limit value and the border radius. This prevents
|
||||
* long spikes being created.
|
||||
* PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE generates a mitered line
|
||||
* join as used in XPS. PVG_FT_STROKER_LINEJOIN_MITER is an alias
|
||||
* for PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE, retained for
|
||||
* backwards compatibility.
|
||||
*/
|
||||
typedef enum PVG_FT_Stroker_LineJoin_
|
||||
{
|
||||
PVG_FT_STROKER_LINEJOIN_ROUND = 0,
|
||||
PVG_FT_STROKER_LINEJOIN_BEVEL = 1,
|
||||
PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE = 2,
|
||||
PVG_FT_STROKER_LINEJOIN_MITER = PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE,
|
||||
PVG_FT_STROKER_LINEJOIN_MITER_FIXED = 3
|
||||
|
||||
} PVG_FT_Stroker_LineJoin;
|
||||
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* @enum:
|
||||
* PVG_FT_Stroker_LineCap
|
||||
*
|
||||
* @description:
|
||||
* These values determine how the end of opened sub-paths are
|
||||
* rendered in a stroke.
|
||||
*
|
||||
* @values:
|
||||
* PVG_FT_STROKER_LINECAP_BUTT ::
|
||||
* The end of lines is rendered as a full stop on the last
|
||||
* point itself.
|
||||
*
|
||||
* PVG_FT_STROKER_LINECAP_ROUND ::
|
||||
* The end of lines is rendered as a half-circle around the
|
||||
* last point.
|
||||
*
|
||||
* PVG_FT_STROKER_LINECAP_SQUARE ::
|
||||
* The end of lines is rendered as a square around the
|
||||
* last point.
|
||||
*/
|
||||
typedef enum PVG_FT_Stroker_LineCap_
|
||||
{
|
||||
PVG_FT_STROKER_LINECAP_BUTT = 0,
|
||||
PVG_FT_STROKER_LINECAP_ROUND,
|
||||
PVG_FT_STROKER_LINECAP_SQUARE
|
||||
|
||||
} PVG_FT_Stroker_LineCap;
|
||||
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* @enum:
|
||||
* PVG_FT_StrokerBorder
|
||||
*
|
||||
* @description:
|
||||
* These values are used to select a given stroke border
|
||||
* in @PVG_FT_Stroker_GetBorderCounts and @PVG_FT_Stroker_ExportBorder.
|
||||
*
|
||||
* @values:
|
||||
* PVG_FT_STROKER_BORDER_LEFT ::
|
||||
* Select the left border, relative to the drawing direction.
|
||||
*
|
||||
* PVG_FT_STROKER_BORDER_RIGHT ::
|
||||
* Select the right border, relative to the drawing direction.
|
||||
*
|
||||
* @note:
|
||||
* Applications are generally interested in the `inside' and `outside'
|
||||
* borders. However, there is no direct mapping between these and the
|
||||
* `left' and `right' ones, since this really depends on the glyph's
|
||||
* drawing orientation, which varies between font formats.
|
||||
*
|
||||
* You can however use @PVG_FT_Outline_GetInsideBorder and
|
||||
* @PVG_FT_Outline_GetOutsideBorder to get these.
|
||||
*/
|
||||
typedef enum PVG_FT_StrokerBorder_
|
||||
{
|
||||
PVG_FT_STROKER_BORDER_LEFT = 0,
|
||||
PVG_FT_STROKER_BORDER_RIGHT
|
||||
|
||||
} PVG_FT_StrokerBorder;
|
||||
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Stroker_New
|
||||
*
|
||||
* @description:
|
||||
* Create a new stroker object.
|
||||
*
|
||||
* @input:
|
||||
* library ::
|
||||
* FreeType library handle.
|
||||
*
|
||||
* @output:
|
||||
* astroker ::
|
||||
* A new stroker object handle. NULL in case of error.
|
||||
*
|
||||
* @return:
|
||||
* FreeType error code. 0~means success.
|
||||
*/
|
||||
PVG_FT_Error
|
||||
PVG_FT_Stroker_New( PVG_FT_Stroker *astroker );
|
||||
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Stroker_Set
|
||||
*
|
||||
* @description:
|
||||
* Reset a stroker object's attributes.
|
||||
*
|
||||
* @input:
|
||||
* stroker ::
|
||||
* The target stroker handle.
|
||||
*
|
||||
* radius ::
|
||||
* The border radius.
|
||||
*
|
||||
* line_cap ::
|
||||
* The line cap style.
|
||||
*
|
||||
* line_join ::
|
||||
* The line join style.
|
||||
*
|
||||
* miter_limit ::
|
||||
* The miter limit for the PVG_FT_STROKER_LINEJOIN_MITER_FIXED and
|
||||
* PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE line join styles,
|
||||
* expressed as 16.16 fixed-point value.
|
||||
*
|
||||
* @note:
|
||||
* The radius is expressed in the same units as the outline
|
||||
* coordinates.
|
||||
*/
|
||||
void
|
||||
PVG_FT_Stroker_Set( PVG_FT_Stroker stroker,
|
||||
PVG_FT_Fixed radius,
|
||||
PVG_FT_Stroker_LineCap line_cap,
|
||||
PVG_FT_Stroker_LineJoin line_join,
|
||||
PVG_FT_Fixed miter_limit );
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Stroker_ParseOutline
|
||||
*
|
||||
* @description:
|
||||
* A convenience function used to parse a whole outline with
|
||||
* the stroker. The resulting outline(s) can be retrieved
|
||||
* later by functions like @PVG_FT_Stroker_GetCounts and @PVG_FT_Stroker_Export.
|
||||
*
|
||||
* @input:
|
||||
* stroker ::
|
||||
* The target stroker handle.
|
||||
*
|
||||
* outline ::
|
||||
* The source outline.
|
||||
*
|
||||
*
|
||||
* @return:
|
||||
* FreeType error code. 0~means success.
|
||||
*
|
||||
* @note:
|
||||
* If `opened' is~0 (the default), the outline is treated as a closed
|
||||
* path, and the stroker generates two distinct `border' outlines.
|
||||
*
|
||||
*
|
||||
* This function calls @PVG_FT_Stroker_Rewind automatically.
|
||||
*/
|
||||
PVG_FT_Error
|
||||
PVG_FT_Stroker_ParseOutline( PVG_FT_Stroker stroker,
|
||||
const PVG_FT_Outline* outline);
|
||||
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Stroker_GetCounts
|
||||
*
|
||||
* @description:
|
||||
* Call this function once you have finished parsing your paths
|
||||
* with the stroker. It returns the number of points and
|
||||
* contours necessary to export all points/borders from the stroked
|
||||
* outline/path.
|
||||
*
|
||||
* @input:
|
||||
* stroker ::
|
||||
* The target stroker handle.
|
||||
*
|
||||
* @output:
|
||||
* anum_points ::
|
||||
* The number of points.
|
||||
*
|
||||
* anum_contours ::
|
||||
* The number of contours.
|
||||
*
|
||||
* @return:
|
||||
* FreeType error code. 0~means success.
|
||||
*/
|
||||
PVG_FT_Error
|
||||
PVG_FT_Stroker_GetCounts( PVG_FT_Stroker stroker,
|
||||
PVG_FT_UInt *anum_points,
|
||||
PVG_FT_UInt *anum_contours );
|
||||
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Stroker_Export
|
||||
*
|
||||
* @description:
|
||||
* Call this function after @PVG_FT_Stroker_GetBorderCounts to
|
||||
* export all borders to your own @PVG_FT_Outline structure.
|
||||
*
|
||||
* Note that this function appends the border points and
|
||||
* contours to your outline, but does not try to resize its
|
||||
* arrays.
|
||||
*
|
||||
* @input:
|
||||
* stroker ::
|
||||
* The target stroker handle.
|
||||
*
|
||||
* outline ::
|
||||
* The target outline handle.
|
||||
*/
|
||||
void
|
||||
PVG_FT_Stroker_Export( PVG_FT_Stroker stroker,
|
||||
PVG_FT_Outline* outline );
|
||||
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* @function:
|
||||
* PVG_FT_Stroker_Done
|
||||
*
|
||||
* @description:
|
||||
* Destroy a stroker object.
|
||||
*
|
||||
* @input:
|
||||
* stroker ::
|
||||
* A stroker handle. Can be NULL.
|
||||
*/
|
||||
void
|
||||
PVG_FT_Stroker_Done( PVG_FT_Stroker stroker );
|
||||
|
||||
|
||||
#endif // PLUTOVG_FT_STROKER_H
|
||||
176
vendor/lunasvg/plutovg/source/plutovg-ft-types.h
vendored
176
vendor/lunasvg/plutovg/source/plutovg-ft-types.h
vendored
@@ -1,176 +0,0 @@
|
||||
/****************************************************************************
|
||||
*
|
||||
* fttypes.h
|
||||
*
|
||||
* FreeType simple types definitions (specification only).
|
||||
*
|
||||
* Copyright (C) 1996-2020 by
|
||||
* David Turner, Robert Wilhelm, and Werner Lemberg.
|
||||
*
|
||||
* This file is part of the FreeType project, and may only be used,
|
||||
* modified, and distributed under the terms of the FreeType project
|
||||
* license, FTL.TXT. By continuing to use, modify, or distribute
|
||||
* this file you indicate that you have read the license and
|
||||
* understand and accept it fully.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PLUTOVG_FT_TYPES_H
|
||||
#define PLUTOVG_FT_TYPES_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Type> */
|
||||
/* PVG_FT_Fixed */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* This type is used to store 16.16 fixed-point values, like scaling */
|
||||
/* values or matrix coefficients. */
|
||||
/* */
|
||||
typedef signed long PVG_FT_Fixed;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Type> */
|
||||
/* PVG_FT_Int */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A typedef for the int type. */
|
||||
/* */
|
||||
typedef signed int PVG_FT_Int;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Type> */
|
||||
/* PVG_FT_UInt */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A typedef for the unsigned int type. */
|
||||
/* */
|
||||
typedef unsigned int PVG_FT_UInt;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Type> */
|
||||
/* PVG_FT_Long */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A typedef for signed long. */
|
||||
/* */
|
||||
typedef signed long PVG_FT_Long;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Type> */
|
||||
/* PVG_FT_ULong */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A typedef for unsigned long. */
|
||||
/* */
|
||||
typedef unsigned long PVG_FT_ULong;
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Type> */
|
||||
/* PVG_FT_Short */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A typedef for signed short. */
|
||||
/* */
|
||||
typedef signed short PVG_FT_Short;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Type> */
|
||||
/* PVG_FT_Byte */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A simple typedef for the _unsigned_ char type. */
|
||||
/* */
|
||||
typedef unsigned char PVG_FT_Byte;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Type> */
|
||||
/* PVG_FT_Bool */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A typedef of unsigned char, used for simple booleans. As usual, */
|
||||
/* values 1 and~0 represent true and false, respectively. */
|
||||
/* */
|
||||
typedef unsigned char PVG_FT_Bool;
|
||||
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Type> */
|
||||
/* PVG_FT_Error */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* The FreeType error code type. A value of~0 is always interpreted */
|
||||
/* as a successful operation. */
|
||||
/* */
|
||||
typedef int PVG_FT_Error;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Type> */
|
||||
/* PVG_FT_Pos */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* The type PVG_FT_Pos is used to store vectorial coordinates. Depending */
|
||||
/* on the context, these can represent distances in integer font */
|
||||
/* units, or 16.16, or 26.6 fixed-point pixel coordinates. */
|
||||
/* */
|
||||
typedef signed long PVG_FT_Pos;
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Struct> */
|
||||
/* PVG_FT_Vector */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A simple structure used to store a 2D vector; coordinates are of */
|
||||
/* the PVG_FT_Pos type. */
|
||||
/* */
|
||||
/* <Fields> */
|
||||
/* x :: The horizontal coordinate. */
|
||||
/* y :: The vertical coordinate. */
|
||||
/* */
|
||||
typedef struct PVG_FT_Vector_
|
||||
{
|
||||
PVG_FT_Pos x;
|
||||
PVG_FT_Pos y;
|
||||
|
||||
} PVG_FT_Vector;
|
||||
|
||||
|
||||
typedef long long int PVG_FT_Int64;
|
||||
typedef unsigned long long int PVG_FT_UInt64;
|
||||
|
||||
typedef signed int PVG_FT_Int32;
|
||||
typedef unsigned int PVG_FT_UInt32;
|
||||
|
||||
#define PVG_FT_BOOL( x ) ( (PVG_FT_Bool)( x ) )
|
||||
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#endif
|
||||
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
#define PVG_FT_BEGIN_STMNT do {
|
||||
#define PVG_FT_END_STMNT } while (0)
|
||||
|
||||
#endif // PLUTOVG_FT_TYPES_H
|
||||
229
vendor/lunasvg/plutovg/source/plutovg-matrix.c
vendored
229
vendor/lunasvg/plutovg/source/plutovg-matrix.c
vendored
@@ -1,229 +0,0 @@
|
||||
#include "plutovg.h"
|
||||
#include "plutovg-utils.h"
|
||||
|
||||
void plutovg_matrix_init(plutovg_matrix_t* matrix, float a, float b, float c, float d, float e, float f)
|
||||
{
|
||||
matrix->a = a; matrix->b = b;
|
||||
matrix->c = c; matrix->d = d;
|
||||
matrix->e = e; matrix->f = f;
|
||||
}
|
||||
|
||||
void plutovg_matrix_init_identity(plutovg_matrix_t* matrix)
|
||||
{
|
||||
plutovg_matrix_init(matrix, 1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
void plutovg_matrix_init_translate(plutovg_matrix_t* matrix, float tx, float ty)
|
||||
{
|
||||
plutovg_matrix_init(matrix, 1, 0, 0, 1, tx, ty);
|
||||
}
|
||||
|
||||
void plutovg_matrix_init_scale(plutovg_matrix_t* matrix, float sx, float sy)
|
||||
{
|
||||
plutovg_matrix_init(matrix, sx, 0, 0, sy, 0, 0);
|
||||
}
|
||||
|
||||
void plutovg_matrix_init_rotate(plutovg_matrix_t* matrix, float angle)
|
||||
{
|
||||
float c = cosf(angle);
|
||||
float s = sinf(angle);
|
||||
plutovg_matrix_init(matrix, c, s, -s, c, 0, 0);
|
||||
}
|
||||
|
||||
void plutovg_matrix_init_shear(plutovg_matrix_t* matrix, float shx, float shy)
|
||||
{
|
||||
plutovg_matrix_init(matrix, 1, tanf(shy), tanf(shx), 1, 0, 0);
|
||||
}
|
||||
|
||||
void plutovg_matrix_translate(plutovg_matrix_t* matrix, float tx, float ty)
|
||||
{
|
||||
plutovg_matrix_t m;
|
||||
plutovg_matrix_init_translate(&m, tx, ty);
|
||||
plutovg_matrix_multiply(matrix, &m, matrix);
|
||||
}
|
||||
|
||||
void plutovg_matrix_scale(plutovg_matrix_t* matrix, float sx, float sy)
|
||||
{
|
||||
plutovg_matrix_t m;
|
||||
plutovg_matrix_init_scale(&m, sx, sy);
|
||||
plutovg_matrix_multiply(matrix, &m, matrix);
|
||||
}
|
||||
|
||||
void plutovg_matrix_rotate(plutovg_matrix_t* matrix, float angle)
|
||||
{
|
||||
plutovg_matrix_t m;
|
||||
plutovg_matrix_init_rotate(&m, angle);
|
||||
plutovg_matrix_multiply(matrix, &m, matrix);
|
||||
}
|
||||
|
||||
void plutovg_matrix_shear(plutovg_matrix_t* matrix, float shx, float shy)
|
||||
{
|
||||
plutovg_matrix_t m;
|
||||
plutovg_matrix_init_shear(&m, shx, shy);
|
||||
plutovg_matrix_multiply(matrix, &m, matrix);
|
||||
}
|
||||
|
||||
void plutovg_matrix_multiply(plutovg_matrix_t* matrix, const plutovg_matrix_t* left, const plutovg_matrix_t* right)
|
||||
{
|
||||
float a = left->a * right->a + left->b * right->c;
|
||||
float b = left->a * right->b + left->b * right->d;
|
||||
float c = left->c * right->a + left->d * right->c;
|
||||
float d = left->c * right->b + left->d * right->d;
|
||||
float e = left->e * right->a + left->f * right->c + right->e;
|
||||
float f = left->e * right->b + left->f * right->d + right->f;
|
||||
plutovg_matrix_init(matrix, a, b, c, d, e, f);
|
||||
}
|
||||
|
||||
bool plutovg_matrix_invert(const plutovg_matrix_t* matrix, plutovg_matrix_t* inverse)
|
||||
{
|
||||
float det = (matrix->a * matrix->d - matrix->b * matrix->c);
|
||||
if(det == 0.f)
|
||||
return false;
|
||||
if(inverse) {
|
||||
float inv_det = 1.f / det;
|
||||
float a = matrix->a * inv_det;
|
||||
float b = matrix->b * inv_det;
|
||||
float c = matrix->c * inv_det;
|
||||
float d = matrix->d * inv_det;
|
||||
float e = (matrix->c * matrix->f - matrix->d * matrix->e) * inv_det;
|
||||
float f = (matrix->b * matrix->e - matrix->a * matrix->f) * inv_det;
|
||||
plutovg_matrix_init(inverse, d, -b, -c, a, e, f);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void plutovg_matrix_map(const plutovg_matrix_t* matrix, float x, float y, float* xx, float* yy)
|
||||
{
|
||||
*xx = x * matrix->a + y * matrix->c + matrix->e;
|
||||
*yy = x * matrix->b + y * matrix->d + matrix->f;
|
||||
}
|
||||
|
||||
void plutovg_matrix_map_point(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst)
|
||||
{
|
||||
plutovg_matrix_map(matrix, src->x, src->y, &dst->x, &dst->y);
|
||||
}
|
||||
|
||||
void plutovg_matrix_map_points(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst, int count)
|
||||
{
|
||||
for(int i = 0; i < count; ++i) {
|
||||
plutovg_matrix_map_point(matrix, &src[i], &dst[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_matrix_map_rect(const plutovg_matrix_t* matrix, const plutovg_rect_t* src, plutovg_rect_t* dst)
|
||||
{
|
||||
plutovg_point_t p[4];
|
||||
p[0].x = src->x;
|
||||
p[0].y = src->y;
|
||||
p[1].x = src->x + src->w;
|
||||
p[1].y = src->y;
|
||||
p[2].x = src->x + src->w;
|
||||
p[2].y = src->y + src->h;
|
||||
p[3].x = src->x;
|
||||
p[3].y = src->y + src->h;
|
||||
plutovg_matrix_map_points(matrix, p, p, 4);
|
||||
|
||||
float l = p[0].x;
|
||||
float t = p[0].y;
|
||||
float r = p[0].x;
|
||||
float b = p[0].y;
|
||||
|
||||
for(int i = 1; i < 4; i++) {
|
||||
if(p[i].x < l) l = p[i].x;
|
||||
if(p[i].x > r) r = p[i].x;
|
||||
if(p[i].y < t) t = p[i].y;
|
||||
if(p[i].y > b) b = p[i].y;
|
||||
}
|
||||
|
||||
dst->x = l;
|
||||
dst->y = t;
|
||||
dst->w = r - l;
|
||||
dst->h = b - t;
|
||||
}
|
||||
|
||||
static int parse_matrix_parameters(const char** begin, const char* end, float values[6], int required, int optional)
|
||||
{
|
||||
if(!plutovg_skip_ws_and_delim(begin, end, '('))
|
||||
return 0;
|
||||
int count = 0;
|
||||
int max_count = required + optional;
|
||||
bool has_trailing_comma = false;
|
||||
for(; count < max_count; ++count) {
|
||||
if(!plutovg_parse_number(begin, end, values + count))
|
||||
break;
|
||||
plutovg_skip_ws_or_comma(begin, end, &has_trailing_comma);
|
||||
}
|
||||
|
||||
if(!has_trailing_comma && (count == required || count == max_count)
|
||||
&& plutovg_skip_delim(begin, end, ')')) {
|
||||
return count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool plutovg_matrix_parse(plutovg_matrix_t* matrix, const char* data, int length)
|
||||
{
|
||||
float values[6];
|
||||
plutovg_matrix_init_identity(matrix);
|
||||
if(length == -1)
|
||||
length = strlen(data);
|
||||
const char* it = data;
|
||||
const char* end = it + length;
|
||||
bool has_trailing_comma = false;
|
||||
plutovg_skip_ws(&it, end);
|
||||
while(it < end) {
|
||||
if(plutovg_skip_string(&it, end, "matrix")) {
|
||||
int count = parse_matrix_parameters(&it, end, values, 6, 0);
|
||||
if(count == 0)
|
||||
return false;
|
||||
plutovg_matrix_t m = { values[0], values[1], values[2], values[3], values[4], values[5] };
|
||||
plutovg_matrix_multiply(matrix, &m, matrix);
|
||||
} else if(plutovg_skip_string(&it, end, "translate")) {
|
||||
int count = parse_matrix_parameters(&it, end, values, 1, 1);
|
||||
if(count == 0)
|
||||
return false;
|
||||
if(count == 1) {
|
||||
plutovg_matrix_translate(matrix, values[0], 0);
|
||||
} else {
|
||||
plutovg_matrix_translate(matrix, values[0], values[1]);
|
||||
}
|
||||
} else if(plutovg_skip_string(&it, end, "scale")) {
|
||||
int count = parse_matrix_parameters(&it, end, values, 1, 1);
|
||||
if(count == 0)
|
||||
return false;
|
||||
if(count == 1) {
|
||||
plutovg_matrix_scale(matrix, values[0], values[0]);
|
||||
} else {
|
||||
plutovg_matrix_scale(matrix, values[0], values[1]);
|
||||
}
|
||||
} else if(plutovg_skip_string(&it, end, "rotate")) {
|
||||
int count = parse_matrix_parameters(&it, end, values, 1, 2);
|
||||
if(count == 0)
|
||||
return false;
|
||||
if(count == 3)
|
||||
plutovg_matrix_translate(matrix, values[1], values[2]);
|
||||
plutovg_matrix_rotate(matrix, PLUTOVG_DEG2RAD(values[0]));
|
||||
if(count == 3) {
|
||||
plutovg_matrix_translate(matrix, -values[1], -values[2]);
|
||||
}
|
||||
} else if(plutovg_skip_string(&it, end, "skewX")) {
|
||||
int count = parse_matrix_parameters(&it, end, values, 1, 0);
|
||||
if(count == 0)
|
||||
return false;
|
||||
plutovg_matrix_shear(matrix, PLUTOVG_DEG2RAD(values[0]), 0);
|
||||
} else if(plutovg_skip_string(&it, end, "skewY")) {
|
||||
int count = parse_matrix_parameters(&it, end, values, 1, 0);
|
||||
if(count == 0)
|
||||
return false;
|
||||
plutovg_matrix_shear(matrix, 0, PLUTOVG_DEG2RAD(values[0]));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
plutovg_skip_ws_or_comma(&it, end, &has_trailing_comma);
|
||||
}
|
||||
|
||||
return !has_trailing_comma;
|
||||
}
|
||||
489
vendor/lunasvg/plutovg/source/plutovg-paint.c
vendored
489
vendor/lunasvg/plutovg/source/plutovg-paint.c
vendored
@@ -1,489 +0,0 @@
|
||||
#include "plutovg-private.h"
|
||||
#include "plutovg-utils.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
void plutovg_color_init_rgb(plutovg_color_t* color, float r, float g, float b)
|
||||
{
|
||||
plutovg_color_init_rgba(color, r, g, b, 1.f);
|
||||
}
|
||||
|
||||
void plutovg_color_init_rgba(plutovg_color_t* color, float r, float g, float b, float a)
|
||||
{
|
||||
color->r = plutovg_clamp(r, 0.f, 1.f);
|
||||
color->g = plutovg_clamp(g, 0.f, 1.f);
|
||||
color->b = plutovg_clamp(b, 0.f, 1.f);
|
||||
color->a = plutovg_clamp(a, 0.f, 1.f);
|
||||
}
|
||||
|
||||
void plutovg_color_init_rgb8(plutovg_color_t* color, int r, int g, int b)
|
||||
{
|
||||
plutovg_color_init_rgba8(color, r, g, b, 255);
|
||||
}
|
||||
|
||||
void plutovg_color_init_rgba8(plutovg_color_t* color, int r, int g, int b, int a)
|
||||
{
|
||||
plutovg_color_init_rgba(color, r / 255.f, g / 255.f, b / 255.f, a / 255.f);
|
||||
}
|
||||
|
||||
void plutovg_color_init_rgba32(plutovg_color_t* color, unsigned int value)
|
||||
{
|
||||
uint8_t r = (value >> 24) & 0xFF;
|
||||
uint8_t g = (value >> 16) & 0xFF;
|
||||
uint8_t b = (value >> 8) & 0xFF;
|
||||
uint8_t a = (value >> 0) & 0xFF;
|
||||
plutovg_color_init_rgba8(color, r, g, b, a);
|
||||
}
|
||||
|
||||
void plutovg_color_init_argb32(plutovg_color_t* color, unsigned int value)
|
||||
{
|
||||
uint8_t a = (value >> 24) & 0xFF;
|
||||
uint8_t r = (value >> 16) & 0xFF;
|
||||
uint8_t g = (value >> 8) & 0xFF;
|
||||
uint8_t b = (value >> 0) & 0xFF;
|
||||
plutovg_color_init_rgba8(color, r, g, b, a);
|
||||
}
|
||||
|
||||
void plutovg_color_init_hsl(plutovg_color_t* color, float h, float s, float l)
|
||||
{
|
||||
plutovg_color_init_hsla(color, h, s, l, 1.f);
|
||||
}
|
||||
|
||||
static inline float hsl_component(float h, float s, float l, float n)
|
||||
{
|
||||
const float k = fmodf(n + h / 30.f, 12.f);
|
||||
const float a = s * plutovg_min(l, 1.f - l);
|
||||
return l - a * plutovg_max(-1.f, plutovg_min(1.f, plutovg_min(k - 3.f, 9.f - k)));
|
||||
}
|
||||
|
||||
void plutovg_color_init_hsla(plutovg_color_t* color, float h, float s, float l, float a)
|
||||
{
|
||||
h = fmodf(h, 360.f);
|
||||
if(h < 0.f) { h += 360.f; }
|
||||
|
||||
float r = hsl_component(h, s, l, 0);
|
||||
float g = hsl_component(h, s, l, 8);
|
||||
float b = hsl_component(h, s, l, 4);
|
||||
plutovg_color_init_rgba(color, r, g, b, a);
|
||||
}
|
||||
|
||||
unsigned int plutovg_color_to_rgba32(const plutovg_color_t* color)
|
||||
{
|
||||
uint32_t r = lroundf(color->r * 255);
|
||||
uint32_t g = lroundf(color->g * 255);
|
||||
uint32_t b = lroundf(color->b * 255);
|
||||
uint32_t a = lroundf(color->a * 255);
|
||||
return (r << 24) | (g << 16) | (b << 8) | (a);
|
||||
}
|
||||
|
||||
unsigned int plutovg_color_to_argb32(const plutovg_color_t* color)
|
||||
{
|
||||
uint32_t a = lroundf(color->a * 255);
|
||||
uint32_t r = lroundf(color->r * 255);
|
||||
uint32_t g = lroundf(color->g * 255);
|
||||
uint32_t b = lroundf(color->b * 255);
|
||||
return (a << 24) | (r << 16) | (g << 8) | (b);
|
||||
}
|
||||
|
||||
static inline uint8_t hex_digit(uint8_t c)
|
||||
{
|
||||
if(c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if(c >= 'a' && c <= 'f')
|
||||
return 10 + c - 'a';
|
||||
return 10 + c - 'A';
|
||||
}
|
||||
|
||||
static inline uint8_t hex_byte(uint8_t c1, uint8_t c2)
|
||||
{
|
||||
uint8_t h1 = hex_digit(c1);
|
||||
uint8_t h2 = hex_digit(c2);
|
||||
return (h1 << 4) | h2;
|
||||
}
|
||||
|
||||
#define MAX_NAME 20
|
||||
typedef struct {
|
||||
const char* name;
|
||||
uint32_t value;
|
||||
} color_entry_t;
|
||||
|
||||
static int color_entry_compare(const void* a, const void* b)
|
||||
{
|
||||
const char* name = a;
|
||||
const color_entry_t* entry = b;
|
||||
return strcmp(name, entry->name);
|
||||
}
|
||||
|
||||
static bool parse_rgb_component(const char** begin, const char* end, float* component)
|
||||
{
|
||||
float value = 0;
|
||||
if(!plutovg_parse_number(begin, end, &value))
|
||||
return false;
|
||||
if(plutovg_skip_delim(begin, end, '%'))
|
||||
value *= 2.55f;
|
||||
*component = plutovg_clamp(value, 0.f, 255.f) / 255.f;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_alpha_component(const char** begin, const char* end, float* component)
|
||||
{
|
||||
float value = 0;
|
||||
if(!plutovg_parse_number(begin, end, &value))
|
||||
return false;
|
||||
if(plutovg_skip_delim(begin, end, '%'))
|
||||
value /= 100.f;
|
||||
*component = plutovg_clamp(value, 0.f, 1.f);
|
||||
return true;
|
||||
}
|
||||
|
||||
int plutovg_color_parse(plutovg_color_t* color, const char* data, int length)
|
||||
{
|
||||
if(length == -1)
|
||||
length = strlen(data);
|
||||
const char* it = data;
|
||||
const char* end = it + length;
|
||||
plutovg_skip_ws(&it, end);
|
||||
if(plutovg_skip_delim(&it, end, '#')) {
|
||||
int r, g, b, a = 255;
|
||||
const char* begin = it;
|
||||
while(it < end && isxdigit(*it))
|
||||
++it;
|
||||
int count = it - begin;
|
||||
if(count == 3 || count == 4) {
|
||||
r = hex_byte(begin[0], begin[0]);
|
||||
g = hex_byte(begin[1], begin[1]);
|
||||
b = hex_byte(begin[2], begin[2]);
|
||||
if(count == 4) {
|
||||
a = hex_byte(begin[3], begin[3]);
|
||||
}
|
||||
} else if(count == 6 || count == 8) {
|
||||
r = hex_byte(begin[0], begin[1]);
|
||||
g = hex_byte(begin[2], begin[3]);
|
||||
b = hex_byte(begin[4], begin[5]);
|
||||
if(count == 8) {
|
||||
a = hex_byte(begin[6], begin[7]);
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
plutovg_color_init_rgba8(color, r, g, b, a);
|
||||
} else {
|
||||
int name_length = 0;
|
||||
char name[MAX_NAME + 1];
|
||||
while(it < end && name_length < MAX_NAME && isalpha(*it))
|
||||
name[name_length++] = tolower(*it++);
|
||||
name[name_length] = '\0';
|
||||
|
||||
if(strcmp(name, "transparent") == 0) {
|
||||
plutovg_color_init_rgba(color, 0, 0, 0, 0);
|
||||
} else if(strcmp(name, "rgb") == 0 || strcmp(name, "rgba") == 0) {
|
||||
if(!plutovg_skip_ws_and_delim(&it, end, '('))
|
||||
return 0;
|
||||
float r, g, b, a = 1.f;
|
||||
if(!parse_rgb_component(&it, end, &r)
|
||||
|| !plutovg_skip_ws_and_comma(&it, end)
|
||||
|| !parse_rgb_component(&it, end, &g)
|
||||
|| !plutovg_skip_ws_and_comma(&it, end)
|
||||
|| !parse_rgb_component(&it, end, &b)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(plutovg_skip_ws_and_comma(&it, end)
|
||||
&& !parse_alpha_component(&it, end, &a)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
plutovg_skip_ws(&it, end);
|
||||
if(!plutovg_skip_delim(&it, end, ')'))
|
||||
return 0;
|
||||
plutovg_color_init_rgba(color, r, g, b, a);
|
||||
} else if(strcmp(name, "hsl") == 0 || strcmp(name, "hsla") == 0) {
|
||||
if(!plutovg_skip_ws_and_delim(&it, end, '('))
|
||||
return 0;
|
||||
float h, s, l, a = 1.f;
|
||||
if(!plutovg_parse_number(&it, end, &h)
|
||||
|| !plutovg_skip_ws_and_comma(&it, end)
|
||||
|| !parse_alpha_component(&it, end, &s)
|
||||
|| !plutovg_skip_ws_and_comma(&it, end)
|
||||
|| !parse_alpha_component(&it, end, &l)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(plutovg_skip_ws_and_comma(&it, end)
|
||||
&& !parse_alpha_component(&it, end, &a)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
plutovg_skip_ws(&it, end);
|
||||
if(!plutovg_skip_delim(&it, end, ')'))
|
||||
return 0;
|
||||
plutovg_color_init_hsla(color, h, s, l, a);
|
||||
} else {
|
||||
static const color_entry_t colormap[] = {
|
||||
{"aliceblue", 0xF0F8FF},
|
||||
{"antiquewhite", 0xFAEBD7},
|
||||
{"aqua", 0x00FFFF},
|
||||
{"aquamarine", 0x7FFFD4},
|
||||
{"azure", 0xF0FFFF},
|
||||
{"beige", 0xF5F5DC},
|
||||
{"bisque", 0xFFE4C4},
|
||||
{"black", 0x000000},
|
||||
{"blanchedalmond", 0xFFEBCD},
|
||||
{"blue", 0x0000FF},
|
||||
{"blueviolet", 0x8A2BE2},
|
||||
{"brown", 0xA52A2A},
|
||||
{"burlywood", 0xDEB887},
|
||||
{"cadetblue", 0x5F9EA0},
|
||||
{"chartreuse", 0x7FFF00},
|
||||
{"chocolate", 0xD2691E},
|
||||
{"coral", 0xFF7F50},
|
||||
{"cornflowerblue", 0x6495ED},
|
||||
{"cornsilk", 0xFFF8DC},
|
||||
{"crimson", 0xDC143C},
|
||||
{"cyan", 0x00FFFF},
|
||||
{"darkblue", 0x00008B},
|
||||
{"darkcyan", 0x008B8B},
|
||||
{"darkgoldenrod", 0xB8860B},
|
||||
{"darkgray", 0xA9A9A9},
|
||||
{"darkgreen", 0x006400},
|
||||
{"darkgrey", 0xA9A9A9},
|
||||
{"darkkhaki", 0xBDB76B},
|
||||
{"darkmagenta", 0x8B008B},
|
||||
{"darkolivegreen", 0x556B2F},
|
||||
{"darkorange", 0xFF8C00},
|
||||
{"darkorchid", 0x9932CC},
|
||||
{"darkred", 0x8B0000},
|
||||
{"darksalmon", 0xE9967A},
|
||||
{"darkseagreen", 0x8FBC8F},
|
||||
{"darkslateblue", 0x483D8B},
|
||||
{"darkslategray", 0x2F4F4F},
|
||||
{"darkslategrey", 0x2F4F4F},
|
||||
{"darkturquoise", 0x00CED1},
|
||||
{"darkviolet", 0x9400D3},
|
||||
{"deeppink", 0xFF1493},
|
||||
{"deepskyblue", 0x00BFFF},
|
||||
{"dimgray", 0x696969},
|
||||
{"dimgrey", 0x696969},
|
||||
{"dodgerblue", 0x1E90FF},
|
||||
{"firebrick", 0xB22222},
|
||||
{"floralwhite", 0xFFFAF0},
|
||||
{"forestgreen", 0x228B22},
|
||||
{"fuchsia", 0xFF00FF},
|
||||
{"gainsboro", 0xDCDCDC},
|
||||
{"ghostwhite", 0xF8F8FF},
|
||||
{"gold", 0xFFD700},
|
||||
{"goldenrod", 0xDAA520},
|
||||
{"gray", 0x808080},
|
||||
{"green", 0x008000},
|
||||
{"greenyellow", 0xADFF2F},
|
||||
{"grey", 0x808080},
|
||||
{"honeydew", 0xF0FFF0},
|
||||
{"hotpink", 0xFF69B4},
|
||||
{"indianred", 0xCD5C5C},
|
||||
{"indigo", 0x4B0082},
|
||||
{"ivory", 0xFFFFF0},
|
||||
{"khaki", 0xF0E68C},
|
||||
{"lavender", 0xE6E6FA},
|
||||
{"lavenderblush", 0xFFF0F5},
|
||||
{"lawngreen", 0x7CFC00},
|
||||
{"lemonchiffon", 0xFFFACD},
|
||||
{"lightblue", 0xADD8E6},
|
||||
{"lightcoral", 0xF08080},
|
||||
{"lightcyan", 0xE0FFFF},
|
||||
{"lightgoldenrodyellow", 0xFAFAD2},
|
||||
{"lightgray", 0xD3D3D3},
|
||||
{"lightgreen", 0x90EE90},
|
||||
{"lightgrey", 0xD3D3D3},
|
||||
{"lightpink", 0xFFB6C1},
|
||||
{"lightsalmon", 0xFFA07A},
|
||||
{"lightseagreen", 0x20B2AA},
|
||||
{"lightskyblue", 0x87CEFA},
|
||||
{"lightslategray", 0x778899},
|
||||
{"lightslategrey", 0x778899},
|
||||
{"lightsteelblue", 0xB0C4DE},
|
||||
{"lightyellow", 0xFFFFE0},
|
||||
{"lime", 0x00FF00},
|
||||
{"limegreen", 0x32CD32},
|
||||
{"linen", 0xFAF0E6},
|
||||
{"magenta", 0xFF00FF},
|
||||
{"maroon", 0x800000},
|
||||
{"mediumaquamarine", 0x66CDAA},
|
||||
{"mediumblue", 0x0000CD},
|
||||
{"mediumorchid", 0xBA55D3},
|
||||
{"mediumpurple", 0x9370DB},
|
||||
{"mediumseagreen", 0x3CB371},
|
||||
{"mediumslateblue", 0x7B68EE},
|
||||
{"mediumspringgreen", 0x00FA9A},
|
||||
{"mediumturquoise", 0x48D1CC},
|
||||
{"mediumvioletred", 0xC71585},
|
||||
{"midnightblue", 0x191970},
|
||||
{"mintcream", 0xF5FFFA},
|
||||
{"mistyrose", 0xFFE4E1},
|
||||
{"moccasin", 0xFFE4B5},
|
||||
{"navajowhite", 0xFFDEAD},
|
||||
{"navy", 0x000080},
|
||||
{"oldlace", 0xFDF5E6},
|
||||
{"olive", 0x808000},
|
||||
{"olivedrab", 0x6B8E23},
|
||||
{"orange", 0xFFA500},
|
||||
{"orangered", 0xFF4500},
|
||||
{"orchid", 0xDA70D6},
|
||||
{"palegoldenrod", 0xEEE8AA},
|
||||
{"palegreen", 0x98FB98},
|
||||
{"paleturquoise", 0xAFEEEE},
|
||||
{"palevioletred", 0xDB7093},
|
||||
{"papayawhip", 0xFFEFD5},
|
||||
{"peachpuff", 0xFFDAB9},
|
||||
{"peru", 0xCD853F},
|
||||
{"pink", 0xFFC0CB},
|
||||
{"plum", 0xDDA0DD},
|
||||
{"powderblue", 0xB0E0E6},
|
||||
{"purple", 0x800080},
|
||||
{"rebeccapurple", 0x663399},
|
||||
{"red", 0xFF0000},
|
||||
{"rosybrown", 0xBC8F8F},
|
||||
{"royalblue", 0x4169E1},
|
||||
{"saddlebrown", 0x8B4513},
|
||||
{"salmon", 0xFA8072},
|
||||
{"sandybrown", 0xF4A460},
|
||||
{"seagreen", 0x2E8B57},
|
||||
{"seashell", 0xFFF5EE},
|
||||
{"sienna", 0xA0522D},
|
||||
{"silver", 0xC0C0C0},
|
||||
{"skyblue", 0x87CEEB},
|
||||
{"slateblue", 0x6A5ACD},
|
||||
{"slategray", 0x708090},
|
||||
{"slategrey", 0x708090},
|
||||
{"snow", 0xFFFAFA},
|
||||
{"springgreen", 0x00FF7F},
|
||||
{"steelblue", 0x4682B4},
|
||||
{"tan", 0xD2B48C},
|
||||
{"teal", 0x008080},
|
||||
{"thistle", 0xD8BFD8},
|
||||
{"tomato", 0xFF6347},
|
||||
{"turquoise", 0x40E0D0},
|
||||
{"violet", 0xEE82EE},
|
||||
{"wheat", 0xF5DEB3},
|
||||
{"white", 0xFFFFFF},
|
||||
{"whitesmoke", 0xF5F5F5},
|
||||
{"yellow", 0xFFFF00},
|
||||
{"yellowgreen", 0x9ACD32}
|
||||
};
|
||||
|
||||
const color_entry_t* entry = bsearch(name, colormap, sizeof(colormap) / sizeof(color_entry_t), sizeof(color_entry_t), color_entry_compare);
|
||||
if(entry == NULL)
|
||||
return 0;
|
||||
plutovg_color_init_argb32(color, 0xFF000000 | entry->value);
|
||||
}
|
||||
}
|
||||
|
||||
plutovg_skip_ws(&it, end);
|
||||
return it - data;
|
||||
}
|
||||
|
||||
static void* plutovg_paint_create(plutovg_paint_type_t type, size_t size)
|
||||
{
|
||||
plutovg_paint_t* paint = malloc(size);
|
||||
plutovg_init_reference(paint);
|
||||
paint->type = type;
|
||||
return paint;
|
||||
}
|
||||
|
||||
plutovg_paint_t* plutovg_paint_create_rgb(float r, float g, float b)
|
||||
{
|
||||
return plutovg_paint_create_rgba(r, g, b, 1.f);
|
||||
}
|
||||
|
||||
plutovg_paint_t* plutovg_paint_create_rgba(float r, float g, float b, float a)
|
||||
{
|
||||
plutovg_solid_paint_t* solid = plutovg_paint_create(PLUTOVG_PAINT_TYPE_COLOR, sizeof(plutovg_solid_paint_t));
|
||||
solid->color.r = plutovg_clamp(r, 0.f, 1.f);
|
||||
solid->color.g = plutovg_clamp(g, 0.f, 1.f);
|
||||
solid->color.b = plutovg_clamp(b, 0.f, 1.f);
|
||||
solid->color.a = plutovg_clamp(a, 0.f, 1.f);
|
||||
return &solid->base;
|
||||
}
|
||||
|
||||
plutovg_paint_t* plutovg_paint_create_color(const plutovg_color_t* color)
|
||||
{
|
||||
return plutovg_paint_create_rgba(color->r, color->g, color->b, color->a);
|
||||
}
|
||||
|
||||
static plutovg_gradient_paint_t* plutovg_gradient_create(plutovg_gradient_type_t type, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
plutovg_gradient_paint_t* gradient = plutovg_paint_create(PLUTOVG_PAINT_TYPE_GRADIENT, sizeof(plutovg_gradient_paint_t) + nstops * sizeof(plutovg_gradient_stop_t));
|
||||
gradient->type = type;
|
||||
gradient->spread = spread;
|
||||
gradient->matrix = matrix ? *matrix : PLUTOVG_IDENTITY_MATRIX;
|
||||
gradient->stops = (plutovg_gradient_stop_t*)(gradient + 1);
|
||||
gradient->nstops = nstops;
|
||||
|
||||
float prev_offset = 0.f;
|
||||
for(int i = 0; i < nstops; ++i) {
|
||||
const plutovg_gradient_stop_t* stop = stops + i;
|
||||
gradient->stops[i].offset = plutovg_max(prev_offset, plutovg_clamp(stop->offset, 0.f, 1.f));
|
||||
gradient->stops[i].color.r = plutovg_clamp(stop->color.r, 0.f, 1.f);
|
||||
gradient->stops[i].color.g = plutovg_clamp(stop->color.g, 0.f, 1.f);
|
||||
gradient->stops[i].color.b = plutovg_clamp(stop->color.b, 0.f, 1.f);
|
||||
gradient->stops[i].color.a = plutovg_clamp(stop->color.a, 0.f, 1.f);
|
||||
prev_offset = gradient->stops[i].offset;
|
||||
}
|
||||
|
||||
return gradient;
|
||||
}
|
||||
|
||||
plutovg_paint_t* plutovg_paint_create_linear_gradient(float x1, float y1, float x2, float y2, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
plutovg_gradient_paint_t* gradient = plutovg_gradient_create(PLUTOVG_GRADIENT_TYPE_LINEAR, spread, stops, nstops, matrix);
|
||||
gradient->values[0] = x1;
|
||||
gradient->values[1] = y1;
|
||||
gradient->values[2] = x2;
|
||||
gradient->values[3] = y2;
|
||||
return &gradient->base;
|
||||
}
|
||||
|
||||
plutovg_paint_t* plutovg_paint_create_radial_gradient(float cx, float cy, float cr, float fx, float fy, float fr, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
plutovg_gradient_paint_t* gradient = plutovg_gradient_create(PLUTOVG_GRADIENT_TYPE_RADIAL, spread, stops, nstops, matrix);
|
||||
gradient->values[0] = cx;
|
||||
gradient->values[1] = cy;
|
||||
gradient->values[2] = cr;
|
||||
gradient->values[3] = fx;
|
||||
gradient->values[4] = fy;
|
||||
gradient->values[5] = fr;
|
||||
return &gradient->base;
|
||||
}
|
||||
|
||||
plutovg_paint_t* plutovg_paint_create_texture(plutovg_surface_t* surface, plutovg_texture_type_t type, float opacity, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
plutovg_texture_paint_t* texture = plutovg_paint_create(PLUTOVG_PAINT_TYPE_TEXTURE, sizeof(plutovg_texture_paint_t));
|
||||
texture->type = type;
|
||||
texture->opacity = plutovg_clamp(opacity, 0.f, 1.f);
|
||||
texture->matrix = matrix ? *matrix : PLUTOVG_IDENTITY_MATRIX;
|
||||
texture->surface = plutovg_surface_reference(surface);
|
||||
return &texture->base;
|
||||
}
|
||||
|
||||
plutovg_paint_t* plutovg_paint_reference(plutovg_paint_t* paint)
|
||||
{
|
||||
plutovg_increment_reference(paint);
|
||||
return paint;
|
||||
}
|
||||
|
||||
void plutovg_paint_destroy(plutovg_paint_t* paint)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
free(paint);
|
||||
}
|
||||
}
|
||||
|
||||
int plutovg_paint_get_reference_count(const plutovg_paint_t* paint)
|
||||
{
|
||||
return plutovg_get_reference_count(paint);
|
||||
}
|
||||
918
vendor/lunasvg/plutovg/source/plutovg-path.c
vendored
918
vendor/lunasvg/plutovg/source/plutovg-path.c
vendored
@@ -1,918 +0,0 @@
|
||||
#include "plutovg-private.h"
|
||||
#include "plutovg-utils.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
void plutovg_path_iterator_init(plutovg_path_iterator_t* it, const plutovg_path_t* path)
|
||||
{
|
||||
it->elements = path->elements.data;
|
||||
it->size = path->elements.size;
|
||||
it->index = 0;
|
||||
}
|
||||
|
||||
bool plutovg_path_iterator_has_next(const plutovg_path_iterator_t* it)
|
||||
{
|
||||
return it->index < it->size;
|
||||
}
|
||||
|
||||
plutovg_path_command_t plutovg_path_iterator_next(plutovg_path_iterator_t* it, plutovg_point_t points[3])
|
||||
{
|
||||
const plutovg_path_element_t* elements = it->elements + it->index;
|
||||
switch(elements[0].header.command) {
|
||||
case PLUTOVG_PATH_COMMAND_MOVE_TO:
|
||||
case PLUTOVG_PATH_COMMAND_LINE_TO:
|
||||
case PLUTOVG_PATH_COMMAND_CLOSE:
|
||||
points[0] = elements[1].point;
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CUBIC_TO:
|
||||
points[0] = elements[1].point;
|
||||
points[1] = elements[2].point;
|
||||
points[2] = elements[3].point;
|
||||
break;
|
||||
}
|
||||
|
||||
it->index += elements[0].header.length;
|
||||
return elements[0].header.command;
|
||||
}
|
||||
|
||||
plutovg_path_t* plutovg_path_create(void)
|
||||
{
|
||||
plutovg_path_t* path = malloc(sizeof(plutovg_path_t));
|
||||
plutovg_init_reference(path);
|
||||
path->num_points = 0;
|
||||
path->num_contours = 0;
|
||||
path->num_curves = 0;
|
||||
path->start_point = PLUTOVG_EMPTY_POINT;
|
||||
plutovg_array_init(path->elements);
|
||||
return path;
|
||||
}
|
||||
|
||||
plutovg_path_t* plutovg_path_reference(plutovg_path_t* path)
|
||||
{
|
||||
plutovg_increment_reference(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
void plutovg_path_destroy(plutovg_path_t* path)
|
||||
{
|
||||
if(plutovg_destroy_reference(path)) {
|
||||
plutovg_array_destroy(path->elements);
|
||||
free(path);
|
||||
}
|
||||
}
|
||||
|
||||
int plutovg_path_get_reference_count(const plutovg_path_t* path)
|
||||
{
|
||||
return plutovg_get_reference_count(path);
|
||||
}
|
||||
|
||||
int plutovg_path_get_elements(const plutovg_path_t* path, const plutovg_path_element_t** elements)
|
||||
{
|
||||
if(elements)
|
||||
*elements = path->elements.data;
|
||||
return path->elements.size;
|
||||
}
|
||||
|
||||
static plutovg_path_element_t* plutovg_path_add_command(plutovg_path_t* path, plutovg_path_command_t command, int npoints)
|
||||
{
|
||||
const int length = npoints + 1;
|
||||
plutovg_array_ensure(path->elements, length);
|
||||
plutovg_path_element_t* elements = path->elements.data + path->elements.size;
|
||||
elements->header.command = command;
|
||||
elements->header.length = length;
|
||||
path->elements.size += length;
|
||||
path->num_points += npoints;
|
||||
return elements + 1;
|
||||
}
|
||||
|
||||
void plutovg_path_move_to(plutovg_path_t* path, float x, float y)
|
||||
{
|
||||
plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_MOVE_TO, 1);
|
||||
elements[0].point = PLUTOVG_MAKE_POINT(x, y);
|
||||
path->start_point = PLUTOVG_MAKE_POINT(x, y);
|
||||
path->num_contours += 1;
|
||||
}
|
||||
|
||||
void plutovg_path_line_to(plutovg_path_t* path, float x, float y)
|
||||
{
|
||||
if(path->elements.size == 0)
|
||||
plutovg_path_move_to(path, 0, 0);
|
||||
plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_LINE_TO, 1);
|
||||
elements[0].point = PLUTOVG_MAKE_POINT(x, y);
|
||||
}
|
||||
|
||||
void plutovg_path_quad_to(plutovg_path_t* path, float x1, float y1, float x2, float y2)
|
||||
{
|
||||
float current_x, current_y;
|
||||
plutovg_path_get_current_point(path, ¤t_x, ¤t_y);
|
||||
float cp1x = 2.f / 3.f * x1 + 1.f / 3.f * current_x;
|
||||
float cp1y = 2.f / 3.f * y1 + 1.f / 3.f * current_y;
|
||||
float cp2x = 2.f / 3.f * x1 + 1.f / 3.f * x2;
|
||||
float cp2y = 2.f / 3.f * y1 + 1.f / 3.f * y2;
|
||||
plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, x2, y2);
|
||||
}
|
||||
|
||||
void plutovg_path_cubic_to(plutovg_path_t* path, float x1, float y1, float x2, float y2, float x3, float y3)
|
||||
{
|
||||
if(path->elements.size == 0)
|
||||
plutovg_path_move_to(path, 0, 0);
|
||||
plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_CUBIC_TO, 3);
|
||||
elements[0].point = PLUTOVG_MAKE_POINT(x1, y1);
|
||||
elements[1].point = PLUTOVG_MAKE_POINT(x2, y2);
|
||||
elements[2].point = PLUTOVG_MAKE_POINT(x3, y3);
|
||||
path->num_curves += 1;
|
||||
}
|
||||
|
||||
void plutovg_path_arc_to(plutovg_path_t* path, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y)
|
||||
{
|
||||
float current_x, current_y;
|
||||
plutovg_path_get_current_point(path, ¤t_x, ¤t_y);
|
||||
if(rx == 0.f || ry == 0.f || (current_x == x && current_y == y)) {
|
||||
plutovg_path_line_to(path, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
if(rx < 0.f) rx = -rx;
|
||||
if(ry < 0.f) ry = -ry;
|
||||
|
||||
float dx = (current_x - x) * 0.5f;
|
||||
float dy = (current_y - y) * 0.5f;
|
||||
|
||||
plutovg_matrix_t matrix;
|
||||
plutovg_matrix_init_rotate(&matrix, -angle);
|
||||
plutovg_matrix_map(&matrix, dx, dy, &dx, &dy);
|
||||
|
||||
float rxrx = rx * rx;
|
||||
float ryry = ry * ry;
|
||||
float dxdx = dx * dx;
|
||||
float dydy = dy * dy;
|
||||
float radius = dxdx / rxrx + dydy / ryry;
|
||||
if(radius > 1.f) {
|
||||
rx *= sqrtf(radius);
|
||||
ry *= sqrtf(radius);
|
||||
}
|
||||
|
||||
plutovg_matrix_init_scale(&matrix, 1.f / rx, 1.f / ry);
|
||||
plutovg_matrix_rotate(&matrix, -angle);
|
||||
|
||||
float x1, y1;
|
||||
float x2, y2;
|
||||
plutovg_matrix_map(&matrix, current_x, current_y, &x1, &y1);
|
||||
plutovg_matrix_map(&matrix, x, y, &x2, &y2);
|
||||
|
||||
float dx1 = x2 - x1;
|
||||
float dy1 = y2 - y1;
|
||||
float d = dx1 * dx1 + dy1 * dy1;
|
||||
float scale_sq = 1.f / d - 0.25f;
|
||||
if(scale_sq < 0.f) scale_sq = 0.f;
|
||||
float scale = sqrtf(scale_sq);
|
||||
if(sweep_flag == large_arc_flag)
|
||||
scale = -scale;
|
||||
dx1 *= scale;
|
||||
dy1 *= scale;
|
||||
|
||||
float cx1 = 0.5f * (x1 + x2) - dy1;
|
||||
float cy1 = 0.5f * (y1 + y2) + dx1;
|
||||
|
||||
float th1 = atan2f(y1 - cy1, x1 - cx1);
|
||||
float th2 = atan2f(y2 - cy1, x2 - cx1);
|
||||
float th_arc = th2 - th1;
|
||||
if(th_arc < 0.f && sweep_flag)
|
||||
th_arc += PLUTOVG_TWO_PI;
|
||||
else if(th_arc > 0.f && !sweep_flag)
|
||||
th_arc -= PLUTOVG_TWO_PI;
|
||||
plutovg_matrix_init_rotate(&matrix, angle);
|
||||
plutovg_matrix_scale(&matrix, rx, ry);
|
||||
int segments = (int)(ceilf(fabsf(th_arc / (PLUTOVG_HALF_PI + 0.001f))));
|
||||
for(int i = 0; i < segments; i++) {
|
||||
float th_start = th1 + i * th_arc / segments;
|
||||
float th_end = th1 + (i + 1) * th_arc / segments;
|
||||
float t = (8.f / 6.f) * tanf(0.25f * (th_end - th_start));
|
||||
|
||||
float x3 = cosf(th_end) + cx1;
|
||||
float y3 = sinf(th_end) + cy1;
|
||||
|
||||
float cp2x = x3 + t * sinf(th_end);
|
||||
float cp2y = y3 - t * cosf(th_end);
|
||||
|
||||
float cp1x = cosf(th_start) - t * sinf(th_start);
|
||||
float cp1y = sinf(th_start) + t * cosf(th_start);
|
||||
|
||||
cp1x += cx1;
|
||||
cp1y += cy1;
|
||||
|
||||
plutovg_matrix_map(&matrix, cp1x, cp1y, &cp1x, &cp1y);
|
||||
plutovg_matrix_map(&matrix, cp2x, cp2y, &cp2x, &cp2y);
|
||||
plutovg_matrix_map(&matrix, x3, y3, &x3, &y3);
|
||||
|
||||
plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, x3, y3);
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_path_close(plutovg_path_t* path)
|
||||
{
|
||||
if(path->elements.size == 0)
|
||||
return;
|
||||
plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_CLOSE, 1);
|
||||
elements[0].point = path->start_point;
|
||||
}
|
||||
|
||||
void plutovg_path_get_current_point(const plutovg_path_t* path, float* x, float* y)
|
||||
{
|
||||
float xx = 0.f;
|
||||
float yy = 0.f;
|
||||
if(path->num_points > 0) {
|
||||
xx = path->elements.data[path->elements.size - 1].point.x;
|
||||
yy = path->elements.data[path->elements.size - 1].point.y;
|
||||
}
|
||||
|
||||
if(x) *x = xx;
|
||||
if(y) *y = yy;
|
||||
}
|
||||
|
||||
void plutovg_path_reserve(plutovg_path_t* path, int count)
|
||||
{
|
||||
plutovg_array_ensure(path->elements, count);
|
||||
}
|
||||
|
||||
void plutovg_path_reset(plutovg_path_t* path)
|
||||
{
|
||||
plutovg_array_clear(path->elements);
|
||||
path->start_point = PLUTOVG_EMPTY_POINT;
|
||||
path->num_points = 0;
|
||||
path->num_contours = 0;
|
||||
path->num_curves = 0;
|
||||
}
|
||||
|
||||
void plutovg_path_add_rect(plutovg_path_t* path, float x, float y, float w, float h)
|
||||
{
|
||||
plutovg_path_reserve(path, 6 * 2);
|
||||
plutovg_path_move_to(path, x, y);
|
||||
plutovg_path_line_to(path, x + w, y);
|
||||
plutovg_path_line_to(path, x + w, y + h);
|
||||
plutovg_path_line_to(path, x, y + h);
|
||||
plutovg_path_line_to(path, x, y);
|
||||
plutovg_path_close(path);
|
||||
}
|
||||
|
||||
void plutovg_path_add_round_rect(plutovg_path_t* path, float x, float y, float w, float h, float rx, float ry)
|
||||
{
|
||||
rx = plutovg_min(rx, w * 0.5f);
|
||||
ry = plutovg_min(ry, h * 0.5f);
|
||||
if(rx == 0.f && ry == 0.f) {
|
||||
plutovg_path_add_rect(path, x, y, w, h);
|
||||
return;
|
||||
}
|
||||
|
||||
float right = x + w;
|
||||
float bottom = y + h;
|
||||
|
||||
float cpx = rx * PLUTOVG_KAPPA;
|
||||
float cpy = ry * PLUTOVG_KAPPA;
|
||||
|
||||
plutovg_path_reserve(path, 6 * 2 + 4 * 4);
|
||||
plutovg_path_move_to(path, x, y+ry);
|
||||
plutovg_path_cubic_to(path, x, y+ry-cpy, x+rx-cpx, y, x+rx, y);
|
||||
plutovg_path_line_to(path, right-rx, y);
|
||||
plutovg_path_cubic_to(path, right-rx+cpx, y, right, y+ry-cpy, right, y+ry);
|
||||
plutovg_path_line_to(path, right, bottom-ry);
|
||||
plutovg_path_cubic_to(path, right, bottom-ry+cpy, right-rx+cpx, bottom, right-rx, bottom);
|
||||
plutovg_path_line_to(path, x+rx, bottom);
|
||||
plutovg_path_cubic_to(path, x+rx-cpx, bottom, x, bottom-ry+cpy, x, bottom-ry);
|
||||
plutovg_path_line_to(path, x, y+ry);
|
||||
plutovg_path_close(path);
|
||||
}
|
||||
|
||||
void plutovg_path_add_ellipse(plutovg_path_t* path, float cx, float cy, float rx, float ry)
|
||||
{
|
||||
float left = cx - rx;
|
||||
float top = cy - ry;
|
||||
float right = cx + rx;
|
||||
float bottom = cy + ry;
|
||||
|
||||
float cpx = rx * PLUTOVG_KAPPA;
|
||||
float cpy = ry * PLUTOVG_KAPPA;
|
||||
|
||||
plutovg_path_reserve(path, 2 * 2 + 4 * 4);
|
||||
plutovg_path_move_to(path, cx, top);
|
||||
plutovg_path_cubic_to(path, cx+cpx, top, right, cy-cpy, right, cy);
|
||||
plutovg_path_cubic_to(path, right, cy+cpy, cx+cpx, bottom, cx, bottom);
|
||||
plutovg_path_cubic_to(path, cx-cpx, bottom, left, cy+cpy, left, cy);
|
||||
plutovg_path_cubic_to(path, left, cy-cpy, cx-cpx, top, cx, top);
|
||||
plutovg_path_close(path);
|
||||
}
|
||||
|
||||
void plutovg_path_add_circle(plutovg_path_t* path, float cx, float cy, float r)
|
||||
{
|
||||
plutovg_path_add_ellipse(path, cx, cy, r, r);
|
||||
}
|
||||
|
||||
void plutovg_path_add_arc(plutovg_path_t* path, float cx, float cy, float r, float a0, float a1, bool ccw)
|
||||
{
|
||||
float da = a1 - a0;
|
||||
if(fabsf(da) > PLUTOVG_TWO_PI) {
|
||||
da = PLUTOVG_TWO_PI;
|
||||
} else if(da != 0.f && ccw != (da < 0.f)) {
|
||||
da += PLUTOVG_TWO_PI * (ccw ? -1 : 1);
|
||||
}
|
||||
|
||||
int seg_n = (int)(ceilf(fabsf(da) / PLUTOVG_HALF_PI));
|
||||
if(seg_n == 0)
|
||||
return;
|
||||
float a = a0;
|
||||
float ax = cx + cosf(a) * r;
|
||||
float ay = cy + sinf(a) * r;
|
||||
|
||||
float seg_a = da / seg_n;
|
||||
float d = (seg_a / PLUTOVG_HALF_PI) * PLUTOVG_KAPPA * r;
|
||||
float dx = -sinf(a) * d;
|
||||
float dy = cosf(a) * d;
|
||||
|
||||
plutovg_path_reserve(path, 2 + 4 * seg_n);
|
||||
if(path->elements.size == 0) {
|
||||
plutovg_path_move_to(path, ax, ay);
|
||||
} else {
|
||||
plutovg_path_line_to(path, ax, ay);
|
||||
}
|
||||
|
||||
for(int i = 0; i < seg_n; i++) {
|
||||
float cp1x = ax + dx;
|
||||
float cp1y = ay + dy;
|
||||
|
||||
a += seg_a;
|
||||
ax = cx + cosf(a) * r;
|
||||
ay = cy + sinf(a) * r;
|
||||
|
||||
dx = -sinf(a) * d;
|
||||
dy = cosf(a) * d;
|
||||
|
||||
float cp2x = ax - dx;
|
||||
float cp2y = ay - dy;
|
||||
|
||||
plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, ax, ay);
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_path_transform(plutovg_path_t* path, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
plutovg_path_element_t* elements = path->elements.data;
|
||||
for(int i = 0; i < path->elements.size; i += elements[i].header.length) {
|
||||
switch(elements[i].header.command) {
|
||||
case PLUTOVG_PATH_COMMAND_MOVE_TO:
|
||||
case PLUTOVG_PATH_COMMAND_LINE_TO:
|
||||
case PLUTOVG_PATH_COMMAND_CLOSE:
|
||||
plutovg_matrix_map_point(matrix, &elements[i + 1].point, &elements[i + 1].point);
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CUBIC_TO:
|
||||
plutovg_matrix_map_point(matrix, &elements[i + 1].point, &elements[i + 1].point);
|
||||
plutovg_matrix_map_point(matrix, &elements[i + 2].point, &elements[i + 2].point);
|
||||
plutovg_matrix_map_point(matrix, &elements[i + 3].point, &elements[i + 3].point);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_path_add_path(plutovg_path_t* path, const plutovg_path_t* source, const plutovg_matrix_t* matrix)
|
||||
{
|
||||
if(matrix == NULL) {
|
||||
plutovg_array_append(path->elements, source->elements);
|
||||
path->start_point = source->start_point;
|
||||
path->num_points += source->num_points;
|
||||
path->num_contours += source->num_contours;
|
||||
path->num_curves += source->num_curves;
|
||||
return;
|
||||
}
|
||||
|
||||
plutovg_path_iterator_t it;
|
||||
plutovg_path_iterator_init(&it, source);
|
||||
|
||||
plutovg_point_t points[3];
|
||||
plutovg_array_ensure(path->elements, source->elements.size);
|
||||
while(plutovg_path_iterator_has_next(&it)) {
|
||||
switch(plutovg_path_iterator_next(&it, points)) {
|
||||
case PLUTOVG_PATH_COMMAND_MOVE_TO:
|
||||
plutovg_matrix_map_points(matrix, points, points, 1);
|
||||
plutovg_path_move_to(path, points[0].x, points[0].y);
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_LINE_TO:
|
||||
plutovg_matrix_map_points(matrix, points, points, 1);
|
||||
plutovg_path_line_to(path, points[0].x, points[0].y);
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CUBIC_TO:
|
||||
plutovg_matrix_map_points(matrix, points, points, 3);
|
||||
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:
|
||||
plutovg_path_close(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_path_traverse(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure)
|
||||
{
|
||||
plutovg_path_iterator_t it;
|
||||
plutovg_path_iterator_init(&it, path);
|
||||
|
||||
plutovg_point_t points[3];
|
||||
while(plutovg_path_iterator_has_next(&it)) {
|
||||
switch(plutovg_path_iterator_next(&it, points)) {
|
||||
case PLUTOVG_PATH_COMMAND_MOVE_TO:
|
||||
traverse_func(closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, 1);
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_LINE_TO:
|
||||
traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, points, 1);
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CUBIC_TO:
|
||||
traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3);
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CLOSE:
|
||||
traverse_func(closure, PLUTOVG_PATH_COMMAND_CLOSE, points, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
float x1; float y1;
|
||||
float x2; float y2;
|
||||
float x3; float y3;
|
||||
float x4; float y4;
|
||||
} bezier_t;
|
||||
|
||||
static inline void split_bezier(const bezier_t* b, bezier_t* first, bezier_t* second)
|
||||
{
|
||||
float c = (b->x2 + b->x3) * 0.5f;
|
||||
first->x2 = (b->x1 + b->x2) * 0.5f;
|
||||
second->x3 = (b->x3 + b->x4) * 0.5f;
|
||||
first->x1 = b->x1;
|
||||
second->x4 = b->x4;
|
||||
first->x3 = (first->x2 + c) * 0.5f;
|
||||
second->x2 = (second->x3 + c) * 0.5f;
|
||||
first->x4 = second->x1 = (first->x3 + second->x2) * 0.5f;
|
||||
|
||||
c = (b->y2 + b->y3) * 0.5f;
|
||||
first->y2 = (b->y1 + b->y2) * 0.5f;
|
||||
second->y3 = (b->y3 + b->y4) * 0.5f;
|
||||
first->y1 = b->y1;
|
||||
second->y4 = b->y4;
|
||||
first->y3 = (first->y2 + c) * 0.5f;
|
||||
second->y2 = (second->y3 + c) * 0.5f;
|
||||
first->y4 = second->y1 = (first->y3 + second->y2) * 0.5f;
|
||||
}
|
||||
|
||||
void plutovg_path_traverse_flatten(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure)
|
||||
{
|
||||
if(path->num_curves == 0) {
|
||||
plutovg_path_traverse(path, traverse_func, closure);
|
||||
return;
|
||||
}
|
||||
|
||||
const float threshold = 0.25f;
|
||||
|
||||
plutovg_path_iterator_t it;
|
||||
plutovg_path_iterator_init(&it, path);
|
||||
|
||||
bezier_t beziers[32];
|
||||
plutovg_point_t points[3];
|
||||
plutovg_point_t current_point = {0, 0};
|
||||
while(plutovg_path_iterator_has_next(&it)) {
|
||||
plutovg_path_command_t command = plutovg_path_iterator_next(&it, points);
|
||||
switch(command) {
|
||||
case PLUTOVG_PATH_COMMAND_MOVE_TO:
|
||||
case PLUTOVG_PATH_COMMAND_LINE_TO:
|
||||
case PLUTOVG_PATH_COMMAND_CLOSE:
|
||||
traverse_func(closure, command, points, 1);
|
||||
current_point = points[0];
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CUBIC_TO:
|
||||
beziers[0].x1 = current_point.x;
|
||||
beziers[0].y1 = current_point.y;
|
||||
beziers[0].x2 = points[0].x;
|
||||
beziers[0].y2 = points[0].y;
|
||||
beziers[0].x3 = points[1].x;
|
||||
beziers[0].y3 = points[1].y;
|
||||
beziers[0].x4 = points[2].x;
|
||||
beziers[0].y4 = points[2].y;
|
||||
bezier_t* b = beziers;
|
||||
while(b >= beziers) {
|
||||
float y4y1 = b->y4 - b->y1;
|
||||
float x4x1 = b->x4 - b->x1;
|
||||
float l = fabsf(x4x1) + fabsf(y4y1);
|
||||
float d;
|
||||
if(l > 1.f) {
|
||||
d = fabsf((x4x1)*(b->y1 - b->y2) - (y4y1)*(b->x1 - b->x2)) + fabsf((x4x1)*(b->y1 - b->y3) - (y4y1)*(b->x1 - b->x3));
|
||||
} else {
|
||||
d = fabsf(b->x1 - b->x2) + fabsf(b->y1 - b->y2) + fabsf(b->x1 - b->x3) + fabsf(b->y1 - b->y3);
|
||||
l = 1.f;
|
||||
}
|
||||
|
||||
if(d < threshold*l || b == beziers + 31) {
|
||||
plutovg_point_t p = { b->x4, b->y4 };
|
||||
traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p, 1);
|
||||
--b;
|
||||
} else {
|
||||
split_bezier(b, b + 1, b);
|
||||
++b;
|
||||
}
|
||||
}
|
||||
|
||||
current_point = points[2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const float* dashes; int ndashes;
|
||||
float start_phase; float phase;
|
||||
int start_index; int index;
|
||||
bool start_toggle; bool toggle;
|
||||
plutovg_point_t current_point;
|
||||
plutovg_path_traverse_func_t traverse_func;
|
||||
void* closure;
|
||||
} dasher_t;
|
||||
|
||||
static void dash_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints)
|
||||
{
|
||||
dasher_t* dasher = (dasher_t*)(closure);
|
||||
if(command == PLUTOVG_PATH_COMMAND_MOVE_TO) {
|
||||
if(dasher->start_toggle)
|
||||
dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, npoints);
|
||||
dasher->current_point = points[0];
|
||||
dasher->phase = dasher->start_phase;
|
||||
dasher->index = dasher->start_index;
|
||||
dasher->toggle = dasher->start_toggle;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(command == PLUTOVG_PATH_COMMAND_LINE_TO || command == PLUTOVG_PATH_COMMAND_CLOSE);
|
||||
plutovg_point_t p0 = dasher->current_point;
|
||||
plutovg_point_t p1 = points[0];
|
||||
float dx = p1.x - p0.x;
|
||||
float dy = p1.y - p0.y;
|
||||
float dist0 = sqrtf(dx*dx + dy*dy);
|
||||
float dist1 = 0.f;
|
||||
while(dist0 - dist1 > dasher->dashes[dasher->index % dasher->ndashes] - dasher->phase) {
|
||||
dist1 += dasher->dashes[dasher->index % dasher->ndashes] - dasher->phase;
|
||||
float a = dist1 / dist0;
|
||||
plutovg_point_t p = { p0.x + a * dx, p0.y + a * dy };
|
||||
if(dasher->toggle) {
|
||||
dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p, 1);
|
||||
} else {
|
||||
dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_MOVE_TO, &p, 1);
|
||||
}
|
||||
|
||||
dasher->phase = 0.f;
|
||||
dasher->toggle = !dasher->toggle;
|
||||
dasher->index++;
|
||||
}
|
||||
|
||||
if(dasher->toggle) {
|
||||
dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p1, 1);
|
||||
}
|
||||
|
||||
dasher->phase += dist0 - dist1;
|
||||
dasher->current_point = p1;
|
||||
}
|
||||
|
||||
void plutovg_path_traverse_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes, plutovg_path_traverse_func_t traverse_func, void* closure)
|
||||
{
|
||||
float dash_sum = 0.f;
|
||||
for(int i = 0; i < ndashes; ++i)
|
||||
dash_sum += dashes[i];
|
||||
if(ndashes % 2 == 1)
|
||||
dash_sum *= 2.f;
|
||||
if(dash_sum <= 0.f) {
|
||||
plutovg_path_traverse(path, traverse_func, closure);
|
||||
return;
|
||||
}
|
||||
|
||||
dasher_t dasher;
|
||||
dasher.dashes = dashes;
|
||||
dasher.ndashes = ndashes;
|
||||
dasher.start_phase = fmodf(offset, dash_sum);
|
||||
if(dasher.start_phase < 0.f)
|
||||
dasher.start_phase += dash_sum;
|
||||
dasher.start_index = 0;
|
||||
dasher.start_toggle = true;
|
||||
while(dasher.start_phase > 0.f && dasher.start_phase >= dasher.dashes[dasher.start_index % dasher.ndashes]) {
|
||||
dasher.start_phase -= dashes[dasher.start_index % dasher.ndashes];
|
||||
dasher.start_toggle = !dasher.start_toggle;
|
||||
dasher.start_index++;
|
||||
}
|
||||
|
||||
dasher.phase = dasher.start_phase;
|
||||
dasher.index = dasher.start_index;
|
||||
dasher.toggle = dasher.start_toggle;
|
||||
dasher.current_point = PLUTOVG_EMPTY_POINT;
|
||||
dasher.traverse_func = traverse_func;
|
||||
dasher.closure = closure;
|
||||
plutovg_path_traverse_flatten(path, dash_traverse_func, &dasher);
|
||||
}
|
||||
|
||||
plutovg_path_t* plutovg_path_clone(const plutovg_path_t* path)
|
||||
{
|
||||
plutovg_path_t* clone = plutovg_path_create();
|
||||
plutovg_array_append(clone->elements, path->elements);
|
||||
clone->start_point = path->start_point;
|
||||
clone->num_points = path->num_points;
|
||||
clone->num_contours = path->num_contours;
|
||||
clone->num_curves = path->num_curves;
|
||||
return clone;
|
||||
}
|
||||
|
||||
static void clone_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:
|
||||
plutovg_path_close(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
plutovg_path_t* plutovg_path_clone_flatten(const plutovg_path_t* path)
|
||||
{
|
||||
plutovg_path_t* clone = plutovg_path_create();
|
||||
plutovg_path_reserve(clone, path->elements.size + path->num_curves * 32);
|
||||
plutovg_path_traverse_flatten(path, clone_traverse_func, clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
plutovg_path_t* plutovg_path_clone_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes)
|
||||
{
|
||||
plutovg_path_t* clone = plutovg_path_create();
|
||||
plutovg_path_reserve(clone, path->elements.size + path->num_curves * 32);
|
||||
plutovg_path_traverse_dashed(path, offset, dashes, ndashes, clone_traverse_func, clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
plutovg_point_t current_point;
|
||||
bool is_first_point;
|
||||
float length;
|
||||
float x1;
|
||||
float y1;
|
||||
float x2;
|
||||
float y2;
|
||||
} extents_calculator_t;
|
||||
|
||||
static void extents_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints)
|
||||
{
|
||||
extents_calculator_t* calculator = (extents_calculator_t*)(closure);
|
||||
if(calculator->is_first_point) {
|
||||
assert(command == PLUTOVG_PATH_COMMAND_MOVE_TO);
|
||||
calculator->is_first_point = false;
|
||||
calculator->current_point = points[0];
|
||||
calculator->x1 = points[0].x;
|
||||
calculator->y1 = points[0].y;
|
||||
calculator->x2 = points[0].x;
|
||||
calculator->y2 = points[0].y;
|
||||
calculator->length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < npoints; ++i) {
|
||||
calculator->x1 = plutovg_min(calculator->x1, points[i].x);
|
||||
calculator->y1 = plutovg_min(calculator->y1, points[i].y);
|
||||
calculator->x2 = plutovg_max(calculator->x2, points[i].x);
|
||||
calculator->y2 = plutovg_max(calculator->y2, points[i].y);
|
||||
if(command != PLUTOVG_PATH_COMMAND_MOVE_TO)
|
||||
calculator->length += hypotf(points[i].x - calculator->current_point.x, points[i].y - calculator->current_point.y);
|
||||
calculator->current_point = points[i];
|
||||
}
|
||||
}
|
||||
|
||||
float plutovg_path_extents(const plutovg_path_t* path, plutovg_rect_t* extents, bool tight)
|
||||
{
|
||||
extents_calculator_t calculator = {{0, 0}, true, 0, 0, 0, 0, 0};
|
||||
if(tight) {
|
||||
plutovg_path_traverse_flatten(path, extents_traverse_func, &calculator);
|
||||
} else {
|
||||
plutovg_path_traverse(path, extents_traverse_func, &calculator);
|
||||
}
|
||||
|
||||
if(extents) {
|
||||
extents->x = calculator.x1;
|
||||
extents->y = calculator.y1;
|
||||
extents->w = calculator.x2 - calculator.x1;
|
||||
extents->h = calculator.y2 - calculator.y1;
|
||||
}
|
||||
|
||||
return calculator.length;
|
||||
}
|
||||
|
||||
float plutovg_path_length(const plutovg_path_t* path)
|
||||
{
|
||||
return plutovg_path_extents(path, NULL, true);
|
||||
}
|
||||
|
||||
static inline bool parse_arc_flag(const char** begin, const char* end, bool* flag)
|
||||
{
|
||||
if(plutovg_skip_delim(begin, end, '0'))
|
||||
*flag = 0;
|
||||
else if(plutovg_skip_delim(begin, end, '1'))
|
||||
*flag = 1;
|
||||
else
|
||||
return false;
|
||||
plutovg_skip_ws_or_comma(begin, end, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool parse_path_coordinates(const char** begin, const char* end, float values[6], int offset, int count)
|
||||
{
|
||||
for(int i = 0; i < count; i++) {
|
||||
if(!plutovg_parse_number(begin, end, values + offset + i))
|
||||
return false;
|
||||
plutovg_skip_ws_or_comma(begin, end, NULL);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool plutovg_path_parse(plutovg_path_t* path, const char* data, int length)
|
||||
{
|
||||
if(length == -1)
|
||||
length = strlen(data);
|
||||
const char* it = data;
|
||||
const char* end = it + length;
|
||||
|
||||
float values[6];
|
||||
bool flags[2];
|
||||
|
||||
float start_x = 0;
|
||||
float start_y = 0;
|
||||
float current_x = 0;
|
||||
float current_y = 0;
|
||||
float last_control_x = 0;
|
||||
float last_control_y = 0;
|
||||
|
||||
char command = 0;
|
||||
char last_command = 0;
|
||||
plutovg_skip_ws(&it, end);
|
||||
while(it < end) {
|
||||
if(PLUTOVG_IS_ALPHA(*it)) {
|
||||
command = *it++;
|
||||
plutovg_skip_ws(&it, end);
|
||||
}
|
||||
|
||||
if(!last_command && !(command == 'M' || command == 'm'))
|
||||
return false;
|
||||
if(command == 'M' || command == 'm') {
|
||||
if(!parse_path_coordinates(&it, end, values, 0, 2))
|
||||
return false;
|
||||
if(command == 'm') {
|
||||
values[0] += current_x;
|
||||
values[1] += current_y;
|
||||
}
|
||||
|
||||
plutovg_path_move_to(path, values[0], values[1]);
|
||||
current_x = start_x = values[0];
|
||||
current_y = start_y = values[1];
|
||||
command = command == 'm' ? 'l' : 'L';
|
||||
} else if(command == 'L' || command == 'l') {
|
||||
if(!parse_path_coordinates(&it, end, values, 0, 2))
|
||||
return false;
|
||||
if(command == 'l') {
|
||||
values[0] += current_x;
|
||||
values[1] += current_y;
|
||||
}
|
||||
|
||||
plutovg_path_line_to(path, values[0], values[1]);
|
||||
current_x = values[0];
|
||||
current_y = values[1];
|
||||
} else if(command == 'H' || command == 'h') {
|
||||
if(!parse_path_coordinates(&it, end, values, 0, 1))
|
||||
return false;
|
||||
if(command == 'h') {
|
||||
values[0] += current_x;
|
||||
}
|
||||
|
||||
plutovg_path_line_to(path, values[0], current_y);
|
||||
current_x = values[0];
|
||||
} else if(command == 'V' || command == 'v') {
|
||||
if(!parse_path_coordinates(&it, end, values, 1, 1))
|
||||
return false;
|
||||
if(command == 'v') {
|
||||
values[1] += current_y;
|
||||
}
|
||||
|
||||
plutovg_path_line_to(path, current_x, values[1]);
|
||||
current_y = values[1];
|
||||
} else if(command == 'Q' || command == 'q') {
|
||||
if(!parse_path_coordinates(&it, end, values, 0, 4))
|
||||
return false;
|
||||
if(command == 'q') {
|
||||
values[0] += current_x;
|
||||
values[1] += current_y;
|
||||
values[2] += current_x;
|
||||
values[3] += current_y;
|
||||
}
|
||||
|
||||
plutovg_path_quad_to(path, values[0], values[1], values[2], values[3]);
|
||||
last_control_x = values[0];
|
||||
last_control_y = values[1];
|
||||
current_x = values[2];
|
||||
current_y = values[3];
|
||||
} else if(command == 'C' || command == 'c') {
|
||||
if(!parse_path_coordinates(&it, end, values, 0, 6))
|
||||
return false;
|
||||
if(command == 'c') {
|
||||
values[0] += current_x;
|
||||
values[1] += current_y;
|
||||
values[2] += current_x;
|
||||
values[3] += current_y;
|
||||
values[4] += current_x;
|
||||
values[5] += current_y;
|
||||
}
|
||||
|
||||
plutovg_path_cubic_to(path, values[0], values[1], values[2], values[3], values[4], values[5]);
|
||||
last_control_x = values[2];
|
||||
last_control_y = values[3];
|
||||
current_x = values[4];
|
||||
current_y = values[5];
|
||||
} else if(command == 'T' || command == 't') {
|
||||
if(last_command != 'Q' && last_command != 'q' && last_command != 'T' && last_command != 't') {
|
||||
values[0] = current_x;
|
||||
values[1] = current_y;
|
||||
} else {
|
||||
values[0] = 2 * current_x - last_control_x;
|
||||
values[1] = 2 * current_y - last_control_y;
|
||||
}
|
||||
|
||||
if(!parse_path_coordinates(&it, end, values, 2, 2))
|
||||
return false;
|
||||
if(command == 't') {
|
||||
values[2] += current_x;
|
||||
values[3] += current_y;
|
||||
}
|
||||
|
||||
plutovg_path_quad_to(path, values[0], values[1], values[2], values[3]);
|
||||
last_control_x = values[0];
|
||||
last_control_y = values[1];
|
||||
current_x = values[2];
|
||||
current_y = values[3];
|
||||
} else if(command == 'S' || command == 's') {
|
||||
if(last_command != 'C' && last_command != 'c' && last_command != 'S' && last_command != 's') {
|
||||
values[0] = current_x;
|
||||
values[1] = current_y;
|
||||
} else {
|
||||
values[0] = 2 * current_x - last_control_x;
|
||||
values[1] = 2 * current_y - last_control_y;
|
||||
}
|
||||
|
||||
if(!parse_path_coordinates(&it, end, values, 2, 4))
|
||||
return false;
|
||||
if(command == 's') {
|
||||
values[2] += current_x;
|
||||
values[3] += current_y;
|
||||
values[4] += current_x;
|
||||
values[5] += current_y;
|
||||
}
|
||||
|
||||
plutovg_path_cubic_to(path, values[0], values[1], values[2], values[3], values[4], values[5]);
|
||||
last_control_x = values[2];
|
||||
last_control_y = values[3];
|
||||
current_x = values[4];
|
||||
current_y = values[5];
|
||||
} else if(command == 'A' || command == 'a') {
|
||||
if(!parse_path_coordinates(&it, end, values, 0, 3)
|
||||
|| !parse_arc_flag(&it, end, &flags[0])
|
||||
|| !parse_arc_flag(&it, end, &flags[1])
|
||||
|| !parse_path_coordinates(&it, end, values, 3, 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(command == 'a') {
|
||||
values[3] += current_x;
|
||||
values[4] += current_y;
|
||||
}
|
||||
|
||||
plutovg_path_arc_to(path, values[0], values[1], PLUTOVG_DEG2RAD(values[2]), flags[0], flags[1], values[3], values[4]);
|
||||
current_x = values[3];
|
||||
current_y = values[4];
|
||||
} else if(command == 'Z' || command == 'z') {
|
||||
if(last_command == 'Z' || last_command == 'z')
|
||||
return false;
|
||||
plutovg_path_close(path);
|
||||
current_x = start_x;
|
||||
current_y = start_y;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
last_command = command;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
180
vendor/lunasvg/plutovg/source/plutovg-private.h
vendored
180
vendor/lunasvg/plutovg/source/plutovg-private.h
vendored
@@ -1,180 +0,0 @@
|
||||
#ifndef PLUTOVG_PRIVATE_H
|
||||
#define PLUTOVG_PRIVATE_H
|
||||
|
||||
#include "plutovg.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
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 <stdatomic.h>
|
||||
|
||||
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 {
|
||||
plutovg_ref_count_t ref_count;
|
||||
int width;
|
||||
int height;
|
||||
int stride;
|
||||
unsigned char* data;
|
||||
};
|
||||
|
||||
struct plutovg_path {
|
||||
plutovg_ref_count_t ref_count;
|
||||
int num_points;
|
||||
int num_contours;
|
||||
int num_curves;
|
||||
plutovg_point_t start_point;
|
||||
struct {
|
||||
plutovg_path_element_t* data;
|
||||
int size;
|
||||
int capacity;
|
||||
} elements;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
PLUTOVG_PAINT_TYPE_COLOR,
|
||||
PLUTOVG_PAINT_TYPE_GRADIENT,
|
||||
PLUTOVG_PAINT_TYPE_TEXTURE
|
||||
} plutovg_paint_type_t;
|
||||
|
||||
struct plutovg_paint {
|
||||
plutovg_ref_count_t ref_count;
|
||||
plutovg_paint_type_t type;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
plutovg_paint_t base;
|
||||
plutovg_color_t color;
|
||||
} plutovg_solid_paint_t;
|
||||
|
||||
typedef enum {
|
||||
PLUTOVG_GRADIENT_TYPE_LINEAR,
|
||||
PLUTOVG_GRADIENT_TYPE_RADIAL
|
||||
} plutovg_gradient_type_t;
|
||||
|
||||
typedef struct {
|
||||
plutovg_paint_t base;
|
||||
plutovg_gradient_type_t type;
|
||||
plutovg_spread_method_t spread;
|
||||
plutovg_matrix_t matrix;
|
||||
plutovg_gradient_stop_t* stops;
|
||||
int nstops;
|
||||
float values[6];
|
||||
} plutovg_gradient_paint_t;
|
||||
|
||||
typedef struct {
|
||||
plutovg_paint_t base;
|
||||
plutovg_texture_type_t type;
|
||||
float opacity;
|
||||
plutovg_matrix_t matrix;
|
||||
plutovg_surface_t* surface;
|
||||
} plutovg_texture_paint_t;
|
||||
|
||||
typedef struct {
|
||||
int x;
|
||||
int len;
|
||||
int y;
|
||||
unsigned char coverage;
|
||||
} plutovg_span_t;
|
||||
|
||||
typedef struct {
|
||||
struct {
|
||||
plutovg_span_t* data;
|
||||
int size;
|
||||
int capacity;
|
||||
} spans;
|
||||
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
int h;
|
||||
} plutovg_span_buffer_t;
|
||||
|
||||
typedef struct {
|
||||
float offset;
|
||||
struct {
|
||||
float* data;
|
||||
int size;
|
||||
int capacity;
|
||||
} array;
|
||||
} plutovg_stroke_dash_t;
|
||||
|
||||
typedef struct {
|
||||
float width;
|
||||
plutovg_line_cap_t cap;
|
||||
plutovg_line_join_t join;
|
||||
float miter_limit;
|
||||
} plutovg_stroke_style_t;
|
||||
|
||||
typedef struct {
|
||||
plutovg_stroke_style_t style;
|
||||
plutovg_stroke_dash_t dash;
|
||||
} plutovg_stroke_data_t;
|
||||
|
||||
typedef struct plutovg_state {
|
||||
plutovg_paint_t* paint;
|
||||
plutovg_font_face_t* font_face;
|
||||
plutovg_color_t color;
|
||||
plutovg_matrix_t matrix;
|
||||
plutovg_stroke_data_t stroke;
|
||||
plutovg_span_buffer_t clip_spans;
|
||||
plutovg_fill_rule_t winding;
|
||||
plutovg_operator_t op;
|
||||
float font_size;
|
||||
float opacity;
|
||||
bool clipping;
|
||||
struct plutovg_state* next;
|
||||
} plutovg_state_t;
|
||||
|
||||
struct plutovg_canvas {
|
||||
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;
|
||||
};
|
||||
|
||||
void plutovg_span_buffer_init(plutovg_span_buffer_t* span_buffer);
|
||||
void plutovg_span_buffer_init_rect(plutovg_span_buffer_t* span_buffer, int x, int y, int width, int height);
|
||||
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);
|
||||
|
||||
void plutovg_rasterize(plutovg_span_buffer_t* span_buffer, const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_rect_t* clip_rect, const plutovg_stroke_data_t* stroke_data, plutovg_fill_rule_t winding);
|
||||
void plutovg_blend(plutovg_canvas_t* canvas, const plutovg_span_buffer_t* span_buffer);
|
||||
void plutovg_memfill32(unsigned int* dest, int length, unsigned int value);
|
||||
|
||||
#endif // PLUTOVG_PRIVATE_H
|
||||
394
vendor/lunasvg/plutovg/source/plutovg-rasterize.c
vendored
394
vendor/lunasvg/plutovg/source/plutovg-rasterize.c
vendored
@@ -1,394 +0,0 @@
|
||||
#include "plutovg-private.h"
|
||||
#include "plutovg-utils.h"
|
||||
|
||||
#include "plutovg-ft-raster.h"
|
||||
#include "plutovg-ft-stroker.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
void plutovg_span_buffer_init(plutovg_span_buffer_t* span_buffer)
|
||||
{
|
||||
plutovg_array_init(span_buffer->spans);
|
||||
plutovg_span_buffer_reset(span_buffer);
|
||||
}
|
||||
|
||||
void plutovg_span_buffer_init_rect(plutovg_span_buffer_t* span_buffer, int x, int y, int width, int height)
|
||||
{
|
||||
plutovg_array_clear(span_buffer->spans);
|
||||
plutovg_array_ensure(span_buffer->spans, height);
|
||||
plutovg_span_t* spans = span_buffer->spans.data;
|
||||
for(int i = 0; i < height; i++) {
|
||||
spans[i].x = x;
|
||||
spans[i].y = y + i;
|
||||
spans[i].len = width;
|
||||
spans[i].coverage = 255;
|
||||
}
|
||||
|
||||
span_buffer->x = x;
|
||||
span_buffer->y = y;
|
||||
span_buffer->w = width;
|
||||
span_buffer->h = height;
|
||||
span_buffer->spans.size = height;
|
||||
}
|
||||
|
||||
void plutovg_span_buffer_reset(plutovg_span_buffer_t* span_buffer)
|
||||
{
|
||||
plutovg_array_clear(span_buffer->spans);
|
||||
span_buffer->x = 0;
|
||||
span_buffer->y = 0;
|
||||
span_buffer->w = -1;
|
||||
span_buffer->h = -1;
|
||||
}
|
||||
|
||||
void plutovg_span_buffer_destroy(plutovg_span_buffer_t* span_buffer)
|
||||
{
|
||||
plutovg_array_destroy(span_buffer->spans);
|
||||
}
|
||||
|
||||
void plutovg_span_buffer_copy(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* source)
|
||||
{
|
||||
plutovg_array_clear(span_buffer->spans);
|
||||
plutovg_array_append(span_buffer->spans, source->spans);
|
||||
span_buffer->x = source->x;
|
||||
span_buffer->y = source->y;
|
||||
span_buffer->w = source->w;
|
||||
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)
|
||||
return;
|
||||
if(span_buffer->spans.size == 0) {
|
||||
span_buffer->x = 0;
|
||||
span_buffer->y = 0;
|
||||
span_buffer->w = 0;
|
||||
span_buffer->h = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
plutovg_span_t* spans = span_buffer->spans.data;
|
||||
int x1 = INT_MAX;
|
||||
int y1 = spans[0].y;
|
||||
int x2 = 0;
|
||||
int y2 = spans[span_buffer->spans.size - 1].y;
|
||||
for(int i = 0; i < span_buffer->spans.size; i++) {
|
||||
if(spans[i].x < x1) x1 = spans[i].x;
|
||||
if(spans[i].x + spans[i].len > x2) x2 = spans[i].x + spans[i].len;
|
||||
}
|
||||
|
||||
span_buffer->x = x1;
|
||||
span_buffer->y = y1;
|
||||
span_buffer->w = x2 - x1;
|
||||
span_buffer->h = y2 - y1 + 1;
|
||||
}
|
||||
|
||||
void plutovg_span_buffer_extents(plutovg_span_buffer_t* span_buffer, plutovg_rect_t* extents)
|
||||
{
|
||||
plutovg_span_buffer_update_extents(span_buffer);
|
||||
extents->x = span_buffer->x;
|
||||
extents->y = span_buffer->y;
|
||||
extents->w = span_buffer->w;
|
||||
extents->h = span_buffer->h;
|
||||
}
|
||||
|
||||
void plutovg_span_buffer_intersect(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* a, const plutovg_span_buffer_t* b)
|
||||
{
|
||||
plutovg_span_buffer_reset(span_buffer);
|
||||
plutovg_array_ensure(span_buffer->spans, plutovg_max(a->spans.size, b->spans.size));
|
||||
|
||||
plutovg_span_t* a_spans = a->spans.data;
|
||||
plutovg_span_t* a_end = a_spans + a->spans.size;
|
||||
|
||||
plutovg_span_t* b_spans = b->spans.data;
|
||||
plutovg_span_t* b_end = b_spans + b->spans.size;
|
||||
while(a_spans < a_end && b_spans < b_end) {
|
||||
if(b_spans->y > a_spans->y) {
|
||||
++a_spans;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(a_spans->y != b_spans->y) {
|
||||
++b_spans;
|
||||
continue;
|
||||
}
|
||||
|
||||
int ax1 = a_spans->x;
|
||||
int ax2 = ax1 + a_spans->len;
|
||||
int bx1 = b_spans->x;
|
||||
int bx2 = bx1 + b_spans->len;
|
||||
if(bx1 < ax1 && bx2 < ax1) {
|
||||
++b_spans;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ax1 < bx1 && ax2 < bx1) {
|
||||
++a_spans;
|
||||
continue;
|
||||
}
|
||||
|
||||
int x = plutovg_max(ax1, bx1);
|
||||
int len = plutovg_min(ax2, bx2) - x;
|
||||
if(len) {
|
||||
plutovg_array_ensure(span_buffer->spans, 1);
|
||||
plutovg_span_t* span = span_buffer->spans.data + span_buffer->spans.size;
|
||||
span->x = x;
|
||||
span->len = len;
|
||||
span->y = a_spans->y;
|
||||
span->coverage = (a_spans->coverage * b_spans->coverage) / 255;
|
||||
span_buffer->spans.size += 1;
|
||||
}
|
||||
|
||||
if(ax2 < bx2) {
|
||||
++a_spans;
|
||||
} else {
|
||||
++b_spans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define ALIGN_SIZE(size) (((size) + 7ul) & ~7ul)
|
||||
static PVG_FT_Outline* ft_outline_create(int points, int contours)
|
||||
{
|
||||
size_t points_size = ALIGN_SIZE((points + contours) * sizeof(PVG_FT_Vector));
|
||||
size_t tags_size = ALIGN_SIZE((points + contours) * sizeof(char));
|
||||
size_t contours_size = ALIGN_SIZE(contours * sizeof(int));
|
||||
size_t contours_flag_size = ALIGN_SIZE(contours * sizeof(char));
|
||||
PVG_FT_Outline* outline = malloc(points_size + tags_size + contours_size + contours_flag_size + sizeof(PVG_FT_Outline));
|
||||
|
||||
PVG_FT_Byte* outline_data = (PVG_FT_Byte*)(outline + 1);
|
||||
outline->points = (PVG_FT_Vector*)(outline_data);
|
||||
outline->tags = (char*)(outline_data + points_size);
|
||||
outline->contours = (int*)(outline_data + points_size + tags_size);
|
||||
outline->contours_flag = (char*)(outline_data + points_size + tags_size + contours_size);
|
||||
outline->n_points = 0;
|
||||
outline->n_contours = 0;
|
||||
outline->flags = 0x0;
|
||||
return outline;
|
||||
}
|
||||
|
||||
static void ft_outline_destroy(PVG_FT_Outline* outline)
|
||||
{
|
||||
free(outline);
|
||||
}
|
||||
|
||||
#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);
|
||||
ft->points[ft->n_points].y = FT_COORD(y);
|
||||
ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON;
|
||||
if(ft->n_points) {
|
||||
ft->contours[ft->n_contours] = ft->n_points - 1;
|
||||
ft->n_contours++;
|
||||
}
|
||||
|
||||
ft->contours_flag[ft->n_contours] = 1;
|
||||
ft->n_points++;
|
||||
}
|
||||
|
||||
static void ft_outline_line_to(PVG_FT_Outline* ft, float x, float y)
|
||||
{
|
||||
ft->points[ft->n_points].x = FT_COORD(x);
|
||||
ft->points[ft->n_points].y = FT_COORD(y);
|
||||
ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON;
|
||||
ft->n_points++;
|
||||
}
|
||||
|
||||
static void ft_outline_cubic_to(PVG_FT_Outline* ft, float x1, float y1, float x2, float y2, float x3, float y3)
|
||||
{
|
||||
ft->points[ft->n_points].x = FT_COORD(x1);
|
||||
ft->points[ft->n_points].y = FT_COORD(y1);
|
||||
ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_CUBIC;
|
||||
ft->n_points++;
|
||||
|
||||
ft->points[ft->n_points].x = FT_COORD(x2);
|
||||
ft->points[ft->n_points].y = FT_COORD(y2);
|
||||
ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_CUBIC;
|
||||
ft->n_points++;
|
||||
|
||||
ft->points[ft->n_points].x = FT_COORD(x3);
|
||||
ft->points[ft->n_points].y = FT_COORD(y3);
|
||||
ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON;
|
||||
ft->n_points++;
|
||||
}
|
||||
|
||||
static void ft_outline_close(PVG_FT_Outline* ft)
|
||||
{
|
||||
ft->contours_flag[ft->n_contours] = 0;
|
||||
int index = ft->n_contours ? ft->contours[ft->n_contours - 1] + 1 : 0;
|
||||
if(index == ft->n_points)
|
||||
return;
|
||||
ft->points[ft->n_points].x = ft->points[index].x;
|
||||
ft->points[ft->n_points].y = ft->points[index].y;
|
||||
ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON;
|
||||
ft->n_points++;
|
||||
}
|
||||
|
||||
static void ft_outline_end(PVG_FT_Outline* ft)
|
||||
{
|
||||
if(ft->n_points) {
|
||||
ft->contours[ft->n_contours] = ft->n_points - 1;
|
||||
ft->n_contours++;
|
||||
}
|
||||
}
|
||||
|
||||
static PVG_FT_Outline* ft_outline_convert_stroke(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_data_t* stroke_data);
|
||||
|
||||
static PVG_FT_Outline* ft_outline_convert(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_data_t* stroke_data)
|
||||
{
|
||||
if(stroke_data) {
|
||||
return ft_outline_convert_stroke(path, matrix, stroke_data);
|
||||
}
|
||||
|
||||
plutovg_path_iterator_t it;
|
||||
plutovg_path_iterator_init(&it, path);
|
||||
|
||||
plutovg_point_t points[3];
|
||||
PVG_FT_Outline* outline = ft_outline_create(path->num_points, path->num_contours);
|
||||
while(plutovg_path_iterator_has_next(&it)) {
|
||||
switch(plutovg_path_iterator_next(&it, points)) {
|
||||
case PLUTOVG_PATH_COMMAND_MOVE_TO:
|
||||
plutovg_matrix_map_points(matrix, points, points, 1);
|
||||
ft_outline_move_to(outline, points[0].x, points[0].y);
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_LINE_TO:
|
||||
plutovg_matrix_map_points(matrix, points, points, 1);
|
||||
ft_outline_line_to(outline, points[0].x, points[0].y);
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CUBIC_TO:
|
||||
plutovg_matrix_map_points(matrix, points, points, 3);
|
||||
ft_outline_cubic_to(outline, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y);
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CLOSE:
|
||||
ft_outline_close(outline);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ft_outline_end(outline);
|
||||
return outline;
|
||||
}
|
||||
|
||||
static PVG_FT_Outline* ft_outline_convert_dash(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_dash_t* stroke_dash)
|
||||
{
|
||||
if(stroke_dash->array.size == 0)
|
||||
return ft_outline_convert(path, matrix, NULL);
|
||||
plutovg_path_t* dashed = plutovg_path_clone_dashed(path, stroke_dash->offset, stroke_dash->array.data, stroke_dash->array.size);
|
||||
PVG_FT_Outline* outline = ft_outline_convert(dashed, matrix, NULL);
|
||||
plutovg_path_destroy(dashed);
|
||||
return outline;
|
||||
}
|
||||
|
||||
static PVG_FT_Outline* ft_outline_convert_stroke(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_data_t* stroke_data)
|
||||
{
|
||||
double scale_x = sqrt(matrix->a * matrix->a + matrix->b * matrix->b);
|
||||
double scale_y = sqrt(matrix->c * matrix->c + matrix->d * matrix->d);
|
||||
|
||||
double scale = hypot(scale_x, scale_y) / PLUTOVG_SQRT2;
|
||||
double width = stroke_data->style.width * scale;
|
||||
|
||||
PVG_FT_Fixed ftWidth = (PVG_FT_Fixed)(width * 0.5 * (1 << 6));
|
||||
PVG_FT_Fixed ftMiterLimit = (PVG_FT_Fixed)(stroke_data->style.miter_limit * (1 << 16));
|
||||
|
||||
PVG_FT_Stroker_LineCap ftCap;
|
||||
switch(stroke_data->style.cap) {
|
||||
case PLUTOVG_LINE_CAP_SQUARE:
|
||||
ftCap = PVG_FT_STROKER_LINECAP_SQUARE;
|
||||
break;
|
||||
case PLUTOVG_LINE_CAP_ROUND:
|
||||
ftCap = PVG_FT_STROKER_LINECAP_ROUND;
|
||||
break;
|
||||
default:
|
||||
ftCap = PVG_FT_STROKER_LINECAP_BUTT;
|
||||
break;
|
||||
}
|
||||
|
||||
PVG_FT_Stroker_LineJoin ftJoin;
|
||||
switch(stroke_data->style.join) {
|
||||
case PLUTOVG_LINE_JOIN_BEVEL:
|
||||
ftJoin = PVG_FT_STROKER_LINEJOIN_BEVEL;
|
||||
break;
|
||||
case PLUTOVG_LINE_JOIN_ROUND:
|
||||
ftJoin = PVG_FT_STROKER_LINEJOIN_ROUND;
|
||||
break;
|
||||
default:
|
||||
ftJoin = PVG_FT_STROKER_LINEJOIN_MITER_FIXED;
|
||||
break;
|
||||
}
|
||||
|
||||
PVG_FT_Stroker stroker;
|
||||
PVG_FT_Stroker_New(&stroker);
|
||||
PVG_FT_Stroker_Set(stroker, ftWidth, ftCap, ftJoin, ftMiterLimit);
|
||||
|
||||
PVG_FT_Outline* outline = ft_outline_convert_dash(path, matrix, &stroke_data->dash);
|
||||
PVG_FT_Stroker_ParseOutline(stroker, outline);
|
||||
|
||||
PVG_FT_UInt points;
|
||||
PVG_FT_UInt contours;
|
||||
PVG_FT_Stroker_GetCounts(stroker, &points, &contours);
|
||||
|
||||
PVG_FT_Outline* stroke_outline = ft_outline_create(points, contours);
|
||||
PVG_FT_Stroker_Export(stroker, stroke_outline);
|
||||
|
||||
PVG_FT_Stroker_Done(stroker);
|
||||
ft_outline_destroy(outline);
|
||||
return stroke_outline;
|
||||
}
|
||||
|
||||
static void spans_generation_callback(int count, const PVG_FT_Span* spans, void* user)
|
||||
{
|
||||
plutovg_span_buffer_t* span_buffer = (plutovg_span_buffer_t*)(user);
|
||||
plutovg_array_append_data(span_buffer->spans, spans, count);
|
||||
}
|
||||
|
||||
void plutovg_rasterize(plutovg_span_buffer_t* span_buffer, const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_rect_t* clip_rect, const plutovg_stroke_data_t* stroke_data, plutovg_fill_rule_t winding)
|
||||
{
|
||||
PVG_FT_Outline* outline = ft_outline_convert(path, matrix, stroke_data);
|
||||
if(stroke_data) {
|
||||
outline->flags = PVG_FT_OUTLINE_NONE;
|
||||
} else {
|
||||
switch(winding) {
|
||||
case PLUTOVG_FILL_RULE_EVEN_ODD:
|
||||
outline->flags = PVG_FT_OUTLINE_EVEN_ODD_FILL;
|
||||
break;
|
||||
default:
|
||||
outline->flags = PVG_FT_OUTLINE_NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PVG_FT_Raster_Params params;
|
||||
params.flags = PVG_FT_RASTER_FLAG_DIRECT | PVG_FT_RASTER_FLAG_AA;
|
||||
params.gray_spans = spans_generation_callback;
|
||||
params.user = span_buffer;
|
||||
params.source = outline;
|
||||
if(clip_rect) {
|
||||
params.flags |= PVG_FT_RASTER_FLAG_CLIP;
|
||||
params.clip_box.xMin = (PVG_FT_Pos)clip_rect->x;
|
||||
params.clip_box.yMin = (PVG_FT_Pos)clip_rect->y;
|
||||
params.clip_box.xMax = (PVG_FT_Pos)(clip_rect->x + clip_rect->w);
|
||||
params.clip_box.yMax = (PVG_FT_Pos)(clip_rect->y + clip_rect->h);
|
||||
}
|
||||
|
||||
plutovg_span_buffer_reset(span_buffer);
|
||||
PVG_FT_Raster_Render(¶ms);
|
||||
ft_outline_destroy(outline);
|
||||
}
|
||||
1724
vendor/lunasvg/plutovg/source/plutovg-stb-image-write.h
vendored
1724
vendor/lunasvg/plutovg/source/plutovg-stb-image-write.h
vendored
File diff suppressed because it is too large
Load Diff
7988
vendor/lunasvg/plutovg/source/plutovg-stb-image.h
vendored
7988
vendor/lunasvg/plutovg/source/plutovg-stb-image.h
vendored
File diff suppressed because it is too large
Load Diff
5077
vendor/lunasvg/plutovg/source/plutovg-stb-truetype.h
vendored
5077
vendor/lunasvg/plutovg/source/plutovg-stb-truetype.h
vendored
File diff suppressed because it is too large
Load Diff
295
vendor/lunasvg/plutovg/source/plutovg-surface.c
vendored
295
vendor/lunasvg/plutovg/source/plutovg-surface.c
vendored
@@ -1,295 +0,0 @@
|
||||
#include "plutovg-private.h"
|
||||
#include "plutovg-utils.h"
|
||||
|
||||
#define STB_IMAGE_WRITE_STATIC
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "plutovg-stb-image-write.h"
|
||||
|
||||
#define STB_IMAGE_STATIC
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "plutovg-stb-image.h"
|
||||
|
||||
static plutovg_surface_t* plutovg_surface_create_uninitialized(int width, int height)
|
||||
{
|
||||
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;
|
||||
plutovg_init_reference(surface);
|
||||
surface->width = width;
|
||||
surface->height = height;
|
||||
surface->stride = width * 4;
|
||||
surface->data = (uint8_t*)(surface + 1);
|
||||
return surface;
|
||||
}
|
||||
|
||||
plutovg_surface_t* plutovg_surface_create(int width, int height)
|
||||
{
|
||||
plutovg_surface_t* surface = plutovg_surface_create_uninitialized(width, height);
|
||||
if(surface)
|
||||
memset(surface->data, 0, surface->height * surface->stride);
|
||||
return surface;
|
||||
}
|
||||
|
||||
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));
|
||||
plutovg_init_reference(surface);
|
||||
surface->width = width;
|
||||
surface->height = height;
|
||||
surface->stride = stride;
|
||||
surface->data = data;
|
||||
return surface;
|
||||
}
|
||||
|
||||
static plutovg_surface_t* plutovg_surface_load_from_image(stbi_uc* image, int width, int height)
|
||||
{
|
||||
plutovg_surface_t* surface = plutovg_surface_create_uninitialized(width, height);
|
||||
if(surface)
|
||||
plutovg_convert_rgba_to_argb(surface->data, image, surface->width, surface->height, surface->stride);
|
||||
stbi_image_free(image);
|
||||
return surface;
|
||||
}
|
||||
|
||||
plutovg_surface_t* plutovg_surface_load_from_image_file(const char* filename)
|
||||
{
|
||||
int width, height, channels;
|
||||
stbi_uc* image = stbi_load(filename, &width, &height, &channels, STBI_rgb_alpha);
|
||||
if(image == NULL)
|
||||
return NULL;
|
||||
return plutovg_surface_load_from_image(image, width, height);
|
||||
}
|
||||
|
||||
plutovg_surface_t* plutovg_surface_load_from_image_data(const void* data, int length)
|
||||
{
|
||||
int width, height, channels;
|
||||
stbi_uc* image = stbi_load_from_memory(data, length, &width, &height, &channels, STBI_rgb_alpha);
|
||||
if(image == NULL)
|
||||
return NULL;
|
||||
return plutovg_surface_load_from_image(image, width, height);
|
||||
}
|
||||
|
||||
static const uint8_t base64_table[128] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3F,
|
||||
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
|
||||
0x3C, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
|
||||
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||
0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
|
||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
|
||||
0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
plutovg_surface_t* plutovg_surface_load_from_image_base64(const char* data, int length)
|
||||
{
|
||||
plutovg_surface_t* surface = NULL;
|
||||
uint8_t* output_data = NULL;
|
||||
size_t output_length = 0;
|
||||
|
||||
size_t equals_sign_count = 0;
|
||||
size_t sidx = 0;
|
||||
size_t didx = 0;
|
||||
|
||||
if(length == -1)
|
||||
length = strlen(data);
|
||||
output_data = malloc(length);
|
||||
if(output_data == NULL)
|
||||
return NULL;
|
||||
for(int i = 0; i < length; ++i) {
|
||||
uint8_t cc = data[i];
|
||||
if(cc == '=') {
|
||||
++equals_sign_count;
|
||||
} else if(cc == '+' || cc == '/' || PLUTOVG_IS_ALNUM(cc)) {
|
||||
if(equals_sign_count > 0)
|
||||
goto cleanup;
|
||||
output_data[output_length++] = base64_table[cc];
|
||||
} else if(!PLUTOVG_IS_WS(cc)) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if(output_length == 0 || equals_sign_count > 2 || (output_length % 4) == 1)
|
||||
goto cleanup;
|
||||
output_length -= (output_length + 3) / 4;
|
||||
if(output_length == 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if(output_length > 1) {
|
||||
while(didx < output_length - 2) {
|
||||
output_data[didx + 0] = (((output_data[sidx + 0] << 2) & 255) | ((output_data[sidx + 1] >> 4) & 003));
|
||||
output_data[didx + 1] = (((output_data[sidx + 1] << 4) & 255) | ((output_data[sidx + 2] >> 2) & 017));
|
||||
output_data[didx + 2] = (((output_data[sidx + 2] << 6) & 255) | ((output_data[sidx + 3] >> 0) & 077));
|
||||
sidx += 4;
|
||||
didx += 3;
|
||||
}
|
||||
}
|
||||
|
||||
if(didx < output_length)
|
||||
output_data[didx] = (((output_data[sidx + 0] << 2) & 255) | ((output_data[sidx + 1] >> 4) & 003));
|
||||
if(++didx < output_length) {
|
||||
output_data[didx] = (((output_data[sidx + 1] << 4) & 255) | ((output_data[sidx + 2] >> 2) & 017));
|
||||
}
|
||||
|
||||
surface = plutovg_surface_load_from_image_data(output_data, output_length);
|
||||
cleanup:
|
||||
free(output_data);
|
||||
return surface;
|
||||
}
|
||||
|
||||
plutovg_surface_t* plutovg_surface_reference(plutovg_surface_t* surface)
|
||||
{
|
||||
plutovg_increment_reference(surface);
|
||||
return surface;
|
||||
}
|
||||
|
||||
void plutovg_surface_destroy(plutovg_surface_t* surface)
|
||||
{
|
||||
if(plutovg_destroy_reference(surface)) {
|
||||
free(surface);
|
||||
}
|
||||
}
|
||||
|
||||
int plutovg_surface_get_reference_count(const plutovg_surface_t* surface)
|
||||
{
|
||||
return plutovg_get_reference_count(surface);
|
||||
}
|
||||
|
||||
unsigned char* plutovg_surface_get_data(const plutovg_surface_t* surface)
|
||||
{
|
||||
return surface->data;
|
||||
}
|
||||
|
||||
int plutovg_surface_get_width(const plutovg_surface_t* surface)
|
||||
{
|
||||
return surface->width;
|
||||
}
|
||||
|
||||
int plutovg_surface_get_height(const plutovg_surface_t* surface)
|
||||
{
|
||||
return surface->height;
|
||||
}
|
||||
|
||||
int plutovg_surface_get_stride(const plutovg_surface_t* surface)
|
||||
{
|
||||
return surface->stride;
|
||||
}
|
||||
|
||||
void plutovg_surface_clear(plutovg_surface_t* surface, const plutovg_color_t* color)
|
||||
{
|
||||
uint32_t pixel = plutovg_premultiply_argb(plutovg_color_to_argb32(color));
|
||||
for(int y = 0; y < surface->height; y++) {
|
||||
uint32_t* pixels = (uint32_t*)(surface->data + surface->stride * y);
|
||||
plutovg_memfill32(pixels, surface->width, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
static void plutovg_surface_write_begin(const plutovg_surface_t* surface)
|
||||
{
|
||||
plutovg_convert_argb_to_rgba(surface->data, surface->data, surface->width, surface->height, surface->stride);
|
||||
}
|
||||
|
||||
static void plutovg_surface_write_end(const plutovg_surface_t* surface)
|
||||
{
|
||||
plutovg_convert_rgba_to_argb(surface->data, surface->data, surface->width, surface->height, surface->stride);
|
||||
}
|
||||
|
||||
bool plutovg_surface_write_to_png(const plutovg_surface_t* surface, const char* filename)
|
||||
{
|
||||
plutovg_surface_write_begin(surface);
|
||||
int success = stbi_write_png(filename, surface->width, surface->height, 4, surface->data, surface->stride);
|
||||
plutovg_surface_write_end(surface);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool plutovg_surface_write_to_jpg(const plutovg_surface_t* surface, const char* filename, int quality)
|
||||
{
|
||||
plutovg_surface_write_begin(surface);
|
||||
int success = stbi_write_jpg(filename, surface->width, surface->height, 4, surface->data, quality);
|
||||
plutovg_surface_write_end(surface);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool plutovg_surface_write_to_png_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure)
|
||||
{
|
||||
plutovg_surface_write_begin(surface);
|
||||
int success = stbi_write_png_to_func(write_func, closure, surface->width, surface->height, 4, surface->data, surface->stride);
|
||||
plutovg_surface_write_end(surface);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool plutovg_surface_write_to_jpg_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure, int quality)
|
||||
{
|
||||
plutovg_surface_write_begin(surface);
|
||||
int success = stbi_write_jpg_to_func(write_func, closure, surface->width, surface->height, 4, surface->data, quality);
|
||||
plutovg_surface_write_end(surface);
|
||||
return success;
|
||||
}
|
||||
|
||||
void plutovg_convert_argb_to_rgba(unsigned char* dst, const unsigned char* src, int width, int height, int stride)
|
||||
{
|
||||
for(int y = 0; y < height; y++) {
|
||||
const uint32_t* src_row = (const uint32_t*)(src + stride * y);
|
||||
unsigned char* dst_row = dst + stride * y;
|
||||
for(int x = 0; x < width; x++) {
|
||||
uint32_t pixel = src_row[x];
|
||||
uint32_t a = (pixel >> 24) & 0xFF;
|
||||
if(a == 0) {
|
||||
*dst_row++ = 0;
|
||||
*dst_row++ = 0;
|
||||
*dst_row++ = 0;
|
||||
*dst_row++ = 0;
|
||||
} else {
|
||||
uint32_t r = (pixel >> 16) & 0xFF;
|
||||
uint32_t g = (pixel >> 8) & 0xFF;
|
||||
uint32_t b = (pixel >> 0) & 0xFF;
|
||||
if(a != 255) {
|
||||
r = (r * 255) / a;
|
||||
g = (g * 255) / a;
|
||||
b = (b * 255) / a;
|
||||
}
|
||||
|
||||
*dst_row++ = r;
|
||||
*dst_row++ = g;
|
||||
*dst_row++ = b;
|
||||
*dst_row++ = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void plutovg_convert_rgba_to_argb(unsigned char* dst, const unsigned char* src, int width, int height, int stride)
|
||||
{
|
||||
for(int y = 0; y < height; y++) {
|
||||
const unsigned char* src_row = src + stride * y;
|
||||
uint32_t* dst_row = (uint32_t*)(dst + stride * y);
|
||||
for(int x = 0; x < width; x++) {
|
||||
uint32_t a = src_row[4 * x + 3];
|
||||
if(a == 0) {
|
||||
dst_row[x] = 0x00000000;
|
||||
} else {
|
||||
uint32_t r = src_row[4 * x + 0];
|
||||
uint32_t g = src_row[4 * x + 1];
|
||||
uint32_t b = src_row[4 * x + 2];
|
||||
if(a != 255) {
|
||||
r = (r * a) / 255;
|
||||
g = (g * a) / 255;
|
||||
b = (b * a) / 255;
|
||||
}
|
||||
|
||||
dst_row[x] = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
vendor/lunasvg/plutovg/source/plutovg-utils.h
vendored
211
vendor/lunasvg/plutovg/source/plutovg-utils.h
vendored
@@ -1,211 +0,0 @@
|
||||
#ifndef PLUTOVG_UTILS_H
|
||||
#define PLUTOVG_UTILS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
|
||||
#define PLUTOVG_IS_NUM(c) ((c) >= '0' && (c) <= '9')
|
||||
#define PLUTOVG_IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z'))
|
||||
#define PLUTOVG_IS_ALNUM(c) (PLUTOVG_IS_ALPHA(c) || PLUTOVG_IS_NUM(c))
|
||||
#define PLUTOVG_IS_WS(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r')
|
||||
|
||||
#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) : ((v) > (hi) ? (hi) : (v)))
|
||||
|
||||
#define plutovg_alpha(c) (((c) >> 24) & 0xff)
|
||||
#define plutovg_red(c) (((c) >> 16) & 0xff)
|
||||
#define plutovg_green(c) (((c) >> 8) & 0xff)
|
||||
#define plutovg_blue(c) (((c) >> 0) & 0xff)
|
||||
|
||||
#define plutovg_array_init(array) \
|
||||
do { \
|
||||
(array).data = NULL; \
|
||||
(array).size = 0; \
|
||||
(array).capacity = 0; \
|
||||
} while(0)
|
||||
|
||||
#define plutovg_array_ensure(array, count) \
|
||||
do { \
|
||||
if((array).size + (count) > (array).capacity) { \
|
||||
int capacity = (array).size + (count); \
|
||||
int newcapacity = (array).capacity == 0 ? 8 : (array).capacity; \
|
||||
while(newcapacity < capacity) { newcapacity *= 2; } \
|
||||
(array).data = realloc((array).data, newcapacity * sizeof((array).data[0])); \
|
||||
(array).capacity = newcapacity; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define plutovg_array_append_data(array, newdata, count) \
|
||||
do { \
|
||||
if(newdata && count > 0) { \
|
||||
plutovg_array_ensure(array, count); \
|
||||
memcpy((array).data + (array).size, newdata, (count) * sizeof((newdata)[0])); \
|
||||
(array).size += count; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define plutovg_array_append(array, other) plutovg_array_append_data(array, (other).data, (other).size)
|
||||
#define plutovg_array_clear(array) ((array).size = 0)
|
||||
#define plutovg_array_destroy(array) free((array).data)
|
||||
|
||||
static inline uint32_t plutovg_premultiply_argb(uint32_t color)
|
||||
{
|
||||
uint32_t a = plutovg_alpha(color);
|
||||
uint32_t r = plutovg_red(color);
|
||||
uint32_t g = plutovg_green(color);
|
||||
uint32_t b = plutovg_blue(color);
|
||||
if(a != 255) {
|
||||
r = (r * a) / 255;
|
||||
g = (g * a) / 255;
|
||||
b = (b * a) / 255;
|
||||
}
|
||||
|
||||
return (a << 24) | (r << 16) | (g << 8) | (b);
|
||||
}
|
||||
|
||||
static inline bool plutovg_parse_number(const char** begin, const char* end, float* number)
|
||||
{
|
||||
const char* it = *begin;
|
||||
float integer = 0;
|
||||
float fraction = 0;
|
||||
float exponent = 0;
|
||||
int sign = 1;
|
||||
int expsign = 1;
|
||||
|
||||
if(it < end && *it == '+') {
|
||||
++it;
|
||||
} else if(it < end && *it == '-') {
|
||||
++it;
|
||||
sign = -1;
|
||||
}
|
||||
|
||||
if(it >= end || (*it != '.' && !PLUTOVG_IS_NUM(*it)))
|
||||
return false;
|
||||
if(PLUTOVG_IS_NUM(*it)) {
|
||||
do {
|
||||
integer = 10.f * integer + (*it++ - '0');
|
||||
} while(it < end && PLUTOVG_IS_NUM(*it));
|
||||
}
|
||||
|
||||
if(it < end && *it == '.') {
|
||||
++it;
|
||||
if(it >= end || !PLUTOVG_IS_NUM(*it))
|
||||
return false;
|
||||
float divisor = 1.f;
|
||||
do {
|
||||
fraction = 10.f * fraction + (*it++ - '0');
|
||||
divisor *= 10.f;
|
||||
} while(it < end && PLUTOVG_IS_NUM(*it));
|
||||
fraction /= divisor;
|
||||
}
|
||||
|
||||
if(it < end && (*it == 'e' || *it == 'E')) {
|
||||
++it;
|
||||
if(it < end && *it == '+') {
|
||||
++it;
|
||||
} else if(it < end && *it == '-') {
|
||||
++it;
|
||||
expsign = -1;
|
||||
}
|
||||
|
||||
if(it >= end || !PLUTOVG_IS_NUM(*it))
|
||||
return false;
|
||||
do {
|
||||
exponent = 10 * exponent + (*it++ - '0');
|
||||
} while(it < end && PLUTOVG_IS_NUM(*it));
|
||||
}
|
||||
|
||||
*begin = it;
|
||||
*number = sign * (integer + fraction);
|
||||
if(exponent)
|
||||
*number *= powf(10.f, expsign * exponent);
|
||||
return *number >= -FLT_MAX && *number <= FLT_MAX;
|
||||
}
|
||||
|
||||
static inline bool plutovg_skip_delim(const char** begin, const char* end, const char delim)
|
||||
{
|
||||
const char* it = *begin;
|
||||
if(it < end && *it == delim) {
|
||||
*begin = it + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool plutovg_skip_string(const char** begin, const char* end, const char* data)
|
||||
{
|
||||
const char* it = *begin;
|
||||
while(it < end && *data && *it == *data) {
|
||||
++data;
|
||||
++it;
|
||||
}
|
||||
|
||||
if(*data == '\0') {
|
||||
*begin = it;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool plutovg_skip_ws(const char** begin, const char* end)
|
||||
{
|
||||
const char* it = *begin;
|
||||
while(it < end && PLUTOVG_IS_WS(*it))
|
||||
++it;
|
||||
*begin = it;
|
||||
return it < end;
|
||||
}
|
||||
|
||||
static inline bool plutovg_skip_ws_and_delim(const char** begin, const char* end, char delim)
|
||||
{
|
||||
const char* it = *begin;
|
||||
if(plutovg_skip_ws(&it, end)) {
|
||||
if(!plutovg_skip_delim(&it, end, delim))
|
||||
return false;
|
||||
plutovg_skip_ws(&it, end);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
*begin = it;
|
||||
return it < end;
|
||||
}
|
||||
|
||||
static inline bool plutovg_skip_ws_and_comma(const char** begin, const char* end)
|
||||
{
|
||||
return plutovg_skip_ws_and_delim(begin, end, ',');
|
||||
}
|
||||
|
||||
static inline bool plutovg_skip_ws_or_delim(const char** begin, const char* end, char delim, bool* has_delim)
|
||||
{
|
||||
const char* it = *begin;
|
||||
if(has_delim)
|
||||
*has_delim = false;
|
||||
if(plutovg_skip_ws(&it, end)) {
|
||||
if(plutovg_skip_delim(&it, end, delim)) {
|
||||
if(has_delim)
|
||||
*has_delim = true;
|
||||
plutovg_skip_ws(&it, end);
|
||||
}
|
||||
}
|
||||
|
||||
if(it == *begin)
|
||||
return false;
|
||||
*begin = it;
|
||||
return it < end;
|
||||
}
|
||||
|
||||
static inline bool plutovg_skip_ws_or_comma(const char** begin, const char* end, bool* has_comma)
|
||||
{
|
||||
return plutovg_skip_ws_or_delim(begin, end, ',', has_comma);
|
||||
}
|
||||
|
||||
#endif // PLUTOVG_UTILS_H
|
||||
693
vendor/lunasvg/source/graphics.cpp
vendored
693
vendor/lunasvg/source/graphics.cpp
vendored
@@ -1,693 +0,0 @@
|
||||
#include "graphics.h"
|
||||
#include "lunasvg.h"
|
||||
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
const Color Color::Black(0xFF000000);
|
||||
const Color Color::White(0xFFFFFFFF);
|
||||
const Color Color::Transparent(0x00000000);
|
||||
|
||||
const Rect Rect::Empty(0, 0, 0, 0);
|
||||
const Rect Rect::Invalid(0, 0, -1, -1);
|
||||
const Rect Rect::Infinite(-FLT_MAX / 2.f, -FLT_MAX / 2.f, FLT_MAX, FLT_MAX);
|
||||
|
||||
Rect::Rect(const Box& box)
|
||||
: x(box.x), y(box.y), w(box.w), h(box.h)
|
||||
{
|
||||
}
|
||||
|
||||
const Transform Transform::Identity(1, 0, 0, 1, 0, 0);
|
||||
|
||||
Transform::Transform()
|
||||
{
|
||||
plutovg_matrix_init_identity(&m_matrix);
|
||||
}
|
||||
|
||||
Transform::Transform(float a, float b, float c, float d, float e, float f)
|
||||
{
|
||||
plutovg_matrix_init(&m_matrix, a, b, c, d, e, f);
|
||||
}
|
||||
|
||||
Transform::Transform(const Matrix& matrix)
|
||||
: Transform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f)
|
||||
{
|
||||
}
|
||||
|
||||
Transform Transform::operator*(const Transform& transform) const
|
||||
{
|
||||
plutovg_matrix_t result;
|
||||
plutovg_matrix_multiply(&result, &transform.m_matrix, &m_matrix);
|
||||
return result;
|
||||
}
|
||||
|
||||
Transform& Transform::operator*=(const Transform& transform)
|
||||
{
|
||||
return (*this = *this * transform);
|
||||
}
|
||||
|
||||
Transform& Transform::multiply(const Transform& transform)
|
||||
{
|
||||
return (*this *= transform);
|
||||
}
|
||||
|
||||
Transform& Transform::translate(float tx, float ty)
|
||||
{
|
||||
return multiply(translated(tx, ty));
|
||||
}
|
||||
|
||||
Transform& Transform::scale(float sx, float sy)
|
||||
{
|
||||
return multiply(scaled(sx, sy));
|
||||
}
|
||||
|
||||
Transform& Transform::rotate(float angle, float cx, float cy)
|
||||
{
|
||||
return multiply(rotated(angle, cx, cy));
|
||||
}
|
||||
|
||||
Transform& Transform::shear(float shx, float shy)
|
||||
{
|
||||
return multiply(sheared(shx, shy));
|
||||
}
|
||||
|
||||
Transform& Transform::postMultiply(const Transform& transform)
|
||||
{
|
||||
return (*this = transform * *this);
|
||||
}
|
||||
|
||||
Transform& Transform::postTranslate(float tx, float ty)
|
||||
{
|
||||
return postMultiply(translated(tx, ty));
|
||||
}
|
||||
|
||||
Transform& Transform::postScale(float sx, float sy)
|
||||
{
|
||||
return postMultiply(scaled(sx, sy));
|
||||
}
|
||||
|
||||
Transform& Transform::postRotate(float angle, float cx, float cy)
|
||||
{
|
||||
return postMultiply(rotated(angle, cx, cy));
|
||||
}
|
||||
|
||||
Transform& Transform::postShear(float shx, float shy)
|
||||
{
|
||||
return postMultiply(sheared(shx, shy));
|
||||
}
|
||||
|
||||
Transform Transform::inverse() const
|
||||
{
|
||||
plutovg_matrix_t inverse;
|
||||
plutovg_matrix_invert(&m_matrix, &inverse);
|
||||
return inverse;
|
||||
}
|
||||
|
||||
Transform& Transform::invert()
|
||||
{
|
||||
plutovg_matrix_invert(&m_matrix, &m_matrix);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Transform::reset()
|
||||
{
|
||||
plutovg_matrix_init_identity(&m_matrix);
|
||||
}
|
||||
|
||||
Point Transform::mapPoint(float x, float y) const
|
||||
{
|
||||
plutovg_matrix_map(&m_matrix, x, y, &x, &y);
|
||||
return Point(x, y);
|
||||
}
|
||||
|
||||
Point Transform::mapPoint(const Point& point) const
|
||||
{
|
||||
return mapPoint(point.x, point.y);
|
||||
}
|
||||
|
||||
Rect Transform::mapRect(const Rect& rect) const
|
||||
{
|
||||
if(!rect.isValid()) {
|
||||
return Rect::Invalid;
|
||||
}
|
||||
|
||||
plutovg_rect_t result = {rect.x, rect.y, rect.w, rect.h};
|
||||
plutovg_matrix_map_rect(&m_matrix, &result, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
float Transform::xScale() const
|
||||
{
|
||||
return std::sqrt(m_matrix.a * m_matrix.a + m_matrix.b * m_matrix.b);
|
||||
}
|
||||
|
||||
float Transform::yScale() const
|
||||
{
|
||||
return std::sqrt(m_matrix.c * m_matrix.c + m_matrix.d * m_matrix.d);
|
||||
}
|
||||
|
||||
bool Transform::parse(const char* data, size_t length)
|
||||
{
|
||||
return plutovg_matrix_parse(&m_matrix, data, length);
|
||||
}
|
||||
|
||||
Transform Transform::rotated(float angle, float cx, float cy)
|
||||
{
|
||||
plutovg_matrix_t matrix;
|
||||
if(cx == 0.f && cy == 0.f) {
|
||||
plutovg_matrix_init_rotate(&matrix, PLUTOVG_DEG2RAD(angle));
|
||||
} else {
|
||||
plutovg_matrix_init_translate(&matrix, cx, cy);
|
||||
plutovg_matrix_rotate(&matrix, PLUTOVG_DEG2RAD(angle));
|
||||
plutovg_matrix_translate(&matrix, -cx, -cy);
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Transform Transform::scaled(float sx, float sy)
|
||||
{
|
||||
plutovg_matrix_t matrix;
|
||||
plutovg_matrix_init_scale(&matrix, sx, sy);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Transform Transform::sheared(float shx, float shy)
|
||||
{
|
||||
plutovg_matrix_t matrix;
|
||||
plutovg_matrix_init_shear(&matrix, PLUTOVG_DEG2RAD(shx), PLUTOVG_DEG2RAD(shy));
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Transform Transform::translated(float tx, float ty)
|
||||
{
|
||||
plutovg_matrix_t matrix;
|
||||
plutovg_matrix_init_translate(&matrix, tx, ty);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Path::Path(const Path& path)
|
||||
: m_data(plutovg_path_reference(path.data()))
|
||||
{
|
||||
}
|
||||
|
||||
Path::Path(Path&& path)
|
||||
: m_data(path.release())
|
||||
{
|
||||
}
|
||||
|
||||
Path::~Path()
|
||||
{
|
||||
plutovg_path_destroy(m_data);
|
||||
}
|
||||
|
||||
Path& Path::operator=(const Path& path)
|
||||
{
|
||||
Path(path).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Path& Path::operator=(Path&& path)
|
||||
{
|
||||
Path(std::move(path)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Path::moveTo(float x, float y)
|
||||
{
|
||||
plutovg_path_move_to(ensure(), x, y);
|
||||
}
|
||||
|
||||
void Path::lineTo(float x, float y)
|
||||
{
|
||||
plutovg_path_line_to(ensure(), x, y);
|
||||
}
|
||||
|
||||
void Path::quadTo(float x1, float y1, float x2, float y2)
|
||||
{
|
||||
plutovg_path_quad_to(ensure(), x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
void Path::cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
|
||||
{
|
||||
plutovg_path_cubic_to(ensure(), x1, y1, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
void Path::arcTo(float rx, float ry, float xAxisRotation, bool largeArcFlag, bool sweepFlag, float x, float y)
|
||||
{
|
||||
plutovg_path_arc_to(ensure(), rx, ry, PLUTOVG_DEG2RAD(xAxisRotation), largeArcFlag, sweepFlag, x, y);
|
||||
}
|
||||
|
||||
void Path::close()
|
||||
{
|
||||
plutovg_path_close(ensure());
|
||||
}
|
||||
|
||||
void Path::addEllipse(float cx, float cy, float rx, float ry)
|
||||
{
|
||||
plutovg_path_add_ellipse(ensure(), cx, cy, rx, ry);
|
||||
}
|
||||
|
||||
void Path::addRoundRect(float x, float y, float w, float h, float rx, float ry)
|
||||
{
|
||||
plutovg_path_add_round_rect(ensure(), x, y, w, h, rx, ry);
|
||||
}
|
||||
|
||||
void Path::addRect(float x, float y, float w, float h)
|
||||
{
|
||||
plutovg_path_add_rect(ensure(), x, y, w, h);
|
||||
}
|
||||
|
||||
void Path::addEllipse(const Point& center, const Size& radii)
|
||||
{
|
||||
addEllipse(center.x, center.y, radii.w, radii.h);
|
||||
}
|
||||
|
||||
void Path::addRoundRect(const Rect& rect, const Size& radii)
|
||||
{
|
||||
addRoundRect(rect.x, rect.y, rect.w, rect.h, radii.w, radii.h);
|
||||
}
|
||||
|
||||
void Path::addRect(const Rect& rect)
|
||||
{
|
||||
addRect(rect.x, rect.y, rect.w, rect.h);
|
||||
}
|
||||
|
||||
void Path::reset()
|
||||
{
|
||||
if(m_data == nullptr)
|
||||
return;
|
||||
if(isUnique()) {
|
||||
plutovg_path_reset(m_data);
|
||||
} else {
|
||||
plutovg_path_destroy(m_data);
|
||||
m_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Rect Path::boundingRect() const
|
||||
{
|
||||
if(m_data == nullptr)
|
||||
return Rect::Empty;
|
||||
plutovg_rect_t extents;
|
||||
plutovg_path_extents(m_data, &extents, false);
|
||||
return extents;
|
||||
}
|
||||
|
||||
bool Path::isEmpty() const
|
||||
{
|
||||
if(m_data)
|
||||
return plutovg_path_get_elements(m_data, nullptr) == 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Path::isUnique() const
|
||||
{
|
||||
return plutovg_path_get_reference_count(m_data) == 1;
|
||||
}
|
||||
|
||||
bool Path::parse(const char* data, size_t length)
|
||||
{
|
||||
plutovg_path_reset(ensure());
|
||||
return plutovg_path_parse(m_data, data, length);
|
||||
}
|
||||
|
||||
plutovg_path_t* Path::ensure()
|
||||
{
|
||||
if(isNull()) {
|
||||
m_data = plutovg_path_create();
|
||||
} else if(!isUnique()) {
|
||||
plutovg_path_destroy(m_data);
|
||||
m_data = plutovg_path_clone(m_data);
|
||||
}
|
||||
|
||||
return m_data;
|
||||
}
|
||||
|
||||
PathIterator::PathIterator(const Path& path)
|
||||
: m_size(plutovg_path_get_elements(path.data(), &m_elements))
|
||||
, m_index(0)
|
||||
{
|
||||
}
|
||||
|
||||
PathCommand PathIterator::currentSegment(std::array<Point, 3>& points) const
|
||||
{
|
||||
auto command = m_elements[m_index].header.command;
|
||||
switch(command) {
|
||||
case PLUTOVG_PATH_COMMAND_MOVE_TO:
|
||||
points[0] = m_elements[m_index + 1].point;
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_LINE_TO:
|
||||
points[0] = m_elements[m_index + 1].point;
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CUBIC_TO:
|
||||
points[0] = m_elements[m_index + 1].point;
|
||||
points[1] = m_elements[m_index + 2].point;
|
||||
points[2] = m_elements[m_index + 3].point;
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CLOSE:
|
||||
points[0] = m_elements[m_index + 1].point;
|
||||
break;
|
||||
}
|
||||
|
||||
return PathCommand(command);
|
||||
}
|
||||
|
||||
void PathIterator::next()
|
||||
{
|
||||
m_index += m_elements[m_index].header.length;
|
||||
}
|
||||
|
||||
FontFace::FontFace(plutovg_font_face_t* face)
|
||||
: m_face(plutovg_font_face_reference(face))
|
||||
{
|
||||
}
|
||||
|
||||
FontFace::FontFace(const void* data, size_t length, plutovg_destroy_func_t destroy_func, void* closure)
|
||||
: m_face(plutovg_font_face_load_from_data(data, length, 0, destroy_func, closure))
|
||||
{
|
||||
}
|
||||
|
||||
FontFace::FontFace(const char* filename)
|
||||
: m_face(plutovg_font_face_load_from_file(filename, 0))
|
||||
{
|
||||
}
|
||||
|
||||
FontFace::FontFace(const FontFace& face)
|
||||
: m_face(plutovg_font_face_reference(face.get()))
|
||||
{
|
||||
}
|
||||
|
||||
FontFace::FontFace(FontFace&& face)
|
||||
: m_face(face.release())
|
||||
{
|
||||
}
|
||||
|
||||
FontFace::~FontFace()
|
||||
{
|
||||
plutovg_font_face_destroy(m_face);
|
||||
}
|
||||
|
||||
FontFace& FontFace::operator=(const FontFace& face)
|
||||
{
|
||||
FontFace(face).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FontFace& FontFace::operator=(FontFace&& face)
|
||||
{
|
||||
FontFace(std::move(face)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void FontFace::swap(FontFace& face)
|
||||
{
|
||||
std::swap(m_face, face.m_face);
|
||||
}
|
||||
|
||||
plutovg_font_face_t* FontFace::release()
|
||||
{
|
||||
return std::exchange(m_face, nullptr);
|
||||
}
|
||||
|
||||
bool FontFaceCache::addFontFace(const std::string& family, bool bold, bool italic, const FontFace& face)
|
||||
{
|
||||
if(!face.isNull())
|
||||
plutovg_font_face_cache_add(m_cache, family.data(), bold, italic, face.get());
|
||||
return !face.isNull();
|
||||
}
|
||||
|
||||
FontFace FontFaceCache::getFontFace(const std::string& family, bool bold, bool italic) const
|
||||
{
|
||||
if(auto face = plutovg_font_face_cache_get(m_cache, family.data(), bold, italic)) {
|
||||
return FontFace(face);
|
||||
}
|
||||
|
||||
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"}
|
||||
};
|
||||
|
||||
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 FontFace();
|
||||
}
|
||||
|
||||
FontFaceCache::FontFaceCache()
|
||||
: m_cache(plutovg_font_face_cache_create())
|
||||
{
|
||||
#ifndef LUNASVG_DISABLE_LOAD_SYSTEM_FONTS
|
||||
plutovg_font_face_cache_load_sys(m_cache);
|
||||
#endif
|
||||
}
|
||||
|
||||
FontFaceCache* fontFaceCache()
|
||||
{
|
||||
static FontFaceCache cache;
|
||||
return &cache;
|
||||
}
|
||||
|
||||
Font::Font(const FontFace& face, float size)
|
||||
: m_face(face), m_size(size)
|
||||
{
|
||||
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
|
||||
{
|
||||
plutovg_rect_t extents = {0};
|
||||
if(m_size > 0.f && !m_face.isNull())
|
||||
plutovg_font_face_get_glyph_metrics(m_face.get(), m_size, 'x', nullptr, nullptr, &extents);
|
||||
return extents.h;
|
||||
}
|
||||
|
||||
float Font::measureText(const std::u32string_view& text) const
|
||||
{
|
||||
if(m_size > 0.f && !m_face.isNull())
|
||||
return plutovg_font_face_text_extents(m_face.get(), m_size, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF32, nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<Canvas> Canvas::create(const Bitmap& bitmap)
|
||||
{
|
||||
return std::shared_ptr<Canvas>(new Canvas(bitmap));
|
||||
}
|
||||
|
||||
std::shared_ptr<Canvas> Canvas::create(float x, float y, float width, float height)
|
||||
{
|
||||
constexpr int kMaxSize = 1 << 15;
|
||||
if(width <= 0 || height <= 0 || width >= kMaxSize || height >= kMaxSize)
|
||||
return std::shared_ptr<Canvas>(new Canvas(0, 0, 1, 1));
|
||||
auto l = static_cast<int>(std::floor(x));
|
||||
auto t = static_cast<int>(std::floor(y));
|
||||
auto r = static_cast<int>(std::ceil(x + width));
|
||||
auto b = static_cast<int>(std::ceil(y + height));
|
||||
return std::shared_ptr<Canvas>(new Canvas(l, t, r - l, b - t));
|
||||
}
|
||||
|
||||
std::shared_ptr<Canvas> Canvas::create(const Rect& extents)
|
||||
{
|
||||
return create(extents.x, extents.y, extents.w, extents.h);
|
||||
}
|
||||
|
||||
void Canvas::setColor(const Color& color)
|
||||
{
|
||||
setColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
|
||||
}
|
||||
|
||||
void Canvas::setColor(float r, float g, float b, float a)
|
||||
{
|
||||
plutovg_canvas_set_rgba(m_canvas, r, g, b, a);
|
||||
}
|
||||
|
||||
void Canvas::setLinearGradient(float x1, float y1, float x2, float y2, SpreadMethod spread, const GradientStops& stops, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_linear_gradient(m_canvas, x1, y1, x2, y2, static_cast<plutovg_spread_method_t>(spread), stops.data(), stops.size(), &transform.matrix());
|
||||
}
|
||||
|
||||
void Canvas::setRadialGradient(float cx, float cy, float r, float fx, float fy, SpreadMethod spread, const GradientStops& stops, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_radial_gradient(m_canvas, cx, cy, r, fx, fy, 0.f, static_cast<plutovg_spread_method_t>(spread), stops.data(), stops.size(), &transform.matrix());
|
||||
}
|
||||
|
||||
void Canvas::setTexture(const Canvas& source, TextureType type, float opacity, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_texture(m_canvas, source.surface(), static_cast<plutovg_texture_type_t>(type), opacity, &transform.matrix());
|
||||
}
|
||||
|
||||
void Canvas::fillPath(const Path& path, FillRule fillRule, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_transform(m_canvas, &transform.matrix());
|
||||
plutovg_canvas_set_fill_rule(m_canvas, static_cast<plutovg_fill_rule_t>(fillRule));
|
||||
plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER);
|
||||
plutovg_canvas_fill_path(m_canvas, path.data());
|
||||
}
|
||||
|
||||
void Canvas::strokePath(const Path& path, const StrokeData& strokeData, const Transform& transform)
|
||||
{
|
||||
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());
|
||||
plutovg_canvas_set_line_cap(m_canvas, static_cast<plutovg_line_cap_t>(strokeData.lineCap()));
|
||||
plutovg_canvas_set_line_join(m_canvas, static_cast<plutovg_line_join_t>(strokeData.lineJoin()));
|
||||
plutovg_canvas_set_dash_offset(m_canvas, strokeData.dashOffset());
|
||||
plutovg_canvas_set_dash_array(m_canvas, strokeData.dashArray().data(), strokeData.dashArray().size());
|
||||
plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER);
|
||||
plutovg_canvas_stroke_path(m_canvas, path.data());
|
||||
}
|
||||
|
||||
void Canvas::fillText(const std::u32string_view& text, const Font& font, const Point& origin, const Transform& transform)
|
||||
{
|
||||
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);
|
||||
plutovg_canvas_set_font(m_canvas, font.face().get(), font.size());
|
||||
plutovg_canvas_fill_text(m_canvas, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF32, origin.x, origin.y);
|
||||
}
|
||||
|
||||
void Canvas::strokeText(const std::u32string_view& text, float strokeWidth, const Font& font, const Point& origin, const Transform& transform)
|
||||
{
|
||||
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);
|
||||
plutovg_canvas_set_line_cap(m_canvas, PLUTOVG_LINE_CAP_BUTT);
|
||||
plutovg_canvas_set_line_join(m_canvas, PLUTOVG_LINE_JOIN_MITER);
|
||||
plutovg_canvas_set_dash_offset(m_canvas, 0.f);
|
||||
plutovg_canvas_set_dash_array(m_canvas, nullptr, 0);
|
||||
plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER);
|
||||
plutovg_canvas_set_font(m_canvas, font.face().get(), font.size());
|
||||
plutovg_canvas_stroke_text(m_canvas, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF32, origin.x, origin.y);
|
||||
}
|
||||
|
||||
void Canvas::clipPath(const Path& path, FillRule clipRule, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_transform(m_canvas, &transform.matrix());
|
||||
plutovg_canvas_set_fill_rule(m_canvas, static_cast<plutovg_fill_rule_t>(clipRule));
|
||||
plutovg_canvas_clip_path(m_canvas, path.data());
|
||||
}
|
||||
|
||||
void Canvas::clipRect(const Rect& rect, FillRule clipRule, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_transform(m_canvas, &transform.matrix());
|
||||
plutovg_canvas_set_fill_rule(m_canvas, static_cast<plutovg_fill_rule_t>(clipRule));
|
||||
plutovg_canvas_clip_rect(m_canvas, rect.x, rect.y, rect.w, rect.h);
|
||||
}
|
||||
|
||||
void Canvas::drawImage(const Bitmap& image, const Rect& dstRect, const Rect& srcRect, const Transform& transform)
|
||||
{
|
||||
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_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);
|
||||
plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER);
|
||||
plutovg_canvas_set_texture(m_canvas, image.surface(), PLUTOVG_TEXTURE_TYPE_PLAIN, 1.f, &matrix);
|
||||
plutovg_canvas_fill_rect(m_canvas, 0, 0, dstRect.w, dstRect.h);
|
||||
}
|
||||
|
||||
void Canvas::blendCanvas(const Canvas& canvas, BlendMode blendMode, float opacity)
|
||||
{
|
||||
plutovg_matrix_t matrix = { 1, 0, 0, 1, static_cast<float>(canvas.x()), static_cast<float>(canvas.y()) };
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_set_operator(m_canvas, static_cast<plutovg_operator_t>(blendMode));
|
||||
plutovg_canvas_set_texture(m_canvas, canvas.surface(), PLUTOVG_TEXTURE_TYPE_PLAIN, opacity, &matrix);
|
||||
plutovg_canvas_paint(m_canvas);
|
||||
}
|
||||
|
||||
void Canvas::save()
|
||||
{
|
||||
plutovg_canvas_save(m_canvas);
|
||||
}
|
||||
|
||||
void Canvas::restore()
|
||||
{
|
||||
plutovg_canvas_restore(m_canvas);
|
||||
}
|
||||
|
||||
int Canvas::width() const
|
||||
{
|
||||
return plutovg_surface_get_width(m_surface);
|
||||
}
|
||||
|
||||
int Canvas::height() const
|
||||
{
|
||||
return plutovg_surface_get_height(m_surface);
|
||||
}
|
||||
|
||||
void Canvas::convertToLuminanceMask()
|
||||
{
|
||||
auto width = plutovg_surface_get_width(m_surface);
|
||||
auto height = plutovg_surface_get_height(m_surface);
|
||||
auto stride = plutovg_surface_get_stride(m_surface);
|
||||
auto data = plutovg_surface_get_data(m_surface);
|
||||
for(int y = 0; y < height; y++) {
|
||||
auto pixels = reinterpret_cast<uint32_t*>(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;
|
||||
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<uint32_t>(l * (a / 255.0)) << 24;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Canvas::~Canvas()
|
||||
{
|
||||
plutovg_canvas_destroy(m_canvas);
|
||||
plutovg_surface_destroy(m_surface);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
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<float>(x), -static_cast<float>(y)})
|
||||
, m_x(x), m_y(y)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
562
vendor/lunasvg/source/graphics.h
vendored
562
vendor/lunasvg/source/graphics.h
vendored
@@ -1,562 +0,0 @@
|
||||
#ifndef LUNASVG_GRAPHICS_H
|
||||
#define LUNASVG_GRAPHICS_H
|
||||
|
||||
#include <plutovg.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
enum class LineCap : uint8_t {
|
||||
Butt = PLUTOVG_LINE_CAP_BUTT,
|
||||
Round = PLUTOVG_LINE_CAP_ROUND,
|
||||
Square = PLUTOVG_LINE_CAP_SQUARE
|
||||
};
|
||||
|
||||
enum class LineJoin : uint8_t {
|
||||
Miter = PLUTOVG_LINE_JOIN_MITER,
|
||||
Round = PLUTOVG_LINE_JOIN_ROUND,
|
||||
Bevel = PLUTOVG_LINE_JOIN_BEVEL
|
||||
};
|
||||
|
||||
enum class FillRule : uint8_t {
|
||||
NonZero = PLUTOVG_FILL_RULE_NON_ZERO,
|
||||
EvenOdd = PLUTOVG_FILL_RULE_EVEN_ODD
|
||||
};
|
||||
|
||||
enum class SpreadMethod : uint8_t {
|
||||
Pad = PLUTOVG_SPREAD_METHOD_PAD,
|
||||
Reflect = PLUTOVG_SPREAD_METHOD_REFLECT,
|
||||
Repeat = PLUTOVG_SPREAD_METHOD_REPEAT
|
||||
};
|
||||
|
||||
class Color {
|
||||
public:
|
||||
constexpr Color() = default;
|
||||
constexpr explicit Color(uint32_t value) : m_value(value) {}
|
||||
constexpr Color(int r, int g, int b, int a = 255) : m_value(a << 24 | r << 16 | g << 8 | b) {}
|
||||
|
||||
constexpr uint8_t alpha() const { return (m_value >> 24) & 0xff; }
|
||||
constexpr uint8_t red() const { return (m_value >> 16) & 0xff; }
|
||||
constexpr uint8_t green() const { return (m_value >> 8) & 0xff; }
|
||||
constexpr uint8_t blue() const { return (m_value >> 0) & 0xff; }
|
||||
|
||||
constexpr float alphaF() const { return alpha() / 255.f; }
|
||||
constexpr float redF() const { return red() / 255.f; }
|
||||
constexpr float greenF() const { return green() / 255.f; }
|
||||
constexpr float blueF() const { return blue() / 255.f; }
|
||||
|
||||
constexpr uint32_t value() const { return m_value; }
|
||||
|
||||
constexpr bool isOpaque() const { return alpha() == 255; }
|
||||
constexpr bool isVisible() const { return alpha() > 0; }
|
||||
|
||||
constexpr Color opaqueColor() const { return Color(m_value | 0xFF000000); }
|
||||
constexpr Color colorWithAlpha(float opacity) const;
|
||||
|
||||
static const Color Transparent;
|
||||
static const Color Black;
|
||||
static const Color White;
|
||||
|
||||
private:
|
||||
uint32_t m_value = 0;
|
||||
};
|
||||
|
||||
constexpr Color Color::colorWithAlpha(float opacity) const
|
||||
{
|
||||
auto rgb = m_value & 0x00FFFFFF;
|
||||
auto a = static_cast<int>(alpha() * std::clamp(opacity, 0.f, 1.f));
|
||||
return Color(rgb | a << 24);
|
||||
}
|
||||
|
||||
class Point {
|
||||
public:
|
||||
constexpr Point() = default;
|
||||
constexpr Point(const plutovg_point_t& point) : Point(point.x, point.y) {}
|
||||
constexpr Point(float x, float y) : x(x), y(y) {}
|
||||
|
||||
constexpr void move(float dx, float dy) { x += dx; y += dy; }
|
||||
constexpr void move(float d) { move(d, d); }
|
||||
constexpr void move(const Point& p) { move(p.x, p.y); }
|
||||
|
||||
constexpr void scale(float sx, float sy) { x *= sx; y *= sy; }
|
||||
constexpr void scale(float s) { scale(s, s); }
|
||||
|
||||
constexpr float dot(const Point& p) const { return x * p.x + y * p.y; }
|
||||
|
||||
public:
|
||||
float x{0};
|
||||
float y{0};
|
||||
};
|
||||
|
||||
constexpr Point operator+(const Point& a, const Point& b)
|
||||
{
|
||||
return Point(a.x + b.x, a.y + b.y);
|
||||
}
|
||||
|
||||
constexpr Point operator-(const Point& a, const Point& b)
|
||||
{
|
||||
return Point(a.x - b.x, a.y - b.y);
|
||||
}
|
||||
|
||||
constexpr Point operator-(const Point& a)
|
||||
{
|
||||
return Point(-a.x, -a.y);
|
||||
}
|
||||
|
||||
constexpr Point& operator+=(Point& a, const Point& b)
|
||||
{
|
||||
a.move(b);
|
||||
return a;
|
||||
}
|
||||
|
||||
constexpr Point& operator-=(Point& a, const Point& b)
|
||||
{
|
||||
a.move(-b);
|
||||
return a;
|
||||
}
|
||||
|
||||
constexpr float operator*(const Point& a, const Point& b)
|
||||
{
|
||||
return a.dot(b);
|
||||
}
|
||||
|
||||
class Size {
|
||||
public:
|
||||
constexpr Size() = default;
|
||||
constexpr Size(float w, float h) : w(w), h(h) {}
|
||||
|
||||
constexpr void expand(float dw, float dh) { w += dw; h += dh; }
|
||||
constexpr void expand(float d) { expand(d, d); }
|
||||
constexpr void expand(const Size& s) { expand(s.w, s.h); }
|
||||
|
||||
constexpr void scale(float sw, float sh) { w *= sw; h *= sh; }
|
||||
constexpr void scale(float s) { scale(s, s); }
|
||||
|
||||
constexpr bool isEmpty() const { return w <= 0.f || h <= 0.f; }
|
||||
constexpr bool isZero() const { return w <= 0.f && h <= 0.f; }
|
||||
constexpr bool isValid() const { return w >= 0.f && h >= 0.f; }
|
||||
|
||||
public:
|
||||
float w{0};
|
||||
float h{0};
|
||||
};
|
||||
|
||||
constexpr Size operator+(const Size& a, const Size& b)
|
||||
{
|
||||
return Size(a.w + b.w, a.h + b.h);
|
||||
}
|
||||
|
||||
constexpr Size operator-(const Size& a, const Size& b)
|
||||
{
|
||||
return Size(a.w - b.w, a.h - b.h);
|
||||
}
|
||||
|
||||
constexpr Size operator-(const Size& a)
|
||||
{
|
||||
return Size(-a.w, -a.h);
|
||||
}
|
||||
|
||||
constexpr Size& operator+=(Size& a, const Size& b)
|
||||
{
|
||||
a.expand(b);
|
||||
return a;
|
||||
}
|
||||
|
||||
constexpr Size& operator-=(Size& a, const Size& b)
|
||||
{
|
||||
a.expand(-b);
|
||||
return a;
|
||||
}
|
||||
|
||||
class Box;
|
||||
|
||||
class Rect {
|
||||
public:
|
||||
constexpr Rect() = default;
|
||||
constexpr explicit Rect(const Size& size) : Rect(size.w, size.h) {}
|
||||
constexpr Rect(float width, float height) : Rect(0, 0, width, height) {}
|
||||
constexpr Rect(const Point& origin, const Size& size) : Rect(origin.x, origin.y, size.w, size.h) {}
|
||||
constexpr Rect(const plutovg_rect_t& rect) : Rect(rect.x, rect.y, rect.w, rect.h) {}
|
||||
constexpr Rect(float x, float y, float w, float h) : x(x), y(y), w(w), h(h) {}
|
||||
|
||||
Rect(const Box& box);
|
||||
|
||||
constexpr void move(float dx, float dy) { x += dx; y += dy; }
|
||||
constexpr void move(float d) { move(d, d); }
|
||||
constexpr void move(const Point& p) { move(p.x, p.y); }
|
||||
|
||||
constexpr void scale(float sx, float sy) { x *= sx; y *= sy; w *= sx; h *= sy; }
|
||||
constexpr void scale(float s) { scale(s, s); }
|
||||
|
||||
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;
|
||||
|
||||
constexpr Rect& intersect(const Rect& o);
|
||||
constexpr Rect& unite(const Rect& o);
|
||||
|
||||
constexpr Point origin() const { return Point(x, y); }
|
||||
constexpr Size size() const { return Size(w, h); }
|
||||
|
||||
constexpr float right() const { return x + w; }
|
||||
constexpr float bottom() const { return y + h; }
|
||||
|
||||
constexpr bool isEmpty() const { return w <= 0.f || h <= 0.f; }
|
||||
constexpr bool isZero() const { return w <= 0.f && h <= 0.f; }
|
||||
constexpr bool isValid() const { return w >= 0.f && h >= 0.f; }
|
||||
|
||||
static const Rect Empty;
|
||||
static const Rect Invalid;
|
||||
static const Rect Infinite;
|
||||
|
||||
public:
|
||||
float x{0};
|
||||
float y{0};
|
||||
float w{0};
|
||||
float h{0};
|
||||
};
|
||||
|
||||
constexpr Rect Rect::intersected(const Rect& rect) const
|
||||
{
|
||||
if(!rect.isValid())
|
||||
return *this;
|
||||
if(!isValid())
|
||||
return rect;
|
||||
auto l = std::max(x, rect.x);
|
||||
auto t = std::max(y, rect.y);
|
||||
auto r = std::min(x + w, rect.x + rect.w);
|
||||
auto b = std::min(y + h, rect.y + rect.h);
|
||||
if(l >= r || t >= b)
|
||||
return Rect::Empty;
|
||||
return Rect(l, t, r - l, b - t);
|
||||
}
|
||||
|
||||
constexpr Rect Rect::united(const Rect& rect) const
|
||||
{
|
||||
if(!rect.isValid())
|
||||
return *this;
|
||||
if(!isValid())
|
||||
return rect;
|
||||
auto l = std::min(x, rect.x);
|
||||
auto t = std::min(y, rect.y);
|
||||
auto r = std::max(x + w, rect.x + rect.w);
|
||||
auto b = std::max(y + h, rect.y + rect.h);
|
||||
return Rect(l, t, r - l, b - t);
|
||||
}
|
||||
|
||||
constexpr Rect& Rect::intersect(const Rect& o)
|
||||
{
|
||||
*this = intersected(o);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Rect& Rect::unite(const Rect& o)
|
||||
{
|
||||
*this = united(o);
|
||||
return *this;
|
||||
}
|
||||
|
||||
class Matrix;
|
||||
|
||||
class Transform {
|
||||
public:
|
||||
Transform();
|
||||
Transform(const Matrix& matrix);
|
||||
Transform(float a, float b, float c, float d, float e, float f);
|
||||
Transform(const plutovg_matrix_t& matrix) : m_matrix(matrix) {}
|
||||
|
||||
Transform operator*(const Transform& transform) const;
|
||||
Transform& operator*=(const Transform& transform);
|
||||
|
||||
Transform& multiply(const Transform& transform);
|
||||
Transform& translate(float tx, float ty);
|
||||
Transform& scale(float sx, float sy);
|
||||
Transform& rotate(float angle, float cx = 0.f, float cy = 0.f);
|
||||
Transform& shear(float shx, float shy);
|
||||
|
||||
Transform& postMultiply(const Transform& transform);
|
||||
Transform& postTranslate(float tx, float ty);
|
||||
Transform& postScale(float sx, float sy);
|
||||
Transform& postRotate(float angle, float cx = 0.f, float cy = 0.f);
|
||||
Transform& postShear(float shx, float shy);
|
||||
|
||||
Transform inverse() const;
|
||||
Transform& invert();
|
||||
|
||||
void reset();
|
||||
|
||||
Point mapPoint(float x, float y) const;
|
||||
Point mapPoint(const Point& point) const;
|
||||
Rect mapRect(const Rect& rect) const;
|
||||
|
||||
float xScale() const;
|
||||
float yScale() const;
|
||||
|
||||
const plutovg_matrix_t& matrix() const { return m_matrix; }
|
||||
plutovg_matrix_t& matrix() { return m_matrix; }
|
||||
|
||||
bool parse(const char* data, size_t length);
|
||||
|
||||
static Transform translated(float tx, float ty);
|
||||
static Transform scaled(float sx, float sy);
|
||||
static Transform rotated(float angle, float cx, float cy);
|
||||
static Transform sheared(float shx, float shy);
|
||||
|
||||
static const Transform Identity;
|
||||
|
||||
private:
|
||||
plutovg_matrix_t m_matrix;
|
||||
};
|
||||
|
||||
enum class PathCommand {
|
||||
MoveTo = PLUTOVG_PATH_COMMAND_MOVE_TO,
|
||||
LineTo = PLUTOVG_PATH_COMMAND_LINE_TO,
|
||||
CubicTo = PLUTOVG_PATH_COMMAND_CUBIC_TO,
|
||||
Close = PLUTOVG_PATH_COMMAND_CLOSE
|
||||
};
|
||||
|
||||
class Path {
|
||||
public:
|
||||
Path() = default;
|
||||
Path(const Path& path);
|
||||
Path(Path&& path);
|
||||
~Path();
|
||||
|
||||
Path& operator=(const Path& path);
|
||||
Path& operator=(Path&& path);
|
||||
|
||||
void swap(Path& path);
|
||||
|
||||
void moveTo(float x, float y);
|
||||
void lineTo(float x, float y);
|
||||
void quadTo(float x1, float y1, float x2, float y2);
|
||||
void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3);
|
||||
void arcTo(float rx, float ry, float xAxisRotation, bool largeArcFlag, bool sweepFlag, float x, float y);
|
||||
void close();
|
||||
|
||||
void addEllipse(float cx, float cy, float rx, float ry);
|
||||
void addRoundRect(float x, float y, float w, float h, float rx, float ry);
|
||||
void addRect(float x, float y, float w, float h);
|
||||
|
||||
void addEllipse(const Point& center, const Size& radii);
|
||||
void addRoundRect(const Rect& rect, const Size& radii);
|
||||
void addRect(const Rect& rect);
|
||||
|
||||
void reset();
|
||||
|
||||
Rect boundingRect() const;
|
||||
bool isEmpty() const;
|
||||
bool isUnique() const;
|
||||
bool isNull() const { return m_data == nullptr; }
|
||||
plutovg_path_t* data() const { return m_data; }
|
||||
|
||||
bool parse(const char* data, size_t length);
|
||||
|
||||
private:
|
||||
plutovg_path_t* release();
|
||||
plutovg_path_t* ensure();
|
||||
plutovg_path_t* m_data = nullptr;
|
||||
};
|
||||
|
||||
inline void Path::swap(Path& path)
|
||||
{
|
||||
std::swap(m_data, path.m_data);
|
||||
}
|
||||
|
||||
inline plutovg_path_t* Path::release()
|
||||
{
|
||||
return std::exchange(m_data, nullptr);
|
||||
}
|
||||
|
||||
class PathIterator {
|
||||
public:
|
||||
PathIterator(const Path& path);
|
||||
|
||||
PathCommand currentSegment(std::array<Point, 3>& points) const;
|
||||
bool isDone() const { return m_index >= m_size; }
|
||||
void next();
|
||||
|
||||
private:
|
||||
const plutovg_path_element_t* m_elements;
|
||||
const int m_size;
|
||||
int m_index;
|
||||
};
|
||||
|
||||
class FontFace {
|
||||
public:
|
||||
FontFace() = default;
|
||||
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);
|
||||
FontFace(FontFace&& face);
|
||||
~FontFace();
|
||||
|
||||
FontFace& operator=(const FontFace& face);
|
||||
FontFace& operator=(FontFace&& face);
|
||||
|
||||
void swap(FontFace& face);
|
||||
|
||||
bool isNull() const { return m_face == nullptr; }
|
||||
plutovg_font_face_t* get() const { return m_face; }
|
||||
|
||||
private:
|
||||
plutovg_font_face_t* release();
|
||||
plutovg_font_face_t* m_face = nullptr;
|
||||
};
|
||||
|
||||
class FontFaceCache {
|
||||
public:
|
||||
bool addFontFace(const std::string& family, bool bold, bool italic, const FontFace& face);
|
||||
FontFace getFontFace(const std::string& family, bool bold, bool italic) const;
|
||||
|
||||
private:
|
||||
FontFaceCache();
|
||||
plutovg_font_face_cache_t* m_cache;
|
||||
friend FontFaceCache* fontFaceCache();
|
||||
};
|
||||
|
||||
FontFaceCache* fontFaceCache();
|
||||
|
||||
class Font {
|
||||
public:
|
||||
Font() = default;
|
||||
Font(const FontFace& face, float size);
|
||||
|
||||
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;
|
||||
|
||||
const FontFace& face() const { return m_face; }
|
||||
float size() const { return m_size; }
|
||||
|
||||
bool isNull() const { return m_size <= 0.f || m_face.isNull(); }
|
||||
|
||||
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 {
|
||||
Plain = PLUTOVG_TEXTURE_TYPE_PLAIN,
|
||||
Tiled = PLUTOVG_TEXTURE_TYPE_TILED
|
||||
};
|
||||
|
||||
enum class BlendMode {
|
||||
Src = PLUTOVG_OPERATOR_SRC,
|
||||
Src_Over = PLUTOVG_OPERATOR_SRC_OVER,
|
||||
Dst_In = PLUTOVG_OPERATOR_DST_IN,
|
||||
Dst_Out = PLUTOVG_OPERATOR_DST_OUT
|
||||
};
|
||||
|
||||
using DashArray = std::vector<float>;
|
||||
|
||||
class StrokeData {
|
||||
public:
|
||||
explicit StrokeData(float lineWidth = 1.f) : m_lineWidth(lineWidth) {}
|
||||
|
||||
void setLineWidth(float lineWidth) { m_lineWidth = lineWidth; }
|
||||
float lineWidth() const { return m_lineWidth; }
|
||||
|
||||
void setMiterLimit(float miterLimit) { m_miterLimit = miterLimit; }
|
||||
float miterLimit() const { return m_miterLimit; }
|
||||
|
||||
void setDashOffset(float dashOffset) { m_dashOffset = dashOffset; }
|
||||
float dashOffset() const { return m_dashOffset; }
|
||||
|
||||
void setDashArray(DashArray dashArray) { m_dashArray = std::move(dashArray); }
|
||||
const DashArray& dashArray() const { return m_dashArray; }
|
||||
|
||||
void setLineCap(LineCap lineCap) { m_lineCap = lineCap; }
|
||||
LineCap lineCap() const { return m_lineCap; }
|
||||
|
||||
void setLineJoin(LineJoin lineJoin) { m_lineJoin = lineJoin; }
|
||||
LineJoin lineJoin() const { return m_lineJoin; }
|
||||
|
||||
private:
|
||||
float m_lineWidth;
|
||||
float m_miterLimit{4.f};
|
||||
float m_dashOffset{0.f};
|
||||
LineCap m_lineCap{LineCap::Butt};
|
||||
LineJoin m_lineJoin{LineJoin::Miter};
|
||||
DashArray m_dashArray;
|
||||
};
|
||||
|
||||
using GradientStop = plutovg_gradient_stop_t;
|
||||
using GradientStops = std::vector<GradientStop>;
|
||||
|
||||
class Bitmap;
|
||||
|
||||
class Canvas {
|
||||
public:
|
||||
static std::shared_ptr<Canvas> create(const Bitmap& bitmap);
|
||||
static std::shared_ptr<Canvas> create(float x, float y, float width, float height);
|
||||
static std::shared_ptr<Canvas> create(const Rect& extents);
|
||||
|
||||
void setColor(const Color& color);
|
||||
void setColor(float r, float g, float b, float a);
|
||||
void setLinearGradient(float x1, float y1, float x2, float y2, SpreadMethod spread, const GradientStops& stops, const Transform& transform);
|
||||
void setRadialGradient(float cx, float cy, float r, float fx, float fy, SpreadMethod spread, const GradientStops& stops, const Transform& transform);
|
||||
void setTexture(const Canvas& source, TextureType type, float opacity, const Transform& transform);
|
||||
|
||||
void fillPath(const Path& path, FillRule fillRule, const Transform& transform);
|
||||
void strokePath(const Path& path, const StrokeData& strokeData, const Transform& transform);
|
||||
|
||||
void fillText(const std::u32string_view& text, const Font& font, const Point& origin, const Transform& transform);
|
||||
void strokeText(const std::u32string_view& text, float strokeWidth, const Font& font, const Point& origin, const Transform& transform);
|
||||
|
||||
void clipPath(const Path& path, FillRule clipRule, const Transform& transform);
|
||||
void clipRect(const Rect& rect, FillRule clipRule, const Transform& transform);
|
||||
|
||||
void drawImage(const Bitmap& image, const Rect& dstRect, const Rect& srcRect, const Transform& transform);
|
||||
void blendCanvas(const Canvas& canvas, BlendMode blendMode, float opacity);
|
||||
|
||||
void save();
|
||||
void restore();
|
||||
|
||||
void convertToLuminanceMask();
|
||||
|
||||
int x() const { return m_x; }
|
||||
int y() const { return m_y; }
|
||||
int width() const;
|
||||
int height() const;
|
||||
|
||||
Rect extents() const { return Rect(m_x, m_y, width(), height()); }
|
||||
|
||||
plutovg_surface_t* surface() const { return m_surface; }
|
||||
plutovg_canvas_t* canvas() const { return m_canvas; }
|
||||
|
||||
~Canvas();
|
||||
|
||||
private:
|
||||
Canvas(const Bitmap& bitmap);
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_GRAPHICS_H
|
||||
536
vendor/lunasvg/source/lunasvg.cpp
vendored
536
vendor/lunasvg/source/lunasvg.cpp
vendored
@@ -1,536 +0,0 @@
|
||||
#include "lunasvg.h"
|
||||
#include "svgelement.h"
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <cmath>
|
||||
|
||||
int lunasvg_version()
|
||||
{
|
||||
return LUNASVG_VERSION;
|
||||
}
|
||||
|
||||
const char* lunasvg_version_string()
|
||||
{
|
||||
return LUNASVG_VERSION_STRING;
|
||||
}
|
||||
|
||||
bool lunasvg_add_font_face_from_file(const char* family, bool bold, bool italic, const char* filename)
|
||||
{
|
||||
return lunasvg::fontFaceCache()->addFontFace(family, bold, italic, lunasvg::FontFace(filename));
|
||||
}
|
||||
|
||||
bool lunasvg_add_font_face_from_data(const char* family, bool bold, bool italic, const void* data, size_t length, lunasvg_destroy_func_t destroy_func, void* closure)
|
||||
{
|
||||
return lunasvg::fontFaceCache()->addFontFace(family, bold, italic, lunasvg::FontFace(data, length, destroy_func, closure));
|
||||
}
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
Bitmap::Bitmap(int width, int height)
|
||||
: m_surface(plutovg_surface_create(width, height))
|
||||
{
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(uint8_t* data, int width, int height, int stride)
|
||||
: m_surface(plutovg_surface_create_for_data(data, width, height, stride))
|
||||
{
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(const Bitmap& bitmap)
|
||||
: m_surface(plutovg_surface_reference(bitmap.surface()))
|
||||
{
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(Bitmap&& bitmap)
|
||||
: m_surface(bitmap.release())
|
||||
{
|
||||
}
|
||||
|
||||
Bitmap::~Bitmap()
|
||||
{
|
||||
plutovg_surface_destroy(m_surface);
|
||||
}
|
||||
|
||||
Bitmap& Bitmap::operator=(const Bitmap& bitmap)
|
||||
{
|
||||
Bitmap(bitmap).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Bitmap::swap(Bitmap& bitmap)
|
||||
{
|
||||
std::swap(m_surface, bitmap.m_surface);
|
||||
}
|
||||
|
||||
uint8_t* Bitmap::data() const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_get_data(m_surface);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Bitmap::width() const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_get_width(m_surface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Bitmap::height() const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_get_height(m_surface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Bitmap::stride() const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_get_stride(m_surface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Bitmap::clear(uint32_t value)
|
||||
{
|
||||
if(m_surface == nullptr)
|
||||
return;
|
||||
plutovg_color_t color;
|
||||
plutovg_color_init_rgba32(&color, value);
|
||||
plutovg_surface_clear(m_surface, &color);
|
||||
}
|
||||
|
||||
void Bitmap::convertToRGBA()
|
||||
{
|
||||
if(m_surface == nullptr)
|
||||
return;
|
||||
auto data = plutovg_surface_get_data(m_surface);
|
||||
auto width = plutovg_surface_get_width(m_surface);
|
||||
auto height = plutovg_surface_get_height(m_surface);
|
||||
auto stride = plutovg_surface_get_stride(m_surface);
|
||||
plutovg_convert_argb_to_rgba(data, data, width, height, stride);
|
||||
}
|
||||
|
||||
Bitmap& Bitmap::operator=(Bitmap&& bitmap)
|
||||
{
|
||||
Bitmap(std::move(bitmap)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Bitmap::writeToPng(const std::string& filename) const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_write_to_png(m_surface, filename.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bitmap::writeToPng(lunasvg_write_func_t callback, void* closure) const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_write_to_png_stream(m_surface, callback, closure);
|
||||
return false;
|
||||
}
|
||||
|
||||
plutovg_surface_t* Bitmap::release()
|
||||
{
|
||||
return std::exchange(m_surface, nullptr);
|
||||
}
|
||||
|
||||
Box::Box(float x, float y, float w, float h)
|
||||
: x(x), y(y), w(w), h(h)
|
||||
{
|
||||
}
|
||||
|
||||
Box::Box(const Rect& rect)
|
||||
: x(rect.x), y(rect.y), w(rect.w), h(rect.h)
|
||||
{
|
||||
}
|
||||
|
||||
Box& Box::transform(const Matrix &matrix)
|
||||
{
|
||||
*this = transformed(matrix);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Box Box::transformed(const Matrix& matrix) const
|
||||
{
|
||||
return Transform(matrix).mapRect(*this);
|
||||
}
|
||||
|
||||
Matrix::Matrix(float a, float b, float c, float d, float e, float f)
|
||||
: a(a), b(b), c(c), d(d), e(e), f(f)
|
||||
{
|
||||
}
|
||||
|
||||
Matrix::Matrix(const plutovg_matrix_t& matrix)
|
||||
: a(matrix.a), b(matrix.b), c(matrix.c), d(matrix.d), e(matrix.e), f(matrix.f)
|
||||
{
|
||||
}
|
||||
|
||||
Matrix::Matrix(const Transform& transform)
|
||||
: Matrix(transform.matrix())
|
||||
{
|
||||
}
|
||||
|
||||
Matrix Matrix::operator*(const Matrix& matrix) const
|
||||
{
|
||||
return Transform(*this) * Transform(matrix);
|
||||
}
|
||||
|
||||
Matrix& Matrix::operator*=(const Matrix &matrix)
|
||||
{
|
||||
return (*this = *this * matrix);
|
||||
}
|
||||
|
||||
Matrix& Matrix::multiply(const Matrix& matrix)
|
||||
{
|
||||
return (*this *= matrix);
|
||||
}
|
||||
|
||||
Matrix& Matrix::scale(float sx, float sy)
|
||||
{
|
||||
return multiply(scaled(sx, sy));
|
||||
}
|
||||
|
||||
Matrix& Matrix::translate(float tx, float ty)
|
||||
{
|
||||
return multiply(translated(tx, ty));
|
||||
}
|
||||
|
||||
Matrix& Matrix::rotate(float angle, float cx, float cy)
|
||||
{
|
||||
return multiply(rotated(angle, cx, cy));
|
||||
}
|
||||
|
||||
Matrix& Matrix::shear(float shx, float shy)
|
||||
{
|
||||
return multiply(sheared(shx, shy));
|
||||
}
|
||||
|
||||
Matrix Matrix::inverse() const
|
||||
{
|
||||
return Transform(*this).inverse();
|
||||
}
|
||||
|
||||
Matrix& Matrix::invert()
|
||||
{
|
||||
return (*this = inverse());
|
||||
}
|
||||
|
||||
void Matrix::reset()
|
||||
{
|
||||
*this = Matrix(1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
Matrix Matrix::translated(float tx, float ty)
|
||||
{
|
||||
return Transform::translated(tx, ty);
|
||||
}
|
||||
|
||||
Matrix Matrix::scaled(float sx, float sy)
|
||||
{
|
||||
return Transform::scaled(sx, sy);
|
||||
}
|
||||
|
||||
Matrix Matrix::rotated(float angle, float cx, float cy)
|
||||
{
|
||||
return Transform::rotated(angle, cx, cy);
|
||||
}
|
||||
|
||||
Matrix Matrix::sheared(float shx, float shy)
|
||||
{
|
||||
return Transform::sheared(shx, shy);
|
||||
}
|
||||
|
||||
Node::Node(SVGNode* node)
|
||||
: m_node(node)
|
||||
{
|
||||
}
|
||||
|
||||
bool Node::isTextNode() const
|
||||
{
|
||||
return m_node && m_node->isTextNode();
|
||||
}
|
||||
|
||||
bool Node::isElement() const
|
||||
{
|
||||
return m_node && m_node->isElement();
|
||||
}
|
||||
|
||||
TextNode Node::toTextNode() const
|
||||
{
|
||||
if(m_node && m_node->isTextNode())
|
||||
return static_cast<SVGTextNode*>(m_node);
|
||||
return TextNode();
|
||||
}
|
||||
|
||||
Element Node::toElement() const
|
||||
{
|
||||
if(m_node && m_node->isElement())
|
||||
return static_cast<SVGElement*>(m_node);
|
||||
return Element();
|
||||
}
|
||||
|
||||
Element Node::parentElement() const
|
||||
{
|
||||
if(m_node)
|
||||
return m_node->parentElement();
|
||||
return Element();
|
||||
}
|
||||
|
||||
TextNode::TextNode(SVGTextNode* text)
|
||||
: Node(text)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string& TextNode::data() const
|
||||
{
|
||||
if(m_node)
|
||||
return text()->data();
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
void TextNode::setData(const std::string& data)
|
||||
{
|
||||
if(m_node) {
|
||||
text()->setData(data);
|
||||
}
|
||||
}
|
||||
|
||||
SVGTextNode* TextNode::text() const
|
||||
{
|
||||
return static_cast<SVGTextNode*>(m_node);
|
||||
}
|
||||
|
||||
Element::Element(SVGElement* element)
|
||||
: Node(element)
|
||||
{
|
||||
}
|
||||
|
||||
bool Element::hasAttribute(const std::string& name) const
|
||||
{
|
||||
if(m_node)
|
||||
return element()->hasAttribute(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& Element::getAttribute(const std::string& name) const
|
||||
{
|
||||
if(m_node)
|
||||
return element()->getAttribute(name);
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
void Element::setAttribute(const std::string& name, const std::string& value)
|
||||
{
|
||||
if(m_node) {
|
||||
element()->setAttribute(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
void Element::render(Bitmap& bitmap, const Matrix& matrix) const
|
||||
{
|
||||
if(m_node == nullptr || bitmap.isNull())
|
||||
return;
|
||||
auto canvas = Canvas::create(bitmap);
|
||||
SVGRenderState state(nullptr, nullptr, matrix, SVGRenderMode::Painting, canvas);
|
||||
element(true)->render(state);
|
||||
}
|
||||
|
||||
Bitmap Element::renderToBitmap(int width, int height, uint32_t backgroundColor) const
|
||||
{
|
||||
if(m_node == nullptr)
|
||||
return Bitmap();
|
||||
auto elementBounds = element(true)->localTransform().mapRect(element()->paintBoundingBox());
|
||||
if(elementBounds.isEmpty())
|
||||
return Bitmap();
|
||||
if(width <= 0 && height <= 0) {
|
||||
width = static_cast<int>(std::ceil(elementBounds.w));
|
||||
height = static_cast<int>(std::ceil(elementBounds.h));
|
||||
} else if(width > 0 && height <= 0) {
|
||||
height = static_cast<int>(std::ceil(width * elementBounds.h / elementBounds.w));
|
||||
} else if(height > 0 && width <= 0) {
|
||||
width = static_cast<int>(std::ceil(height * elementBounds.w / elementBounds.h));
|
||||
}
|
||||
|
||||
auto xScale = width / elementBounds.w;
|
||||
auto yScale = height / elementBounds.h;
|
||||
|
||||
Matrix matrix(xScale, 0, 0, yScale, -elementBounds.x * xScale, -elementBounds.y * yScale);
|
||||
Bitmap bitmap(width, height);
|
||||
if(backgroundColor) bitmap.clear(backgroundColor);
|
||||
render(bitmap, matrix);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
Matrix Element::getLocalMatrix() const
|
||||
{
|
||||
if(m_node)
|
||||
return element(true)->localTransform();
|
||||
return Matrix();
|
||||
}
|
||||
|
||||
Matrix Element::getGlobalMatrix() const
|
||||
{
|
||||
if(m_node == nullptr)
|
||||
return Matrix();
|
||||
auto transform = element(true)->localTransform();
|
||||
for(auto parent = element()->parentElement(); parent; parent = parent->parentElement())
|
||||
transform.postMultiply(parent->localTransform());
|
||||
return transform;
|
||||
}
|
||||
|
||||
Box Element::getLocalBoundingBox() const
|
||||
{
|
||||
return getBoundingBox().transformed(getLocalMatrix());
|
||||
}
|
||||
|
||||
Box Element::getGlobalBoundingBox() const
|
||||
{
|
||||
return getBoundingBox().transformed(getGlobalMatrix());
|
||||
}
|
||||
|
||||
Box Element::getBoundingBox() const
|
||||
{
|
||||
if(m_node)
|
||||
return element(true)->paintBoundingBox();
|
||||
return Box();
|
||||
}
|
||||
|
||||
NodeList Element::children() const
|
||||
{
|
||||
if(m_node == nullptr)
|
||||
return NodeList();
|
||||
NodeList children;
|
||||
for(const auto& child : element()->children())
|
||||
children.push_back(child.get());
|
||||
return children;
|
||||
}
|
||||
|
||||
SVGElement* Element::element(bool layoutIfNeeded) const
|
||||
{
|
||||
auto element = static_cast<SVGElement*>(m_node);
|
||||
if(element && layoutIfNeeded)
|
||||
element->rootElement()->layoutIfNeeded();
|
||||
return element;
|
||||
}
|
||||
|
||||
std::unique_ptr<Document> Document::loadFromFile(const std::string& filename)
|
||||
{
|
||||
std::ifstream fs;
|
||||
fs.open(filename);
|
||||
if(!fs.is_open())
|
||||
return nullptr;
|
||||
std::string content;
|
||||
std::getline(fs, content, '\0');
|
||||
fs.close();
|
||||
return loadFromData(content);
|
||||
}
|
||||
|
||||
std::unique_ptr<Document> Document::loadFromData(const std::string& string)
|
||||
{
|
||||
return loadFromData(string.data(), string.size());
|
||||
}
|
||||
|
||||
std::unique_ptr<Document> Document::loadFromData(const char* data)
|
||||
{
|
||||
return loadFromData(data, std::strlen(data));
|
||||
}
|
||||
|
||||
std::unique_ptr<Document> Document::loadFromData(const char* data, size_t length)
|
||||
{
|
||||
std::unique_ptr<Document> document(new Document);
|
||||
if(!document->parse(data, length))
|
||||
return nullptr;
|
||||
return document;
|
||||
}
|
||||
|
||||
float Document::width() const
|
||||
{
|
||||
return rootElement(true)->intrinsicWidth();
|
||||
}
|
||||
|
||||
float Document::height() const
|
||||
{
|
||||
return rootElement(true)->intrinsicHeight();
|
||||
}
|
||||
|
||||
Box Document::boundingBox() const
|
||||
{
|
||||
return rootElement(true)->localTransform().mapRect(rootElement()->paintBoundingBox());
|
||||
}
|
||||
|
||||
void Document::updateLayout()
|
||||
{
|
||||
m_rootElement->layoutIfNeeded();
|
||||
}
|
||||
|
||||
void Document::forceLayout()
|
||||
{
|
||||
m_rootElement->forceLayout();
|
||||
}
|
||||
|
||||
void Document::render(Bitmap& bitmap, const Matrix& matrix) const
|
||||
{
|
||||
if(bitmap.isNull())
|
||||
return;
|
||||
auto canvas = Canvas::create(bitmap);
|
||||
SVGRenderState state(nullptr, nullptr, matrix, SVGRenderMode::Painting, canvas);
|
||||
rootElement(true)->render(state);
|
||||
}
|
||||
|
||||
Bitmap Document::renderToBitmap(int width, int height, uint32_t backgroundColor) const
|
||||
{
|
||||
auto intrinsicWidth = rootElement(true)->intrinsicWidth();
|
||||
auto intrinsicHeight = rootElement()->intrinsicHeight();
|
||||
if(intrinsicWidth == 0.f || intrinsicHeight == 0.f)
|
||||
return Bitmap();
|
||||
if(width <= 0 && height <= 0) {
|
||||
width = static_cast<int>(std::ceil(intrinsicWidth));
|
||||
height = static_cast<int>(std::ceil(intrinsicHeight));
|
||||
} else if(width > 0 && height <= 0) {
|
||||
height = static_cast<int>(std::ceil(width * intrinsicHeight / intrinsicWidth));
|
||||
} else if(height > 0 && width <= 0) {
|
||||
width = static_cast<int>(std::ceil(height * intrinsicWidth / intrinsicHeight));
|
||||
}
|
||||
|
||||
auto xScale = width / intrinsicWidth;
|
||||
auto yScale = height / intrinsicHeight;
|
||||
|
||||
Matrix matrix(xScale, 0, 0, yScale, 0, 0);
|
||||
Bitmap bitmap(width, height);
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Document::Document() = default;
|
||||
Document::~Document() = default;
|
||||
|
||||
} // namespace lunasvg
|
||||
1225
vendor/lunasvg/source/svgelement.cpp
vendored
1225
vendor/lunasvg/source/svgelement.cpp
vendored
File diff suppressed because it is too large
Load Diff
498
vendor/lunasvg/source/svgelement.h
vendored
498
vendor/lunasvg/source/svgelement.h
vendored
@@ -1,498 +0,0 @@
|
||||
#ifndef LUNASVG_SVGELEMENT_H
|
||||
#define LUNASVG_SVGELEMENT_H
|
||||
|
||||
#include "lunasvg.h"
|
||||
#include "svgproperty.h"
|
||||
|
||||
#include <string>
|
||||
#include <forward_list>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
class Document;
|
||||
class SVGElement;
|
||||
class SVGRootElement;
|
||||
|
||||
class SVGNode {
|
||||
public:
|
||||
SVGNode(Document* document)
|
||||
: m_document(document)
|
||||
{}
|
||||
|
||||
virtual ~SVGNode() = default;
|
||||
virtual bool isTextNode() const { return false; }
|
||||
virtual bool isElement() const { return false; }
|
||||
virtual bool isPaintElement() const { return false; }
|
||||
virtual bool isGraphicsElement() const { return false; }
|
||||
virtual bool isGeometryElement() const { return false; }
|
||||
virtual bool isTextPositioningElement() const { return false; }
|
||||
|
||||
Document* document() const { return m_document; }
|
||||
SVGRootElement* rootElement() const { return m_document->rootElement(); }
|
||||
|
||||
SVGElement* parentElement() const { return m_parentElement; }
|
||||
void setParentElement(SVGElement* parent) { m_parentElement = parent; }
|
||||
|
||||
bool isRootElement() const { return m_parentElement == nullptr; }
|
||||
|
||||
virtual std::unique_ptr<SVGNode> clone(bool deep) const = 0;
|
||||
|
||||
private:
|
||||
SVGNode(const SVGNode&) = delete;
|
||||
SVGNode& operator=(const SVGNode&) = delete;
|
||||
Document* m_document;
|
||||
SVGElement* m_parentElement = nullptr;
|
||||
};
|
||||
|
||||
class SVGTextNode final : public SVGNode {
|
||||
public:
|
||||
SVGTextNode(Document* document);
|
||||
|
||||
bool isTextNode() const final { return true; }
|
||||
|
||||
const std::string& data() const { return m_data; }
|
||||
void setData(const std::string& data);
|
||||
|
||||
std::unique_ptr<SVGNode> clone(bool deep) const final;
|
||||
|
||||
private:
|
||||
std::string m_data;
|
||||
};
|
||||
|
||||
class Attribute {
|
||||
public:
|
||||
Attribute() = default;
|
||||
Attribute(int specificity, PropertyID id, std::string value)
|
||||
: m_specificity(specificity), m_id(id), m_value(std::move(value))
|
||||
{}
|
||||
|
||||
int specificity() const { return m_specificity; }
|
||||
PropertyID id() const { return m_id; }
|
||||
const std::string& value() const { return m_value; }
|
||||
|
||||
private:
|
||||
int m_specificity;
|
||||
PropertyID m_id;
|
||||
std::string m_value;
|
||||
};
|
||||
|
||||
using AttributeList = std::forward_list<Attribute>;
|
||||
|
||||
enum class ElementID : uint8_t {
|
||||
Unknown = 0,
|
||||
Star,
|
||||
Circle,
|
||||
ClipPath,
|
||||
Defs,
|
||||
Ellipse,
|
||||
G,
|
||||
Image,
|
||||
Line,
|
||||
LinearGradient,
|
||||
Marker,
|
||||
Mask,
|
||||
Path,
|
||||
Pattern,
|
||||
Polygon,
|
||||
Polyline,
|
||||
RadialGradient,
|
||||
Rect,
|
||||
Stop,
|
||||
Style,
|
||||
Svg,
|
||||
Symbol,
|
||||
Text,
|
||||
Tspan,
|
||||
Use
|
||||
};
|
||||
|
||||
ElementID elementid(std::string_view name);
|
||||
|
||||
using SVGNodeList = std::list<std::unique_ptr<SVGNode>>;
|
||||
using SVGPropertyList = std::forward_list<SVGProperty*>;
|
||||
|
||||
class SVGMarkerElement;
|
||||
class SVGClipPathElement;
|
||||
class SVGMaskElement;
|
||||
class SVGPaintElement;
|
||||
class SVGLayoutState;
|
||||
class SVGRenderState;
|
||||
|
||||
extern const std::string emptyString;
|
||||
|
||||
class SVGElement : public SVGNode {
|
||||
public:
|
||||
static std::unique_ptr<SVGElement> create(Document* document, ElementID id);
|
||||
|
||||
SVGElement(Document* document, ElementID id);
|
||||
virtual ~SVGElement() = default;
|
||||
|
||||
bool hasAttribute(std::string_view name) const;
|
||||
const std::string& getAttribute(std::string_view name) const;
|
||||
bool setAttribute(std::string_view name, const std::string& value);
|
||||
|
||||
const Attribute* findAttribute(PropertyID id) const;
|
||||
bool hasAttribute(PropertyID id) const;
|
||||
const std::string& getAttribute(PropertyID id) const;
|
||||
bool setAttribute(int specificity, PropertyID id, const std::string& value);
|
||||
void setAttributes(const AttributeList& attributes);
|
||||
bool setAttribute(const Attribute& attribute);
|
||||
|
||||
virtual void parseAttribute(PropertyID id, const std::string& value);
|
||||
|
||||
SVGElement* previousElement() const;
|
||||
SVGElement* nextElement() const;
|
||||
|
||||
SVGNode* addChild(std::unique_ptr<SVGNode> child);
|
||||
SVGNode* firstChild() const;
|
||||
SVGNode* lastChild() const;
|
||||
|
||||
ElementID id() const { return m_id; }
|
||||
const AttributeList& attributes() const { return m_attributes; }
|
||||
const SVGPropertyList& properties() const { return m_properties; }
|
||||
const SVGNodeList& children() const { return m_children; }
|
||||
|
||||
virtual Transform localTransform() const { return Transform::Identity; }
|
||||
virtual Rect fillBoundingBox() const;
|
||||
virtual Rect strokeBoundingBox() const;
|
||||
virtual Rect paintBoundingBox() const;
|
||||
|
||||
SVGMarkerElement* getMarker(std::string_view id) const;
|
||||
SVGClipPathElement* getClipper(std::string_view id) const;
|
||||
SVGMaskElement* getMasker(std::string_view id) const;
|
||||
SVGPaintElement* getPainter(std::string_view id) const;
|
||||
|
||||
SVGElement* elementFromPoint(float x, float y);
|
||||
|
||||
template<typename T>
|
||||
void transverse(T callback);
|
||||
|
||||
void addProperty(SVGProperty& value);
|
||||
SVGProperty* getProperty(PropertyID id) const;
|
||||
Size currentViewportSize() const;
|
||||
float font_size() const { return m_font_size; }
|
||||
|
||||
void cloneChildren(SVGElement* parentElement) const;
|
||||
std::unique_ptr<SVGNode> clone(bool deep) const final;
|
||||
|
||||
virtual void build();
|
||||
|
||||
virtual void layoutElement(const SVGLayoutState& state);
|
||||
void layoutChildren(SVGLayoutState& state);
|
||||
virtual void layout(SVGLayoutState& state);
|
||||
|
||||
void renderChildren(SVGRenderState& state) const;
|
||||
virtual void render(SVGRenderState& state) const;
|
||||
|
||||
bool isDisplayNone() const { return m_display == Display::None; }
|
||||
bool isOverflowHidden() const { return m_overflow == Overflow::Hidden; }
|
||||
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; }
|
||||
float opacity() const { return m_opacity; }
|
||||
|
||||
bool isElement() const final { return true; }
|
||||
|
||||
private:
|
||||
mutable Rect m_paintBoundingBox = Rect::Invalid;
|
||||
const SVGClipPathElement* m_clipper = nullptr;
|
||||
const SVGMaskElement* m_masker = nullptr;
|
||||
float m_opacity = 1.f;
|
||||
|
||||
float m_font_size = 12.f;
|
||||
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;
|
||||
SVGPropertyList m_properties;
|
||||
SVGNodeList m_children;
|
||||
};
|
||||
|
||||
inline const SVGElement* toSVGElement(const SVGNode* node)
|
||||
{
|
||||
if(node && node->isElement())
|
||||
return static_cast<const SVGElement*>(node);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline SVGElement* toSVGElement(SVGNode* node)
|
||||
{
|
||||
if(node && node->isElement())
|
||||
return static_cast<SVGElement*>(node);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline SVGElement* toSVGElement(const std::unique_ptr<SVGNode>& node)
|
||||
{
|
||||
return toSVGElement(node.get());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void SVGElement::transverse(T callback)
|
||||
{
|
||||
callback(this);
|
||||
for(const auto& child : m_children) {
|
||||
if(auto element = toSVGElement(child)) {
|
||||
element->transverse(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SVGStyleElement final : public SVGElement {
|
||||
public:
|
||||
SVGStyleElement(Document* document);
|
||||
};
|
||||
|
||||
class SVGFitToViewBox {
|
||||
public:
|
||||
SVGFitToViewBox(SVGElement* element);
|
||||
|
||||
const SVGRect& viewBox() const { return m_viewBox; }
|
||||
const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; }
|
||||
Transform viewBoxToViewTransform(const Size& viewportSize) const;
|
||||
Rect getClipRect(const Size& viewportSize) const;
|
||||
|
||||
private:
|
||||
SVGRect m_viewBox;
|
||||
SVGPreserveAspectRatio m_preserveAspectRatio;
|
||||
};
|
||||
|
||||
class SVGURIReference {
|
||||
public:
|
||||
SVGURIReference(SVGElement* element);
|
||||
|
||||
const SVGString& href() const { return m_href; }
|
||||
const std::string& hrefString() const { return m_href.value(); }
|
||||
SVGElement* getTargetElement(const Document* document) const;
|
||||
|
||||
private:
|
||||
SVGString m_href;
|
||||
};
|
||||
|
||||
class SVGPaintServer {
|
||||
public:
|
||||
SVGPaintServer() = default;
|
||||
SVGPaintServer(const SVGPaintElement* element, const Color& color, float opacity)
|
||||
: m_element(element), m_color(color), m_opacity(opacity)
|
||||
{}
|
||||
|
||||
bool isRenderable() const { return m_opacity > 0.f && (m_element || m_color.alpha() > 0); }
|
||||
|
||||
const SVGPaintElement* element() const { return m_element; }
|
||||
const Color& color() const { return m_color; }
|
||||
float opacity() const { return m_opacity; }
|
||||
|
||||
bool applyPaint(SVGRenderState& state) const;
|
||||
|
||||
private:
|
||||
const SVGPaintElement* m_element = nullptr;
|
||||
Color m_color = Color::Transparent;
|
||||
float m_opacity = 0.f;
|
||||
};
|
||||
|
||||
class SVGGraphicsElement : public SVGElement {
|
||||
public:
|
||||
SVGGraphicsElement(Document* document, ElementID id);
|
||||
|
||||
bool isGraphicsElement() const final { return true; }
|
||||
|
||||
const SVGTransform& transform() const { return m_transform; }
|
||||
Transform localTransform() const override { return m_transform.value(); }
|
||||
|
||||
SVGPaintServer getPaintServer(const Paint& paint, float opacity) const;
|
||||
StrokeData getStrokeData(const SVGLayoutState& state) const;
|
||||
|
||||
private:
|
||||
SVGTransform m_transform;
|
||||
};
|
||||
|
||||
class SVGSVGElement : public SVGGraphicsElement, public SVGFitToViewBox {
|
||||
public:
|
||||
SVGSVGElement(Document* document);
|
||||
|
||||
const SVGLength& x() const { return m_x; }
|
||||
const SVGLength& y() const { return m_y; }
|
||||
const SVGLength& width() const { return m_width; }
|
||||
const SVGLength& height() const { return m_height; }
|
||||
|
||||
Transform localTransform() const override;
|
||||
void render(SVGRenderState& state) const override;
|
||||
|
||||
private:
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
};
|
||||
|
||||
class SVGRootElement final : public SVGSVGElement {
|
||||
public:
|
||||
SVGRootElement(Document* document);
|
||||
|
||||
float intrinsicWidth() const { return m_intrinsicWidth; }
|
||||
float intrinsicHeight() const { return m_intrinsicHeight; }
|
||||
|
||||
void setNeedsLayout() { m_intrinsicWidth = -1.f; }
|
||||
bool needsLayout() const { return m_intrinsicWidth == -1.f; }
|
||||
|
||||
SVGRootElement* layoutIfNeeded();
|
||||
|
||||
SVGElement* getElementById(std::string_view id) const;
|
||||
void addElementById(const std::string& id, SVGElement* element);
|
||||
void layout(SVGLayoutState& state) final;
|
||||
|
||||
void forceLayout();
|
||||
|
||||
private:
|
||||
std::map<std::string, SVGElement*, std::less<>> m_idCache;
|
||||
float m_intrinsicWidth{-1.f};
|
||||
float m_intrinsicHeight{-1.f};
|
||||
};
|
||||
|
||||
class SVGUseElement final : public SVGGraphicsElement, public SVGURIReference {
|
||||
public:
|
||||
SVGUseElement(Document* document);
|
||||
|
||||
const SVGLength& x() const { return m_x; }
|
||||
const SVGLength& y() const { return m_y; }
|
||||
const SVGLength& width() const { return m_width; }
|
||||
const SVGLength& height() const { return m_height; }
|
||||
|
||||
Transform localTransform() const final;
|
||||
void render(SVGRenderState& state) const final;
|
||||
void build() final;
|
||||
|
||||
private:
|
||||
std::unique_ptr<SVGElement> cloneTargetElement(SVGElement* targetElement);
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
};
|
||||
|
||||
class SVGImageElement final : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGImageElement(Document* document);
|
||||
|
||||
const SVGLength& x() const { return m_x; }
|
||||
const SVGLength& y() const { return m_y; }
|
||||
const SVGLength& width() const { return m_width; }
|
||||
const SVGLength& height() const { return m_height; }
|
||||
const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; }
|
||||
const Bitmap& image() const { return m_image; }
|
||||
|
||||
Rect fillBoundingBox() const final;
|
||||
Rect strokeBoundingBox() const final;
|
||||
void render(SVGRenderState& state) const final;
|
||||
void parseAttribute(PropertyID id, const std::string& value) final;
|
||||
|
||||
private:
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
SVGPreserveAspectRatio m_preserveAspectRatio;
|
||||
Bitmap m_image;
|
||||
};
|
||||
|
||||
class SVGSymbolElement final : public SVGGraphicsElement, public SVGFitToViewBox {
|
||||
public:
|
||||
SVGSymbolElement(Document* document);
|
||||
};
|
||||
|
||||
class SVGGElement final : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGGElement(Document* document);
|
||||
|
||||
void render(SVGRenderState& state) const final;
|
||||
};
|
||||
|
||||
class SVGDefsElement final : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGDefsElement(Document* document);
|
||||
};
|
||||
|
||||
class SVGMarkerElement final : public SVGElement, public SVGFitToViewBox {
|
||||
public:
|
||||
SVGMarkerElement(Document* document);
|
||||
|
||||
const SVGLength& refX() const { return m_refX; }
|
||||
const SVGLength& refY() const { return m_refY; }
|
||||
const SVGLength& markerWidth() const { return m_markerWidth; }
|
||||
const SVGLength& markerHeight() const { return m_markerHeight; }
|
||||
const SVGEnumeration<MarkerUnits>& markerUnits() const { return m_markerUnits; }
|
||||
const SVGAngle& orient() const { return m_orient; }
|
||||
|
||||
Point refPoint() const;
|
||||
Size markerSize() const;
|
||||
|
||||
Transform markerTransform(const Point& origin, float angle, float strokeWidth) const;
|
||||
Rect markerBoundingBox(const Point& origin, float angle, float strokeWidth) const;
|
||||
void renderMarker(SVGRenderState& state, const Point& origin, float angle, float strokeWidth) const;
|
||||
|
||||
Transform localTransform() const final;
|
||||
|
||||
private:
|
||||
SVGLength m_refX;
|
||||
SVGLength m_refY;
|
||||
SVGLength m_markerWidth;
|
||||
SVGLength m_markerHeight;
|
||||
SVGEnumeration<MarkerUnits> m_markerUnits;
|
||||
SVGAngle m_orient;
|
||||
};
|
||||
|
||||
class SVGClipPathElement final : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGClipPathElement(Document* document);
|
||||
|
||||
const SVGEnumeration<Units>& clipPathUnits() const { return m_clipPathUnits; }
|
||||
Rect clipBoundingBox(const SVGElement* element) const;
|
||||
|
||||
void applyClipMask(SVGRenderState& state) const;
|
||||
void applyClipPath(SVGRenderState& state) const;
|
||||
|
||||
bool requiresMasking() const;
|
||||
|
||||
private:
|
||||
SVGEnumeration<Units> m_clipPathUnits;
|
||||
};
|
||||
|
||||
class SVGMaskElement final : public SVGElement {
|
||||
public:
|
||||
SVGMaskElement(Document* document);
|
||||
|
||||
const SVGLength& x() const { return m_x; }
|
||||
const SVGLength& y() const { return m_y; }
|
||||
const SVGLength& width() const { return m_width; }
|
||||
const SVGLength& height() const { return m_height; }
|
||||
const SVGEnumeration<Units>& maskUnits() const { return m_maskUnits; }
|
||||
const SVGEnumeration<Units>& maskContentUnits() const { return m_maskContentUnits; }
|
||||
|
||||
Rect maskRect(const SVGElement* element) const;
|
||||
Rect maskBoundingBox(const SVGElement* element) const;
|
||||
void applyMask(SVGRenderState& state) const;
|
||||
|
||||
void layoutElement(const SVGLayoutState& state) final;
|
||||
|
||||
private:
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
SVGEnumeration<Units> m_maskUnits;
|
||||
SVGEnumeration<Units> m_maskContentUnits;
|
||||
MaskType m_mask_type = MaskType::Luminance;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGELEMENT_H
|
||||
324
vendor/lunasvg/source/svggeometryelement.cpp
vendored
324
vendor/lunasvg/source/svggeometryelement.cpp
vendored
@@ -1,324 +0,0 @@
|
||||
#include "svggeometryelement.h"
|
||||
#include "svglayoutstate.h"
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
Rect SVGMarkerPosition::markerBoundingBox(float strokeWidth) const
|
||||
{
|
||||
return m_element->markerBoundingBox(m_origin, m_angle, strokeWidth);
|
||||
}
|
||||
|
||||
void SVGMarkerPosition::renderMarker(SVGRenderState& state, float strokeWidth) const
|
||||
{
|
||||
m_element->renderMarker(state, m_origin, m_angle, strokeWidth);
|
||||
}
|
||||
|
||||
SVGGeometryElement::SVGGeometryElement(Document* document, ElementID id)
|
||||
: SVGGraphicsElement(document, id)
|
||||
{
|
||||
}
|
||||
|
||||
Rect SVGGeometryElement::strokeBoundingBox() const
|
||||
{
|
||||
auto strokeBoundingBox = fillBoundingBox();
|
||||
if(m_stroke.isRenderable()) {
|
||||
float capLimit = m_strokeData.lineWidth() / 2.f;
|
||||
if(m_strokeData.lineCap() == LineCap::Square)
|
||||
capLimit *= PLUTOVG_SQRT2;
|
||||
float joinLimit = m_strokeData.lineWidth() / 2.f;
|
||||
if(m_strokeData.lineJoin() == LineJoin::Miter) {
|
||||
joinLimit *= m_strokeData.miterLimit();
|
||||
}
|
||||
|
||||
strokeBoundingBox.inflate(std::max(capLimit, joinLimit));
|
||||
}
|
||||
|
||||
for(const auto& markerPosition : m_markerPositions)
|
||||
strokeBoundingBox.unite(markerPosition.markerBoundingBox(m_strokeData.lineWidth()));
|
||||
return strokeBoundingBox;
|
||||
}
|
||||
|
||||
void SVGGeometryElement::layoutElement(const SVGLayoutState& state)
|
||||
{
|
||||
m_fill_rule = state.fill_rule();
|
||||
m_clip_rule = state.clip_rule();
|
||||
m_fill = getPaintServer(state.fill(), state.fill_opacity());
|
||||
m_stroke = getPaintServer(state.stroke(), state.stroke_opacity());
|
||||
m_strokeData = getStrokeData(state);
|
||||
SVGGraphicsElement::layoutElement(state);
|
||||
|
||||
m_path.reset();
|
||||
m_markerPositions.clear();
|
||||
m_fillBoundingBox = updateShape(m_path);
|
||||
updateMarkerPositions(m_markerPositions, state);
|
||||
}
|
||||
|
||||
void SVGGeometryElement::updateMarkerPositions(SVGMarkerPositionList& positions, const SVGLayoutState& state)
|
||||
{
|
||||
if(m_path.isEmpty())
|
||||
return;
|
||||
auto markerStart = getMarker(state.marker_start());
|
||||
auto markerMid = getMarker(state.marker_mid());
|
||||
auto markerEnd = getMarker(state.marker_end());
|
||||
if(markerStart == nullptr && markerMid == nullptr && markerEnd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Point origin;
|
||||
Point startPoint;
|
||||
Point inslopePoints[2];
|
||||
Point outslopePoints[2];
|
||||
|
||||
int index = 0;
|
||||
std::array<Point, 3> points;
|
||||
PathIterator it(m_path);
|
||||
while(!it.isDone()) {
|
||||
switch(it.currentSegment(points)) {
|
||||
case PathCommand::MoveTo:
|
||||
startPoint = points[0];
|
||||
inslopePoints[0] = origin;
|
||||
inslopePoints[1] = points[0];
|
||||
origin = points[0];
|
||||
break;
|
||||
case PathCommand::LineTo:
|
||||
inslopePoints[0] = origin;
|
||||
inslopePoints[1] = points[0];
|
||||
origin = points[0];
|
||||
break;
|
||||
case PathCommand::CubicTo:
|
||||
inslopePoints[0] = points[1];
|
||||
inslopePoints[1] = points[2];
|
||||
origin = points[2];
|
||||
break;
|
||||
case PathCommand::Close:
|
||||
inslopePoints[0] = origin;
|
||||
inslopePoints[1] = points[0];
|
||||
origin = startPoint;
|
||||
startPoint = Point();
|
||||
break;
|
||||
}
|
||||
|
||||
it.next();
|
||||
|
||||
if(!it.isDone() && (markerStart || markerMid)) {
|
||||
it.currentSegment(points);
|
||||
outslopePoints[0] = origin;
|
||||
outslopePoints[1] = points[0];
|
||||
if(index == 0 && markerStart) {
|
||||
auto slope = outslopePoints[1] - outslopePoints[0];
|
||||
auto angle = 180.f * std::atan2(slope.y, slope.x) / PLUTOVG_PI;
|
||||
const auto& orient = markerStart->orient();
|
||||
if(orient.orientType() == SVGAngle::OrientType::AutoStartReverse)
|
||||
angle -= 180.f;
|
||||
positions.emplace_back(markerStart, origin, angle);
|
||||
}
|
||||
|
||||
if(index > 0 && markerMid) {
|
||||
auto inslope = inslopePoints[1] - inslopePoints[0];
|
||||
auto outslope = outslopePoints[1] - outslopePoints[0];
|
||||
auto inangle = 180.f * std::atan2(inslope.y, inslope.x) / PLUTOVG_PI;
|
||||
auto outangle = 180.f * std::atan2(outslope.y, outslope.x) / PLUTOVG_PI;
|
||||
if(std::abs(inangle - outangle) > 180.f)
|
||||
inangle += 360.f;
|
||||
auto angle = (inangle + outangle) * 0.5f;
|
||||
positions.emplace_back(markerMid, origin, angle);
|
||||
}
|
||||
}
|
||||
|
||||
if(markerEnd && it.isDone()) {
|
||||
auto slope = inslopePoints[1] - inslopePoints[0];
|
||||
auto angle = 180.f * std::atan2(slope.y, slope.x) / PLUTOVG_PI;
|
||||
positions.emplace_back(markerEnd, origin, angle);
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void SVGGeometryElement::render(SVGRenderState& state) const
|
||||
{
|
||||
if(!isRenderable())
|
||||
return;
|
||||
SVGBlendInfo blendInfo(this);
|
||||
SVGRenderState newState(this, state, localTransform());
|
||||
newState.beginGroup(blendInfo);
|
||||
if(newState.mode() == SVGRenderMode::Clipping) {
|
||||
newState->setColor(Color::White);
|
||||
newState->fillPath(m_path, m_clip_rule, newState.currentTransform());
|
||||
} else {
|
||||
if(m_fill.applyPaint(newState))
|
||||
newState->fillPath(m_path, m_fill_rule, newState.currentTransform());
|
||||
if(m_stroke.applyPaint(newState)) {
|
||||
newState->strokePath(m_path, m_strokeData, newState.currentTransform());
|
||||
}
|
||||
|
||||
for(const auto& markerPosition : m_markerPositions) {
|
||||
markerPosition.renderMarker(newState, m_strokeData.lineWidth());
|
||||
}
|
||||
}
|
||||
|
||||
newState.endGroup(blendInfo);
|
||||
}
|
||||
|
||||
SVGLineElement::SVGLineElement(Document* document)
|
||||
: SVGGeometryElement(document, ElementID::Line)
|
||||
, m_x1(PropertyID::X1, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_y1(PropertyID::Y1, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_x2(PropertyID::X2, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_y2(PropertyID::Y2, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
{
|
||||
addProperty(m_x1);
|
||||
addProperty(m_y1);
|
||||
addProperty(m_x2);
|
||||
addProperty(m_y2);
|
||||
}
|
||||
|
||||
Rect SVGLineElement::updateShape(Path& path)
|
||||
{
|
||||
LengthContext lengthContext(this);
|
||||
auto x1 = lengthContext.valueForLength(m_x1);
|
||||
auto y1 = lengthContext.valueForLength(m_y1);
|
||||
auto x2 = lengthContext.valueForLength(m_x2);
|
||||
auto y2 = lengthContext.valueForLength(m_y2);
|
||||
|
||||
path.moveTo(x1, y1);
|
||||
path.lineTo(x2, y2);
|
||||
return Rect(x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
|
||||
SVGRectElement::SVGRectElement(Document* document)
|
||||
: SVGGeometryElement(document, ElementID::Rect)
|
||||
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid)
|
||||
, m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid)
|
||||
, m_rx(PropertyID::Rx, LengthDirection::Horizontal, LengthNegativeMode::Forbid)
|
||||
, m_ry(PropertyID::Ry, LengthDirection::Vertical, LengthNegativeMode::Forbid)
|
||||
{
|
||||
addProperty(m_x);
|
||||
addProperty(m_y);
|
||||
addProperty(m_width);
|
||||
addProperty(m_height);
|
||||
addProperty(m_rx);
|
||||
addProperty(m_ry);
|
||||
}
|
||||
|
||||
Rect SVGRectElement::updateShape(Path& path)
|
||||
{
|
||||
LengthContext lengthContext(this);
|
||||
auto width = lengthContext.valueForLength(m_width);
|
||||
auto height = lengthContext.valueForLength(m_height);
|
||||
if(width <= 0.f || height <= 0.f) {
|
||||
return Rect::Empty;
|
||||
}
|
||||
|
||||
auto x = lengthContext.valueForLength(m_x);
|
||||
auto y = lengthContext.valueForLength(m_y);
|
||||
|
||||
auto rx = lengthContext.valueForLength(m_rx);
|
||||
auto ry = lengthContext.valueForLength(m_ry);
|
||||
|
||||
if(rx <= 0.f) rx = ry;
|
||||
if(ry <= 0.f) ry = rx;
|
||||
|
||||
rx = std::min(rx, width / 2.f);
|
||||
ry = std::min(ry, height / 2.f);
|
||||
|
||||
path.addRoundRect(x, y, width, height, rx, ry);
|
||||
return Rect(x, y, width, height);
|
||||
}
|
||||
|
||||
SVGEllipseElement::SVGEllipseElement(Document* document)
|
||||
: SVGGeometryElement(document, ElementID::Ellipse)
|
||||
, m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_rx(PropertyID::Rx, LengthDirection::Diagonal, LengthNegativeMode::Forbid)
|
||||
, m_ry(PropertyID::Ry, LengthDirection::Diagonal, LengthNegativeMode::Forbid)
|
||||
{
|
||||
addProperty(m_cx);
|
||||
addProperty(m_cy);
|
||||
addProperty(m_rx);
|
||||
addProperty(m_ry);
|
||||
}
|
||||
|
||||
Rect SVGEllipseElement::updateShape(Path& path)
|
||||
{
|
||||
LengthContext lengthContext(this);
|
||||
auto rx = lengthContext.valueForLength(m_rx);
|
||||
auto ry = lengthContext.valueForLength(m_ry);
|
||||
if(rx <= 0.f || ry <= 0.f) {
|
||||
return Rect::Empty;
|
||||
}
|
||||
|
||||
auto cx = lengthContext.valueForLength(m_cx);
|
||||
auto cy = lengthContext.valueForLength(m_cy);
|
||||
path.addEllipse(cx, cy, rx, ry);
|
||||
return Rect(cx - rx, cy - ry, rx + rx, ry + ry);
|
||||
}
|
||||
|
||||
SVGCircleElement::SVGCircleElement(Document* document)
|
||||
: SVGGeometryElement(document, ElementID::Circle)
|
||||
, m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_r(PropertyID::R, LengthDirection::Diagonal, LengthNegativeMode::Forbid)
|
||||
{
|
||||
addProperty(m_cx);
|
||||
addProperty(m_cy);
|
||||
addProperty(m_r);
|
||||
}
|
||||
|
||||
Rect SVGCircleElement::updateShape(Path& path)
|
||||
{
|
||||
LengthContext lengthContext(this);
|
||||
auto r = lengthContext.valueForLength(m_r);
|
||||
if(r <= 0.f) {
|
||||
return Rect::Empty;
|
||||
}
|
||||
|
||||
auto cx = lengthContext.valueForLength(m_cx);
|
||||
auto cy = lengthContext.valueForLength(m_cy);
|
||||
path.addEllipse(cx, cy, r, r);
|
||||
return Rect(cx - r, cy - r, r + r, r + r);
|
||||
}
|
||||
|
||||
SVGPolyElement::SVGPolyElement(Document* document, ElementID id)
|
||||
: SVGGeometryElement(document, id)
|
||||
, m_points(PropertyID::Points)
|
||||
{
|
||||
addProperty(m_points);
|
||||
}
|
||||
|
||||
Rect SVGPolyElement::updateShape(Path& path)
|
||||
{
|
||||
const auto& points = m_points.values();
|
||||
if(points.empty()) {
|
||||
return Rect::Empty;
|
||||
}
|
||||
|
||||
path.moveTo(points[0].x, points[0].y);
|
||||
for(size_t i = 1; i < points.size(); i++) {
|
||||
path.lineTo(points[i].x, points[i].y);
|
||||
}
|
||||
|
||||
if(id() == ElementID::Polygon)
|
||||
path.close();
|
||||
return path.boundingRect();
|
||||
}
|
||||
|
||||
SVGPathElement::SVGPathElement(Document* document)
|
||||
: SVGGeometryElement(document, ElementID::Path)
|
||||
, m_d(PropertyID::D)
|
||||
{
|
||||
addProperty(m_d);
|
||||
}
|
||||
|
||||
Rect SVGPathElement::updateShape(Path& path)
|
||||
{
|
||||
path = m_d.value();
|
||||
return path.boundingRect();
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
139
vendor/lunasvg/source/svggeometryelement.h
vendored
139
vendor/lunasvg/source/svggeometryelement.h
vendored
@@ -1,139 +0,0 @@
|
||||
#ifndef LUNASVG_SVGGEOMETRYELEMENT_H
|
||||
#define LUNASVG_SVGGEOMETRYELEMENT_H
|
||||
|
||||
#include "svgelement.h"
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
class SVGMarkerPosition {
|
||||
public:
|
||||
SVGMarkerPosition(const SVGMarkerElement* element, const Point& origin, float angle)
|
||||
: m_element(element), m_origin(origin), m_angle(angle)
|
||||
{}
|
||||
|
||||
const SVGMarkerElement* element() const { return m_element; }
|
||||
const Point& origin() const { return m_origin; }
|
||||
float angle() const { return m_angle; }
|
||||
|
||||
Rect markerBoundingBox(float strokeWidth) const;
|
||||
void renderMarker(SVGRenderState& state, float strokeWidth) const;
|
||||
|
||||
private:
|
||||
const SVGMarkerElement* m_element;
|
||||
Point m_origin;
|
||||
float m_angle;
|
||||
};
|
||||
|
||||
using SVGMarkerPositionList = std::vector<SVGMarkerPosition>;
|
||||
|
||||
class SVGGeometryElement : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGGeometryElement(Document* document, ElementID id);
|
||||
|
||||
bool isGeometryElement() const final { return true; }
|
||||
|
||||
Rect fillBoundingBox() const override { return m_fillBoundingBox; }
|
||||
Rect strokeBoundingBox() const override;
|
||||
void layoutElement(const SVGLayoutState& state) override;
|
||||
|
||||
bool isRenderable() const { return !m_path.isNull() && !isDisplayNone() && !isVisibilityHidden(); }
|
||||
|
||||
FillRule fill_rule() const { return m_fill_rule; }
|
||||
FillRule clip_rule() const { return m_clip_rule; }
|
||||
|
||||
virtual Rect updateShape(Path& path) = 0;
|
||||
|
||||
void updateMarkerPositions(SVGMarkerPositionList& positions, const SVGLayoutState& state);
|
||||
void render(SVGRenderState& state) const override;
|
||||
|
||||
const Path& path() const { return m_path; }
|
||||
|
||||
private:
|
||||
Path m_path;
|
||||
Rect m_fillBoundingBox;
|
||||
StrokeData m_strokeData;
|
||||
|
||||
SVGPaintServer m_fill;
|
||||
SVGPaintServer m_stroke;
|
||||
SVGMarkerPositionList m_markerPositions;
|
||||
|
||||
FillRule m_fill_rule = FillRule::NonZero;
|
||||
FillRule m_clip_rule = FillRule::NonZero;
|
||||
};
|
||||
|
||||
class SVGLineElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGLineElement(Document* document);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGLength m_x1;
|
||||
SVGLength m_y1;
|
||||
SVGLength m_x2;
|
||||
SVGLength m_y2;
|
||||
};
|
||||
|
||||
class SVGRectElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGRectElement(Document* document);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
SVGLength m_rx;
|
||||
SVGLength m_ry;
|
||||
};
|
||||
|
||||
class SVGEllipseElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGEllipseElement(Document* document);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGLength m_cx;
|
||||
SVGLength m_cy;
|
||||
SVGLength m_rx;
|
||||
SVGLength m_ry;
|
||||
};
|
||||
|
||||
class SVGCircleElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGCircleElement(Document* document);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGLength m_cx;
|
||||
SVGLength m_cy;
|
||||
SVGLength m_r;
|
||||
};
|
||||
|
||||
class SVGPolyElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGPolyElement(Document* document, ElementID id);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGPointList m_points;
|
||||
};
|
||||
|
||||
class SVGPathElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGPathElement(Document* document);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGPath m_d;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGGEOMETRYELEMENT_H
|
||||
607
vendor/lunasvg/source/svglayoutstate.cpp
vendored
607
vendor/lunasvg/source/svglayoutstate.cpp
vendored
@@ -1,607 +0,0 @@
|
||||
#include "svglayoutstate.h"
|
||||
#include "svgelement.h"
|
||||
#include "svgparserutils.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
static std::optional<Color> parseColorValue(std::string_view& input, const SVGLayoutState* state)
|
||||
{
|
||||
if(skipString(input, "currentColor")) {
|
||||
return state->color();
|
||||
}
|
||||
|
||||
plutovg_color_t color;
|
||||
int length = plutovg_color_parse(&color, input.data(), input.length());
|
||||
if(length == 0)
|
||||
return std::nullopt;
|
||||
input.remove_prefix(length);
|
||||
return Color(plutovg_color_to_argb32(&color));
|
||||
}
|
||||
|
||||
static Color parseColor(std::string_view input, const SVGLayoutState* state, const Color& defaultValue)
|
||||
{
|
||||
auto color = parseColorValue(input, state);
|
||||
if(!color || !input.empty())
|
||||
color = defaultValue;
|
||||
return color.value();
|
||||
}
|
||||
|
||||
static Color parseColorOrNone(std::string_view input, const SVGLayoutState* state, const Color& defaultValue)
|
||||
{
|
||||
if(input.compare("none") == 0)
|
||||
return Color::Transparent;
|
||||
return parseColor(input, state, defaultValue);
|
||||
}
|
||||
|
||||
static bool parseUrlValue(std::string_view& input, std::string& value)
|
||||
{
|
||||
if(!skipString(input, "url")
|
||||
|| !skipOptionalSpaces(input)
|
||||
|| !skipDelimiter(input, '(')
|
||||
|| !skipOptionalSpaces(input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(input.front()) {
|
||||
case '\'':
|
||||
case '\"': {
|
||||
auto delim = input.front();
|
||||
input.remove_prefix(1);
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '#'))
|
||||
return false;
|
||||
while(!input.empty() && input.front() != delim) {
|
||||
value += input.front();
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, delim))
|
||||
return false;
|
||||
break;
|
||||
} case '#': {
|
||||
input.remove_prefix(1);
|
||||
while(!input.empty() && input.front() != ')') {
|
||||
value += input.front();
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
break;
|
||||
} default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return skipOptionalSpaces(input) && skipDelimiter(input, ')');
|
||||
}
|
||||
|
||||
static std::string parseUrl(std::string_view input)
|
||||
{
|
||||
std::string value;
|
||||
if(!parseUrlValue(input, value) || !input.empty())
|
||||
value.clear();
|
||||
return value;
|
||||
}
|
||||
|
||||
static Paint parsePaint(std::string_view input, const SVGLayoutState* state, const Color& defaultValue)
|
||||
{
|
||||
std::string id;
|
||||
if(!parseUrlValue(input, id))
|
||||
return Paint(parseColorOrNone(input, state, defaultValue));
|
||||
if(skipOptionalSpaces(input))
|
||||
return Paint(id, parseColorOrNone(input, state, defaultValue));
|
||||
return Paint(id, Color::Transparent);
|
||||
}
|
||||
|
||||
static float parseNumberOrPercentage(std::string_view input, bool allowPercentage, float defaultValue)
|
||||
{
|
||||
float value;
|
||||
if(!parseNumber(input, value))
|
||||
return defaultValue;
|
||||
if(allowPercentage) {
|
||||
if(skipDelimiter(input, '%'))
|
||||
value /= 100.f;
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
|
||||
if(!input.empty())
|
||||
return defaultValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
static Length parseLength(std::string_view input, LengthNegativeMode mode, const Length& defaultValue)
|
||||
{
|
||||
Length value;
|
||||
if(!value.parse(input, mode))
|
||||
value = defaultValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
static BaselineShift parseBaselineShift(std::string_view input)
|
||||
{
|
||||
if(input.compare("baseline") == 0)
|
||||
return BaselineShift::Type::Baseline;
|
||||
if(input.compare("sub") == 0)
|
||||
return BaselineShift::Type::Sub;
|
||||
if(input.compare("super") == 0)
|
||||
return BaselineShift::Type::Super;
|
||||
return parseLength(input, LengthNegativeMode::Allow, Length(0.f, LengthUnits::None));
|
||||
}
|
||||
|
||||
static LengthList parseDashArray(std::string_view input)
|
||||
{
|
||||
if(input.compare("none") == 0)
|
||||
return LengthList();
|
||||
LengthList values;
|
||||
do {
|
||||
size_t count = 0;
|
||||
while(count < input.length() && input[count] != ',' && !IS_WS(input[count]))
|
||||
++count;
|
||||
Length value(0, LengthUnits::None);
|
||||
if(!value.parse(input.substr(0, count), LengthNegativeMode::Forbid))
|
||||
return LengthList();
|
||||
input.remove_prefix(count);
|
||||
values.push_back(std::move(value));
|
||||
} while(skipOptionalSpacesOrComma(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));
|
||||
if(length.units() == LengthUnits::Percent)
|
||||
return length.value() * state->font_size() / 100.f;
|
||||
if(length.units() == LengthUnits::Ex)
|
||||
return length.value() * state->font_size() / 2.f;
|
||||
if(length.units() == LengthUnits::Em)
|
||||
return length.value() * state->font_size();
|
||||
return length.value();
|
||||
}
|
||||
|
||||
template<typename Enum, unsigned int N>
|
||||
static Enum parseEnumValue(std::string_view input, const SVGEnumerationEntry<Enum>(&entries)[N], Enum defaultValue)
|
||||
{
|
||||
for(const auto& entry : entries) {
|
||||
if(input == entry.second) {
|
||||
return entry.first;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
static Display parseDisplay(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<Display> entries[] = {
|
||||
{Display::Inline, "inline"},
|
||||
{Display::None, "none"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, Display::Inline);
|
||||
}
|
||||
|
||||
static Visibility parseVisibility(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<Visibility> entries[] = {
|
||||
{Visibility::Visible, "visible"},
|
||||
{Visibility::Hidden, "hidden"},
|
||||
{Visibility::Collapse, "collapse"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, Visibility::Visible);
|
||||
}
|
||||
|
||||
static Overflow parseOverflow(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<Overflow> entries[] = {
|
||||
{Overflow::Visible, "visible"},
|
||||
{Overflow::Hidden, "hidden"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, Overflow::Visible);
|
||||
}
|
||||
|
||||
static PointerEvents parsePointerEvents(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<PointerEvents> 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(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<FontWeight> entries[] = {
|
||||
{FontWeight::Normal, "normal"},
|
||||
{FontWeight::Bold, "bold"},
|
||||
{FontWeight::Bold, "bolder"},
|
||||
{FontWeight::Normal, "lighter"},
|
||||
{FontWeight::Normal, "100"},
|
||||
{FontWeight::Normal, "200"},
|
||||
{FontWeight::Normal, "300"},
|
||||
{FontWeight::Normal, "400"},
|
||||
{FontWeight::Normal, "500"},
|
||||
{FontWeight::Bold, "600"},
|
||||
{FontWeight::Bold, "700"},
|
||||
{FontWeight::Bold, "800"},
|
||||
{FontWeight::Bold, "900"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, FontWeight::Normal);
|
||||
}
|
||||
|
||||
static FontStyle parseFontStyle(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<FontStyle> entries[] = {
|
||||
{FontStyle::Normal, "normal"},
|
||||
{FontStyle::Italic, "italic"},
|
||||
{FontStyle::Italic, "oblique"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, FontStyle::Normal);
|
||||
}
|
||||
|
||||
static AlignmentBaseline parseAlignmentBaseline(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<AlignmentBaseline> entries[] = {
|
||||
{AlignmentBaseline::Auto, "auto"},
|
||||
{AlignmentBaseline::Baseline, "baseline"},
|
||||
{AlignmentBaseline::BeforeEdge, "before-edge"},
|
||||
{AlignmentBaseline::TextBeforeEdge, "text-before-edge"},
|
||||
{AlignmentBaseline::Middle, "middle"},
|
||||
{AlignmentBaseline::Central, "central"},
|
||||
{AlignmentBaseline::AfterEdge, "after-edge"},
|
||||
{AlignmentBaseline::TextAfterEdge, "text-after-edge"},
|
||||
{AlignmentBaseline::Ideographic, "ideographic"},
|
||||
{AlignmentBaseline::Alphabetic, "alphabetic"},
|
||||
{AlignmentBaseline::Hanging, "hanging"},
|
||||
{AlignmentBaseline::Mathematical, "mathematical"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, AlignmentBaseline::Auto);
|
||||
}
|
||||
|
||||
static DominantBaseline parseDominantBaseline(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<DominantBaseline> entries[] = {
|
||||
{DominantBaseline::Auto, "auto"},
|
||||
{DominantBaseline::UseScript, "use-script"},
|
||||
{DominantBaseline::NoChange, "no-change"},
|
||||
{DominantBaseline::ResetSize, "reset-size"},
|
||||
{DominantBaseline::Ideographic, "ideographic"},
|
||||
{DominantBaseline::Alphabetic, "alphabetic"},
|
||||
{DominantBaseline::Hanging, "hanging"},
|
||||
{DominantBaseline::Mathematical, "mathematical"},
|
||||
{DominantBaseline::Central, "central"},
|
||||
{DominantBaseline::Middle, "middle"},
|
||||
{DominantBaseline::TextAfterEdge, "text-after-edge"},
|
||||
{DominantBaseline::TextBeforeEdge, "text-before-edge"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, DominantBaseline::Auto);
|
||||
}
|
||||
|
||||
static Direction parseDirection(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<Direction> entries[] = {
|
||||
{Direction::Ltr, "ltr"},
|
||||
{Direction::Rtl, "rtl"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, Direction::Ltr);
|
||||
}
|
||||
|
||||
static WritingMode parseWritingMode(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<WritingMode> 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(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<TextOrientation> entries[] = {
|
||||
{TextOrientation::Mixed, "mixed"},
|
||||
{TextOrientation::Upright, "upright"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, TextOrientation::Mixed);
|
||||
}
|
||||
|
||||
static TextAnchor parseTextAnchor(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<TextAnchor> entries[] = {
|
||||
{TextAnchor::Start, "start"},
|
||||
{TextAnchor::Middle, "middle"},
|
||||
{TextAnchor::End, "end"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, TextAnchor::Start);
|
||||
}
|
||||
|
||||
static WhiteSpace parseWhiteSpace(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<WhiteSpace> entries[] = {
|
||||
{WhiteSpace::Default, "default"},
|
||||
{WhiteSpace::Preserve, "preserve"},
|
||||
{WhiteSpace::Default, "normal"},
|
||||
{WhiteSpace::Default, "nowrap"},
|
||||
{WhiteSpace::Default, "pre-line"},
|
||||
{WhiteSpace::Preserve, "pre-wrap"},
|
||||
{WhiteSpace::Preserve, "pre"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, WhiteSpace::Default);
|
||||
}
|
||||
|
||||
static MaskType parseMaskType(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<MaskType> entries[] = {
|
||||
{MaskType::Luminance, "luminance"},
|
||||
{MaskType::Alpha, "alpha"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, MaskType::Luminance);
|
||||
}
|
||||
|
||||
static FillRule parseFillRule(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<FillRule> entries[] = {
|
||||
{FillRule::NonZero, "nonzero"},
|
||||
{FillRule::EvenOdd, "evenodd"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, FillRule::NonZero);
|
||||
}
|
||||
|
||||
static LineCap parseLineCap(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<LineCap> entries[] = {
|
||||
{LineCap::Butt, "butt"},
|
||||
{LineCap::Round, "round"},
|
||||
{LineCap::Square, "square"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, LineCap::Butt);
|
||||
}
|
||||
|
||||
static LineJoin parseLineJoin(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<LineJoin> entries[] = {
|
||||
{LineJoin::Miter, "miter"},
|
||||
{LineJoin::Round, "round"},
|
||||
{LineJoin::Bevel, "bevel"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, LineJoin::Miter);
|
||||
}
|
||||
|
||||
SVGLayoutState::SVGLayoutState(const SVGLayoutState& parent, const SVGElement* element)
|
||||
: m_parent(&parent)
|
||||
, m_element(element)
|
||||
, m_fill(parent.fill())
|
||||
, m_stroke(parent.stroke())
|
||||
, m_color(parent.color())
|
||||
, m_fill_opacity(parent.fill_opacity())
|
||||
, 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())
|
||||
, m_stroke_linecap(parent.stroke_linecap())
|
||||
, m_stroke_linejoin(parent.stroke_linejoin())
|
||||
, m_fill_rule(parent.fill_rule())
|
||||
, m_clip_rule(parent.clip_rule())
|
||||
, m_font_weight(parent.font_weight())
|
||||
, m_font_style(parent.font_style())
|
||||
, 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())
|
||||
, m_font_family(parent.font_family())
|
||||
{
|
||||
for(const auto& attribute : element->attributes()) {
|
||||
std::string_view input(attribute.value());
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(input.empty() || input.compare("inherit") == 0)
|
||||
continue;
|
||||
switch(attribute.id()) {
|
||||
case PropertyID::Fill:
|
||||
m_fill = parsePaint(input, this, Color::Black);
|
||||
break;
|
||||
case PropertyID::Stroke:
|
||||
m_stroke = parsePaint(input, this, Color::Transparent);
|
||||
break;
|
||||
case PropertyID::Color:
|
||||
m_color = parseColor(input, this, Color::Black);
|
||||
break;
|
||||
case PropertyID::Stop_Color:
|
||||
m_stop_color = parseColor(input, this, Color::Black);
|
||||
break;
|
||||
case PropertyID::Opacity:
|
||||
m_opacity = parseNumberOrPercentage(input, true, 1.f);
|
||||
break;
|
||||
case PropertyID::Fill_Opacity:
|
||||
m_fill_opacity = parseNumberOrPercentage(input, true, 1.f);
|
||||
break;
|
||||
case PropertyID::Stroke_Opacity:
|
||||
m_stroke_opacity = parseNumberOrPercentage(input, true, 1.f);
|
||||
break;
|
||||
case PropertyID::Stop_Opacity:
|
||||
m_stop_opacity = parseNumberOrPercentage(input, true, 1.f);
|
||||
break;
|
||||
case PropertyID::Stroke_Miterlimit:
|
||||
m_stroke_miterlimit = parseNumberOrPercentage(input, false, 4.f);
|
||||
break;
|
||||
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_shift = parseBaselineShift(input);
|
||||
break;
|
||||
case PropertyID::Stroke_Width:
|
||||
m_stroke_width = parseLength(input, LengthNegativeMode::Forbid, Length(1.f, LengthUnits::None));
|
||||
break;
|
||||
case PropertyID::Stroke_Dashoffset:
|
||||
m_stroke_dashoffset = parseLength(input, LengthNegativeMode::Allow, Length(0.f, LengthUnits::None));
|
||||
break;
|
||||
case PropertyID::Stroke_Dasharray:
|
||||
m_stroke_dasharray = parseDashArray(input);
|
||||
break;
|
||||
case PropertyID::Stroke_Linecap:
|
||||
m_stroke_linecap = parseLineCap(input);
|
||||
break;
|
||||
case PropertyID::Stroke_Linejoin:
|
||||
m_stroke_linejoin = parseLineJoin(input);
|
||||
break;
|
||||
case PropertyID::Fill_Rule:
|
||||
m_fill_rule = parseFillRule(input);
|
||||
break;
|
||||
case PropertyID::Clip_Rule:
|
||||
m_clip_rule = parseFillRule(input);
|
||||
break;
|
||||
case PropertyID::Font_Weight:
|
||||
m_font_weight = parseFontWeight(input);
|
||||
break;
|
||||
case PropertyID::Font_Style:
|
||||
m_font_style = parseFontStyle(input);
|
||||
break;
|
||||
case PropertyID::Alignment_Baseline:
|
||||
m_alignment_baseline = parseAlignmentBaseline(input);
|
||||
break;
|
||||
case PropertyID::Dominant_Baseline:
|
||||
m_dominant_baseline = parseDominantBaseline(input);
|
||||
break;
|
||||
case PropertyID::Direction:
|
||||
m_direction = parseDirection(input);
|
||||
break;
|
||||
case PropertyID::Text_Anchor:
|
||||
m_text_anchor = parseTextAnchor(input);
|
||||
break;
|
||||
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;
|
||||
case PropertyID::Visibility:
|
||||
m_visibility = parseVisibility(input);
|
||||
break;
|
||||
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;
|
||||
case PropertyID::Mask:
|
||||
m_mask = parseUrl(input);
|
||||
break;
|
||||
case PropertyID::Clip_Path:
|
||||
m_clip_path = parseUrl(input);
|
||||
break;
|
||||
case PropertyID::Marker_Start:
|
||||
m_marker_start = parseUrl(input);
|
||||
break;
|
||||
case PropertyID::Marker_Mid:
|
||||
m_marker_mid = parseUrl(input);
|
||||
break;
|
||||
case PropertyID::Marker_End:
|
||||
m_marker_end = parseUrl(input);
|
||||
break;
|
||||
case PropertyID::Font_Family:
|
||||
m_font_family.assign(input);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Font SVGLayoutState::font() const
|
||||
{
|
||||
auto bold = m_font_weight == FontWeight::Bold;
|
||||
auto italic = m_font_style == FontStyle::Italic;
|
||||
|
||||
FontFace face;
|
||||
std::string_view input(m_font_family);
|
||||
while(!input.empty() && face.isNull()) {
|
||||
auto family = input.substr(0, input.find(','));
|
||||
input.remove_prefix(family.length());
|
||||
if(!input.empty() && input.front() == ',')
|
||||
input.remove_prefix(1);
|
||||
stripLeadingAndTrailingSpaces(family);
|
||||
if(!family.empty() && (family.front() == '\'' || family.front() == '"')) {
|
||||
auto quote = family.front();
|
||||
family.remove_prefix(1);
|
||||
if(!family.empty() && family.back() == quote)
|
||||
family.remove_suffix(1);
|
||||
stripLeadingAndTrailingSpaces(family);
|
||||
}
|
||||
|
||||
std::string font_family(family);
|
||||
if(!font_family.empty()) {
|
||||
face = fontFaceCache()->getFontFace(font_family, bold, italic);
|
||||
}
|
||||
}
|
||||
|
||||
if(face.isNull())
|
||||
face = fontFaceCache()->getFontFace(emptyString, bold, italic);
|
||||
return Font(face, m_font_size);
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
129
vendor/lunasvg/source/svglayoutstate.h
vendored
129
vendor/lunasvg/source/svglayoutstate.h
vendored
@@ -1,129 +0,0 @@
|
||||
#ifndef LUNASVG_SVGLAYOUTSTATE_H
|
||||
#define LUNASVG_SVGLAYOUTSTATE_H
|
||||
|
||||
#include "svgproperty.h"
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
class SVGLayoutState {
|
||||
public:
|
||||
SVGLayoutState() = default;
|
||||
SVGLayoutState(const SVGLayoutState& parent, const SVGElement* element);
|
||||
|
||||
const SVGLayoutState* parent() const { return m_parent; }
|
||||
const SVGElement* element() const { return m_element; }
|
||||
|
||||
const Paint& fill() const { return m_fill; }
|
||||
const Paint& stroke() const { return m_stroke; }
|
||||
|
||||
const Color& color() const { return m_color; }
|
||||
const Color& stop_color() const { return m_stop_color; }
|
||||
|
||||
float opacity() const { return m_opacity; }
|
||||
float stop_opacity() const { return m_stop_opacity; }
|
||||
float fill_opacity() const { return m_fill_opacity; }
|
||||
float stroke_opacity() const { return m_stroke_opacity; }
|
||||
float stroke_miterlimit() const { return m_stroke_miterlimit; }
|
||||
float font_size() const { return m_font_size; }
|
||||
|
||||
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; }
|
||||
|
||||
LineCap stroke_linecap() const { return m_stroke_linecap; }
|
||||
LineJoin stroke_linejoin() const { return m_stroke_linejoin; }
|
||||
|
||||
FillRule fill_rule() const { return m_fill_rule; }
|
||||
FillRule clip_rule() const { return m_clip_rule; }
|
||||
|
||||
FontWeight font_weight() const { return m_font_weight; }
|
||||
FontStyle font_style() const { return m_font_style; }
|
||||
|
||||
AlignmentBaseline alignment_baseline() const { return m_alignment_baseline; }
|
||||
DominantBaseline dominant_baseline() const { return m_dominant_baseline; }
|
||||
|
||||
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; }
|
||||
const std::string& clip_path() const { return m_clip_path; }
|
||||
const std::string& marker_start() const { return m_marker_start; }
|
||||
const std::string& marker_mid() const { return m_marker_mid; }
|
||||
const std::string& marker_end() const { return m_marker_end; }
|
||||
const std::string& font_family() const { return m_font_family; }
|
||||
|
||||
Font font() const;
|
||||
|
||||
private:
|
||||
const SVGLayoutState* m_parent = nullptr;
|
||||
const SVGElement* m_element = nullptr;
|
||||
|
||||
Paint m_fill{Color::Black};
|
||||
Paint m_stroke{Color::Transparent};
|
||||
|
||||
Color m_color = Color::Black;
|
||||
Color m_stop_color = Color::Black;
|
||||
|
||||
float m_opacity = 1.f;
|
||||
float m_fill_opacity = 1.f;
|
||||
float m_stroke_opacity = 1.f;
|
||||
float m_stop_opacity = 1.f;
|
||||
float m_stroke_miterlimit = 4.f;
|
||||
float m_font_size = 12.f;
|
||||
|
||||
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;
|
||||
|
||||
LineCap m_stroke_linecap = LineCap::Butt;
|
||||
LineJoin m_stroke_linejoin = LineJoin::Miter;
|
||||
|
||||
FillRule m_fill_rule = FillRule::NonZero;
|
||||
FillRule m_clip_rule = FillRule::NonZero;
|
||||
|
||||
FontWeight m_font_weight = FontWeight::Normal;
|
||||
FontStyle m_font_style = FontStyle::Normal;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
std::string m_clip_path;
|
||||
std::string m_marker_start;
|
||||
std::string m_marker_mid;
|
||||
std::string m_marker_end;
|
||||
std::string m_font_family;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGLAYOUTSTATE_H
|
||||
364
vendor/lunasvg/source/svgpaintelement.cpp
vendored
364
vendor/lunasvg/source/svgpaintelement.cpp
vendored
@@ -1,364 +0,0 @@
|
||||
#include "svgpaintelement.h"
|
||||
#include "svglayoutstate.h"
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
SVGPaintElement::SVGPaintElement(Document* document, ElementID id)
|
||||
: SVGElement(document, id)
|
||||
{
|
||||
}
|
||||
|
||||
SVGStopElement::SVGStopElement(Document* document)
|
||||
: SVGElement(document, ElementID::Stop)
|
||||
, m_offset(PropertyID::Offset, 0.f)
|
||||
{
|
||||
addProperty(m_offset);
|
||||
}
|
||||
|
||||
void SVGStopElement::layoutElement(const SVGLayoutState& state)
|
||||
{
|
||||
m_stop_color = state.stop_color();
|
||||
m_stop_opacity = state.stop_opacity();
|
||||
SVGElement::layoutElement(state);
|
||||
}
|
||||
|
||||
GradientStop SVGStopElement::gradientStop(float opacity) const
|
||||
{
|
||||
Color stopColor = m_stop_color.colorWithAlpha(m_stop_opacity * opacity);
|
||||
GradientStop gradientStop = {
|
||||
m_offset.value(), { stopColor.redF(), stopColor.greenF(), stopColor.blueF(), stopColor.alphaF() }
|
||||
};
|
||||
|
||||
return gradientStop;
|
||||
}
|
||||
|
||||
SVGGradientElement::SVGGradientElement(Document* document, ElementID id)
|
||||
: SVGPaintElement(document, id)
|
||||
, SVGURIReference(this)
|
||||
, m_gradientTransform(PropertyID::GradientTransform)
|
||||
, m_gradientUnits(PropertyID::GradientUnits, Units::ObjectBoundingBox)
|
||||
, m_spreadMethod(PropertyID::SpreadMethod, SpreadMethod::Pad)
|
||||
{
|
||||
addProperty(m_gradientTransform);
|
||||
addProperty(m_gradientUnits);
|
||||
addProperty(m_spreadMethod);
|
||||
}
|
||||
|
||||
void SVGGradientElement::collectGradientAttributes(SVGGradientAttributes& attributes) const
|
||||
{
|
||||
if(!attributes.hasGradientTransform() && hasAttribute(PropertyID::GradientTransform))
|
||||
attributes.setGradientTransform(this);
|
||||
if(!attributes.hasSpreadMethod() && hasAttribute(PropertyID::SpreadMethod))
|
||||
attributes.setSpreadMethod(this);
|
||||
if(!attributes.hasGradientUnits() && hasAttribute(PropertyID::GradientUnits))
|
||||
attributes.setGradientUnits(this);
|
||||
if(!attributes.hasGradientContentElement()) {
|
||||
for(const auto& child : children()) {
|
||||
if(auto element = toSVGElement(child); element && element->id() == ElementID::Stop) {
|
||||
attributes.setGradientContentElement(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SVGLinearGradientElement::SVGLinearGradientElement(Document* document)
|
||||
: SVGGradientElement(document, ElementID::LinearGradient)
|
||||
, m_x1(PropertyID::X1, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
|
||||
, m_y1(PropertyID::Y1, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
|
||||
, m_x2(PropertyID::X2, LengthDirection::Horizontal, LengthNegativeMode::Allow, 100.f, LengthUnits::Percent)
|
||||
, m_y2(PropertyID::Y2, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
|
||||
{
|
||||
addProperty(m_x1);
|
||||
addProperty(m_y1);
|
||||
addProperty(m_x2);
|
||||
addProperty(m_y2);
|
||||
}
|
||||
|
||||
SVGLinearGradientAttributes SVGLinearGradientElement::collectGradientAttributes() const
|
||||
{
|
||||
SVGLinearGradientAttributes attributes;
|
||||
std::set<const SVGGradientElement*> processedGradients;
|
||||
const SVGGradientElement* current = this;
|
||||
while(true) {
|
||||
current->collectGradientAttributes(attributes);
|
||||
if(current->id() == ElementID::LinearGradient) {
|
||||
auto element = static_cast<const SVGLinearGradientElement*>(current);
|
||||
if(!attributes.hasX1() && element->hasAttribute(PropertyID::X1))
|
||||
attributes.setX1(element);
|
||||
if(!attributes.hasY1() && element->hasAttribute(PropertyID::Y1))
|
||||
attributes.setY1(element);
|
||||
if(!attributes.hasX2() && element->hasAttribute(PropertyID::X2))
|
||||
attributes.setX2(element);
|
||||
if(!attributes.hasY2() && element->hasAttribute(PropertyID::Y2)) {
|
||||
attributes.setY2(element);
|
||||
}
|
||||
}
|
||||
|
||||
auto targetElement = current->getTargetElement(document());
|
||||
if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient))
|
||||
break;
|
||||
processedGradients.insert(current);
|
||||
current = static_cast<const SVGGradientElement*>(targetElement);
|
||||
if(processedGradients.count(current) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
attributes.setDefaultValues(this);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
static GradientStops buildGradientStops(const SVGGradientElement* element, float opacity)
|
||||
{
|
||||
GradientStops gradientStops;
|
||||
|
||||
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<SVGStopElement*>(childElement);
|
||||
gradientStops.push_back(stopElement->gradientStop(opacity));
|
||||
}
|
||||
}
|
||||
|
||||
return gradientStops;
|
||||
}
|
||||
|
||||
bool SVGLinearGradientElement::applyPaint(SVGRenderState& state, float opacity) const
|
||||
{
|
||||
auto attributes = collectGradientAttributes();
|
||||
auto gradientContentElement = attributes.gradientContentElement();
|
||||
auto gradientStops = buildGradientStops(gradientContentElement, opacity);
|
||||
if(gradientStops.empty())
|
||||
return false;
|
||||
LengthContext lengthContext(this, attributes.gradientUnits());
|
||||
auto x1 = lengthContext.valueForLength(attributes.x1());
|
||||
auto y1 = lengthContext.valueForLength(attributes.y1());
|
||||
auto x2 = lengthContext.valueForLength(attributes.x2());
|
||||
auto y2 = lengthContext.valueForLength(attributes.y2());
|
||||
if(gradientStops.size() == 1 || (x1 == x2 && y1 == y2)) {
|
||||
const auto& lastStop = gradientStops.back();
|
||||
state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto spreadMethod = attributes.spreadMethod();
|
||||
auto gradientUnits = attributes.gradientUnits();
|
||||
auto gradientTransform = attributes.gradientTransform();
|
||||
if(gradientUnits == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y));
|
||||
}
|
||||
|
||||
state->setLinearGradient(x1, y1, x2, y2, spreadMethod, gradientStops, gradientTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
SVGRadialGradientElement::SVGRadialGradientElement(Document* document)
|
||||
: SVGGradientElement(document, ElementID::RadialGradient)
|
||||
, m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow, 50.f, LengthUnits::Percent)
|
||||
, m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow, 50.f, LengthUnits::Percent)
|
||||
, m_r(PropertyID::R, LengthDirection::Diagonal, LengthNegativeMode::Forbid, 50.f, LengthUnits::Percent)
|
||||
, m_fx(PropertyID::Fx, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
|
||||
, m_fy(PropertyID::Fy, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
|
||||
{
|
||||
addProperty(m_cx);
|
||||
addProperty(m_cy);
|
||||
addProperty(m_r);
|
||||
addProperty(m_fx);
|
||||
addProperty(m_fy);
|
||||
}
|
||||
|
||||
bool SVGRadialGradientElement::applyPaint(SVGRenderState& state, float opacity) const
|
||||
{
|
||||
auto attributes = collectGradientAttributes();
|
||||
auto gradientContentElement = attributes.gradientContentElement();
|
||||
auto gradientStops = buildGradientStops(gradientContentElement, opacity);
|
||||
if(gradientStops.empty())
|
||||
return false;
|
||||
LengthContext lengthContext(this, attributes.gradientUnits());
|
||||
auto r = lengthContext.valueForLength(attributes.r());
|
||||
if(r == 0.f || gradientStops.size() == 1) {
|
||||
const auto& lastStop = gradientStops.back();
|
||||
state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto fx = lengthContext.valueForLength(attributes.fx());
|
||||
auto fy = lengthContext.valueForLength(attributes.fy());
|
||||
auto cx = lengthContext.valueForLength(attributes.cx());
|
||||
auto cy = lengthContext.valueForLength(attributes.cy());
|
||||
|
||||
auto spreadMethod = attributes.spreadMethod();
|
||||
auto gradientUnits = attributes.gradientUnits();
|
||||
auto gradientTransform = attributes.gradientTransform();
|
||||
if(gradientUnits == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y));
|
||||
}
|
||||
|
||||
state->setRadialGradient(cx, cy, r, fx, fy, spreadMethod, gradientStops, gradientTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
SVGRadialGradientAttributes SVGRadialGradientElement::collectGradientAttributes() const
|
||||
{
|
||||
SVGRadialGradientAttributes attributes;
|
||||
std::set<const SVGGradientElement*> processedGradients;
|
||||
const SVGGradientElement* current = this;
|
||||
while(true) {
|
||||
current->collectGradientAttributes(attributes);
|
||||
if(current->id() == ElementID::RadialGradient) {
|
||||
auto element = static_cast<const SVGRadialGradientElement*>(current);
|
||||
if(!attributes.hasCx() && element->hasAttribute(PropertyID::Cx))
|
||||
attributes.setCx(element);
|
||||
if(!attributes.hasCy() && element->hasAttribute(PropertyID::Cy))
|
||||
attributes.setCy(element);
|
||||
if(!attributes.hasR() && element->hasAttribute(PropertyID::R))
|
||||
attributes.setR(element);
|
||||
if(!attributes.hasFx() && element->hasAttribute(PropertyID::Fx))
|
||||
attributes.setFx(element);
|
||||
if(!attributes.hasFy() && element->hasAttribute(PropertyID::Fy)) {
|
||||
attributes.setFy(element);
|
||||
}
|
||||
}
|
||||
|
||||
auto targetElement = current->getTargetElement(document());
|
||||
if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient))
|
||||
break;
|
||||
processedGradients.insert(current);
|
||||
current = static_cast<const SVGGradientElement*>(targetElement);
|
||||
if(processedGradients.count(current) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
attributes.setDefaultValues(this);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
SVGPatternElement::SVGPatternElement(Document* document)
|
||||
: SVGPaintElement(document, ElementID::Pattern)
|
||||
, SVGURIReference(this)
|
||||
, SVGFitToViewBox(this)
|
||||
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid)
|
||||
, m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid)
|
||||
, m_patternTransform(PropertyID::PatternTransform)
|
||||
, m_patternUnits(PropertyID::PatternUnits, Units::ObjectBoundingBox)
|
||||
, m_patternContentUnits(PropertyID::PatternContentUnits, Units::UserSpaceOnUse)
|
||||
{
|
||||
addProperty(m_x);
|
||||
addProperty(m_y);
|
||||
addProperty(m_width);
|
||||
addProperty(m_height);
|
||||
addProperty(m_patternTransform);
|
||||
addProperty(m_patternUnits);
|
||||
addProperty(m_patternContentUnits);
|
||||
}
|
||||
|
||||
bool SVGPatternElement::applyPaint(SVGRenderState& state, float opacity) const
|
||||
{
|
||||
if(state.hasCycleReference(this))
|
||||
return false;
|
||||
auto attributes = collectPatternAttributes();
|
||||
auto patternContentElement = attributes.patternContentElement();
|
||||
if(patternContentElement == nullptr)
|
||||
return false;
|
||||
LengthContext lengthContext(this, attributes.patternUnits());
|
||||
Rect patternRect = {
|
||||
lengthContext.valueForLength(attributes.x()),
|
||||
lengthContext.valueForLength(attributes.y()),
|
||||
lengthContext.valueForLength(attributes.width()),
|
||||
lengthContext.valueForLength(attributes.height())
|
||||
};
|
||||
|
||||
if(attributes.patternUnits() == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
patternRect.x = patternRect.x * bbox.w + bbox.x;
|
||||
patternRect.y = patternRect.y * bbox.h + bbox.y;
|
||||
patternRect.w = patternRect.w * bbox.w;
|
||||
patternRect.h = patternRect.h * bbox.h;
|
||||
}
|
||||
|
||||
auto currentTransform = attributes.patternTransform() * state.currentTransform();
|
||||
auto xScale = currentTransform.xScale();
|
||||
auto yScale = currentTransform.yScale();
|
||||
|
||||
auto patternImage = Canvas::create(0, 0, patternRect.w * xScale, patternRect.h * yScale);
|
||||
auto patternImageTransform = Transform::scaled(xScale, yScale);
|
||||
|
||||
const auto& viewBoxRect = attributes.viewBox();
|
||||
if(viewBoxRect.isValid()) {
|
||||
const auto& preserveAspectRatio = attributes.preserveAspectRatio();
|
||||
patternImageTransform.multiply(preserveAspectRatio.getTransform(viewBoxRect, patternRect.size()));
|
||||
} else if(attributes.patternContentUnits() == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
patternImageTransform.scale(bbox.w, bbox.h);
|
||||
}
|
||||
|
||||
SVGRenderState newState(this, &state, patternImageTransform, SVGRenderMode::Painting, patternImage);
|
||||
patternContentElement->renderChildren(newState);
|
||||
|
||||
auto patternTransform = attributes.patternTransform();
|
||||
patternTransform.translate(patternRect.x, patternRect.y);
|
||||
patternTransform.scale(patternRect.w / patternImage->width(), patternRect.h / patternImage->height());
|
||||
state->setTexture(*patternImage, TextureType::Tiled, opacity, patternTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
SVGPatternAttributes SVGPatternElement::collectPatternAttributes() const
|
||||
{
|
||||
SVGPatternAttributes attributes;
|
||||
std::set<const SVGPatternElement*> processedPatterns;
|
||||
const SVGPatternElement* current = this;
|
||||
while(true) {
|
||||
if(!attributes.hasX() && current->hasAttribute(PropertyID::X))
|
||||
attributes.setX(current);
|
||||
if(!attributes.hasY() && current->hasAttribute(PropertyID::Y))
|
||||
attributes.setY(current);
|
||||
if(!attributes.hasWidth() && current->hasAttribute(PropertyID::Width))
|
||||
attributes.setWidth(current);
|
||||
if(!attributes.hasHeight() && current->hasAttribute(PropertyID::Height))
|
||||
attributes.setHeight(current);
|
||||
if(!attributes.hasPatternTransform() && current->hasAttribute(PropertyID::PatternTransform))
|
||||
attributes.setPatternTransform(current);
|
||||
if(!attributes.hasPatternUnits() && current->hasAttribute(PropertyID::PatternUnits))
|
||||
attributes.setPatternUnits(current);
|
||||
if(!attributes.hasPatternContentUnits() && current->hasAttribute(PropertyID::PatternContentUnits))
|
||||
attributes.setPatternContentUnits(current);
|
||||
if(!attributes.hasViewBox() && current->hasAttribute(PropertyID::ViewBox))
|
||||
attributes.setViewBox(current);
|
||||
if(!attributes.hasPreserveAspectRatio() && current->hasAttribute(PropertyID::PreserveAspectRatio))
|
||||
attributes.setPreserveAspectRatio(current);
|
||||
if(!attributes.hasPatternContentElement()) {
|
||||
for(const auto& child : current->children()) {
|
||||
if(child->isElement()) {
|
||||
attributes.setPatternContentElement(current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto targetElement = current->getTargetElement(document());
|
||||
if(!targetElement || targetElement->id() != ElementID::Pattern)
|
||||
break;
|
||||
processedPatterns.insert(current);
|
||||
current = static_cast<const SVGPatternElement*>(targetElement);
|
||||
if(processedPatterns.count(current) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
attributes.setDefaultValues(this);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
288
vendor/lunasvg/source/svgpaintelement.h
vendored
288
vendor/lunasvg/source/svgpaintelement.h
vendored
@@ -1,288 +0,0 @@
|
||||
#ifndef LUNASVG_SVGPAINTELEMENT_H
|
||||
#define LUNASVG_SVGPAINTELEMENT_H
|
||||
|
||||
#include "svgelement.h"
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
class SVGPaintElement : public SVGElement {
|
||||
public:
|
||||
SVGPaintElement(Document* document, ElementID id);
|
||||
|
||||
bool isPaintElement() const final { return true; }
|
||||
|
||||
virtual bool applyPaint(SVGRenderState& state, float opacity) const = 0;
|
||||
};
|
||||
|
||||
class SVGStopElement final : public SVGElement {
|
||||
public:
|
||||
SVGStopElement(Document* document);
|
||||
|
||||
void layoutElement(const SVGLayoutState& state) final;
|
||||
const SVGNumberPercentage& offset() const { return m_offset; }
|
||||
GradientStop gradientStop(float opacity) const;
|
||||
|
||||
private:
|
||||
SVGNumberPercentage m_offset;
|
||||
Color m_stop_color = Color::Black;
|
||||
float m_stop_opacity = 1.f;
|
||||
};
|
||||
|
||||
class SVGGradientAttributes;
|
||||
|
||||
class SVGGradientElement : public SVGPaintElement, public SVGURIReference {
|
||||
public:
|
||||
SVGGradientElement(Document* document, ElementID id);
|
||||
|
||||
const SVGTransform& gradientTransform() const { return m_gradientTransform; }
|
||||
const SVGEnumeration<Units>& gradientUnits() const { return m_gradientUnits; }
|
||||
const SVGEnumeration<SpreadMethod>& spreadMethod() const { return m_spreadMethod; }
|
||||
void collectGradientAttributes(SVGGradientAttributes& attributes) const;
|
||||
|
||||
private:
|
||||
SVGTransform m_gradientTransform;
|
||||
SVGEnumeration<Units> m_gradientUnits;
|
||||
SVGEnumeration<SpreadMethod> m_spreadMethod;
|
||||
};
|
||||
|
||||
class SVGGradientAttributes {
|
||||
public:
|
||||
SVGGradientAttributes() = default;
|
||||
|
||||
const Transform& gradientTransform() const { return m_gradientTransform->gradientTransform().value(); }
|
||||
SpreadMethod spreadMethod() const { return m_spreadMethod->spreadMethod().value(); }
|
||||
Units gradientUnits() const { return m_gradientUnits->gradientUnits().value(); }
|
||||
const SVGGradientElement* gradientContentElement() const { return m_gradientContentElement; }
|
||||
|
||||
bool hasGradientTransform() const { return m_gradientTransform; }
|
||||
bool hasSpreadMethod() const { return m_spreadMethod; }
|
||||
bool hasGradientUnits() const { return m_gradientUnits; }
|
||||
bool hasGradientContentElement() const { return m_gradientContentElement; }
|
||||
|
||||
void setGradientTransform(const SVGGradientElement* value) { m_gradientTransform = value; }
|
||||
void setSpreadMethod(const SVGGradientElement* value) { m_spreadMethod = value; }
|
||||
void setGradientUnits(const SVGGradientElement* value) { m_gradientUnits = value; }
|
||||
void setGradientContentElement(const SVGGradientElement* value) { m_gradientContentElement = value; }
|
||||
|
||||
void setDefaultValues(const SVGGradientElement* element) {
|
||||
if(!m_gradientTransform) { m_gradientTransform = element; }
|
||||
if(!m_spreadMethod) { m_spreadMethod = element; }
|
||||
if(!m_gradientUnits) { m_gradientUnits = element; }
|
||||
if(!m_gradientContentElement) { m_gradientContentElement = element; }
|
||||
}
|
||||
|
||||
private:
|
||||
const SVGGradientElement* m_gradientTransform{nullptr};
|
||||
const SVGGradientElement* m_spreadMethod{nullptr};
|
||||
const SVGGradientElement* m_gradientUnits{nullptr};
|
||||
const SVGGradientElement* m_gradientContentElement{nullptr};
|
||||
};
|
||||
|
||||
class SVGLinearGradientAttributes;
|
||||
|
||||
class SVGLinearGradientElement final : public SVGGradientElement {
|
||||
public:
|
||||
SVGLinearGradientElement(Document* document);
|
||||
|
||||
const SVGLength& x1() const { return m_x1; }
|
||||
const SVGLength& y1() const { return m_y1; }
|
||||
const SVGLength& x2() const { return m_x2; }
|
||||
const SVGLength& y2() const { return m_y2; }
|
||||
|
||||
bool applyPaint(SVGRenderState& state, float opacity) const final;
|
||||
|
||||
private:
|
||||
SVGLinearGradientAttributes collectGradientAttributes() const;
|
||||
SVGLength m_x1;
|
||||
SVGLength m_y1;
|
||||
SVGLength m_x2;
|
||||
SVGLength m_y2;
|
||||
};
|
||||
|
||||
class SVGLinearGradientAttributes : public SVGGradientAttributes {
|
||||
public:
|
||||
SVGLinearGradientAttributes() = default;
|
||||
|
||||
const SVGLength& x1() const { return m_x1->x1(); }
|
||||
const SVGLength& y1() const { return m_y1->y1(); }
|
||||
const SVGLength& x2() const { return m_x2->x2(); }
|
||||
const SVGLength& y2() const { return m_y2->y2(); }
|
||||
|
||||
bool hasX1() const { return m_x1; }
|
||||
bool hasY1() const { return m_y1; }
|
||||
bool hasX2() const { return m_x2; }
|
||||
bool hasY2() const { return m_y2; }
|
||||
|
||||
void setX1(const SVGLinearGradientElement* value) { m_x1 = value; }
|
||||
void setY1(const SVGLinearGradientElement* value) { m_y1 = value; }
|
||||
void setX2(const SVGLinearGradientElement* value) { m_x2 = value; }
|
||||
void setY2(const SVGLinearGradientElement* value) { m_y2 = value; }
|
||||
|
||||
void setDefaultValues(const SVGLinearGradientElement* element) {
|
||||
SVGGradientAttributes::setDefaultValues(element);
|
||||
if(!m_x1) { m_x1 = element; }
|
||||
if(!m_y1) { m_y1 = element; }
|
||||
if(!m_x2) { m_x2 = element; }
|
||||
if(!m_y2) { m_y2 = element; }
|
||||
}
|
||||
|
||||
private:
|
||||
const SVGLinearGradientElement* m_x1{nullptr};
|
||||
const SVGLinearGradientElement* m_y1{nullptr};
|
||||
const SVGLinearGradientElement* m_x2{nullptr};
|
||||
const SVGLinearGradientElement* m_y2{nullptr};
|
||||
};
|
||||
|
||||
class SVGRadialGradientAttributes;
|
||||
|
||||
class SVGRadialGradientElement final : public SVGGradientElement {
|
||||
public:
|
||||
SVGRadialGradientElement(Document* document);
|
||||
|
||||
const SVGLength& cx() const { return m_cx; }
|
||||
const SVGLength& cy() const { return m_cy; }
|
||||
const SVGLength& r() const { return m_r; }
|
||||
const SVGLength& fx() const { return m_fx; }
|
||||
const SVGLength& fy() const { return m_fy; }
|
||||
|
||||
bool applyPaint(SVGRenderState& state, float opacity) const final;
|
||||
|
||||
private:
|
||||
SVGRadialGradientAttributes collectGradientAttributes() const;
|
||||
SVGLength m_cx;
|
||||
SVGLength m_cy;
|
||||
SVGLength m_r;
|
||||
SVGLength m_fx;
|
||||
SVGLength m_fy;
|
||||
};
|
||||
|
||||
class SVGRadialGradientAttributes : public SVGGradientAttributes {
|
||||
public:
|
||||
SVGRadialGradientAttributes() = default;
|
||||
|
||||
const SVGLength& cx() const { return m_cx->cx(); }
|
||||
const SVGLength& cy() const { return m_cy->cy(); }
|
||||
const SVGLength& r() const { return m_r->r(); }
|
||||
const SVGLength& fx() const { return m_fx ? m_fx->fx() : m_cx->cx(); }
|
||||
const SVGLength& fy() const { return m_fy ? m_fy->fy() : m_cy->cy(); }
|
||||
|
||||
bool hasCx() const { return m_cx; }
|
||||
bool hasCy() const { return m_cy; }
|
||||
bool hasR() const { return m_r; }
|
||||
bool hasFx() const { return m_fx; }
|
||||
bool hasFy() const { return m_fy; }
|
||||
|
||||
void setCx(const SVGRadialGradientElement* value) { m_cx = value; }
|
||||
void setCy(const SVGRadialGradientElement* value) { m_cy = value; }
|
||||
void setR(const SVGRadialGradientElement* value) { m_r = value; }
|
||||
void setFx(const SVGRadialGradientElement* value) { m_fx = value; }
|
||||
void setFy(const SVGRadialGradientElement* value) { m_fy = value; }
|
||||
|
||||
void setDefaultValues(const SVGRadialGradientElement* element) {
|
||||
SVGGradientAttributes::setDefaultValues(element);
|
||||
if(!m_cx) { m_cx = element; }
|
||||
if(!m_cy) { m_cy = element; }
|
||||
if(!m_r) { m_r = element; }
|
||||
}
|
||||
|
||||
private:
|
||||
const SVGRadialGradientElement* m_cx{nullptr};
|
||||
const SVGRadialGradientElement* m_cy{nullptr};
|
||||
const SVGRadialGradientElement* m_r{nullptr};
|
||||
const SVGRadialGradientElement* m_fx{nullptr};
|
||||
const SVGRadialGradientElement* m_fy{nullptr};
|
||||
};
|
||||
|
||||
class SVGPatternAttributes;
|
||||
|
||||
class SVGPatternElement final : public SVGPaintElement, public SVGURIReference, public SVGFitToViewBox {
|
||||
public:
|
||||
SVGPatternElement(Document* document);
|
||||
|
||||
const SVGLength& x() const { return m_x; }
|
||||
const SVGLength& y() const { return m_y; }
|
||||
const SVGLength& width() const { return m_width; }
|
||||
const SVGLength& height() const { return m_height; }
|
||||
const SVGTransform& patternTransform() const { return m_patternTransform; }
|
||||
const SVGEnumeration<Units>& patternUnits() const { return m_patternUnits; }
|
||||
const SVGEnumeration<Units>& patternContentUnits() const { return m_patternContentUnits; }
|
||||
|
||||
bool applyPaint(SVGRenderState& state, float opacity) const final;
|
||||
|
||||
private:
|
||||
SVGPatternAttributes collectPatternAttributes() const;
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
SVGTransform m_patternTransform;
|
||||
SVGEnumeration<Units> m_patternUnits;
|
||||
SVGEnumeration<Units> m_patternContentUnits;
|
||||
};
|
||||
|
||||
class SVGPatternAttributes {
|
||||
public:
|
||||
SVGPatternAttributes() = default;
|
||||
|
||||
const SVGLength& x() const { return m_x->x(); }
|
||||
const SVGLength& y() const { return m_y->y(); }
|
||||
const SVGLength& width() const { return m_width->width(); }
|
||||
const SVGLength& height() const { return m_height->height(); }
|
||||
const Transform& patternTransform() const { return m_patternTransform->patternTransform().value(); }
|
||||
Units patternUnits() const { return m_patternUnits->patternUnits().value(); }
|
||||
Units patternContentUnits() const { return m_patternContentUnits->patternContentUnits().value(); }
|
||||
const Rect& viewBox() const { return m_viewBox->viewBox().value(); }
|
||||
const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio->preserveAspectRatio(); }
|
||||
const SVGPatternElement* patternContentElement() const { return m_patternContentElement; }
|
||||
|
||||
bool hasX() const { return m_x; }
|
||||
bool hasY() const { return m_y; }
|
||||
bool hasWidth() const { return m_width; }
|
||||
bool hasHeight() const { return m_height; }
|
||||
bool hasPatternTransform() const { return m_patternTransform; }
|
||||
bool hasPatternUnits() const { return m_patternUnits; }
|
||||
bool hasPatternContentUnits() const { return m_patternContentUnits; }
|
||||
bool hasViewBox() const { return m_viewBox; }
|
||||
bool hasPreserveAspectRatio() const { return m_preserveAspectRatio; }
|
||||
bool hasPatternContentElement() const { return m_patternContentElement; }
|
||||
|
||||
void setX(const SVGPatternElement* value) { m_x = value; }
|
||||
void setY(const SVGPatternElement* value) { m_y = value; }
|
||||
void setWidth(const SVGPatternElement* value) { m_width = value; }
|
||||
void setHeight(const SVGPatternElement* value) { m_height = value; }
|
||||
void setPatternTransform(const SVGPatternElement* value) { m_patternTransform = value; }
|
||||
void setPatternUnits(const SVGPatternElement* value) { m_patternUnits = value; }
|
||||
void setPatternContentUnits(const SVGPatternElement* value) { m_patternContentUnits = value; }
|
||||
void setViewBox(const SVGPatternElement* value) { m_viewBox = value; }
|
||||
void setPreserveAspectRatio(const SVGPatternElement* value) { m_preserveAspectRatio = value; }
|
||||
void setPatternContentElement(const SVGPatternElement* value) { m_patternContentElement = value; }
|
||||
|
||||
void setDefaultValues(const SVGPatternElement* element) {
|
||||
if(!m_x) { m_x = element; }
|
||||
if(!m_y) { m_y = element; }
|
||||
if(!m_width) { m_width = element; }
|
||||
if(!m_height) { m_height = element; }
|
||||
if(!m_patternTransform) { m_patternTransform = element; }
|
||||
if(!m_patternUnits) { m_patternUnits = element; }
|
||||
if(!m_patternContentUnits) { m_patternContentUnits = element; }
|
||||
if(!m_viewBox) { m_viewBox = element; }
|
||||
if(!m_preserveAspectRatio) { m_preserveAspectRatio = element; }
|
||||
if(!m_patternContentElement) { m_patternContentElement = element; }
|
||||
}
|
||||
|
||||
private:
|
||||
const SVGPatternElement* m_x{nullptr};
|
||||
const SVGPatternElement* m_y{nullptr};
|
||||
const SVGPatternElement* m_width{nullptr};
|
||||
const SVGPatternElement* m_height{nullptr};
|
||||
const SVGPatternElement* m_patternTransform{nullptr};
|
||||
const SVGPatternElement* m_patternUnits{nullptr};
|
||||
const SVGPatternElement* m_patternContentUnits{nullptr};
|
||||
const SVGPatternElement* m_viewBox{nullptr};
|
||||
const SVGPatternElement* m_preserveAspectRatio{nullptr};
|
||||
const SVGPatternElement* m_patternContentElement{nullptr};
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGPAINTELEMENT_H
|
||||
956
vendor/lunasvg/source/svgparser.cpp
vendored
956
vendor/lunasvg/source/svgparser.cpp
vendored
@@ -1,956 +0,0 @@
|
||||
#include "lunasvg.h"
|
||||
#include "svgelement.h"
|
||||
#include "svgparserutils.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
struct SimpleSelector;
|
||||
|
||||
using Selector = std::vector<SimpleSelector>;
|
||||
using SelectorList = std::vector<Selector>;
|
||||
|
||||
struct AttributeSelector {
|
||||
enum class MatchType {
|
||||
None,
|
||||
Equals,
|
||||
Contains,
|
||||
Includes,
|
||||
StartsWith,
|
||||
EndsWith,
|
||||
DashEquals
|
||||
};
|
||||
|
||||
MatchType matchType{MatchType::None};
|
||||
PropertyID id{PropertyID::Unknown};
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct PseudoClassSelector {
|
||||
enum class Type {
|
||||
Unknown,
|
||||
Empty,
|
||||
Root,
|
||||
Is,
|
||||
Not,
|
||||
FirstChild,
|
||||
LastChild,
|
||||
OnlyChild,
|
||||
FirstOfType,
|
||||
LastOfType,
|
||||
OnlyOfType
|
||||
};
|
||||
|
||||
Type type{Type::Unknown};
|
||||
SelectorList subSelectors;
|
||||
};
|
||||
|
||||
struct SimpleSelector {
|
||||
enum class Combinator {
|
||||
None,
|
||||
Descendant,
|
||||
Child,
|
||||
DirectAdjacent,
|
||||
InDirectAdjacent
|
||||
};
|
||||
|
||||
explicit SimpleSelector(Combinator combinator) : combinator(combinator) {}
|
||||
|
||||
Combinator combinator{Combinator::Descendant};
|
||||
ElementID id{ElementID::Star};
|
||||
std::vector<AttributeSelector> attributeSelectors;
|
||||
std::vector<PseudoClassSelector> pseudoClassSelectors;
|
||||
};
|
||||
|
||||
struct Declaration {
|
||||
int specificity;
|
||||
PropertyID id;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
using DeclarationList = std::vector<Declaration>;
|
||||
|
||||
struct Rule {
|
||||
SelectorList selectors;
|
||||
DeclarationList declarations;
|
||||
};
|
||||
|
||||
class RuleData {
|
||||
public:
|
||||
RuleData(const Selector& selector, const DeclarationList& declarations, size_t specificity, size_t position)
|
||||
: m_selector(selector), m_declarations(declarations), m_specificity(specificity), m_position(position)
|
||||
{}
|
||||
|
||||
bool isLessThan(const RuleData& rule) const { return std::tie(m_specificity, m_position) < std::tie(rule.m_specificity, rule.m_position); }
|
||||
|
||||
const Selector& selector() const { return m_selector; }
|
||||
const DeclarationList& declarations() const { return m_declarations; }
|
||||
size_t specificity() const { return m_specificity; }
|
||||
size_t position() const { return m_position; }
|
||||
|
||||
bool match(const SVGElement* element) const;
|
||||
|
||||
private:
|
||||
Selector m_selector;
|
||||
DeclarationList m_declarations;
|
||||
size_t m_specificity;
|
||||
size_t m_position;
|
||||
};
|
||||
|
||||
inline bool operator<(const RuleData& a, const RuleData& b) { return a.isLessThan(b); }
|
||||
|
||||
using RuleDataList = std::vector<RuleData>;
|
||||
|
||||
constexpr bool equals(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
return value.compare(subvalue) == 0;
|
||||
}
|
||||
|
||||
constexpr bool contains(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
return value.find(subvalue) != std::string_view::npos;
|
||||
}
|
||||
|
||||
constexpr bool includes(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(subvalue.empty() || subvalue.length() > value.length())
|
||||
return false;
|
||||
std::string_view input(value);
|
||||
while(!input.empty()) {
|
||||
skipOptionalSpaces(input);
|
||||
std::string_view start(input);
|
||||
while(!input.empty() && !IS_WS(input.front()))
|
||||
input.remove_prefix(1);
|
||||
if(subvalue == start.substr(0, start.length() - input.length())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool startswith(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(subvalue.empty() || subvalue.length() > value.length())
|
||||
return false;
|
||||
return subvalue == value.substr(0, subvalue.size());
|
||||
}
|
||||
|
||||
constexpr bool endswith(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(subvalue.empty() || subvalue.length() > value.length())
|
||||
return false;
|
||||
return subvalue == value.substr(value.size() - subvalue.size(), subvalue.size());
|
||||
}
|
||||
|
||||
constexpr bool dashequals(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(startswith(value, subvalue))
|
||||
return (value.length() == subvalue.length() || value.at(subvalue.length()) == '-');
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchAttributeSelector(const AttributeSelector& selector, const SVGElement* element)
|
||||
{
|
||||
const auto& value = element->getAttribute(selector.id);
|
||||
if(selector.matchType == AttributeSelector::MatchType::None)
|
||||
return !value.empty();
|
||||
if(selector.matchType == AttributeSelector::MatchType::Equals)
|
||||
return equals(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::Contains)
|
||||
return contains(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::Includes)
|
||||
return includes(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::StartsWith)
|
||||
return startswith(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::EndsWith)
|
||||
return endswith(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::DashEquals)
|
||||
return dashequals(value, selector.value);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchSimpleSelector(const SimpleSelector& selector, const SVGElement* element);
|
||||
|
||||
static bool matchPseudoClassSelector(const PseudoClassSelector& selector, const SVGElement* element)
|
||||
{
|
||||
if(selector.type == PseudoClassSelector::Type::Empty)
|
||||
return element->children().empty();
|
||||
if(selector.type == PseudoClassSelector::Type::Root)
|
||||
return element->isRootElement();
|
||||
if(selector.type == PseudoClassSelector::Type::Is) {
|
||||
for(const auto& subSelector : selector.subSelectors) {
|
||||
for(const auto& simpleSelector : subSelector) {
|
||||
if(!matchSimpleSelector(simpleSelector, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(selector.type == PseudoClassSelector::Type::Not) {
|
||||
for(const auto& subSelector : selector.subSelectors) {
|
||||
for(const auto& simpleSelector : subSelector) {
|
||||
if(matchSimpleSelector(simpleSelector, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(selector.type == PseudoClassSelector::Type::FirstChild)
|
||||
return !element->previousElement();
|
||||
if(selector.type == PseudoClassSelector::Type::LastChild)
|
||||
return !element->nextElement();
|
||||
if(selector.type == PseudoClassSelector::Type::OnlyChild)
|
||||
return !(element->previousElement() || element->nextElement());
|
||||
if(selector.type == PseudoClassSelector::Type::FirstOfType) {
|
||||
auto sibling = element->previousElement();
|
||||
while(sibling) {
|
||||
if(sibling->id() == element->id())
|
||||
return false;
|
||||
sibling = sibling->previousElement();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(selector.type == PseudoClassSelector::Type::LastOfType) {
|
||||
auto sibling = element->nextElement();
|
||||
while(sibling) {
|
||||
if(sibling->id() == element->id())
|
||||
return false;
|
||||
sibling = sibling->nextElement();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchSimpleSelector(const SimpleSelector& selector, const SVGElement* element)
|
||||
{
|
||||
if(selector.id != ElementID::Star && selector.id != element->id())
|
||||
return false;
|
||||
for(const auto& sel : selector.attributeSelectors) {
|
||||
if(!matchAttributeSelector(sel, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& sel : selector.pseudoClassSelectors) {
|
||||
if(!matchPseudoClassSelector(sel, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool matchSelector(const Selector& selector, const SVGElement* element)
|
||||
{
|
||||
if(selector.empty())
|
||||
return false;
|
||||
auto it = selector.rbegin();
|
||||
auto end = selector.rend();
|
||||
if(!matchSimpleSelector(*it, element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto combinator = it->combinator;
|
||||
++it;
|
||||
|
||||
while(it != end) {
|
||||
switch(combinator) {
|
||||
case SimpleSelector::Combinator::Child:
|
||||
case SimpleSelector::Combinator::Descendant:
|
||||
element = element->parentElement();
|
||||
break;
|
||||
case SimpleSelector::Combinator::DirectAdjacent:
|
||||
case SimpleSelector::Combinator::InDirectAdjacent:
|
||||
element = element->previousElement();
|
||||
break;
|
||||
case SimpleSelector::Combinator::None:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if(element == nullptr)
|
||||
return false;
|
||||
if(matchSimpleSelector(*it, element)) {
|
||||
combinator = it->combinator;
|
||||
++it;
|
||||
} else if(combinator != SimpleSelector::Combinator::Descendant
|
||||
&& combinator != SimpleSelector::Combinator::InDirectAdjacent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuleData::match(const SVGElement* element) const
|
||||
{
|
||||
return matchSelector(m_selector, element);
|
||||
}
|
||||
|
||||
constexpr bool IS_CSS_STARTNAMECHAR(int c) { return IS_ALPHA(c) || c == '_' || c == '-'; }
|
||||
constexpr bool IS_CSS_NAMECHAR(int c) { return IS_CSS_STARTNAMECHAR(c) || IS_NUM(c); }
|
||||
|
||||
inline bool readCSSIdentifier(std::string_view& input, std::string& output)
|
||||
{
|
||||
if(input.empty() || !IS_CSS_STARTNAMECHAR(input.front()))
|
||||
return false;
|
||||
output.clear();
|
||||
do {
|
||||
output.push_back(input.front());
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_CSS_NAMECHAR(input.front()));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseTagSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
std::string name;
|
||||
if(skipDelimiter(input, '*'))
|
||||
simpleSelector.id = ElementID::Star;
|
||||
else if(readCSSIdentifier(input, name))
|
||||
simpleSelector.id = elementid(name);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseIdSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
AttributeSelector a;
|
||||
a.id = PropertyID::Id;
|
||||
a.matchType = AttributeSelector::MatchType::Equals;
|
||||
if(!readCSSIdentifier(input, a.value))
|
||||
return false;
|
||||
simpleSelector.attributeSelectors.push_back(std::move(a));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseClassSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
AttributeSelector a;
|
||||
a.id = PropertyID::Class;
|
||||
a.matchType = AttributeSelector::MatchType::Includes;
|
||||
if(!readCSSIdentifier(input, a.value))
|
||||
return false;
|
||||
simpleSelector.attributeSelectors.push_back(std::move(a));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseAttributeSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
std::string name;
|
||||
skipOptionalSpaces(input);
|
||||
if(!readCSSIdentifier(input, name))
|
||||
return false;
|
||||
AttributeSelector a;
|
||||
a.id = propertyid(name);
|
||||
a.matchType = AttributeSelector::MatchType::None;
|
||||
if(skipDelimiter(input, '='))
|
||||
a.matchType = AttributeSelector::MatchType::Equals;
|
||||
else if(skipString(input, "*="))
|
||||
a.matchType = AttributeSelector::MatchType::Contains;
|
||||
else if(skipString(input, "~="))
|
||||
a.matchType = AttributeSelector::MatchType::Includes;
|
||||
else if(skipString(input, "^="))
|
||||
a.matchType = AttributeSelector::MatchType::StartsWith;
|
||||
else if(skipString(input, "$="))
|
||||
a.matchType = AttributeSelector::MatchType::EndsWith;
|
||||
else if(skipString(input, "|="))
|
||||
a.matchType = AttributeSelector::MatchType::DashEquals;
|
||||
if(a.matchType != AttributeSelector::MatchType::None) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!readCSSIdentifier(input, a.value)) {
|
||||
if(input.empty() || !(input.front() == '\"' || input.front() == '\''))
|
||||
return false;
|
||||
auto quote = input.front();
|
||||
input.remove_prefix(1);
|
||||
auto n = input.find(quote);
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
a.value.assign(input.substr(0, n));
|
||||
input.remove_prefix(n + 1);
|
||||
}
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ']'))
|
||||
return false;
|
||||
simpleSelector.attributeSelectors.push_back(std::move(a));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseSelectors(std::string_view& input, SelectorList& selectors);
|
||||
|
||||
static bool parsePseudoClassSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
std::string name;
|
||||
if(!readCSSIdentifier(input, name))
|
||||
return false;
|
||||
PseudoClassSelector selector;
|
||||
if(name.compare("empty") == 0)
|
||||
selector.type = PseudoClassSelector::Type::Empty;
|
||||
else if(name.compare("root") == 0)
|
||||
selector.type = PseudoClassSelector::Type::Root;
|
||||
else if(name.compare("not") == 0)
|
||||
selector.type = PseudoClassSelector::Type::Not;
|
||||
else if(name.compare("first-child") == 0)
|
||||
selector.type = PseudoClassSelector::Type::FirstChild;
|
||||
else if(name.compare("last-child") == 0)
|
||||
selector.type = PseudoClassSelector::Type::LastChild;
|
||||
else if(name.compare("only-child") == 0)
|
||||
selector.type = PseudoClassSelector::Type::OnlyChild;
|
||||
else if(name.compare("first-of-type") == 0)
|
||||
selector.type = PseudoClassSelector::Type::FirstOfType;
|
||||
else if(name.compare("last-of-type") == 0)
|
||||
selector.type = PseudoClassSelector::Type::LastOfType;
|
||||
else if(name.compare("only-of-type") == 0)
|
||||
selector.type = PseudoClassSelector::Type::OnlyOfType;
|
||||
if(selector.type == PseudoClassSelector::Type::Is || selector.type == PseudoClassSelector::Type::Not) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '('))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(!parseSelectors(input, selector.subSelectors))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ')')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
simpleSelector.pseudoClassSelectors.push_back(std::move(selector));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseSimpleSelector(std::string_view& input, SimpleSelector& simpleSelector, bool& failed)
|
||||
{
|
||||
auto consumed = parseTagSelector(input, simpleSelector);
|
||||
do {
|
||||
if(skipDelimiter(input, '#'))
|
||||
failed = !parseIdSelector(input, simpleSelector);
|
||||
else if(skipDelimiter(input, '.'))
|
||||
failed = !parseClassSelector(input, simpleSelector);
|
||||
else if(skipDelimiter(input, '['))
|
||||
failed = !parseAttributeSelector(input, simpleSelector);
|
||||
else if(skipDelimiter(input, ':'))
|
||||
failed = !parsePseudoClassSelector(input, simpleSelector);
|
||||
else
|
||||
break;
|
||||
consumed = true;
|
||||
} while(!failed);
|
||||
return consumed && !failed;
|
||||
}
|
||||
|
||||
static bool parseCombinator(std::string_view& input, SimpleSelector::Combinator& combinator)
|
||||
{
|
||||
combinator = SimpleSelector::Combinator::None;
|
||||
while(!input.empty() && IS_WS(input.front())) {
|
||||
combinator = SimpleSelector::Combinator::Descendant;
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
if(skipDelimiterAndOptionalSpaces(input, '>'))
|
||||
combinator = SimpleSelector::Combinator::Child;
|
||||
else if(skipDelimiterAndOptionalSpaces(input, '+'))
|
||||
combinator = SimpleSelector::Combinator::DirectAdjacent;
|
||||
else if(skipDelimiterAndOptionalSpaces(input, '~'))
|
||||
combinator = SimpleSelector::Combinator::InDirectAdjacent;
|
||||
return combinator != SimpleSelector::Combinator::None;
|
||||
}
|
||||
|
||||
static bool parseSelector(std::string_view& input, Selector& selector)
|
||||
{
|
||||
auto combinator = SimpleSelector::Combinator::None;
|
||||
do {
|
||||
bool failed = false;
|
||||
SimpleSelector simpleSelector(combinator);
|
||||
if(!parseSimpleSelector(input, simpleSelector, failed))
|
||||
return !failed && (combinator == SimpleSelector::Combinator::Descendant);
|
||||
selector.push_back(std::move(simpleSelector));
|
||||
} while(parseCombinator(input, combinator));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseSelectors(std::string_view& input, SelectorList& selectors)
|
||||
{
|
||||
do {
|
||||
Selector selector;
|
||||
if(!parseSelector(input, selector))
|
||||
return false;
|
||||
selectors.push_back(std::move(selector));
|
||||
} while(skipDelimiterAndOptionalSpaces(input, ','));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseDeclarations(std::string_view& input, DeclarationList& declarations)
|
||||
{
|
||||
if(!skipDelimiter(input, '{'))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
do {
|
||||
std::string name;
|
||||
if(!readCSSIdentifier(input, name))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ':'))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
std::string_view value(input);
|
||||
while(!input.empty() && !(input.front() == '!' || input.front() == ';' || input.front() == '}'))
|
||||
input.remove_prefix(1);
|
||||
value.remove_suffix(input.length());
|
||||
stripTrailingSpaces(value);
|
||||
|
||||
Declaration declaration;
|
||||
declaration.specificity = 0x10;
|
||||
declaration.id = csspropertyid(name);
|
||||
declaration.value.assign(value);
|
||||
if(skipDelimiter(input, '!')) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipString(input, "important"))
|
||||
return false;
|
||||
declaration.specificity = 0x1000;
|
||||
}
|
||||
|
||||
if(declaration.id != PropertyID::Unknown)
|
||||
declarations.push_back(std::move(declaration));
|
||||
skipOptionalSpacesOrDelimiter(input, ';');
|
||||
} while(!input.empty() && input.front() != '}');
|
||||
return skipDelimiter(input, '}');
|
||||
}
|
||||
|
||||
static bool parseRule(std::string_view& input, Rule& rule)
|
||||
{
|
||||
if(!parseSelectors(input, rule.selectors))
|
||||
return false;
|
||||
return parseDeclarations(input, rule.declarations);
|
||||
}
|
||||
|
||||
static RuleDataList parseStyleSheet(std::string_view input)
|
||||
{
|
||||
RuleDataList rules;
|
||||
while(!input.empty()) {
|
||||
skipOptionalSpaces(input);
|
||||
if(skipDelimiter(input, '@')) {
|
||||
int depth = 0;
|
||||
while(!input.empty()) {
|
||||
auto ch = input.front();
|
||||
input.remove_prefix(1);
|
||||
if(ch == ';' && depth == 0)
|
||||
break;
|
||||
if(ch == '{') ++depth;
|
||||
else if(ch == '}' && depth > 0) {
|
||||
if(depth == 1)
|
||||
break;
|
||||
--depth;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Rule rule;
|
||||
if(!parseRule(input, rule))
|
||||
break;
|
||||
for(const auto& selector : rule.selectors) {
|
||||
size_t specificity = 0;
|
||||
for(const auto& simpleSelector : selector) {
|
||||
specificity += (simpleSelector.id == ElementID::Star) ? 0x0 : 0x1;
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
static SelectorList parseQuerySelectors(std::string_view input)
|
||||
{
|
||||
SelectorList selectors;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseSelectors(input, selectors)
|
||||
|| !input.empty()) {
|
||||
return SelectorList();
|
||||
}
|
||||
|
||||
return selectors;
|
||||
}
|
||||
|
||||
inline void parseInlineStyle(std::string_view input, SVGElement* element)
|
||||
{
|
||||
std::string name;
|
||||
skipOptionalSpaces(input);
|
||||
while(readCSSIdentifier(input, name)) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ':'))
|
||||
return;
|
||||
std::string value;
|
||||
while(!input.empty() && input.front() != ';') {
|
||||
value.push_back(input.front());
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
auto id = csspropertyid(name);
|
||||
if(id != PropertyID::Unknown)
|
||||
element->setAttribute(0x100, id, value);
|
||||
skipOptionalSpacesOrDelimiter(input, ';');
|
||||
}
|
||||
}
|
||||
|
||||
inline void removeStyleComments(std::string& value)
|
||||
{
|
||||
auto start = value.find("/*");
|
||||
while(start != std::string::npos) {
|
||||
auto end = value.find("*/", start + 2);
|
||||
value.erase(start, end - start + 2);
|
||||
start = value.find("/*");
|
||||
}
|
||||
}
|
||||
|
||||
inline bool decodeText(std::string_view input, std::string& output)
|
||||
{
|
||||
output.clear();
|
||||
while(!input.empty()) {
|
||||
auto ch = input.front();
|
||||
input.remove_prefix(1);
|
||||
if(ch != '&') {
|
||||
output.push_back(ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '#')) {
|
||||
int base = 10;
|
||||
if(skipDelimiter(input, 'x'))
|
||||
base = 16;
|
||||
unsigned int cp;
|
||||
if(!parseInteger(input, cp, base))
|
||||
return false;
|
||||
char c[5] = {0, 0, 0, 0, 0};
|
||||
if(cp < 0x80) {
|
||||
c[1] = 0;
|
||||
c[0] = char(cp);
|
||||
} else if(cp < 0x800) {
|
||||
c[2] = 0;
|
||||
c[1] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[0] = char(cp | 0xC0);
|
||||
} else if(cp < 0x10000) {
|
||||
c[3] = 0;
|
||||
c[2] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[1] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[0] = char(cp | 0xE0);
|
||||
} else if(cp < 0x200000) {
|
||||
c[4] = 0;
|
||||
c[3] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[2] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[1] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[0] = char(cp | 0xF0);
|
||||
}
|
||||
|
||||
output.append(c);
|
||||
} else {
|
||||
if(skipString(input, "amp")) {
|
||||
output.push_back('&');
|
||||
} else if(skipString(input, "lt")) {
|
||||
output.push_back('<');
|
||||
} else if(skipString(input, "gt")) {
|
||||
output.push_back('>');
|
||||
} else if(skipString(input, "quot")) {
|
||||
output.push_back('\"');
|
||||
} else if(skipString(input, "apos")) {
|
||||
output.push_back('\'');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!skipDelimiter(input, ';')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool IS_STARTNAMECHAR(int c) { return IS_ALPHA(c) || c == '_' || c == ':'; }
|
||||
constexpr bool IS_NAMECHAR(int c) { return IS_STARTNAMECHAR(c) || IS_NUM(c) || c == '-' || c == '.'; }
|
||||
|
||||
inline bool readIdentifier(std::string_view& input, std::string& output)
|
||||
{
|
||||
if(input.empty() || !IS_STARTNAMECHAR(input.front()))
|
||||
return false;
|
||||
output.clear();
|
||||
do {
|
||||
output.push_back(input.front());
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_NAMECHAR(input.front()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Document::parse(const char* data, size_t length)
|
||||
{
|
||||
std::string buffer;
|
||||
std::string styleSheet;
|
||||
SVGElement* currentElement = nullptr;
|
||||
int ignoring = 0;
|
||||
auto handleText = [&](std::string_view text, bool in_cdata) {
|
||||
if(text.empty() || currentElement == nullptr || ignoring > 0)
|
||||
return;
|
||||
if(currentElement->id() != ElementID::Text && currentElement->id() != ElementID::Tspan && currentElement->id() != ElementID::Style) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(in_cdata) {
|
||||
buffer.assign(text);
|
||||
} else {
|
||||
decodeText(text, buffer);
|
||||
}
|
||||
|
||||
if(currentElement->id() == ElementID::Style) {
|
||||
removeStyleComments(buffer);
|
||||
styleSheet.append(buffer);
|
||||
} else {
|
||||
auto node = std::make_unique<SVGTextNode>(this);
|
||||
node->setData(buffer);
|
||||
currentElement->addChild(std::move(node));
|
||||
}
|
||||
};
|
||||
|
||||
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('<'));
|
||||
handleText(text, false);
|
||||
input.remove_prefix(text.length());
|
||||
} else {
|
||||
if(!skipOptionalSpaces(input)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!skipDelimiter(input, '<'))
|
||||
return false;
|
||||
if(skipDelimiter(input, '?')) {
|
||||
if(!readIdentifier(input, buffer))
|
||||
return false;
|
||||
auto n = input.find("?>");
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
input.remove_prefix(n + 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '!')) {
|
||||
if(skipString(input, "--")) {
|
||||
auto n = input.find("-->");
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
handleText(input.substr(0, n), false);
|
||||
input.remove_prefix(n + 3);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipString(input, "[CDATA[")) {
|
||||
auto n = input.find("]]>");
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
handleText(input.substr(0, n), true);
|
||||
input.remove_prefix(n + 3);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipString(input, "DOCTYPE")) {
|
||||
while(!input.empty() && input.front() != '>') {
|
||||
if(input.front() == '[') {
|
||||
int depth = 1;
|
||||
input.remove_prefix(1);
|
||||
while(!input.empty() && depth > 0) {
|
||||
if(input.front() == '[') ++depth;
|
||||
else if(input.front() == ']') --depth;
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
} else {
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
}
|
||||
|
||||
if(!skipDelimiter(input, '>'))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '/')) {
|
||||
if(currentElement == nullptr && ignoring == 0)
|
||||
return false;
|
||||
if(!readIdentifier(input, buffer))
|
||||
return false;
|
||||
if(ignoring == 0) {
|
||||
auto id = elementid(buffer);
|
||||
if(id != currentElement->id())
|
||||
return false;
|
||||
currentElement = currentElement->parentElement();
|
||||
} else {
|
||||
--ignoring;
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '>'))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!readIdentifier(input, buffer))
|
||||
return false;
|
||||
SVGElement* element = nullptr;
|
||||
if(ignoring > 0) {
|
||||
++ignoring;
|
||||
} else {
|
||||
auto id = elementid(buffer);
|
||||
if(id == ElementID::Unknown) {
|
||||
ignoring = 1;
|
||||
} else {
|
||||
if(m_rootElement && currentElement == nullptr)
|
||||
return false;
|
||||
if(m_rootElement == nullptr) {
|
||||
if(id != ElementID::Svg)
|
||||
return false;
|
||||
m_rootElement = std::make_unique<SVGRootElement>(this);
|
||||
element = m_rootElement.get();
|
||||
} else {
|
||||
auto child = SVGElement::create(this, id);
|
||||
element = child.get();
|
||||
currentElement->addChild(std::move(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
while(readIdentifier(input, buffer)) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '='))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(input.empty() || !(input.front() == '\"' || input.front() == '\''))
|
||||
return false;
|
||||
auto quote = input.front();
|
||||
input.remove_prefix(1);
|
||||
auto n = input.find(quote);
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
auto id = PropertyID::Unknown;
|
||||
if(element != nullptr)
|
||||
id = propertyid(buffer);
|
||||
if(id != PropertyID::Unknown) {
|
||||
decodeText(input.substr(0, n), buffer);
|
||||
if(id == PropertyID::Style) {
|
||||
removeStyleComments(buffer);
|
||||
parseInlineStyle(buffer, element);
|
||||
} else {
|
||||
if(id == PropertyID::Id)
|
||||
m_rootElement->addElementById(buffer, element);
|
||||
element->setAttribute(0x1, id, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
input.remove_prefix(n + 1);
|
||||
skipOptionalSpaces(input);
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '>')) {
|
||||
if(element != nullptr)
|
||||
currentElement = element;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '/')) {
|
||||
if(!skipDelimiter(input, '>'))
|
||||
return false;
|
||||
if(ignoring > 0)
|
||||
--ignoring;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(m_rootElement == nullptr || ignoring > 0 || !input.empty())
|
||||
return false;
|
||||
applyStyleSheet(styleSheet);
|
||||
m_rootElement->build();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Document::applyStyleSheet(const std::string& content)
|
||||
{
|
||||
auto rules = parseStyleSheet(content);
|
||||
if(!rules.empty()) {
|
||||
std::sort(rules.begin(), rules.end());
|
||||
m_rootElement->transverse([&rules](SVGElement* element) {
|
||||
for(const auto& rule : rules) {
|
||||
if(rule.match(element)) {
|
||||
for(const auto& declaration : rule.declarations()) {
|
||||
element->setAttribute(declaration.specificity, declaration.id, declaration.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ElementList Document::querySelectorAll(const std::string& content) const
|
||||
{
|
||||
auto selectors = parseQuerySelectors(content);
|
||||
if(selectors.empty())
|
||||
return ElementList();
|
||||
ElementList elements;
|
||||
m_rootElement->transverse([&](SVGElement* element) {
|
||||
for(const auto& selector : selectors) {
|
||||
if(matchSelector(selector, element)) {
|
||||
elements.push_back(element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
210
vendor/lunasvg/source/svgparserutils.h
vendored
210
vendor/lunasvg/source/svgparserutils.h
vendored
@@ -1,210 +0,0 @@
|
||||
#ifndef LUNASVG_SVGPARSERUTILS_H
|
||||
#define LUNASVG_SVGPARSERUTILS_H
|
||||
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
#include <limits>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
constexpr bool IS_NUM(int cc) { return cc >= '0' && cc <= '9'; }
|
||||
constexpr bool IS_ALPHA(int cc) { return (cc >= 'a' && cc <= 'z') || (cc >= 'A' && cc <= 'Z'); }
|
||||
constexpr bool IS_WS(int cc) { return cc == ' ' || cc == '\t' || cc == '\n' || cc == '\r'; }
|
||||
|
||||
constexpr void stripLeadingSpaces(std::string_view& input)
|
||||
{
|
||||
while(!input.empty() && IS_WS(input.front())) {
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void stripTrailingSpaces(std::string_view& input)
|
||||
{
|
||||
while(!input.empty() && IS_WS(input.back())) {
|
||||
input.remove_suffix(1);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void stripLeadingAndTrailingSpaces(std::string_view& input)
|
||||
{
|
||||
stripLeadingSpaces(input);
|
||||
stripTrailingSpaces(input);
|
||||
}
|
||||
|
||||
constexpr bool skipOptionalSpaces(std::string_view& input)
|
||||
{
|
||||
while(!input.empty() && IS_WS(input.front()))
|
||||
input.remove_prefix(1);
|
||||
return !input.empty();
|
||||
}
|
||||
|
||||
constexpr bool skipOptionalSpacesOrDelimiter(std::string_view& input, char delimiter)
|
||||
{
|
||||
if(!input.empty() && !IS_WS(input.front()) && delimiter != input.front())
|
||||
return false;
|
||||
if(skipOptionalSpaces(input)) {
|
||||
if(delimiter == input.front()) {
|
||||
input.remove_prefix(1);
|
||||
skipOptionalSpaces(input);
|
||||
}
|
||||
}
|
||||
|
||||
return !input.empty();
|
||||
}
|
||||
|
||||
constexpr bool skipOptionalSpacesOrComma(std::string_view& input)
|
||||
{
|
||||
return skipOptionalSpacesOrDelimiter(input, ',');
|
||||
}
|
||||
|
||||
constexpr bool skipDelimiterAndOptionalSpaces(std::string_view& input, char delimiter)
|
||||
{
|
||||
if(!input.empty() && input.front() == delimiter) {
|
||||
input.remove_prefix(1);
|
||||
skipOptionalSpaces(input);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool skipDelimiter(std::string_view& input, char delimiter)
|
||||
{
|
||||
if(!input.empty() && input.front() == delimiter) {
|
||||
input.remove_prefix(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool skipString(std::string_view& input, std::string_view value)
|
||||
{
|
||||
if(input.size() >= value.size() && value == input.substr(0, value.size())) {
|
||||
input.remove_prefix(value.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool isIntegralDigit(char ch, int base)
|
||||
{
|
||||
if(IS_NUM(ch))
|
||||
return ch - '0' < base;
|
||||
if(IS_ALPHA(ch))
|
||||
return (ch >= 'a' && ch < 'a' + base - 10) || (ch >= 'A' && ch < 'A' + base - 10);
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr int toIntegralDigit(char ch)
|
||||
{
|
||||
if(IS_NUM(ch))
|
||||
return ch - '0';
|
||||
if(ch >= 'a')
|
||||
return ch - 'a' + 10;
|
||||
return ch - 'A' + 10;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool parseInteger(std::string_view& input, T& integer, int base = 10)
|
||||
{
|
||||
constexpr bool isSigned = std::numeric_limits<T>::is_signed;
|
||||
constexpr T intMax = std::numeric_limits<T>::max();
|
||||
const T maxMultiplier = intMax / static_cast<T>(base);
|
||||
|
||||
T value = 0;
|
||||
bool isNegative = false;
|
||||
|
||||
if(!input.empty() && input.front() == '+') {
|
||||
input.remove_prefix(1);
|
||||
} else if(!input.empty() && isSigned && input.front() == '-') {
|
||||
input.remove_prefix(1);
|
||||
isNegative = true;
|
||||
}
|
||||
|
||||
if(input.empty() || !isIntegralDigit(input.front(), base))
|
||||
return false;
|
||||
do {
|
||||
const int digitValue = toIntegralDigit(input.front());
|
||||
if(value > maxMultiplier || (value == maxMultiplier && static_cast<T>(digitValue) > (intMax % static_cast<T>(base)) + isNegative))
|
||||
return false;
|
||||
value = static_cast<T>(base) * value + static_cast<T>(digitValue);
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && isIntegralDigit(input.front(), base));
|
||||
|
||||
using SignedType = typename std::make_signed<T>::type;
|
||||
if(isNegative)
|
||||
integer = -static_cast<SignedType>(value);
|
||||
else
|
||||
integer = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool parseNumber(std::string_view& input, T& number)
|
||||
{
|
||||
constexpr T maxValue = std::numeric_limits<T>::max();
|
||||
T integer = 0;
|
||||
T fraction = 0;
|
||||
int exponent = 0;
|
||||
int sign = 1;
|
||||
int expsign = 1;
|
||||
|
||||
if(!input.empty() && input.front() == '+') {
|
||||
input.remove_prefix(1);
|
||||
} else if(!input.empty() && input.front() == '-') {
|
||||
input.remove_prefix(1);
|
||||
sign = -1;
|
||||
}
|
||||
|
||||
if(input.empty() || (!IS_NUM(input.front()) && input.front() != '.'))
|
||||
return false;
|
||||
if(IS_NUM(input.front())) {
|
||||
do {
|
||||
integer = static_cast<T>(10) * integer + (input.front() - '0');
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_NUM(input.front()));
|
||||
}
|
||||
|
||||
if(!input.empty() && input.front() == '.') {
|
||||
input.remove_prefix(1);
|
||||
if(input.empty() || !IS_NUM(input.front()))
|
||||
return false;
|
||||
T divisor = static_cast<T>(1);
|
||||
do {
|
||||
fraction = static_cast<T>(10) * fraction + (input.front() - '0');
|
||||
divisor *= static_cast<T>(10);
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_NUM(input.front()));
|
||||
fraction /= divisor;
|
||||
}
|
||||
|
||||
if(input.size() > 1 && (input[0] == 'e' || input[0] == 'E')
|
||||
&& (input[1] != 'x' && input[1] != 'm'))
|
||||
{
|
||||
input.remove_prefix(1);
|
||||
if(!input.empty() && input.front() == '+')
|
||||
input.remove_prefix(1);
|
||||
else if(!input.empty() && input.front() == '-') {
|
||||
input.remove_prefix(1);
|
||||
expsign = -1;
|
||||
}
|
||||
|
||||
if(input.empty() || !IS_NUM(input.front()))
|
||||
return false;
|
||||
do {
|
||||
exponent = 10 * exponent + (input.front() - '0');
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_NUM(input.front()));
|
||||
}
|
||||
|
||||
number = sign * (integer + fraction);
|
||||
if(exponent)
|
||||
number *= static_cast<T>(std::pow(10.0, expsign * exponent));
|
||||
return number >= -maxValue && number <= maxValue;
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGPARSERUTILS_H
|
||||
705
vendor/lunasvg/source/svgproperty.cpp
vendored
705
vendor/lunasvg/source/svgproperty.cpp
vendored
@@ -1,705 +0,0 @@
|
||||
#include "svgproperty.h"
|
||||
#include "svgelement.h"
|
||||
#include "svgparserutils.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
PropertyID propertyid(std::string_view name)
|
||||
{
|
||||
static const struct {
|
||||
std::string_view name;
|
||||
PropertyID value;
|
||||
} table[] = {
|
||||
{"class", PropertyID::Class},
|
||||
{"clipPathUnits", PropertyID::ClipPathUnits},
|
||||
{"cx", PropertyID::Cx},
|
||||
{"cy", PropertyID::Cy},
|
||||
{"d", PropertyID::D},
|
||||
{"dx", PropertyID::Dx},
|
||||
{"dy", PropertyID::Dy},
|
||||
{"fx", PropertyID::Fx},
|
||||
{"fy", PropertyID::Fy},
|
||||
{"gradientTransform", PropertyID::GradientTransform},
|
||||
{"gradientUnits", PropertyID::GradientUnits},
|
||||
{"height", PropertyID::Height},
|
||||
{"href", PropertyID::Href},
|
||||
{"id", PropertyID::Id},
|
||||
{"lengthAdjust", PropertyID::LengthAdjust},
|
||||
{"markerHeight", PropertyID::MarkerHeight},
|
||||
{"markerUnits", PropertyID::MarkerUnits},
|
||||
{"markerWidth", PropertyID::MarkerWidth},
|
||||
{"maskContentUnits", PropertyID::MaskContentUnits},
|
||||
{"maskUnits", PropertyID::MaskUnits},
|
||||
{"offset", PropertyID::Offset},
|
||||
{"orient", PropertyID::Orient},
|
||||
{"patternContentUnits", PropertyID::PatternContentUnits},
|
||||
{"patternTransform", PropertyID::PatternTransform},
|
||||
{"patternUnits", PropertyID::PatternUnits},
|
||||
{"points", PropertyID::Points},
|
||||
{"preserveAspectRatio", PropertyID::PreserveAspectRatio},
|
||||
{"r", PropertyID::R},
|
||||
{"refX", PropertyID::RefX},
|
||||
{"refY", PropertyID::RefY},
|
||||
{"rotate", PropertyID::Rotate},
|
||||
{"rx", PropertyID::Rx},
|
||||
{"ry", PropertyID::Ry},
|
||||
{"spreadMethod", PropertyID::SpreadMethod},
|
||||
{"style", PropertyID::Style},
|
||||
{"textLength", PropertyID::TextLength},
|
||||
{"transform", PropertyID::Transform},
|
||||
{"viewBox", PropertyID::ViewBox},
|
||||
{"width", PropertyID::Width},
|
||||
{"x", PropertyID::X},
|
||||
{"x1", PropertyID::X1},
|
||||
{"x2", PropertyID::X2},
|
||||
{"xlink:href", PropertyID::Href},
|
||||
{"xml:space", PropertyID::White_Space},
|
||||
{"y", PropertyID::Y},
|
||||
{"y1", PropertyID::Y1},
|
||||
{"y2", PropertyID::Y2}
|
||||
};
|
||||
|
||||
auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; });
|
||||
if(it == std::end(table) || it->name != name)
|
||||
return csspropertyid(name);
|
||||
return it->value;
|
||||
}
|
||||
|
||||
PropertyID csspropertyid(std::string_view name)
|
||||
{
|
||||
static const struct {
|
||||
std::string_view name;
|
||||
PropertyID value;
|
||||
} table[] = {
|
||||
{"alignment-baseline", PropertyID::Alignment_Baseline},
|
||||
{"baseline-shift", PropertyID::Baseline_Shift},
|
||||
{"clip-path", PropertyID::Clip_Path},
|
||||
{"clip-rule", PropertyID::Clip_Rule},
|
||||
{"color", PropertyID::Color},
|
||||
{"direction", PropertyID::Direction},
|
||||
{"display", PropertyID::Display},
|
||||
{"dominant-baseline", PropertyID::Dominant_Baseline},
|
||||
{"fill", PropertyID::Fill},
|
||||
{"fill-opacity", PropertyID::Fill_Opacity},
|
||||
{"fill-rule", PropertyID::Fill_Rule},
|
||||
{"font-family", PropertyID::Font_Family},
|
||||
{"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},
|
||||
{"mask", PropertyID::Mask},
|
||||
{"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},
|
||||
{"stroke-dasharray", PropertyID::Stroke_Dasharray},
|
||||
{"stroke-dashoffset", PropertyID::Stroke_Dashoffset},
|
||||
{"stroke-linecap", PropertyID::Stroke_Linecap},
|
||||
{"stroke-linejoin", PropertyID::Stroke_Linejoin},
|
||||
{"stroke-miterlimit", PropertyID::Stroke_Miterlimit},
|
||||
{"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::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; });
|
||||
if(it == std::end(table) || it->name != name)
|
||||
return PropertyID::Unknown;
|
||||
return it->value;
|
||||
}
|
||||
|
||||
SVGProperty::SVGProperty(PropertyID id)
|
||||
: m_id(id)
|
||||
{
|
||||
}
|
||||
|
||||
bool SVGString::parse(std::string_view input)
|
||||
{
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
m_value.assign(input);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool SVGEnumeration<SpreadMethod>::parse(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<SpreadMethod> entries[] = {
|
||||
{SpreadMethod::Pad, "pad"},
|
||||
{SpreadMethod::Reflect, "reflect"},
|
||||
{SpreadMethod::Repeat, "repeat"}
|
||||
};
|
||||
|
||||
return parseEnum(input, entries);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool SVGEnumeration<Units>::parse(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<Units> entries[] = {
|
||||
{Units::UserSpaceOnUse, "userSpaceOnUse"},
|
||||
{Units::ObjectBoundingBox, "objectBoundingBox"}
|
||||
};
|
||||
|
||||
return parseEnum(input, entries);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool SVGEnumeration<MarkerUnits>::parse(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<MarkerUnits> entries[] = {
|
||||
{MarkerUnits::StrokeWidth, "strokeWidth"},
|
||||
{MarkerUnits::UserSpaceOnUse, "userSpaceOnUse"}
|
||||
};
|
||||
|
||||
return parseEnum(input, entries);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool SVGEnumeration<LengthAdjust>::parse(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<LengthAdjust> entries[] = {
|
||||
{LengthAdjust::Spacing, "spacing"},
|
||||
{LengthAdjust::SpacingAndGlyphs, "spacingAndGlyphs"}
|
||||
};
|
||||
|
||||
return parseEnum(input, entries);
|
||||
}
|
||||
|
||||
template<typename Enum>
|
||||
template<unsigned int N>
|
||||
bool SVGEnumeration<Enum>::parseEnum(std::string_view input, const SVGEnumerationEntry<Enum>(&entries)[N])
|
||||
{
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
for(const auto& entry : entries) {
|
||||
if(input == entry.second) {
|
||||
m_value = entry.first;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SVGAngle::parse(std::string_view input)
|
||||
{
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(input == "auto") {
|
||||
m_value = 0.f;
|
||||
m_orientType = OrientType::Auto;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(input == "auto-start-reverse") {
|
||||
m_value = 0.f;
|
||||
m_orientType = OrientType::AutoStartReverse;
|
||||
return true;
|
||||
}
|
||||
|
||||
float value = 0.f;
|
||||
if(!parseNumber(input, value))
|
||||
return false;
|
||||
if(!input.empty()) {
|
||||
if(input == "rad")
|
||||
value *= 180.f / PLUTOVG_PI;
|
||||
else if(input == "grad")
|
||||
value *= 360.f / 400.f;
|
||||
else if(input == "turn")
|
||||
value *= 360.f;
|
||||
else if(input != "deg") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_value = value;
|
||||
m_orientType = OrientType::Angle;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Length::parse(std::string_view input, LengthNegativeMode mode)
|
||||
{
|
||||
float value = 0.f;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseNumber(input, value))
|
||||
return false;
|
||||
if(value < 0.f && mode == LengthNegativeMode::Forbid)
|
||||
return false;
|
||||
if(input.empty()) {
|
||||
m_value = value;
|
||||
m_units = LengthUnits::None;
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr auto dpi = 96.f;
|
||||
switch(input.front()) {
|
||||
case '%':
|
||||
m_value = value;
|
||||
m_units = LengthUnits::Percent;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
case 'p':
|
||||
input.remove_prefix(1);
|
||||
if(input.empty())
|
||||
return false;
|
||||
else if(input.front() == 'x')
|
||||
m_value = value;
|
||||
else if(input.front() == 'c')
|
||||
m_value = value * dpi / 6.f;
|
||||
else if(input.front() == 't')
|
||||
m_value = value * dpi / 72.f;
|
||||
else
|
||||
return false;
|
||||
m_units = LengthUnits::Px;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
case 'i':
|
||||
input.remove_prefix(1);
|
||||
if(input.empty())
|
||||
return false;
|
||||
else if(input.front() == 'n')
|
||||
m_value = value * dpi;
|
||||
else
|
||||
return false;
|
||||
m_units = LengthUnits::Px;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
case 'c':
|
||||
input.remove_prefix(1);
|
||||
if(input.empty())
|
||||
return false;
|
||||
else if(input.front() == 'm')
|
||||
m_value = value * dpi / 2.54f;
|
||||
else
|
||||
return false;
|
||||
m_units = LengthUnits::Px;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
case 'm':
|
||||
input.remove_prefix(1);
|
||||
if(input.empty())
|
||||
return false;
|
||||
else if(input.front() == 'm')
|
||||
m_value = value * dpi / 25.4f;
|
||||
else
|
||||
return false;
|
||||
m_units = LengthUnits::Px;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
case 'e':
|
||||
input.remove_prefix(1);
|
||||
if(input.empty())
|
||||
return false;
|
||||
else if(input.front() == 'm')
|
||||
m_units = LengthUnits::Em;
|
||||
else if(input.front() == 'x')
|
||||
m_units = LengthUnits::Ex;
|
||||
else
|
||||
return false;
|
||||
m_value = value;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return input.empty();
|
||||
}
|
||||
|
||||
float LengthContext::valueForLength(const Length& length, LengthDirection direction) const
|
||||
{
|
||||
if(length.units() == LengthUnits::Percent) {
|
||||
if(m_units == Units::UserSpaceOnUse)
|
||||
return length.value() * viewportDimension(direction) / 100.f;
|
||||
return length.value() / 100.f;
|
||||
}
|
||||
|
||||
if(length.units() == LengthUnits::Ex)
|
||||
return length.value() * m_element->font_size() / 2.f;
|
||||
if(length.units() == LengthUnits::Em)
|
||||
return length.value() * m_element->font_size();
|
||||
return length.value();
|
||||
}
|
||||
|
||||
float LengthContext::viewportDimension(LengthDirection direction) const
|
||||
{
|
||||
auto viewportSize = m_element->currentViewportSize();
|
||||
switch(direction) {
|
||||
case LengthDirection::Horizontal:
|
||||
return viewportSize.w;
|
||||
case LengthDirection::Vertical:
|
||||
return viewportSize.h;
|
||||
default:
|
||||
return std::sqrt(viewportSize.w * viewportSize.w + viewportSize.h * viewportSize.h) / PLUTOVG_SQRT2;
|
||||
}
|
||||
}
|
||||
|
||||
bool SVGLength::parse(std::string_view input)
|
||||
{
|
||||
return m_value.parse(input, m_negativeMode);
|
||||
}
|
||||
|
||||
bool SVGLengthList::parse(std::string_view input)
|
||||
{
|
||||
m_values.clear();
|
||||
while(!input.empty()) {
|
||||
size_t count = 0;
|
||||
while(count < input.length() && input[count] != ',' && !IS_WS(input[count]))
|
||||
++count;
|
||||
if(count == 0)
|
||||
break;
|
||||
Length value(0, LengthUnits::None);
|
||||
if(!value.parse(input.substr(0, count), m_negativeMode))
|
||||
return false;
|
||||
input.remove_prefix(count);
|
||||
skipOptionalSpacesOrComma(input);
|
||||
m_values.push_back(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGNumber::parse(std::string_view input)
|
||||
{
|
||||
float value = 0.f;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseNumber(input, value))
|
||||
return false;
|
||||
if(!input.empty())
|
||||
return false;
|
||||
m_value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGNumberPercentage::parse(std::string_view input)
|
||||
{
|
||||
float value = 0.f;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseNumber(input, value))
|
||||
return false;
|
||||
if(!input.empty() && input.front() == '%') {
|
||||
value /= 100.f;
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
if(!input.empty())
|
||||
return false;
|
||||
m_value = std::clamp(value, 0.f, 1.f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGNumberList::parse(std::string_view input)
|
||||
{
|
||||
m_values.clear();
|
||||
stripLeadingSpaces(input);
|
||||
while(!input.empty()) {
|
||||
float value = 0.f;
|
||||
if(!parseNumber(input, value))
|
||||
return false;
|
||||
skipOptionalSpacesOrComma(input);
|
||||
m_values.push_back(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGPath::parse(std::string_view input)
|
||||
{
|
||||
return m_value.parse(input.data(), input.length());
|
||||
}
|
||||
|
||||
bool SVGPoint::parse(std::string_view input)
|
||||
{
|
||||
Point value;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseNumber(input, value.x)
|
||||
|| !skipOptionalSpaces(input)
|
||||
|| !parseNumber(input, value.y)
|
||||
|| !input.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGPointList::parse(std::string_view input)
|
||||
{
|
||||
m_values.clear();
|
||||
stripLeadingSpaces(input);
|
||||
while(!input.empty()) {
|
||||
Point value;
|
||||
if(!parseNumber(input, value.x)
|
||||
|| !skipOptionalSpacesOrComma(input)
|
||||
|| !parseNumber(input, value.y)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_values.push_back(value);
|
||||
skipOptionalSpacesOrComma(input);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGRect::parse(std::string_view input)
|
||||
{
|
||||
Rect value;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseNumber(input, value.x)
|
||||
|| !skipOptionalSpacesOrComma(input)
|
||||
|| !parseNumber(input, value.y)
|
||||
|| !skipOptionalSpacesOrComma(input)
|
||||
|| !parseNumber(input, value.w)
|
||||
|| !skipOptionalSpacesOrComma(input)
|
||||
|| !parseNumber(input, value.h)
|
||||
|| !input.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(value.w < 0.f || value.h < 0.f)
|
||||
return false;
|
||||
m_value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGTransform::parse(std::string_view input)
|
||||
{
|
||||
return m_value.parse(input.data(), input.length());
|
||||
}
|
||||
|
||||
bool SVGPreserveAspectRatio::parse(std::string_view input)
|
||||
{
|
||||
auto alignType = AlignType::xMidYMid;
|
||||
stripLeadingSpaces(input);
|
||||
if(skipString(input, "none"))
|
||||
alignType = AlignType::None;
|
||||
else if(skipString(input, "xMinYMin"))
|
||||
alignType = AlignType::xMinYMin;
|
||||
else if(skipString(input, "xMidYMin"))
|
||||
alignType = AlignType::xMidYMin;
|
||||
else if(skipString(input, "xMaxYMin"))
|
||||
alignType = AlignType::xMaxYMin;
|
||||
else if(skipString(input, "xMinYMid"))
|
||||
alignType = AlignType::xMinYMid;
|
||||
else if(skipString(input, "xMidYMid"))
|
||||
alignType = AlignType::xMidYMid;
|
||||
else if(skipString(input, "xMaxYMid"))
|
||||
alignType = AlignType::xMaxYMid;
|
||||
else if(skipString(input, "xMinYMax"))
|
||||
alignType = AlignType::xMinYMax;
|
||||
else if(skipString(input, "xMidYMax"))
|
||||
alignType = AlignType::xMidYMax;
|
||||
else if(skipString(input, "xMaxYMax"))
|
||||
alignType = AlignType::xMaxYMax;
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto meetOrSlice = MeetOrSlice::Meet;
|
||||
skipOptionalSpaces(input);
|
||||
if(skipString(input, "meet")) {
|
||||
meetOrSlice = MeetOrSlice::Meet;
|
||||
} else if(skipString(input, "slice")) {
|
||||
meetOrSlice = MeetOrSlice::Slice;
|
||||
}
|
||||
|
||||
if(alignType == AlignType::None)
|
||||
meetOrSlice = MeetOrSlice::Meet;
|
||||
skipOptionalSpaces(input);
|
||||
if(!input.empty())
|
||||
return false;
|
||||
m_alignType = alignType;
|
||||
m_meetOrSlice = meetOrSlice;
|
||||
return true;
|
||||
}
|
||||
|
||||
Rect SVGPreserveAspectRatio::getClipRect(const Rect& viewBoxRect, const Size& viewportSize) const
|
||||
{
|
||||
assert(!viewBoxRect.isEmpty() && !viewportSize.isEmpty());
|
||||
auto xScale = viewportSize.w / viewBoxRect.w;
|
||||
auto yScale = viewportSize.h / viewBoxRect.h;
|
||||
if(m_alignType == AlignType::None) {
|
||||
return Rect(viewBoxRect.x, viewBoxRect.y, viewportSize.w / xScale, viewportSize.h / yScale);
|
||||
}
|
||||
|
||||
auto scale = (m_meetOrSlice == MeetOrSlice::Meet) ? std::min(xScale, yScale) : std::max(xScale, yScale);
|
||||
auto xOffset = -viewBoxRect.x * scale;
|
||||
auto yOffset = -viewBoxRect.y * scale;
|
||||
auto viewWidth = viewBoxRect.w * scale;
|
||||
auto viewHeight = viewBoxRect.h * scale;
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMidYMin:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMidYMax:
|
||||
xOffset += (viewportSize.w - viewWidth) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMaxYMin:
|
||||
case AlignType::xMaxYMid:
|
||||
case AlignType::xMaxYMax:
|
||||
xOffset += (viewportSize.w - viewWidth);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMinYMid:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMaxYMid:
|
||||
yOffset += (viewportSize.h - viewHeight) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMinYMax:
|
||||
case AlignType::xMidYMax:
|
||||
case AlignType::xMaxYMax:
|
||||
yOffset += (viewportSize.h - viewHeight);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Rect(-xOffset / scale, -yOffset / scale, viewportSize.w / scale, viewportSize.h / scale);
|
||||
}
|
||||
|
||||
Transform SVGPreserveAspectRatio::getTransform(const Rect& viewBoxRect, const Size& viewportSize) const
|
||||
{
|
||||
assert(!viewBoxRect.isEmpty() && !viewportSize.isEmpty());
|
||||
auto xScale = viewportSize.w / viewBoxRect.w;
|
||||
auto yScale = viewportSize.h / viewBoxRect.h;
|
||||
if(m_alignType == AlignType::None) {
|
||||
return Transform(xScale, 0, 0, yScale, -viewBoxRect.x * xScale, -viewBoxRect.y * yScale);
|
||||
}
|
||||
|
||||
auto scale = (m_meetOrSlice == MeetOrSlice::Meet) ? std::min(xScale, yScale) : std::max(xScale, yScale);
|
||||
auto xOffset = -viewBoxRect.x * scale;
|
||||
auto yOffset = -viewBoxRect.y * scale;
|
||||
auto viewWidth = viewBoxRect.w * scale;
|
||||
auto viewHeight = viewBoxRect.h * scale;
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMidYMin:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMidYMax:
|
||||
xOffset += (viewportSize.w - viewWidth) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMaxYMin:
|
||||
case AlignType::xMaxYMid:
|
||||
case AlignType::xMaxYMax:
|
||||
xOffset += (viewportSize.w - viewWidth);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMinYMid:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMaxYMid:
|
||||
yOffset += (viewportSize.h - viewHeight) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMinYMax:
|
||||
case AlignType::xMidYMax:
|
||||
case AlignType::xMaxYMax:
|
||||
yOffset += (viewportSize.h - viewHeight);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Transform(scale, 0, 0, scale, xOffset, yOffset);
|
||||
}
|
||||
|
||||
void SVGPreserveAspectRatio::transformRect(Rect& dstRect, Rect& srcRect) const
|
||||
{
|
||||
if(m_alignType == AlignType::None)
|
||||
return;
|
||||
auto viewSize = dstRect.size();
|
||||
auto imageSize = srcRect.size();
|
||||
if(m_meetOrSlice == MeetOrSlice::Meet) {
|
||||
auto scale = imageSize.h / imageSize.w;
|
||||
if(viewSize.h > viewSize.w * scale) {
|
||||
dstRect.h = viewSize.w * scale;
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMinYMid:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMaxYMid:
|
||||
dstRect.y += (viewSize.h - dstRect.h) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMinYMax:
|
||||
case AlignType::xMidYMax:
|
||||
case AlignType::xMaxYMax:
|
||||
dstRect.y += viewSize.h - dstRect.h;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(viewSize.w > viewSize.h / scale) {
|
||||
dstRect.w = viewSize.h / scale;
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMidYMin:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMidYMax:
|
||||
dstRect.x += (viewSize.w - dstRect.w) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMaxYMin:
|
||||
case AlignType::xMaxYMid:
|
||||
case AlignType::xMaxYMax:
|
||||
dstRect.x += viewSize.w - dstRect.w;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(m_meetOrSlice == MeetOrSlice::Slice) {
|
||||
auto scale = imageSize.h / imageSize.w;
|
||||
if(viewSize.h < viewSize.w * scale) {
|
||||
srcRect.h = viewSize.h * (imageSize.w / viewSize.w);
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMinYMid:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMaxYMid:
|
||||
srcRect.y += (imageSize.h - srcRect.h) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMinYMax:
|
||||
case AlignType::xMidYMax:
|
||||
case AlignType::xMaxYMax:
|
||||
srcRect.y += imageSize.h - srcRect.h;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(viewSize.w < viewSize.h / scale) {
|
||||
srcRect.w = viewSize.w * (imageSize.h / viewSize.h);
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMidYMin:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMidYMax:
|
||||
srcRect.x += (imageSize.w - srcRect.w) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMaxYMin:
|
||||
case AlignType::xMaxYMid:
|
||||
case AlignType::xMaxYMax:
|
||||
srcRect.x += imageSize.w - srcRect.w;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
570
vendor/lunasvg/source/svgproperty.h
vendored
570
vendor/lunasvg/source/svgproperty.h
vendored
@@ -1,570 +0,0 @@
|
||||
#ifndef LUNASVG_SVGPROPERTY_H
|
||||
#define LUNASVG_SVGPROPERTY_H
|
||||
|
||||
#include "graphics.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
enum class PropertyID : uint8_t {
|
||||
Unknown = 0,
|
||||
Alignment_Baseline,
|
||||
Baseline_Shift,
|
||||
Class,
|
||||
Clip_Path,
|
||||
Clip_Rule,
|
||||
ClipPathUnits,
|
||||
Color,
|
||||
Cx,
|
||||
Cy,
|
||||
D,
|
||||
Direction,
|
||||
Display,
|
||||
Dominant_Baseline,
|
||||
Dx,
|
||||
Dy,
|
||||
Fill,
|
||||
Fill_Opacity,
|
||||
Fill_Rule,
|
||||
Font_Family,
|
||||
Font_Size,
|
||||
Font_Style,
|
||||
Font_Weight,
|
||||
Fx,
|
||||
Fy,
|
||||
GradientTransform,
|
||||
GradientUnits,
|
||||
Height,
|
||||
Href,
|
||||
Id,
|
||||
LengthAdjust,
|
||||
Letter_Spacing,
|
||||
Marker_End,
|
||||
Marker_Mid,
|
||||
Marker_Start,
|
||||
MarkerHeight,
|
||||
MarkerUnits,
|
||||
MarkerWidth,
|
||||
Mask,
|
||||
Mask_Type,
|
||||
MaskContentUnits,
|
||||
MaskUnits,
|
||||
Offset,
|
||||
Opacity,
|
||||
Orient,
|
||||
Overflow,
|
||||
PatternContentUnits,
|
||||
PatternTransform,
|
||||
PatternUnits,
|
||||
Pointer_Events,
|
||||
Points,
|
||||
PreserveAspectRatio,
|
||||
R,
|
||||
RefX,
|
||||
RefY,
|
||||
Rotate,
|
||||
Rx,
|
||||
Ry,
|
||||
SpreadMethod,
|
||||
Stop_Color,
|
||||
Stop_Opacity,
|
||||
Stroke,
|
||||
Stroke_Dasharray,
|
||||
Stroke_Dashoffset,
|
||||
Stroke_Linecap,
|
||||
Stroke_Linejoin,
|
||||
Stroke_Miterlimit,
|
||||
Stroke_Opacity,
|
||||
Stroke_Width,
|
||||
Style,
|
||||
Text_Anchor,
|
||||
Text_Orientation,
|
||||
TextLength,
|
||||
Transform,
|
||||
ViewBox,
|
||||
Visibility,
|
||||
White_Space,
|
||||
Width,
|
||||
Word_Spacing,
|
||||
Writing_Mode,
|
||||
X,
|
||||
X1,
|
||||
X2,
|
||||
Y,
|
||||
Y1,
|
||||
Y2
|
||||
};
|
||||
|
||||
PropertyID propertyid(std::string_view name);
|
||||
PropertyID csspropertyid(std::string_view name);
|
||||
|
||||
class SVGElement;
|
||||
|
||||
class SVGProperty {
|
||||
public:
|
||||
SVGProperty(PropertyID id);
|
||||
virtual ~SVGProperty() = default;
|
||||
PropertyID id() const { return m_id; }
|
||||
|
||||
virtual bool parse(std::string_view input) = 0;
|
||||
|
||||
private:
|
||||
SVGProperty(const SVGProperty&) = delete;
|
||||
SVGProperty& operator=(const SVGProperty&) = delete;
|
||||
PropertyID m_id;
|
||||
};
|
||||
|
||||
class SVGString final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGString(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const std::string& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
std::string m_value;
|
||||
};
|
||||
|
||||
class Paint {
|
||||
public:
|
||||
Paint() = default;
|
||||
explicit Paint(const Color& color) : m_color(color) {}
|
||||
Paint(const std::string& id, const Color& color)
|
||||
: m_id(id), m_color(color)
|
||||
{}
|
||||
|
||||
const Color& color() const { return m_color; }
|
||||
const std::string& id() const { return m_id; }
|
||||
bool isNone() const { return m_id.empty() && !m_color.isVisible(); }
|
||||
|
||||
private:
|
||||
std::string m_id;
|
||||
Color m_color = Color::Transparent;
|
||||
};
|
||||
|
||||
enum class Display : uint8_t {
|
||||
Inline,
|
||||
None
|
||||
};
|
||||
|
||||
enum class Visibility : uint8_t {
|
||||
Visible,
|
||||
Hidden,
|
||||
Collapse
|
||||
};
|
||||
|
||||
enum class Overflow : uint8_t {
|
||||
Visible,
|
||||
Hidden
|
||||
};
|
||||
|
||||
enum class PointerEvents : uint8_t {
|
||||
None,
|
||||
Auto,
|
||||
Stroke,
|
||||
Fill,
|
||||
Painted,
|
||||
Visible,
|
||||
VisibleStroke,
|
||||
VisibleFill,
|
||||
VisiblePainted,
|
||||
BoundingBox,
|
||||
All
|
||||
};
|
||||
|
||||
enum class FontStyle : uint8_t {
|
||||
Normal,
|
||||
Italic
|
||||
};
|
||||
|
||||
enum class FontWeight : uint8_t {
|
||||
Normal,
|
||||
Bold
|
||||
};
|
||||
|
||||
enum class AlignmentBaseline : uint8_t {
|
||||
Auto,
|
||||
Baseline,
|
||||
BeforeEdge,
|
||||
TextBeforeEdge,
|
||||
Middle,
|
||||
Central,
|
||||
AfterEdge,
|
||||
TextAfterEdge,
|
||||
Ideographic,
|
||||
Alphabetic,
|
||||
Hanging,
|
||||
Mathematical
|
||||
};
|
||||
|
||||
enum class DominantBaseline : uint8_t {
|
||||
Auto,
|
||||
UseScript,
|
||||
NoChange,
|
||||
ResetSize,
|
||||
Ideographic,
|
||||
Alphabetic,
|
||||
Hanging,
|
||||
Mathematical,
|
||||
Central,
|
||||
Middle,
|
||||
TextAfterEdge,
|
||||
TextBeforeEdge
|
||||
};
|
||||
|
||||
enum class TextAnchor : uint8_t {
|
||||
Start,
|
||||
Middle,
|
||||
End
|
||||
};
|
||||
|
||||
enum class WhiteSpace : uint8_t {
|
||||
Default,
|
||||
Preserve
|
||||
};
|
||||
|
||||
enum class WritingMode : uint8_t {
|
||||
Horizontal,
|
||||
Vertical
|
||||
};
|
||||
|
||||
enum class TextOrientation : uint8_t {
|
||||
Mixed,
|
||||
Upright
|
||||
};
|
||||
|
||||
enum class Direction : uint8_t {
|
||||
Ltr,
|
||||
Rtl
|
||||
};
|
||||
|
||||
enum class MaskType : uint8_t {
|
||||
Luminance,
|
||||
Alpha
|
||||
};
|
||||
|
||||
enum class Units : uint8_t {
|
||||
UserSpaceOnUse,
|
||||
ObjectBoundingBox
|
||||
};
|
||||
|
||||
enum class MarkerUnits : uint8_t {
|
||||
StrokeWidth,
|
||||
UserSpaceOnUse
|
||||
};
|
||||
|
||||
enum class LengthAdjust : uint8_t {
|
||||
Spacing,
|
||||
SpacingAndGlyphs
|
||||
};
|
||||
|
||||
template<typename Enum>
|
||||
using SVGEnumerationEntry = std::pair<Enum, std::string_view>;
|
||||
|
||||
template<typename Enum>
|
||||
class SVGEnumeration final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGEnumeration(PropertyID id, Enum value)
|
||||
: SVGProperty(id)
|
||||
, m_value(value)
|
||||
{}
|
||||
|
||||
Enum value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
template<unsigned int N>
|
||||
bool parseEnum(std::string_view input, const SVGEnumerationEntry<Enum>(&entries)[N]);
|
||||
Enum m_value;
|
||||
};
|
||||
|
||||
class SVGAngle final : public SVGProperty {
|
||||
public:
|
||||
enum class OrientType {
|
||||
Auto,
|
||||
AutoStartReverse,
|
||||
Angle
|
||||
};
|
||||
|
||||
explicit SVGAngle(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
float value() const { return m_value; }
|
||||
OrientType orientType() const { return m_orientType; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
float m_value = 0;
|
||||
OrientType m_orientType = OrientType::Angle;
|
||||
};
|
||||
|
||||
enum class LengthUnits : uint8_t {
|
||||
None,
|
||||
Percent,
|
||||
Px,
|
||||
Em,
|
||||
Ex
|
||||
};
|
||||
|
||||
enum class LengthDirection : uint8_t {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Diagonal
|
||||
};
|
||||
|
||||
enum class LengthNegativeMode : uint8_t {
|
||||
Allow,
|
||||
Forbid
|
||||
};
|
||||
|
||||
class Length {
|
||||
public:
|
||||
Length() = default;
|
||||
Length(float value, LengthUnits units)
|
||||
: m_value(value), m_units(units)
|
||||
{}
|
||||
|
||||
float value() const { return m_value; }
|
||||
LengthUnits units() const { return m_units; }
|
||||
|
||||
bool parse(std::string_view input, LengthNegativeMode mode);
|
||||
|
||||
private:
|
||||
float m_value = 0.f;
|
||||
LengthUnits m_units = LengthUnits::None;
|
||||
};
|
||||
|
||||
class SVGLength final : public SVGProperty {
|
||||
public:
|
||||
SVGLength(PropertyID id, LengthDirection direction, LengthNegativeMode negativeMode, float value = 0, LengthUnits units = LengthUnits::None)
|
||||
: SVGProperty(id)
|
||||
, m_direction(direction)
|
||||
, m_negativeMode(negativeMode)
|
||||
, m_value(value, units)
|
||||
{}
|
||||
|
||||
bool isPercent() const { return m_value.units() == LengthUnits::Percent; }
|
||||
|
||||
LengthDirection direction() const { return m_direction; }
|
||||
LengthNegativeMode negativeMode() const { return m_negativeMode; }
|
||||
const Length& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
const LengthDirection m_direction;
|
||||
const LengthNegativeMode m_negativeMode;
|
||||
Length m_value;
|
||||
};
|
||||
|
||||
class LengthContext {
|
||||
public:
|
||||
LengthContext(const SVGElement* element, Units units = Units::UserSpaceOnUse)
|
||||
: m_element(element), m_units(units)
|
||||
{}
|
||||
|
||||
float valueForLength(const Length& length, LengthDirection direction) const;
|
||||
float valueForLength(const SVGLength& length) const { return valueForLength(length.value(), length.direction()); }
|
||||
|
||||
private:
|
||||
float viewportDimension(LengthDirection direction) const;
|
||||
const SVGElement* m_element;
|
||||
const Units m_units;
|
||||
};
|
||||
|
||||
using LengthList = std::vector<Length>;
|
||||
|
||||
class SVGLengthList final : public SVGProperty {
|
||||
public:
|
||||
SVGLengthList(PropertyID id, LengthDirection direction, LengthNegativeMode negativeMode)
|
||||
: SVGProperty(id)
|
||||
, m_direction(direction)
|
||||
, m_negativeMode(negativeMode)
|
||||
{}
|
||||
|
||||
LengthDirection direction() const { return m_direction; }
|
||||
LengthNegativeMode negativeMode() const { return m_negativeMode; }
|
||||
const LengthList& values() const { return m_values; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
const LengthDirection m_direction;
|
||||
const LengthNegativeMode m_negativeMode;
|
||||
LengthList m_values;
|
||||
};
|
||||
|
||||
class BaselineShift {
|
||||
public:
|
||||
enum class Type {
|
||||
Baseline,
|
||||
Sub,
|
||||
Super,
|
||||
Length
|
||||
};
|
||||
|
||||
BaselineShift() = default;
|
||||
BaselineShift(Type type) : m_type(type) {}
|
||||
BaselineShift(const Length& length) : m_type(Type::Length), m_length(length) {}
|
||||
|
||||
Type type() const { return m_type; }
|
||||
const Length& length() const { return m_length; }
|
||||
|
||||
private:
|
||||
Type m_type{Type::Baseline};
|
||||
Length m_length;
|
||||
};
|
||||
|
||||
class SVGNumber : public SVGProperty {
|
||||
public:
|
||||
SVGNumber(PropertyID id, float value)
|
||||
: SVGProperty(id)
|
||||
, m_value(value)
|
||||
{}
|
||||
|
||||
float value() const { return m_value; }
|
||||
bool parse(std::string_view input) override;
|
||||
|
||||
private:
|
||||
float m_value;
|
||||
};
|
||||
|
||||
class SVGNumberPercentage final : public SVGProperty {
|
||||
public:
|
||||
SVGNumberPercentage(PropertyID id, float value)
|
||||
: SVGProperty(id)
|
||||
, m_value(value)
|
||||
{}
|
||||
|
||||
float value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
float m_value;
|
||||
};
|
||||
|
||||
using NumberList = std::vector<float>;
|
||||
|
||||
class SVGNumberList final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGNumberList(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const NumberList& values() const { return m_values; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
NumberList m_values;
|
||||
};
|
||||
|
||||
class SVGPath final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGPath(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const Path& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
Path m_value;
|
||||
};
|
||||
|
||||
class SVGPoint final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGPoint(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const Point& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
Point m_value;
|
||||
};
|
||||
|
||||
using PointList = std::vector<Point>;
|
||||
|
||||
class SVGPointList final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGPointList(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const PointList& values() const { return m_values; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
PointList m_values;
|
||||
};
|
||||
|
||||
class SVGRect final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGRect(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
, m_value(Rect::Invalid)
|
||||
{}
|
||||
|
||||
const Rect& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
Rect m_value;
|
||||
};
|
||||
|
||||
class SVGTransform final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGTransform(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const Transform& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
Transform m_value;
|
||||
};
|
||||
|
||||
class SVGPreserveAspectRatio final : public SVGProperty {
|
||||
public:
|
||||
enum class AlignType {
|
||||
None,
|
||||
xMinYMin,
|
||||
xMidYMin,
|
||||
xMaxYMin,
|
||||
xMinYMid,
|
||||
xMidYMid,
|
||||
xMaxYMid,
|
||||
xMinYMax,
|
||||
xMidYMax,
|
||||
xMaxYMax
|
||||
};
|
||||
|
||||
enum class MeetOrSlice {
|
||||
Meet,
|
||||
Slice
|
||||
};
|
||||
|
||||
explicit SVGPreserveAspectRatio(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
AlignType alignType() const { return m_alignType; }
|
||||
MeetOrSlice meetOrSlice() const { return m_meetOrSlice; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
Rect getClipRect(const Rect& viewBoxRect, const Size& viewportSize) const;
|
||||
Transform getTransform(const Rect& viewBoxRect, const Size& viewportSize) const;
|
||||
void transformRect(Rect& dstRect, Rect& srcRect) const;
|
||||
|
||||
private:
|
||||
AlignType m_alignType = AlignType::xMidYMid;
|
||||
MeetOrSlice m_meetOrSlice = MeetOrSlice::Meet;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGPROPERTY_H
|
||||
61
vendor/lunasvg/source/svgrenderstate.cpp
vendored
61
vendor/lunasvg/source/svgrenderstate.cpp
vendored
@@ -1,61 +0,0 @@
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
SVGBlendInfo::SVGBlendInfo(const SVGElement* element)
|
||||
: m_clipper(element->clipper())
|
||||
, m_masker(element->masker())
|
||||
, m_opacity(element->opacity())
|
||||
{
|
||||
}
|
||||
|
||||
bool SVGBlendInfo::requiresCompositing(SVGRenderMode mode) const
|
||||
{
|
||||
return (m_clipper && m_clipper->requiresMasking()) || (mode == SVGRenderMode::Painting && (m_masker || m_opacity < 1.f));
|
||||
}
|
||||
|
||||
bool SVGRenderState::hasCycleReference(const SVGElement* element) const
|
||||
{
|
||||
auto current = this;
|
||||
do {
|
||||
if(element == current->element())
|
||||
return true;
|
||||
current = current->parent();
|
||||
} while(current);
|
||||
return false;
|
||||
}
|
||||
|
||||
void SVGRenderState::beginGroup(const SVGBlendInfo& blendInfo)
|
||||
{
|
||||
auto requiresCompositing = blendInfo.requiresCompositing(m_mode);
|
||||
if(requiresCompositing) {
|
||||
auto boundingBox = m_currentTransform.mapRect(m_element->paintBoundingBox());
|
||||
boundingBox.intersect(m_canvas->extents());
|
||||
m_canvas = Canvas::create(boundingBox);
|
||||
} else {
|
||||
m_canvas->save();
|
||||
}
|
||||
|
||||
if(!requiresCompositing && blendInfo.clipper()) {
|
||||
blendInfo.clipper()->applyClipPath(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void SVGRenderState::endGroup(const SVGBlendInfo& blendInfo)
|
||||
{
|
||||
if(m_canvas == m_parent->canvas()) {
|
||||
m_canvas->restore();
|
||||
return;
|
||||
}
|
||||
|
||||
auto opacity = m_mode == SVGRenderMode::Clipping ? 1.f : blendInfo.opacity();
|
||||
if(blendInfo.clipper())
|
||||
blendInfo.clipper()->applyClipMask(*this);
|
||||
if(m_mode == SVGRenderMode::Painting && blendInfo.masker()) {
|
||||
blendInfo.masker()->applyMask(*this);
|
||||
}
|
||||
|
||||
m_parent->m_canvas->blendCanvas(*m_canvas, BlendMode::Src_Over, opacity);
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
69
vendor/lunasvg/source/svgrenderstate.h
vendored
69
vendor/lunasvg/source/svgrenderstate.h
vendored
@@ -1,69 +0,0 @@
|
||||
#ifndef LUNASVG_SVGRENDERSTATE_H
|
||||
#define LUNASVG_SVGRENDERSTATE_H
|
||||
|
||||
#include "svgelement.h"
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
enum class SVGRenderMode {
|
||||
Painting,
|
||||
Clipping
|
||||
};
|
||||
|
||||
class SVGBlendInfo {
|
||||
public:
|
||||
explicit SVGBlendInfo(const SVGElement* element);
|
||||
SVGBlendInfo(const SVGClipPathElement* clipper, const SVGMaskElement* masker, float opacity)
|
||||
: m_clipper(clipper), m_masker(masker), m_opacity(opacity)
|
||||
{}
|
||||
|
||||
bool requiresCompositing(SVGRenderMode mode) const;
|
||||
const SVGClipPathElement* clipper() const { return m_clipper; }
|
||||
const SVGMaskElement* masker() const { return m_masker; }
|
||||
float opacity() const { return m_opacity; }
|
||||
|
||||
private:
|
||||
const SVGClipPathElement* m_clipper;
|
||||
const SVGMaskElement* m_masker;
|
||||
const float m_opacity;
|
||||
};
|
||||
|
||||
class SVGRenderState {
|
||||
public:
|
||||
SVGRenderState(const SVGElement* element, const SVGRenderState& parent, const Transform& localTransform)
|
||||
: m_element(element), m_parent(&parent), m_currentTransform(parent.currentTransform() * localTransform)
|
||||
, m_mode(parent.mode()), m_canvas(parent.canvas())
|
||||
{}
|
||||
|
||||
SVGRenderState(const SVGElement* element, const SVGRenderState* parent, const Transform& currentTransform, SVGRenderMode mode, std::shared_ptr<Canvas> canvas)
|
||||
: m_element(element), m_parent(parent), m_currentTransform(currentTransform), m_mode(mode), m_canvas(std::move(canvas))
|
||||
{}
|
||||
|
||||
Canvas& operator*() const { return *m_canvas; }
|
||||
Canvas* operator->() const { return &*m_canvas; }
|
||||
|
||||
const SVGElement* element() const { return m_element; }
|
||||
const SVGRenderState* parent() const { return m_parent; }
|
||||
const Transform& currentTransform() const { return m_currentTransform; }
|
||||
const SVGRenderMode mode() const { return m_mode; }
|
||||
const std::shared_ptr<Canvas>& canvas() const { return m_canvas; }
|
||||
|
||||
Rect fillBoundingBox() const { return m_element->fillBoundingBox(); }
|
||||
Rect paintBoundingBox() const { return m_element->paintBoundingBox(); }
|
||||
|
||||
bool hasCycleReference(const SVGElement* element) const;
|
||||
|
||||
void beginGroup(const SVGBlendInfo& blendInfo);
|
||||
void endGroup(const SVGBlendInfo& blendInfo);
|
||||
|
||||
private:
|
||||
const SVGElement* m_element;
|
||||
const SVGRenderState* m_parent;
|
||||
const Transform m_currentTransform;
|
||||
const SVGRenderMode m_mode;
|
||||
std::shared_ptr<Canvas> m_canvas;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGRENDERSTATE_H
|
||||
579
vendor/lunasvg/source/svgtextelement.cpp
vendored
579
vendor/lunasvg/source/svgtextelement.cpp
vendored
@@ -1,579 +0,0 @@
|
||||
#include "svgtextelement.h"
|
||||
#include "svglayoutstate.h"
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
inline const SVGTextNode* toSVGTextNode(const SVGNode* node)
|
||||
{
|
||||
assert(node && node->isTextNode());
|
||||
return static_cast<const SVGTextNode*>(node);
|
||||
}
|
||||
|
||||
inline const SVGTextPositioningElement* toSVGTextPositioningElement(const SVGNode* node)
|
||||
{
|
||||
assert(node && node->isTextPositioningElement());
|
||||
return static_cast<const SVGTextPositioningElement*>(node);
|
||||
}
|
||||
|
||||
static AlignmentBaseline resolveDominantBaseline(const SVGTextPositioningElement* element)
|
||||
{
|
||||
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:
|
||||
return AlignmentBaseline::Auto;
|
||||
case DominantBaseline::Ideographic:
|
||||
return AlignmentBaseline::Ideographic;
|
||||
case DominantBaseline::Alphabetic:
|
||||
return AlignmentBaseline::Alphabetic;
|
||||
case DominantBaseline::Hanging:
|
||||
return AlignmentBaseline::Hanging;
|
||||
case DominantBaseline::Mathematical:
|
||||
return AlignmentBaseline::Mathematical;
|
||||
case DominantBaseline::Central:
|
||||
return AlignmentBaseline::Central;
|
||||
case DominantBaseline::Middle:
|
||||
return AlignmentBaseline::Middle;
|
||||
case DominantBaseline::TextAfterEdge:
|
||||
return AlignmentBaseline::TextAfterEdge;
|
||||
case DominantBaseline::TextBeforeEdge:
|
||||
return AlignmentBaseline::TextBeforeEdge;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return AlignmentBaseline::Auto;
|
||||
}
|
||||
|
||||
static float calculateBaselineOffset(const SVGTextPositioningElement* element)
|
||||
{
|
||||
auto offset = element->baseline_offset();
|
||||
auto parent = element->parentElement();
|
||||
while(parent->isTextPositioningElement()) {
|
||||
offset += toSVGTextPositioningElement(parent)->baseline_offset();
|
||||
parent = parent->parentElement();
|
||||
}
|
||||
|
||||
auto baseline = element->alignment_baseline();
|
||||
if(baseline == AlignmentBaseline::Auto || baseline == AlignmentBaseline::Baseline) {
|
||||
baseline = resolveDominantBaseline(element);
|
||||
}
|
||||
|
||||
const auto& font = element->font();
|
||||
switch(baseline) {
|
||||
case AlignmentBaseline::BeforeEdge:
|
||||
case AlignmentBaseline::TextBeforeEdge:
|
||||
offset -= font.ascent();
|
||||
break;
|
||||
case AlignmentBaseline::Middle:
|
||||
offset -= font.xHeight() / 2.f;
|
||||
break;
|
||||
case AlignmentBaseline::Central:
|
||||
offset -= (font.ascent() + font.descent()) / 2.f;
|
||||
break;
|
||||
case AlignmentBaseline::AfterEdge:
|
||||
case AlignmentBaseline::TextAfterEdge:
|
||||
case AlignmentBaseline::Ideographic:
|
||||
offset -= font.descent();
|
||||
break;
|
||||
case AlignmentBaseline::Hanging:
|
||||
offset -= font.ascent() * 8.f / 10.f;
|
||||
break;
|
||||
case AlignmentBaseline::Mathematical:
|
||||
offset -= font.ascent() / 2.f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static bool needsTextAnchorAdjustment(const SVGTextPositioningElement* element)
|
||||
{
|
||||
auto direction = element->direction();
|
||||
switch(element->text_anchor()) {
|
||||
case TextAnchor::Start:
|
||||
return direction == Direction::Rtl;
|
||||
case TextAnchor::Middle:
|
||||
return true;
|
||||
case TextAnchor::End:
|
||||
return direction == Direction::Ltr;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static float calculateTextAnchorOffset(const SVGTextPositioningElement* element, float width)
|
||||
{
|
||||
auto direction = element->direction();
|
||||
switch(element->text_anchor()) {
|
||||
case TextAnchor::Start:
|
||||
if(direction == Direction::Ltr)
|
||||
return 0.f;
|
||||
return -width;
|
||||
case TextAnchor::Middle:
|
||||
return -width / 2.f;
|
||||
case TextAnchor::End:
|
||||
if(direction == Direction::Ltr)
|
||||
return -width;
|
||||
return 0.f;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
m_text.clear();
|
||||
m_fragments.clear();
|
||||
}
|
||||
|
||||
void SVGTextFragmentsBuilder::build(const SVGTextElement* textElement)
|
||||
{
|
||||
handleElement(textElement);
|
||||
for(const auto& position : m_textPositions) {
|
||||
fillCharacterPositions(position);
|
||||
}
|
||||
|
||||
std::u32string_view wholeText(m_text);
|
||||
for(const auto& textPosition : m_textPositions) {
|
||||
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 = 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);
|
||||
};
|
||||
|
||||
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(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 = needsTextLengthSpacing || isVerticalText || applySpacingToNextCharacter
|
||||
|| characterPosition.x || characterPosition.y || dx || dy || angle || angle != lastAngle;
|
||||
if(shouldStartNewFragment && didStartTextFragment) {
|
||||
recordTextFragment(startOffset, textOffset);
|
||||
applySpacingToNextCharacter = false;
|
||||
startOffset = textOffset;
|
||||
}
|
||||
|
||||
auto startsNewTextChunk = (characterPosition.x || characterPosition.y) && textOffset == textPosition.startOffset;
|
||||
if(startsNewTextChunk || shouldStartNewFragment || !didStartTextFragment) {
|
||||
m_x = dx + characterPosition.x.value_or(m_x);
|
||||
m_y = dy + characterPosition.y.value_or(m_y);
|
||||
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;
|
||||
}
|
||||
|
||||
recordTextFragment(startOffset, textOffset);
|
||||
}
|
||||
|
||||
if(m_fragments.empty())
|
||||
return;
|
||||
auto it = m_fragments.begin();
|
||||
auto begin = m_fragments.begin();
|
||||
auto end = m_fragments.end();
|
||||
for(++it; it != end; ++it) {
|
||||
const SVGTextFragment& fragment = *it;
|
||||
if(!fragment.startsNewTextChunk)
|
||||
continue;
|
||||
handleTextChunk(begin, it);
|
||||
begin = it;
|
||||
}
|
||||
|
||||
handleTextChunk(begin, it);
|
||||
}
|
||||
|
||||
void SVGTextFragmentsBuilder::handleText(const SVGTextNode* node)
|
||||
{
|
||||
const auto& text = node->data();
|
||||
if(text.empty())
|
||||
return;
|
||||
auto element = toSVGTextPositioningElement(node->parentElement());
|
||||
const auto startOffset = m_text.length();
|
||||
uint32_t lastCharacter = ' ';
|
||||
if(!m_text.empty()) {
|
||||
lastCharacter = m_text.back();
|
||||
}
|
||||
|
||||
plutovg_text_iterator_t it;
|
||||
plutovg_text_iterator_init(&it, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF8);
|
||||
while(plutovg_text_iterator_has_next(&it)) {
|
||||
auto currentCharacter = plutovg_text_iterator_next(&it);
|
||||
if(currentCharacter == '\t' || currentCharacter == '\n' || currentCharacter == '\r')
|
||||
currentCharacter = ' ';
|
||||
if(currentCharacter == ' ' && lastCharacter == ' ' && element->white_space() == WhiteSpace::Default)
|
||||
continue;
|
||||
m_text.push_back(currentCharacter);
|
||||
lastCharacter = currentCharacter;
|
||||
}
|
||||
|
||||
if(startOffset < m_text.length()) {
|
||||
m_textPositions.emplace_back(node, startOffset, m_text.length());
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
if(child->isTextNode()) {
|
||||
handleText(toSVGTextNode(child.get()));
|
||||
} else if(child->isTextPositioningElement()) {
|
||||
handleElement(toSVGTextPositioningElement(child.get()));
|
||||
}
|
||||
}
|
||||
|
||||
auto& position = m_textPositions[itemIndex];
|
||||
assert(position.node == element);
|
||||
position.endOffset = m_text.length();
|
||||
}
|
||||
|
||||
void SVGTextFragmentsBuilder::fillCharacterPositions(const SVGTextPosition& position)
|
||||
{
|
||||
if(!position.node->isTextPositioningElement())
|
||||
return;
|
||||
auto element = toSVGTextPositioningElement(position.node);
|
||||
const auto& xList = element->x();
|
||||
const auto& yList = element->y();
|
||||
const auto& dxList = element->dx();
|
||||
const auto& dyList = element->dy();
|
||||
const auto& rotateList = element->rotate();
|
||||
|
||||
auto xListSize = xList.size();
|
||||
auto yListSize = yList.size();
|
||||
auto dxListSize = dxList.size();
|
||||
auto dyListSize = dyList.size();
|
||||
auto rotateListSize = rotateList.size();
|
||||
if(!xListSize && !yListSize && !dxListSize && !dyListSize && !rotateListSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
LengthContext lengthContext(element);
|
||||
std::optional<float> lastRotation;
|
||||
for(auto offset = position.startOffset; offset < position.endOffset; ++offset) {
|
||||
auto index = offset - position.startOffset;
|
||||
if(index >= xListSize && index >= yListSize && index >= dxListSize && index >= dyListSize && index >= rotateListSize)
|
||||
break;
|
||||
auto& characterPosition = m_characterPositions[offset];
|
||||
if(index < xListSize)
|
||||
characterPosition.x = lengthContext.valueForLength(xList[index], LengthDirection::Horizontal);
|
||||
if(index < yListSize)
|
||||
characterPosition.y = lengthContext.valueForLength(yList[index], LengthDirection::Vertical);
|
||||
if(index < dxListSize)
|
||||
characterPosition.dx = lengthContext.valueForLength(dxList[index], LengthDirection::Horizontal);
|
||||
if(index < dyListSize)
|
||||
characterPosition.dy = lengthContext.valueForLength(dyList[index], LengthDirection::Vertical);
|
||||
if(index < rotateListSize) {
|
||||
characterPosition.rotate = rotateList[index];
|
||||
lastRotation = characterPosition.rotate;
|
||||
}
|
||||
}
|
||||
|
||||
if(lastRotation == std::nullopt)
|
||||
return;
|
||||
auto offset = position.startOffset + rotateList.size();
|
||||
while(offset < position.endOffset) {
|
||||
m_characterPositions[offset++].rotate = lastRotation;
|
||||
}
|
||||
}
|
||||
|
||||
SVGTextPositioningElement::SVGTextPositioningElement(Document* document, ElementID id)
|
||||
: SVGGraphicsElement(document, id)
|
||||
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, 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)
|
||||
{
|
||||
m_font = state.font();
|
||||
m_fill = getPaintServer(state.fill(), state.fill_opacity());
|
||||
m_stroke = getPaintServer(state.stroke(), state.stroke_opacity());
|
||||
SVGGraphicsElement::layoutElement(state);
|
||||
|
||||
LengthContext lengthContext(this);
|
||||
m_stroke_width = lengthContext.valueForLength(state.stroke_width(), LengthDirection::Diagonal);
|
||||
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();
|
||||
}
|
||||
|
||||
float SVGTextPositioningElement::convertBaselineOffset(const BaselineShift& baselineShift) const
|
||||
{
|
||||
if(baselineShift.type() == BaselineShift::Type::Baseline)
|
||||
return 0.f;
|
||||
if(baselineShift.type() == BaselineShift::Type::Sub)
|
||||
return -m_font.height() / 2.f;
|
||||
if(baselineShift.type() == BaselineShift::Type::Super) {
|
||||
return m_font.height() / 2.f;
|
||||
}
|
||||
|
||||
const auto& length = baselineShift.length();
|
||||
if(length.units() == LengthUnits::Percent)
|
||||
return length.value() * m_font.size() / 100.f;
|
||||
if(length.units() == LengthUnits::Ex)
|
||||
return length.value() * m_font.size() / 2.f;
|
||||
if(length.units() == LengthUnits::Em)
|
||||
return length.value() * m_font.size();
|
||||
return length.value();
|
||||
}
|
||||
|
||||
SVGTSpanElement::SVGTSpanElement(Document* document)
|
||||
: SVGTextPositioningElement(document, ElementID::Tspan)
|
||||
{
|
||||
}
|
||||
|
||||
SVGTextElement::SVGTextElement(Document* document)
|
||||
: SVGTextPositioningElement(document, ElementID::Text)
|
||||
{
|
||||
}
|
||||
|
||||
void SVGTextElement::layout(SVGLayoutState& state)
|
||||
{
|
||||
SVGTextPositioningElement::layout(state);
|
||||
SVGTextFragmentsBuilder(m_text, m_fragments).build(this);
|
||||
}
|
||||
|
||||
void SVGTextElement::render(SVGRenderState& state) const
|
||||
{
|
||||
if(m_fragments.empty() || isVisibilityHidden() || isDisplayNone())
|
||||
return;
|
||||
SVGBlendInfo blendInfo(this);
|
||||
SVGRenderState newState(this, state, localTransform());
|
||||
newState.beginGroup(blendInfo);
|
||||
if(newState.mode() == SVGRenderMode::Clipping) {
|
||||
newState->setColor(Color::White);
|
||||
}
|
||||
|
||||
std::u32string_view wholeText(m_text);
|
||||
for(const auto& fragment : m_fragments) {
|
||||
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);
|
||||
|
||||
const auto& font = fragment.element->font();
|
||||
if(newState.mode() == SVGRenderMode::Clipping) {
|
||||
newState->fillText(text, font, origin, transform);
|
||||
} else {
|
||||
const auto& fill = fragment.element->fill();
|
||||
const auto& stroke = fragment.element->stroke();
|
||||
auto stroke_width = fragment.element->stroke_width();
|
||||
if(fill.applyPaint(newState))
|
||||
newState->fillText(text, font, origin, transform);
|
||||
if(stroke.applyPaint(newState)) {
|
||||
newState->strokeText(text, stroke_width, font, origin, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newState.endGroup(blendInfo);
|
||||
}
|
||||
|
||||
Rect SVGTextElement::boundingBox(bool includeStroke) const
|
||||
{
|
||||
auto boundingBox = Rect::Invalid;
|
||||
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) * 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));
|
||||
}
|
||||
|
||||
if(!boundingBox.isValid())
|
||||
boundingBox = Rect::Empty;
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
155
vendor/lunasvg/source/svgtextelement.h
vendored
155
vendor/lunasvg/source/svgtextelement.h
vendored
@@ -1,155 +0,0 @@
|
||||
#ifndef LUNASVG_SVGTEXTELEMENT_H
|
||||
#define LUNASVG_SVGTEXTELEMENT_H
|
||||
|
||||
#include "svgelement.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
class SVGTextPositioningElement;
|
||||
class SVGTextElement;
|
||||
|
||||
struct SVGCharacterPosition {
|
||||
std::optional<float> x;
|
||||
std::optional<float> y;
|
||||
std::optional<float> dx;
|
||||
std::optional<float> dy;
|
||||
std::optional<float> rotate;
|
||||
};
|
||||
|
||||
using SVGCharacterPositions = std::map<size_t, SVGCharacterPosition>;
|
||||
|
||||
struct SVGTextPosition {
|
||||
SVGTextPosition(const SVGNode* node, size_t startOffset, size_t endOffset)
|
||||
: node(node), startOffset(startOffset), endOffset(endOffset)
|
||||
{}
|
||||
|
||||
const SVGNode* node;
|
||||
size_t startOffset;
|
||||
size_t endOffset;
|
||||
};
|
||||
|
||||
using SVGTextPositionList = std::vector<SVGTextPosition>;
|
||||
|
||||
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 width = 0;
|
||||
float height = 0;
|
||||
float angle = 0;
|
||||
};
|
||||
|
||||
using SVGTextFragmentList = std::vector<SVGTextFragment>;
|
||||
|
||||
class SVGTextFragmentsBuilder {
|
||||
public:
|
||||
SVGTextFragmentsBuilder(std::u32string& text, SVGTextFragmentList& fragments);
|
||||
|
||||
void build(const SVGTextElement* textElement);
|
||||
|
||||
private:
|
||||
void handleText(const SVGTextNode* node);
|
||||
void handleElement(const SVGTextPositioningElement* element);
|
||||
void fillCharacterPositions(const SVGTextPosition& position);
|
||||
std::u32string& m_text;
|
||||
SVGTextFragmentList& m_fragments;
|
||||
SVGCharacterPositions m_characterPositions;
|
||||
SVGTextPositionList m_textPositions;
|
||||
size_t m_characterOffset = 0;
|
||||
float m_x = 0;
|
||||
float m_y = 0;
|
||||
};
|
||||
|
||||
class SVGTextPositioningElement : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGTextPositioningElement(Document* document, ElementID id);
|
||||
|
||||
bool isTextPositioningElement() const final { return true; }
|
||||
|
||||
const LengthList& x() const { return m_x.values(); }
|
||||
const LengthList& y() const { return m_y.values(); }
|
||||
const LengthList& dx() const { return m_dx.values(); }
|
||||
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; }
|
||||
TextAnchor text_anchor() const { return m_text_anchor; }
|
||||
WhiteSpace white_space() const { return m_white_space; }
|
||||
Direction direction() const { return m_direction; }
|
||||
|
||||
void layoutElement(const SVGLayoutState& state) override;
|
||||
|
||||
private:
|
||||
float convertBaselineOffset(const BaselineShift& baselineShift) const;
|
||||
SVGLengthList m_x;
|
||||
SVGLengthList m_y;
|
||||
SVGLengthList m_dx;
|
||||
SVGLengthList m_dy;
|
||||
SVGNumberList m_rotate;
|
||||
|
||||
SVGLength m_textLength;
|
||||
SVGEnumeration<LengthAdjust> 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;
|
||||
};
|
||||
|
||||
class SVGTSpanElement final : public SVGTextPositioningElement {
|
||||
public:
|
||||
SVGTSpanElement(Document* document);
|
||||
};
|
||||
|
||||
class SVGTextElement final : public SVGTextPositioningElement {
|
||||
public:
|
||||
SVGTextElement(Document* document);
|
||||
|
||||
Rect fillBoundingBox() const final { return boundingBox(false); }
|
||||
Rect strokeBoundingBox() const final { return boundingBox(true); }
|
||||
|
||||
void layout(SVGLayoutState& state) final;
|
||||
void render(SVGRenderState& state) const final;
|
||||
|
||||
private:
|
||||
Rect boundingBox(bool includeStroke) const;
|
||||
SVGTextFragmentList m_fragments;
|
||||
std::u32string m_text;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGTEXTELEMENT_H
|
||||
3132
vendor/nanosvg/nanosvg.h
vendored
Normal file
3132
vendor/nanosvg/nanosvg.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1472
vendor/nanosvg/nanosvgrast.h
vendored
Normal file
1472
vendor/nanosvg/nanosvgrast.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user