Compare commits
3 Commits
06e1212483
...
8695f69282
| Author | SHA1 | Date | |
|---|---|---|---|
| 8695f69282 | |||
| d5d2f6db8e | |||
| 48f2c51d92 |
@@ -1,31 +0,0 @@
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
AlignConsecutiveDeclarations: true
|
||||
AlignConsecutiveAssignments: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||
AlignConsecutiveMacros: true
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
Cpp11BracedListStyle: false
|
||||
ColumnLimit: 0
|
||||
@@ -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:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
18
.vscode/tasks.json
vendored
18
.vscode/tasks.json
vendored
@@ -4,15 +4,15 @@
|
||||
{
|
||||
"label": "bootstrap",
|
||||
"type": "shell",
|
||||
"command": "c++",
|
||||
"args": ["build.cpp", "-o", "build"],
|
||||
"command": "cc",
|
||||
"args": ["build.c", "-o", "build"],
|
||||
"windows": {
|
||||
"command": "cl",
|
||||
"args": ["/nologo", "build.cpp"]
|
||||
"args": ["/nologo", "build.c"]
|
||||
},
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"detail": "One-time bootstrap: compile build.cpp"
|
||||
"detail": "One-time bootstrap: compile build.c"
|
||||
},
|
||||
{
|
||||
"label": "build",
|
||||
@@ -63,15 +63,15 @@
|
||||
{
|
||||
"label": "clean",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/build",
|
||||
"args": ["clean"],
|
||||
"command": "rm",
|
||||
"args": ["-rf", "build_debug", "build_release"],
|
||||
"windows": {
|
||||
"command": "${workspaceFolder}/build.exe",
|
||||
"args": ["clean"]
|
||||
"command": "PowerShell",
|
||||
"args": ["-Command", "Remove-Item -Recurse -Force -ErrorAction SilentlyContinue build_debug, build_release"]
|
||||
},
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"detail": "Clean build artifacts"
|
||||
"detail": "Remove build directories"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
12
README.md
12
README.md
@@ -11,7 +11,7 @@ Requires MSVC (Visual Studio 2019 Build Tools or later) with the Windows SDK and
|
||||
Open a Developer Command Prompt, then:
|
||||
|
||||
```
|
||||
cl /nologo build.cpp
|
||||
cl /nologo build.c
|
||||
build.exe debug
|
||||
build_debug\autosample.exe
|
||||
```
|
||||
@@ -21,12 +21,12 @@ build_debug\autosample.exe
|
||||
Requires Xcode Command Line Tools (`xcode-select --install`).
|
||||
|
||||
```
|
||||
c++ build.cpp -o build
|
||||
cc build.c -o build
|
||||
./build debug
|
||||
open build_debug/autosample.app
|
||||
```
|
||||
|
||||
The first command is a one-time bootstrap. After that, `./build` detects changes to `build.cpp` and rebuilds itself automatically. Pass `debug` for a debug build (output in `build_debug/`) or omit for release (`build_release/`). Pass `clean` to wipe both build directories, vendor libraries, and generated sources. Invalid arguments produce an error with usage info.
|
||||
The first command is a one-time bootstrap. After that, `./build` detects changes to `build.c` and rebuilds itself automatically. Pass `debug` for a debug build (output in `build_debug/`) or omit for release (`build_release/`). Pass `clean` to wipe both build directories, vendor libraries, and generated sources. Invalid arguments produce an error with usage info.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -96,13 +96,14 @@ MIDI device enumeration with real-time input monitoring.
|
||||
## Project structure
|
||||
|
||||
```
|
||||
build.h Build system library (stb-style single header)
|
||||
build.cpp Build script (c++ build.cpp -o build)
|
||||
build.c Build script (cc build.c -o build)
|
||||
assets/
|
||||
fonts/
|
||||
Inter-Regular.ttf Inter typeface (SIL Open Font License)
|
||||
OFL.txt Font license
|
||||
vendor/
|
||||
nob/
|
||||
nob.h nob build system (vendored, single-header)
|
||||
clay/
|
||||
clay.h Clay v0.14 (modified for MSVC C++ compatibility)
|
||||
freetype/
|
||||
@@ -166,6 +167,7 @@ Memory should be managed with arena allocators where possible rather than indivi
|
||||
|
||||
All dependencies are vendored as source. On Windows, nothing to install beyond the Windows SDK. On macOS, only Xcode Command Line Tools are needed — all frameworks (Metal, Cocoa, CoreAudio, CoreMIDI, etc.) ship with the OS.
|
||||
|
||||
- [nob.h](https://github.com/tsoding/nob.h) — build system
|
||||
- [Clay](https://github.com/nicbarker/clay) — single-header C layout library (v0.14, with MSVC C++ patches)
|
||||
- [FreeType](https://freetype.org/) — font rasterizer (v2.13.3, FreeType License). Compiled as a static library with a minimal module set (truetype, sfnt, smooth, autofit, psnames, pshinter, gzip). Replaces platform-specific font backends (GDI on Windows, Core Text on macOS) for consistent cross-platform text rendering. Static libraries are built into their vendor directories (`vendor/freetype/freetype.lib` on Windows, `vendor/freetype/libfreetype.a` on macOS); debug builds on Windows use a `_d` suffix to avoid CRT mismatch.
|
||||
- [Inter](https://rsms.me/inter/) — typeface (v4.1, SIL Open Font License). Embedded at build time as a C byte array header so there are no runtime font file dependencies.
|
||||
|
||||
525
build.c
Normal file
525
build.c
Normal file
@@ -0,0 +1,525 @@
|
||||
// Bootstrap:
|
||||
// Windows: cl /nologo build.c
|
||||
// macOS: cc build.c -o build
|
||||
// After that, just run: ./build (or build.exe on Windows)
|
||||
|
||||
#define NOB_IMPLEMENTATION
|
||||
#include "vendor/nob/nob.h"
|
||||
|
||||
////////////////////////////////
|
||||
// FreeType source files
|
||||
|
||||
static const char *freetype_sources[] = {
|
||||
"vendor/freetype/src/base/ftsystem.c",
|
||||
"vendor/freetype/src/base/ftinit.c",
|
||||
"vendor/freetype/src/base/ftdebug.c",
|
||||
"vendor/freetype/src/base/ftbase.c",
|
||||
"vendor/freetype/src/base/ftbbox.c",
|
||||
"vendor/freetype/src/base/ftglyph.c",
|
||||
"vendor/freetype/src/base/ftbitmap.c",
|
||||
"vendor/freetype/src/base/ftmm.c",
|
||||
"vendor/freetype/src/truetype/truetype.c",
|
||||
"vendor/freetype/src/sfnt/sfnt.c",
|
||||
"vendor/freetype/src/smooth/smooth.c",
|
||||
"vendor/freetype/src/autofit/autofit.c",
|
||||
"vendor/freetype/src/psnames/psnames.c",
|
||||
"vendor/freetype/src/pshinter/pshinter.c",
|
||||
"vendor/freetype/src/gzip/ftgzip.c",
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// Font embedding — reads a .ttf and writes a C header with the data as a byte array
|
||||
|
||||
static bool embed_font_file(const char *ttf_path, const char *header_path) {
|
||||
if (!nob_needs_rebuild1(header_path, ttf_path)) {
|
||||
nob_log(NOB_INFO, "Font header %s is up to date", header_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
Nob_String_Builder sb = {0};
|
||||
if (!nob_read_entire_file(ttf_path, &sb)) return false;
|
||||
|
||||
FILE *out = fopen(header_path, "wb");
|
||||
if (!out) {
|
||||
nob_log(NOB_ERROR, "Could not open %s for writing", header_path);
|
||||
nob_sb_free(sb);
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(out, "// Auto-generated from %s — do not edit\n", ttf_path);
|
||||
fprintf(out, "#pragma once\n\n");
|
||||
fprintf(out, "static const unsigned char font_inter_data[] = {\n");
|
||||
for (size_t i = 0; i < sb.count; i++) {
|
||||
if (i % 16 == 0) fprintf(out, " ");
|
||||
fprintf(out, "0x%02x,", (unsigned char)sb.items[i]);
|
||||
if (i % 16 == 15 || i == sb.count - 1) fprintf(out, "\n");
|
||||
}
|
||||
fprintf(out, "};\n\n");
|
||||
fprintf(out, "static const unsigned int font_inter_size = %zu;\n", sb.count);
|
||||
|
||||
fclose(out);
|
||||
nob_sb_free(sb);
|
||||
nob_log(NOB_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
////////////////////////////////
|
||||
// macOS build (clang with Objective-C)
|
||||
|
||||
static const char *frameworks[] = {
|
||||
"-framework", "Metal",
|
||||
"-framework", "Cocoa",
|
||||
"-framework", "CoreAudio",
|
||||
"-framework", "AudioToolbox",
|
||||
"-framework", "CoreMIDI",
|
||||
"-framework", "QuartzCore",
|
||||
"-framework", "CoreFoundation",
|
||||
};
|
||||
|
||||
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";
|
||||
|
||||
if (!nob_needs_rebuild(lib_path, freetype_sources, NOB_ARRAY_LEN(freetype_sources))) {
|
||||
nob_log(NOB_INFO, "freetype is up to date");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!nob_mkdir_if_not_exists(obj_dir)) return false;
|
||||
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(freetype_sources); i++) {
|
||||
const char *src = freetype_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=c11", "-c");
|
||||
nob_cmd_append(&cmd, "-DFT2_BUILD_LIBRARY");
|
||||
nob_cmd_append(&cmd, "-Ivendor/freetype/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
|
||||
{
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "ar", "rcs", lib_path);
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(freetype_sources); i++) {
|
||||
const char *src = freetype_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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
NOB_GO_REBUILD_URSELF(argc, argv);
|
||||
|
||||
bool debug = false;
|
||||
bool clean = false;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "debug") == 0) debug = true;
|
||||
else if (strcmp(argv[i], "clean") == 0) clean = true;
|
||||
else {
|
||||
nob_log(NOB_ERROR, "unknown argument: %s", argv[i]);
|
||||
nob_log(NOB_ERROR, "usage: %s [debug] [clean]", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
const char *build_dir = debug ? "build_debug" : "build_release";
|
||||
|
||||
if (clean) {
|
||||
nob_log(NOB_INFO, "Cleaning build artifacts");
|
||||
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "rm", "-rf", "build_debug");
|
||||
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/freetype/libfreetype.a");
|
||||
remove("src/renderer/font_inter.gen.h");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// App bundle paths
|
||||
const char *app_dir = nob_temp_sprintf("%s/autosample.app", build_dir);
|
||||
const char *contents = nob_temp_sprintf("%s/Contents", app_dir);
|
||||
const char *macos_dir = nob_temp_sprintf("%s/Contents/MacOS", app_dir);
|
||||
const char *res_dir = nob_temp_sprintf("%s/Contents/Resources", app_dir);
|
||||
const char *binary_path = nob_temp_sprintf("%s/Contents/MacOS/autosample", app_dir);
|
||||
|
||||
if (!nob_mkdir_if_not_exists(build_dir)) return 1;
|
||||
if (!nob_mkdir_if_not_exists(app_dir)) return 1;
|
||||
if (!nob_mkdir_if_not_exists(contents)) return 1;
|
||||
if (!nob_mkdir_if_not_exists(macos_dir)) return 1;
|
||||
if (!nob_mkdir_if_not_exists(res_dir)) return 1;
|
||||
|
||||
// Build static libraries
|
||||
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.c (which #includes everything)
|
||||
{
|
||||
Nob_Cmd cmd = {0};
|
||||
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/nanosvg");
|
||||
nob_cmd_append(&cmd, "-Ivendor/freetype/include");
|
||||
|
||||
if (debug) {
|
||||
nob_cmd_append(&cmd, "-g", "-O0", "-D_DEBUG");
|
||||
} else {
|
||||
nob_cmd_append(&cmd, "-O2", "-DNDEBUG");
|
||||
}
|
||||
|
||||
nob_cmd_append(&cmd, "-o", binary_path);
|
||||
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/freetype/libfreetype.a");
|
||||
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < NOB_ARRAY_LEN(frameworks); i++)
|
||||
nob_cmd_append(&cmd, frameworks[i]);
|
||||
}
|
||||
nob_cmd_append(&cmd, "-lm");
|
||||
{ Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; }
|
||||
}
|
||||
|
||||
// Write Info.plist
|
||||
{
|
||||
const char *plist_path = nob_temp_sprintf("%s/Contents/Info.plist", app_dir);
|
||||
nob_write_entire_file(plist_path,
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n"
|
||||
" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
|
||||
"<plist version=\"1.0\">\n"
|
||||
"<dict>\n"
|
||||
" <key>CFBundleExecutable</key>\n"
|
||||
" <string>autosample</string>\n"
|
||||
" <key>CFBundleIdentifier</key>\n"
|
||||
" <string>com.autosample.app</string>\n"
|
||||
" <key>CFBundleName</key>\n"
|
||||
" <string>autosample</string>\n"
|
||||
" <key>CFBundleVersion</key>\n"
|
||||
" <string>0.1</string>\n"
|
||||
" <key>CFBundleShortVersionString</key>\n"
|
||||
" <string>0.1</string>\n"
|
||||
" <key>CFBundlePackageType</key>\n"
|
||||
" <string>APPL</string>\n"
|
||||
" <key>NSHighResolutionCapable</key>\n"
|
||||
" <true/>\n"
|
||||
" <key>NSSupportsAutomaticGraphicsSwitching</key>\n"
|
||||
" <true/>\n"
|
||||
"</dict>\n"
|
||||
"</plist>\n",
|
||||
strlen(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n"
|
||||
" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
|
||||
"<plist version=\"1.0\">\n"
|
||||
"<dict>\n"
|
||||
" <key>CFBundleExecutable</key>\n"
|
||||
" <string>autosample</string>\n"
|
||||
" <key>CFBundleIdentifier</key>\n"
|
||||
" <string>com.autosample.app</string>\n"
|
||||
" <key>CFBundleName</key>\n"
|
||||
" <string>autosample</string>\n"
|
||||
" <key>CFBundleVersion</key>\n"
|
||||
" <string>0.1</string>\n"
|
||||
" <key>CFBundleShortVersionString</key>\n"
|
||||
" <string>0.1</string>\n"
|
||||
" <key>CFBundlePackageType</key>\n"
|
||||
" <string>APPL</string>\n"
|
||||
" <key>NSHighResolutionCapable</key>\n"
|
||||
" <true/>\n"
|
||||
" <key>NSSupportsAutomaticGraphicsSwitching</key>\n"
|
||||
" <true/>\n"
|
||||
"</dict>\n"
|
||||
"</plist>\n"
|
||||
));
|
||||
}
|
||||
|
||||
nob_log(NOB_INFO, "Build complete: %s", app_dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
////////////////////////////////
|
||||
// Windows build (MSVC cl.exe)
|
||||
|
||||
// Vulkan SDK path — detected from environment or default install location
|
||||
static const char *get_vulkan_sdk_path(void) {
|
||||
const char *env = getenv("VULKAN_SDK");
|
||||
if (env && env[0]) return env;
|
||||
return "C:\\VulkanSDK\\1.4.341.1";
|
||||
}
|
||||
|
||||
static const char *link_libs[] = {
|
||||
"user32.lib",
|
||||
"gdi32.lib",
|
||||
"shell32.lib",
|
||||
"ole32.lib",
|
||||
"advapi32.lib",
|
||||
"dwmapi.lib",
|
||||
"winmm.lib",
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// SPIR-V shader compilation — compiles .glsl to .spv, then embeds as C header
|
||||
|
||||
static bool compile_shader(const char *glslc_path, const char *src, const char *spv_path, const char *stage) {
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, glslc_path, nob_temp_sprintf("-fshader-stage=%s", stage), "-o", spv_path, src);
|
||||
Nob_Cmd_Opt opt = {0};
|
||||
return nob_cmd_run_opt(&cmd, opt);
|
||||
}
|
||||
|
||||
static bool embed_spirv(const char *spv_path, const char *header_path, const char *array_name) {
|
||||
Nob_String_Builder sb = {0};
|
||||
if (!nob_read_entire_file(spv_path, &sb)) return false;
|
||||
|
||||
FILE *out = fopen(header_path, "wb");
|
||||
if (!out) {
|
||||
nob_log(NOB_ERROR, "Could not open %s for writing", header_path);
|
||||
nob_sb_free(sb);
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(out, "// Auto-generated from %s — do not edit\n", spv_path);
|
||||
fprintf(out, "#pragma once\n\n");
|
||||
// SPIR-V is U32-aligned, emit as uint32_t array
|
||||
size_t word_count = sb.count / 4;
|
||||
fprintf(out, "#include <stdint.h>\n\n");
|
||||
fprintf(out, "static const uint32_t %s[] = {\n", array_name);
|
||||
const uint32_t *words = (const uint32_t *)sb.items;
|
||||
for (size_t i = 0; i < word_count; i++) {
|
||||
if (i % 8 == 0) fprintf(out, " ");
|
||||
fprintf(out, "0x%08x,", words[i]);
|
||||
if (i % 8 == 7 || i == word_count - 1) fprintf(out, "\n");
|
||||
}
|
||||
fprintf(out, "};\n");
|
||||
|
||||
fclose(out);
|
||||
nob_sb_free(sb);
|
||||
nob_log(NOB_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool compile_and_embed_shaders(const char *build_dir) {
|
||||
const char *vk_sdk = get_vulkan_sdk_path();
|
||||
const char *glslc = nob_temp_sprintf("%s\\Bin\\glslc.exe", vk_sdk);
|
||||
|
||||
const char *vert_src = "src/renderer/ui.v.glsl";
|
||||
const char *frag_src = "src/renderer/ui.f.glsl";
|
||||
const char *vert_spv = nob_temp_sprintf("%s/ui_vert.spv", build_dir);
|
||||
const char *frag_spv = nob_temp_sprintf("%s/ui_frag.spv", build_dir);
|
||||
const char *vert_hdr = "src/renderer/ui_vert.spv.h";
|
||||
const char *frag_hdr = "src/renderer/ui_frag.spv.h";
|
||||
|
||||
// Check if rebuild needed
|
||||
if (nob_needs_rebuild1(vert_hdr, vert_src)) {
|
||||
nob_log(NOB_INFO, "Compiling vertex shader");
|
||||
if (!compile_shader(glslc, vert_src, vert_spv, "vertex")) return false;
|
||||
if (!embed_spirv(vert_spv, vert_hdr, "ui_vert_spv")) return false;
|
||||
} else {
|
||||
nob_log(NOB_INFO, "Vertex shader is up to date");
|
||||
}
|
||||
|
||||
if (nob_needs_rebuild1(frag_hdr, frag_src)) {
|
||||
nob_log(NOB_INFO, "Compiling fragment shader");
|
||||
if (!compile_shader(glslc, frag_src, frag_spv, "fragment")) return false;
|
||||
if (!embed_spirv(frag_spv, frag_hdr, "ui_frag_spv")) return false;
|
||||
} else {
|
||||
nob_log(NOB_INFO, "Fragment shader is up to date");
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
if (!nob_needs_rebuild(lib_path, freetype_sources, NOB_ARRAY_LEN(freetype_sources))) {
|
||||
nob_log(NOB_INFO, "freetype is up to date");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!nob_mkdir_if_not_exists(obj_dir)) return false;
|
||||
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(freetype_sources); i++) {
|
||||
const char *src = freetype_sources[i];
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c");
|
||||
nob_cmd_append(&cmd, "/std:c11");
|
||||
nob_cmd_append(&cmd, "/DFT2_BUILD_LIBRARY");
|
||||
nob_cmd_append(&cmd, "/Ivendor/freetype/include");
|
||||
nob_cmd_append(&cmd, "/W3");
|
||||
if (debug) {
|
||||
nob_cmd_append(&cmd, "/MTd", "/Zi");
|
||||
} 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
|
||||
{
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
NOB_GO_REBUILD_URSELF(argc, argv);
|
||||
|
||||
bool debug = false;
|
||||
bool clean = false;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "debug") == 0) debug = true;
|
||||
else if (strcmp(argv[i], "clean") == 0) clean = true;
|
||||
else {
|
||||
nob_log(NOB_ERROR, "unknown argument: %s", argv[i]);
|
||||
nob_log(NOB_ERROR, "usage: %s [debug] [clean]", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
const char *build_dir = debug ? "build_debug" : "build_release";
|
||||
|
||||
if (clean) {
|
||||
nob_log(NOB_INFO, "Cleaning build artifacts");
|
||||
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c",
|
||||
"if exist build_debug rmdir /s /q build_debug");
|
||||
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); }
|
||||
{ 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\\freetype\\freetype.lib");
|
||||
remove("vendor\\freetype\\freetype_d.lib");
|
||||
remove("src\\renderer\\font_inter.gen.h");
|
||||
remove("src\\renderer\\ui_vert.spv.h");
|
||||
remove("src\\renderer\\ui_frag.spv.h");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!nob_mkdir_if_not_exists(build_dir)) return 1;
|
||||
|
||||
// Build static libraries
|
||||
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;
|
||||
|
||||
// Compile GLSL shaders to SPIR-V and embed as C headers
|
||||
if (!compile_and_embed_shaders(build_dir)) return 1;
|
||||
|
||||
const char *vk_sdk = get_vulkan_sdk_path();
|
||||
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.c (which #includes everything)
|
||||
{
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "cl.exe");
|
||||
nob_cmd_append(&cmd, "/nologo", "/std:c11", "/TC", "/W3");
|
||||
nob_cmd_append(&cmd, "/Isrc", "/Ivendor/clay");
|
||||
nob_cmd_append(&cmd, "/Ivendor/nanosvg");
|
||||
nob_cmd_append(&cmd, "/Ivendor/freetype/include");
|
||||
nob_cmd_append(&cmd, vk_include);
|
||||
|
||||
if (debug) {
|
||||
nob_cmd_append(&cmd, "/MTd", "/Zi", "/D_DEBUG");
|
||||
} else {
|
||||
nob_cmd_append(&cmd, "/MT", "/Zi", "/O2", "/DNDEBUG");
|
||||
}
|
||||
|
||||
nob_cmd_append(&cmd, nob_temp_sprintf("/Fe:%s/autosample.exe", build_dir));
|
||||
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.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/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib");
|
||||
nob_cmd_append(&cmd, vk_lib);
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < NOB_ARRAY_LEN(link_libs); i++)
|
||||
nob_cmd_append(&cmd, link_libs[i]);
|
||||
}
|
||||
{ Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; }
|
||||
}
|
||||
|
||||
// Clean up obj files
|
||||
nob_delete_file(nob_temp_sprintf("%s/main.obj", build_dir));
|
||||
|
||||
nob_log(NOB_INFO, "Build complete: %s/autosample.exe", build_dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
726
build.cpp
726
build.cpp
@@ -1,726 +0,0 @@
|
||||
// build.cpp — Build script for autosample
|
||||
//
|
||||
// Bootstrap (one-time):
|
||||
// Windows: cl /nologo build.cpp
|
||||
// macOS: c++ build.cpp -o build
|
||||
// After that, just run: ./build (or build.exe on Windows)
|
||||
|
||||
#define BUILD_IMPLEMENTATION
|
||||
#include "build.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
|
||||
|
||||
static const char *freetype_sources[] = {
|
||||
"vendor/freetype/src/base/ftsystem.c",
|
||||
"vendor/freetype/src/base/ftinit.c",
|
||||
"vendor/freetype/src/base/ftdebug.c",
|
||||
"vendor/freetype/src/base/ftbase.c",
|
||||
"vendor/freetype/src/base/ftbbox.c",
|
||||
"vendor/freetype/src/base/ftglyph.c",
|
||||
"vendor/freetype/src/base/ftbitmap.c",
|
||||
"vendor/freetype/src/base/ftmm.c",
|
||||
"vendor/freetype/src/truetype/truetype.c",
|
||||
"vendor/freetype/src/sfnt/sfnt.c",
|
||||
"vendor/freetype/src/smooth/smooth.c",
|
||||
"vendor/freetype/src/autofit/autofit.c",
|
||||
"vendor/freetype/src/psnames/psnames.c",
|
||||
"vendor/freetype/src/pshinter/pshinter.c",
|
||||
"vendor/freetype/src/gzip/ftgzip.c",
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// Font embedding — reads a .ttf and writes a C header with the data as a byte array
|
||||
|
||||
static bool embed_font_file(const char *ttf_path, const char *header_path) {
|
||||
if (!needs_rebuild1(header_path, ttf_path)) {
|
||||
build_log(LOG_INFO, "Font header %s is up to date", header_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
String_Builder sb = {};
|
||||
if (!sb_read_file(&sb, ttf_path)) return false;
|
||||
|
||||
FILE *out = fopen(header_path, "wb");
|
||||
if (!out) {
|
||||
build_log(LOG_ERROR, "Could not open %s for writing", header_path);
|
||||
sb_free(&sb);
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(out, "// Auto-generated from %s — do not edit\n", ttf_path);
|
||||
fprintf(out, "#pragma once\n\n");
|
||||
fprintf(out, "static const unsigned char font_inter_data[] = {\n");
|
||||
for (size_t i = 0; i < sb.count; i++) {
|
||||
if (i % 16 == 0) fprintf(out, " ");
|
||||
fprintf(out, "0x%02x,", (unsigned char)sb.items[i]);
|
||||
if (i % 16 == 15 || i == sb.count - 1) fprintf(out, "\n");
|
||||
}
|
||||
fprintf(out, "};\n\n");
|
||||
fprintf(out, "static const unsigned int font_inter_size = %zu;\n", sb.count);
|
||||
|
||||
fclose(out);
|
||||
build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
|
||||
sb_free(&sb);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
////////////////////////////////
|
||||
// macOS build (clang++ with Objective-C++)
|
||||
|
||||
static const char *frameworks[] = {
|
||||
"-framework", "Metal",
|
||||
"-framework", "Cocoa",
|
||||
"-framework", "CoreAudio",
|
||||
"-framework", "AudioToolbox",
|
||||
"-framework", "CoreMIDI",
|
||||
"-framework", "QuartzCore",
|
||||
"-framework", "CoreFoundation",
|
||||
};
|
||||
|
||||
static bool build_lunasvg_lib(const char *build_dir, bool debug) {
|
||||
const char *obj_dir = 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[ARRAY_LEN(plutovg_sources) + ARRAY_LEN(lunasvg_sources)];
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < ARRAY_LEN(plutovg_sources); i++)
|
||||
all_sources[n++] = plutovg_sources[i];
|
||||
for (size_t i = 0; i < ARRAY_LEN(lunasvg_sources); i++)
|
||||
all_sources[n++] = lunasvg_sources[i];
|
||||
|
||||
if (!needs_rebuild(lib_path, all_sources, n)) {
|
||||
build_log(LOG_INFO, "lunasvg is up to date");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mkdir_if_not_exists(obj_dir)) return false;
|
||||
|
||||
// Compile plutovg C sources
|
||||
for (size_t i = 0; i < 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'; }
|
||||
const char *obj_path = temp_sprintf("%s/%s", obj_dir, obj_name);
|
||||
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "clang");
|
||||
cmd_append(&cmd, "-std=c11", "-c");
|
||||
cmd_append(&cmd, "-DPLUTOVG_BUILD", "-DPLUTOVG_BUILD_STATIC");
|
||||
cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include");
|
||||
cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/source");
|
||||
cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
|
||||
if (debug) {
|
||||
cmd_append(&cmd, "-g", "-O0");
|
||||
} else {
|
||||
cmd_append(&cmd, "-O2");
|
||||
}
|
||||
cmd_append(&cmd, "-o", obj_path);
|
||||
cmd_append(&cmd, src);
|
||||
if (!cmd_run(&cmd)) return false;
|
||||
}
|
||||
|
||||
// Compile lunasvg C++ sources
|
||||
for (size_t i = 0; i < 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 = temp_sprintf("%s/%s", obj_dir, obj_name);
|
||||
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "clang++");
|
||||
cmd_append(&cmd, "-std=c++17", "-c");
|
||||
cmd_append(&cmd, "-fno-exceptions", "-fno-rtti");
|
||||
cmd_append(&cmd, "-DLUNASVG_BUILD", "-DLUNASVG_BUILD_STATIC", "-DPLUTOVG_BUILD_STATIC");
|
||||
cmd_append(&cmd, "-Ivendor/lunasvg/include");
|
||||
cmd_append(&cmd, "-Ivendor/lunasvg/source");
|
||||
cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include");
|
||||
cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
|
||||
if (debug) {
|
||||
cmd_append(&cmd, "-g", "-O0");
|
||||
} else {
|
||||
cmd_append(&cmd, "-O2");
|
||||
}
|
||||
cmd_append(&cmd, "-o", obj_path);
|
||||
cmd_append(&cmd, src);
|
||||
if (!cmd_run(&cmd)) return false;
|
||||
}
|
||||
|
||||
// Archive into static library
|
||||
{
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "ar", "rcs", lib_path);
|
||||
for (size_t i = 0; i < 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'; }
|
||||
cmd__append(&cmd, 1, temp_sprintf("%s/%s", obj_dir, obj_name));
|
||||
}
|
||||
for (size_t i = 0; i < 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'; }
|
||||
cmd__append(&cmd, 1, temp_sprintf("%s/%s", obj_dir, obj_name));
|
||||
}
|
||||
if (!cmd_run(&cmd)) return false;
|
||||
}
|
||||
|
||||
// Clean up obj dir — only the .a is needed
|
||||
{
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "rm", "-rf", obj_dir);
|
||||
cmd_run(&cmd);
|
||||
}
|
||||
|
||||
build_log(LOG_INFO, "Built %s", lib_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_freetype_lib(const char *build_dir, bool debug) {
|
||||
const char *obj_dir = temp_sprintf("%s/freetype_obj", build_dir);
|
||||
const char *lib_path = "vendor/freetype/libfreetype.a";
|
||||
|
||||
if (!needs_rebuild(lib_path, freetype_sources, ARRAY_LEN(freetype_sources))) {
|
||||
build_log(LOG_INFO, "freetype is up to date");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mkdir_if_not_exists(obj_dir)) return false;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_LEN(freetype_sources); i++) {
|
||||
const char *src = freetype_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 = temp_sprintf("%s/%s", obj_dir, obj_name);
|
||||
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "clang");
|
||||
cmd_append(&cmd, "-std=c11", "-c");
|
||||
cmd_append(&cmd, "-DFT2_BUILD_LIBRARY");
|
||||
cmd_append(&cmd, "-Ivendor/freetype/include");
|
||||
cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
|
||||
if (debug) {
|
||||
cmd_append(&cmd, "-g", "-O0");
|
||||
} else {
|
||||
cmd_append(&cmd, "-O2");
|
||||
}
|
||||
cmd_append(&cmd, "-o", obj_path);
|
||||
cmd_append(&cmd, src);
|
||||
if (!cmd_run(&cmd)) return false;
|
||||
}
|
||||
|
||||
// Archive
|
||||
{
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "ar", "rcs", lib_path);
|
||||
for (size_t i = 0; i < ARRAY_LEN(freetype_sources); i++) {
|
||||
const char *src = freetype_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'; }
|
||||
cmd__append(&cmd, 1, temp_sprintf("%s/%s", obj_dir, obj_name));
|
||||
}
|
||||
if (!cmd_run(&cmd)) return false;
|
||||
}
|
||||
|
||||
// Clean up obj dir
|
||||
{
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "rm", "-rf", obj_dir);
|
||||
cmd_run(&cmd);
|
||||
}
|
||||
|
||||
build_log(LOG_INFO, "Built %s", lib_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
GO_REBUILD_URSELF(argc, argv);
|
||||
|
||||
bool debug = false;
|
||||
bool clean = false;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "debug") == 0) debug = true;
|
||||
else if (strcmp(argv[i], "clean") == 0) clean = true;
|
||||
else {
|
||||
build_log(LOG_ERROR, "unknown argument: %s", argv[i]);
|
||||
build_log(LOG_ERROR, "usage: %s [debug] [clean]", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
const char *build_dir = debug ? "build_debug" : "build_release";
|
||||
|
||||
if (clean) {
|
||||
build_log(LOG_INFO, "Cleaning build artifacts");
|
||||
{ Cmd cmd = {}; cmd_append(&cmd, "rm", "-rf", "build_debug"); cmd_run(&cmd); }
|
||||
{ Cmd cmd = {}; cmd_append(&cmd, "rm", "-rf", "build_release"); cmd_run(&cmd); }
|
||||
remove("vendor/lunasvg/liblunasvg.a");
|
||||
remove("vendor/freetype/libfreetype.a");
|
||||
remove("src/renderer/font_inter.gen.h");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// App bundle paths
|
||||
const char *app_dir = temp_sprintf("%s/autosample.app", build_dir);
|
||||
const char *contents = temp_sprintf("%s/Contents", app_dir);
|
||||
const char *macos_dir = temp_sprintf("%s/Contents/MacOS", app_dir);
|
||||
const char *res_dir = temp_sprintf("%s/Contents/Resources", app_dir);
|
||||
const char *binary_path = temp_sprintf("%s/Contents/MacOS/autosample", app_dir);
|
||||
|
||||
if (!mkdir_if_not_exists(build_dir)) return 1;
|
||||
if (!mkdir_if_not_exists(app_dir)) return 1;
|
||||
if (!mkdir_if_not_exists(contents)) return 1;
|
||||
if (!mkdir_if_not_exists(macos_dir)) return 1;
|
||||
if (!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)
|
||||
{
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "clang++");
|
||||
cmd_append(&cmd, "-std=c++20", "-x", "objective-c++");
|
||||
cmd_append(&cmd, "-fno-exceptions", "-fno-rtti");
|
||||
cmd_append(&cmd, "-Wall", "-Wextra", "-Wno-missing-field-initializers");
|
||||
cmd_append(&cmd, "-Wno-deprecated-declarations");
|
||||
cmd_append(&cmd, "-Isrc", "-Ivendor/clay");
|
||||
cmd_append(&cmd, "-Ivendor/lunasvg/include");
|
||||
cmd_append(&cmd, "-Ivendor/freetype/include");
|
||||
cmd_append(&cmd, "-DLUNASVG_BUILD_STATIC");
|
||||
|
||||
if (debug) {
|
||||
cmd_append(&cmd, "-g", "-O0", "-D_DEBUG");
|
||||
} else {
|
||||
cmd_append(&cmd, "-O2", "-DNDEBUG");
|
||||
}
|
||||
|
||||
cmd_append(&cmd, "-o", binary_path);
|
||||
cmd_append(&cmd, "src/main.cpp");
|
||||
|
||||
// Reset language mode so .a is treated as a library, not source
|
||||
cmd_append(&cmd, "-x", "none");
|
||||
cmd_append(&cmd, "vendor/lunasvg/liblunasvg.a");
|
||||
cmd_append(&cmd, "vendor/freetype/libfreetype.a");
|
||||
|
||||
for (size_t i = 0; i < ARRAY_LEN(frameworks); i++)
|
||||
cmd__append(&cmd, 1, frameworks[i]);
|
||||
cmd_append(&cmd, "-lstdc++");
|
||||
if (!cmd_run(&cmd)) return 1;
|
||||
}
|
||||
|
||||
// Write Info.plist
|
||||
{
|
||||
const char *plist_path = temp_sprintf("%s/Contents/Info.plist", app_dir);
|
||||
const char *plist =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n"
|
||||
" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
|
||||
"<plist version=\"1.0\">\n"
|
||||
"<dict>\n"
|
||||
" <key>CFBundleExecutable</key>\n"
|
||||
" <string>autosample</string>\n"
|
||||
" <key>CFBundleIdentifier</key>\n"
|
||||
" <string>com.autosample.app</string>\n"
|
||||
" <key>CFBundleName</key>\n"
|
||||
" <string>autosample</string>\n"
|
||||
" <key>CFBundleVersion</key>\n"
|
||||
" <string>0.1</string>\n"
|
||||
" <key>CFBundleShortVersionString</key>\n"
|
||||
" <string>0.1</string>\n"
|
||||
" <key>CFBundlePackageType</key>\n"
|
||||
" <string>APPL</string>\n"
|
||||
" <key>NSHighResolutionCapable</key>\n"
|
||||
" <true/>\n"
|
||||
" <key>NSSupportsAutomaticGraphicsSwitching</key>\n"
|
||||
" <true/>\n"
|
||||
"</dict>\n"
|
||||
"</plist>\n";
|
||||
write_entire_file(plist_path, plist, strlen(plist));
|
||||
}
|
||||
|
||||
build_log(LOG_INFO, "Build complete: %s", app_dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
////////////////////////////////
|
||||
// Windows build (MSVC cl.exe)
|
||||
|
||||
// Vulkan SDK path — detected from environment or default install location
|
||||
static const char *get_vulkan_sdk_path(void) {
|
||||
const char *env = getenv("VULKAN_SDK");
|
||||
if (env && env[0]) return env;
|
||||
return "C:\\VulkanSDK\\1.4.341.1";
|
||||
}
|
||||
|
||||
static const char *link_libs[] = {
|
||||
"user32.lib",
|
||||
"gdi32.lib",
|
||||
"shell32.lib",
|
||||
"ole32.lib",
|
||||
"advapi32.lib",
|
||||
"dwmapi.lib",
|
||||
"winmm.lib",
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// SPIR-V shader compilation — compiles .glsl to .spv, then embeds as C header
|
||||
|
||||
static bool compile_shader(const char *glslc_path, const char *src, const char *spv_path, const char *stage) {
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, glslc_path, temp_sprintf("-fshader-stage=%s", stage), "-o", spv_path, src);
|
||||
return cmd_run(&cmd);
|
||||
}
|
||||
|
||||
static bool embed_spirv(const char *spv_path, const char *header_path, const char *array_name) {
|
||||
String_Builder sb = {};
|
||||
if (!sb_read_file(&sb, spv_path)) return false;
|
||||
|
||||
FILE *out = fopen(header_path, "wb");
|
||||
if (!out) {
|
||||
build_log(LOG_ERROR, "Could not open %s for writing", header_path);
|
||||
sb_free(&sb);
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(out, "// Auto-generated from %s — do not edit\n", spv_path);
|
||||
fprintf(out, "#pragma once\n\n");
|
||||
// SPIR-V is U32-aligned, emit as uint32_t array
|
||||
size_t word_count = sb.count / 4;
|
||||
fprintf(out, "#include <stdint.h>\n\n");
|
||||
fprintf(out, "static const uint32_t %s[] = {\n", array_name);
|
||||
const uint32_t *words = (const uint32_t *)sb.items;
|
||||
for (size_t i = 0; i < word_count; i++) {
|
||||
if (i % 8 == 0) fprintf(out, " ");
|
||||
fprintf(out, "0x%08x,", words[i]);
|
||||
if (i % 8 == 7 || i == word_count - 1) fprintf(out, "\n");
|
||||
}
|
||||
fprintf(out, "};\n");
|
||||
|
||||
fclose(out);
|
||||
build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
|
||||
sb_free(&sb);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool compile_and_embed_shaders(const char *build_dir) {
|
||||
const char *vk_sdk = get_vulkan_sdk_path();
|
||||
const char *glslc = temp_sprintf("%s\\Bin\\glslc.exe", vk_sdk);
|
||||
|
||||
const char *vert_src = "src/renderer/ui.v.glsl";
|
||||
const char *frag_src = "src/renderer/ui.f.glsl";
|
||||
const char *vert_spv = temp_sprintf("%s/ui_vert.spv", build_dir);
|
||||
const char *frag_spv = temp_sprintf("%s/ui_frag.spv", build_dir);
|
||||
const char *vert_hdr = "src/renderer/ui_vert.spv.h";
|
||||
const char *frag_hdr = "src/renderer/ui_frag.spv.h";
|
||||
|
||||
if (needs_rebuild1(vert_hdr, vert_src)) {
|
||||
build_log(LOG_INFO, "Compiling vertex shader");
|
||||
if (!compile_shader(glslc, vert_src, vert_spv, "vertex")) return false;
|
||||
if (!embed_spirv(vert_spv, vert_hdr, "ui_vert_spv")) return false;
|
||||
} else {
|
||||
build_log(LOG_INFO, "Vertex shader is up to date");
|
||||
}
|
||||
|
||||
if (needs_rebuild1(frag_hdr, frag_src)) {
|
||||
build_log(LOG_INFO, "Compiling fragment shader");
|
||||
if (!compile_shader(glslc, frag_src, frag_spv, "fragment")) return false;
|
||||
if (!embed_spirv(frag_spv, frag_hdr, "ui_frag_spv")) return false;
|
||||
} else {
|
||||
build_log(LOG_INFO, "Fragment shader is up to date");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_lunasvg_lib(const char *build_dir, bool debug) {
|
||||
const char *obj_dir = 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[ARRAY_LEN(plutovg_sources) + ARRAY_LEN(lunasvg_sources)];
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < ARRAY_LEN(plutovg_sources); i++)
|
||||
all_sources[n++] = plutovg_sources[i];
|
||||
for (size_t i = 0; i < ARRAY_LEN(lunasvg_sources); i++)
|
||||
all_sources[n++] = lunasvg_sources[i];
|
||||
|
||||
if (!needs_rebuild(lib_path, all_sources, n)) {
|
||||
build_log(LOG_INFO, "lunasvg is up to date");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mkdir_if_not_exists(obj_dir)) return false;
|
||||
|
||||
// Compile plutovg C sources
|
||||
for (size_t i = 0; i < ARRAY_LEN(plutovg_sources); i++) {
|
||||
const char *src = plutovg_sources[i];
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "cl.exe", "/nologo", "/c");
|
||||
cmd_append(&cmd, "/std:c11");
|
||||
cmd_append(&cmd, "/DPLUTOVG_BUILD", "/DPLUTOVG_BUILD_STATIC");
|
||||
cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include");
|
||||
cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/source");
|
||||
cmd_append(&cmd, "/W3");
|
||||
if (debug) {
|
||||
cmd_append(&cmd, "/MTd", "/Zi", "/Od");
|
||||
} else {
|
||||
cmd_append(&cmd, "/MT", "/O2");
|
||||
}
|
||||
cmd_append(&cmd, temp_sprintf("/Fo:%s/", obj_dir));
|
||||
cmd_append(&cmd, src);
|
||||
if (!cmd_run(&cmd)) return false;
|
||||
}
|
||||
|
||||
// Compile lunasvg C++ sources
|
||||
for (size_t i = 0; i < ARRAY_LEN(lunasvg_sources); i++) {
|
||||
const char *src = lunasvg_sources[i];
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "cl.exe", "/nologo", "/c");
|
||||
cmd_append(&cmd, "/std:c++17", "/EHsc");
|
||||
cmd_append(&cmd, "/DLUNASVG_BUILD", "/DLUNASVG_BUILD_STATIC", "/DPLUTOVG_BUILD_STATIC");
|
||||
cmd_append(&cmd, "/Ivendor/lunasvg/include");
|
||||
cmd_append(&cmd, "/Ivendor/lunasvg/source");
|
||||
cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include");
|
||||
cmd_append(&cmd, "/W3");
|
||||
if (debug) {
|
||||
cmd_append(&cmd, "/MTd", "/Zi", "/Od");
|
||||
} else {
|
||||
cmd_append(&cmd, "/MT", "/O2");
|
||||
}
|
||||
cmd_append(&cmd, temp_sprintf("/Fo:%s/", obj_dir));
|
||||
cmd_append(&cmd, src);
|
||||
if (!cmd_run(&cmd)) return false;
|
||||
}
|
||||
|
||||
// Archive into static library
|
||||
{
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "lib.exe", "/nologo", temp_sprintf("/OUT:%s", lib_path));
|
||||
cmd_append(&cmd, temp_sprintf("%s/*.obj", obj_dir));
|
||||
if (!cmd_run(&cmd)) return false;
|
||||
}
|
||||
|
||||
// Clean up obj dir — only the .lib is needed
|
||||
{
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "cmd.exe", "/c",
|
||||
temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir));
|
||||
cmd_run(&cmd);
|
||||
}
|
||||
|
||||
build_log(LOG_INFO, "Built %s", lib_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_freetype_lib(const char *build_dir, bool debug) {
|
||||
const char *obj_dir = temp_sprintf("%s\\freetype_obj", build_dir);
|
||||
const char *lib_path = debug ? "vendor\\freetype\\freetype_d.lib" : "vendor\\freetype\\freetype.lib";
|
||||
|
||||
if (!needs_rebuild(lib_path, freetype_sources, ARRAY_LEN(freetype_sources))) {
|
||||
build_log(LOG_INFO, "freetype is up to date");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mkdir_if_not_exists(obj_dir)) return false;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_LEN(freetype_sources); i++) {
|
||||
const char *src = freetype_sources[i];
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "cl.exe", "/nologo", "/c");
|
||||
cmd_append(&cmd, "/std:c11");
|
||||
cmd_append(&cmd, "/DFT2_BUILD_LIBRARY");
|
||||
cmd_append(&cmd, "/Ivendor/freetype/include");
|
||||
cmd_append(&cmd, "/W3");
|
||||
if (debug) {
|
||||
cmd_append(&cmd, "/MTd", "/Zi", "/Od");
|
||||
} else {
|
||||
cmd_append(&cmd, "/MT", "/O2");
|
||||
}
|
||||
cmd_append(&cmd, temp_sprintf("/Fo:%s/", obj_dir));
|
||||
cmd_append(&cmd, src);
|
||||
if (!cmd_run(&cmd)) return false;
|
||||
}
|
||||
|
||||
// Archive
|
||||
{
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "lib.exe", "/nologo", temp_sprintf("/OUT:%s", lib_path));
|
||||
cmd_append(&cmd, temp_sprintf("%s/*.obj", obj_dir));
|
||||
if (!cmd_run(&cmd)) return false;
|
||||
}
|
||||
|
||||
// Clean up obj dir
|
||||
{
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "cmd.exe", "/c",
|
||||
temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir));
|
||||
cmd_run(&cmd);
|
||||
}
|
||||
|
||||
build_log(LOG_INFO, "Built %s", lib_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
GO_REBUILD_URSELF(argc, argv);
|
||||
|
||||
bool debug = false;
|
||||
bool clean = false;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "debug") == 0) debug = true;
|
||||
else if (strcmp(argv[i], "clean") == 0) clean = true;
|
||||
else {
|
||||
build_log(LOG_ERROR, "unknown argument: %s", argv[i]);
|
||||
build_log(LOG_ERROR, "usage: %s [debug] [clean]", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
const char *build_dir = debug ? "build_debug" : "build_release";
|
||||
|
||||
if (clean) {
|
||||
build_log(LOG_INFO, "Cleaning build artifacts");
|
||||
{ Cmd cmd = {}; cmd_append(&cmd, "cmd.exe", "/c",
|
||||
"if exist build_debug rmdir /s /q build_debug");
|
||||
cmd_run(&cmd); }
|
||||
{ Cmd cmd = {}; cmd_append(&cmd, "cmd.exe", "/c",
|
||||
"if exist build_release rmdir /s /q build_release");
|
||||
cmd_run(&cmd); }
|
||||
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");
|
||||
remove("src\\renderer\\ui_vert.spv.h");
|
||||
remove("src\\renderer\\ui_frag.spv.h");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!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
|
||||
if (!embed_font_file("assets/fonts/Inter-Regular.ttf",
|
||||
"src\\renderer\\font_inter.gen.h")) return 1;
|
||||
|
||||
// Compile GLSL shaders to SPIR-V and embed as C headers
|
||||
if (!compile_and_embed_shaders(build_dir)) return 1;
|
||||
|
||||
const char *vk_sdk = get_vulkan_sdk_path();
|
||||
const char *vk_include = temp_sprintf("/I%s/Include", vk_sdk);
|
||||
const char *vk_lib = 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)
|
||||
{
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, "cl.exe");
|
||||
cmd_append(&cmd, "/nologo", "/std:c++20", "/EHsc", "/W3");
|
||||
cmd_append(&cmd, "/Isrc", "/Ivendor/clay");
|
||||
cmd_append(&cmd, "/Ivendor/lunasvg/include");
|
||||
cmd_append(&cmd, "/Ivendor/freetype/include");
|
||||
cmd_append(&cmd, vk_include);
|
||||
cmd_append(&cmd, "/DLUNASVG_BUILD_STATIC");
|
||||
|
||||
if (debug) {
|
||||
cmd_append(&cmd, "/MTd", "/Zi", "/Od", "/D_DEBUG");
|
||||
} else {
|
||||
cmd_append(&cmd, "/MT", "/Zi", "/O2", "/DNDEBUG");
|
||||
}
|
||||
|
||||
cmd_append(&cmd, temp_sprintf("/Fe:%s/autosample.exe", build_dir));
|
||||
cmd_append(&cmd, temp_sprintf("/Fo:%s/", build_dir));
|
||||
cmd_append(&cmd, temp_sprintf("/Fd:%s/autosample.pdb", build_dir));
|
||||
|
||||
cmd_append(&cmd, "src/main.cpp");
|
||||
|
||||
cmd_append(&cmd, "/link");
|
||||
cmd_append(&cmd, "/MACHINE:X64");
|
||||
cmd_append(&cmd, "/SUBSYSTEM:CONSOLE");
|
||||
cmd_append(&cmd, temp_sprintf("/PDB:%s/autosample.pdb", build_dir));
|
||||
cmd_append(&cmd, "/DEBUG");
|
||||
cmd_append(&cmd, debug ? "vendor/lunasvg/lunasvg_d.lib" : "vendor/lunasvg/lunasvg.lib");
|
||||
cmd_append(&cmd, debug ? "vendor/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib");
|
||||
cmd_append(&cmd, vk_lib);
|
||||
for (size_t i = 0; i < ARRAY_LEN(link_libs); i++)
|
||||
cmd__append(&cmd, 1, link_libs[i]);
|
||||
if (!cmd_run(&cmd)) return 1;
|
||||
}
|
||||
|
||||
// Clean up obj files
|
||||
delete_file(temp_sprintf("%s/main.obj", build_dir));
|
||||
|
||||
build_log(LOG_INFO, "Build complete: %s/autosample.exe", build_dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
595
build.h
595
build.h
@@ -1,595 +0,0 @@
|
||||
// build.h — Minimal C-style C++ build system (stb-style single header)
|
||||
//
|
||||
// Define BUILD_IMPLEMENTATION in exactly one file before including this header.
|
||||
//
|
||||
// Bootstrap (one-time):
|
||||
// Windows: cl /nologo build.cpp
|
||||
// macOS: c++ build.cpp -o build
|
||||
// After that, just run ./build (or build.exe) — it rebuilds itself.
|
||||
|
||||
#ifndef BUILD_H
|
||||
#define BUILD_H
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
# include <direct.h>
|
||||
# include <io.h>
|
||||
#else
|
||||
# include <sys/stat.h>
|
||||
# include <sys/wait.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
////////////////////////////////
|
||||
// Macros
|
||||
|
||||
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
|
||||
|
||||
// Self-rebuild: call at the top of main(). Recompiles the build script
|
||||
// if the source file is newer than the running binary, then re-executes.
|
||||
#define GO_REBUILD_URSELF(argc, argv) \
|
||||
go_rebuild_urself((argc), (argv), __FILE__)
|
||||
|
||||
////////////////////////////////
|
||||
// Logging
|
||||
|
||||
enum Log_Level {
|
||||
LOG_INFO,
|
||||
LOG_WARNING,
|
||||
LOG_ERROR,
|
||||
};
|
||||
|
||||
void build_log(Log_Level level, const char *fmt, ...);
|
||||
|
||||
////////////////////////////////
|
||||
// Temp allocator — ring buffer for short-lived sprintf results
|
||||
|
||||
#ifndef TEMP_CAPACITY
|
||||
#define TEMP_CAPACITY (8 * 1024 * 1024)
|
||||
#endif
|
||||
|
||||
char *temp_sprintf(const char *fmt, ...);
|
||||
void temp_reset(void);
|
||||
|
||||
////////////////////////////////
|
||||
// String builder — growable byte buffer
|
||||
|
||||
struct String_Builder {
|
||||
char *items = nullptr;
|
||||
size_t count = 0;
|
||||
size_t capacity = 0;
|
||||
};
|
||||
|
||||
bool sb_read_file(String_Builder *sb, const char *path);
|
||||
void sb_free(String_Builder *sb);
|
||||
|
||||
////////////////////////////////
|
||||
// File I/O
|
||||
|
||||
bool write_entire_file(const char *path, const void *data, size_t size);
|
||||
bool delete_file(const char *path);
|
||||
bool rename_file(const char *old_path, const char *new_path);
|
||||
bool mkdir_if_not_exists(const char *path);
|
||||
|
||||
////////////////////////////////
|
||||
// Rebuild checking — returns 1 if rebuild needed, 0 if up to date, -1 on error
|
||||
|
||||
int needs_rebuild(const char *output, const char **inputs, size_t count);
|
||||
int needs_rebuild1(const char *output, const char *input);
|
||||
|
||||
////////////////////////////////
|
||||
// Command builder and runner
|
||||
|
||||
struct Cmd {
|
||||
const char **items = nullptr;
|
||||
size_t count = 0;
|
||||
size_t capacity = 0;
|
||||
};
|
||||
|
||||
// Appends arguments to a command. Use via the macro which auto-counts args.
|
||||
void cmd__append(Cmd *cmd, size_t n, ...);
|
||||
void cmd__append_arr(Cmd *cmd, const char **args, size_t n);
|
||||
#define cmd_append(cmd, ...) do { \
|
||||
const char *_cmd_args[] = {__VA_ARGS__}; \
|
||||
cmd__append_arr((cmd), _cmd_args, sizeof(_cmd_args) / sizeof(_cmd_args[0])); \
|
||||
} while(0)
|
||||
|
||||
// Runs the command synchronously, resets cmd->count to 0, returns success.
|
||||
bool cmd_run(Cmd *cmd);
|
||||
|
||||
// Frees the command's allocated memory.
|
||||
void cmd_free(Cmd *cmd);
|
||||
|
||||
////////////////////////////////
|
||||
// Self-rebuild
|
||||
|
||||
void go_rebuild_urself(int argc, char **argv, const char *source);
|
||||
|
||||
#endif // BUILD_H
|
||||
|
||||
////////////////////////////////
|
||||
// Implementation
|
||||
////////////////////////////////
|
||||
|
||||
#ifdef BUILD_IMPLEMENTATION
|
||||
|
||||
////////////////////////////////
|
||||
// Temp allocator
|
||||
|
||||
static size_t g_temp_size = 0;
|
||||
static char g_temp[TEMP_CAPACITY];
|
||||
|
||||
void temp_reset(void) {
|
||||
g_temp_size = 0;
|
||||
}
|
||||
|
||||
static char *temp_alloc(size_t size) {
|
||||
if (g_temp_size + size > TEMP_CAPACITY) {
|
||||
g_temp_size = 0; // wrap around
|
||||
}
|
||||
char *result = g_temp + g_temp_size;
|
||||
g_temp_size += size;
|
||||
return result;
|
||||
}
|
||||
|
||||
char *temp_sprintf(const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
va_list args2;
|
||||
va_copy(args2, args);
|
||||
int n = vsnprintf(nullptr, 0, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
char *result = temp_alloc(n + 1);
|
||||
vsnprintf(result, n + 1, fmt, args2);
|
||||
va_end(args2);
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Logging
|
||||
|
||||
void build_log(Log_Level level, const char *fmt, ...) {
|
||||
switch (level) {
|
||||
case LOG_INFO: fprintf(stderr, "[INFO] "); break;
|
||||
case LOG_WARNING: fprintf(stderr, "[WARNING] "); break;
|
||||
case LOG_ERROR: fprintf(stderr, "[ERROR] "); break;
|
||||
}
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// String builder
|
||||
|
||||
static void sb_ensure(String_Builder *sb, size_t needed) {
|
||||
if (sb->count + needed <= sb->capacity) return;
|
||||
size_t new_cap = sb->capacity ? sb->capacity * 2 : 256;
|
||||
while (new_cap < sb->count + needed) new_cap *= 2;
|
||||
sb->items = (char *)realloc(sb->items, new_cap);
|
||||
sb->capacity = new_cap;
|
||||
}
|
||||
|
||||
bool sb_read_file(String_Builder *sb, const char *path) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) {
|
||||
build_log(LOG_ERROR, "Could not open %s: %s", path, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
#ifdef _WIN32
|
||||
long long m = _telli64(_fileno(f));
|
||||
#else
|
||||
long long m = ftell(f);
|
||||
#endif
|
||||
if (m < 0) {
|
||||
build_log(LOG_ERROR, "Could not get size of %s: %s", path, strerror(errno));
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
sb_ensure(sb, (size_t)m);
|
||||
fread(sb->items + sb->count, (size_t)m, 1, f);
|
||||
if (ferror(f)) {
|
||||
build_log(LOG_ERROR, "Could not read %s: %s", path, strerror(errno));
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
sb->count += (size_t)m;
|
||||
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
void sb_free(String_Builder *sb) {
|
||||
free(sb->items);
|
||||
sb->items = nullptr;
|
||||
sb->count = 0;
|
||||
sb->capacity = 0;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// File I/O
|
||||
|
||||
bool write_entire_file(const char *path, const void *data, size_t size) {
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) {
|
||||
build_log(LOG_ERROR, "Could not open %s for writing: %s", path, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
const char *buf = (const char *)data;
|
||||
while (size > 0) {
|
||||
size_t n = fwrite(buf, 1, size, f);
|
||||
if (ferror(f)) {
|
||||
build_log(LOG_ERROR, "Could not write to %s: %s", path, strerror(errno));
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
size -= n;
|
||||
buf += n;
|
||||
}
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool delete_file(const char *path) {
|
||||
build_log(LOG_INFO, "Deleting %s", path);
|
||||
#ifdef _WIN32
|
||||
DWORD attr = GetFileAttributesA(path);
|
||||
if (attr == INVALID_FILE_ATTRIBUTES) return true; // doesn't exist
|
||||
if (attr & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
if (!RemoveDirectoryA(path)) {
|
||||
build_log(LOG_ERROR, "Could not delete directory %s", path);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!DeleteFileA(path)) {
|
||||
build_log(LOG_ERROR, "Could not delete file %s", path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (remove(path) < 0 && errno != ENOENT) {
|
||||
build_log(LOG_ERROR, "Could not delete %s: %s", path, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rename_file(const char *old_path, const char *new_path) {
|
||||
build_log(LOG_INFO, "Renaming %s -> %s", old_path, new_path);
|
||||
#ifdef _WIN32
|
||||
if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) {
|
||||
build_log(LOG_ERROR, "Could not rename %s to %s", old_path, new_path);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (rename(old_path, new_path) < 0) {
|
||||
build_log(LOG_ERROR, "Could not rename %s to %s: %s", old_path, new_path, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mkdir_if_not_exists(const char *path) {
|
||||
#ifdef _WIN32
|
||||
int result = _mkdir(path);
|
||||
#else
|
||||
int result = mkdir(path, 0755);
|
||||
#endif
|
||||
if (result < 0) {
|
||||
if (errno == EEXIST) return true;
|
||||
build_log(LOG_ERROR, "Could not create directory %s: %s", path, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
build_log(LOG_INFO, "Created directory %s", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Rebuild checking
|
||||
|
||||
int needs_rebuild(const char *output, const char **inputs, size_t count) {
|
||||
#ifdef _WIN32
|
||||
HANDLE out_h = CreateFile(output, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr);
|
||||
if (out_h == INVALID_HANDLE_VALUE) {
|
||||
if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1;
|
||||
build_log(LOG_ERROR, "Could not open %s", output);
|
||||
return -1;
|
||||
}
|
||||
FILETIME out_time;
|
||||
if (!GetFileTime(out_h, nullptr, nullptr, &out_time)) {
|
||||
CloseHandle(out_h);
|
||||
build_log(LOG_ERROR, "Could not get time of %s", output);
|
||||
return -1;
|
||||
}
|
||||
CloseHandle(out_h);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
HANDLE in_h = CreateFile(inputs[i], GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr);
|
||||
if (in_h == INVALID_HANDLE_VALUE) {
|
||||
build_log(LOG_ERROR, "Could not open %s", inputs[i]);
|
||||
return -1;
|
||||
}
|
||||
FILETIME in_time;
|
||||
if (!GetFileTime(in_h, nullptr, nullptr, &in_time)) {
|
||||
CloseHandle(in_h);
|
||||
build_log(LOG_ERROR, "Could not get time of %s", inputs[i]);
|
||||
return -1;
|
||||
}
|
||||
CloseHandle(in_h);
|
||||
if (CompareFileTime(&in_time, &out_time) == 1) return 1;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
struct stat sb = {};
|
||||
if (stat(output, &sb) < 0) {
|
||||
if (errno == ENOENT) return 1;
|
||||
build_log(LOG_ERROR, "Could not stat %s: %s", output, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
time_t out_time = sb.st_mtime;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (stat(inputs[i], &sb) < 0) {
|
||||
build_log(LOG_ERROR, "Could not stat %s: %s", inputs[i], strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (sb.st_mtime > out_time) return 1;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int needs_rebuild1(const char *output, const char *input) {
|
||||
return needs_rebuild(output, &input, 1);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Command builder and runner
|
||||
|
||||
static void cmd__grow(Cmd *cmd) {
|
||||
size_t new_cap = cmd->capacity ? cmd->capacity * 2 : 32;
|
||||
cmd->items = (const char **)realloc(cmd->items, new_cap * sizeof(const char *));
|
||||
cmd->capacity = new_cap;
|
||||
}
|
||||
|
||||
void cmd__append(Cmd *cmd, size_t n, ...) {
|
||||
va_list args;
|
||||
va_start(args, n);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
const char *arg = va_arg(args, const char *);
|
||||
if (cmd->count >= cmd->capacity) cmd__grow(cmd);
|
||||
cmd->items[cmd->count++] = arg;
|
||||
}
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void cmd__append_arr(Cmd *cmd, const char **args, size_t n) {
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (cmd->count >= cmd->capacity) cmd__grow(cmd);
|
||||
cmd->items[cmd->count++] = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_render(Cmd *cmd, char *buf, size_t buf_size) {
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i < cmd->count && pos < buf_size - 1; i++) {
|
||||
if (i > 0 && pos < buf_size - 1) buf[pos++] = ' ';
|
||||
const char *arg = cmd->items[i];
|
||||
size_t len = strlen(arg);
|
||||
if (pos + len < buf_size - 1) {
|
||||
memcpy(buf + pos, arg, len);
|
||||
pos += len;
|
||||
}
|
||||
}
|
||||
buf[pos] = '\0';
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Properly quote a command line for CreateProcess on Windows.
|
||||
// See: https://learn.microsoft.com/en-gb/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
|
||||
static void cmd_quote_win32(Cmd *cmd, String_Builder *quoted) {
|
||||
for (size_t i = 0; i < cmd->count; i++) {
|
||||
const char *arg = cmd->items[i];
|
||||
size_t len = strlen(arg);
|
||||
if (i > 0) {
|
||||
sb_ensure(quoted, 1);
|
||||
quoted->items[quoted->count++] = ' ';
|
||||
}
|
||||
if (len != 0 && strpbrk(arg, " \t\n\v\"") == nullptr) {
|
||||
sb_ensure(quoted, len);
|
||||
memcpy(quoted->items + quoted->count, arg, len);
|
||||
quoted->count += len;
|
||||
} else {
|
||||
sb_ensure(quoted, len * 2 + 3);
|
||||
quoted->items[quoted->count++] = '"';
|
||||
size_t backslashes = 0;
|
||||
for (size_t j = 0; j < len; j++) {
|
||||
char c = arg[j];
|
||||
if (c == '\\') {
|
||||
backslashes++;
|
||||
} else {
|
||||
if (c == '"') {
|
||||
for (size_t k = 0; k < backslashes + 1; k++)
|
||||
quoted->items[quoted->count++] = '\\';
|
||||
}
|
||||
backslashes = 0;
|
||||
}
|
||||
quoted->items[quoted->count++] = c;
|
||||
}
|
||||
for (size_t k = 0; k < backslashes; k++)
|
||||
quoted->items[quoted->count++] = '\\';
|
||||
quoted->items[quoted->count++] = '"';
|
||||
}
|
||||
}
|
||||
sb_ensure(quoted, 1);
|
||||
quoted->items[quoted->count] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
||||
bool cmd_run(Cmd *cmd) {
|
||||
if (cmd->count == 0) {
|
||||
build_log(LOG_ERROR, "Cannot run empty command");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Log the command
|
||||
{
|
||||
char render_buf[4096];
|
||||
cmd_render(cmd, render_buf, sizeof(render_buf));
|
||||
build_log(LOG_INFO, "CMD: %s", render_buf);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
STARTUPINFO si = {};
|
||||
si.cb = sizeof(si);
|
||||
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
|
||||
PROCESS_INFORMATION pi = {};
|
||||
|
||||
String_Builder quoted = {};
|
||||
cmd_quote_win32(cmd, "ed);
|
||||
|
||||
BOOL ok = CreateProcessA(nullptr, quoted.items, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi);
|
||||
sb_free("ed);
|
||||
|
||||
cmd->count = 0;
|
||||
|
||||
if (!ok) {
|
||||
build_log(LOG_ERROR, "Could not create process for %s", cmd->items[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseHandle(pi.hThread);
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
|
||||
DWORD exit_code;
|
||||
if (!GetExitCodeProcess(pi.hProcess, &exit_code)) {
|
||||
build_log(LOG_ERROR, "Could not get exit code");
|
||||
CloseHandle(pi.hProcess);
|
||||
return false;
|
||||
}
|
||||
CloseHandle(pi.hProcess);
|
||||
|
||||
if (exit_code != 0) {
|
||||
build_log(LOG_ERROR, "Command exited with code %lu", exit_code);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
build_log(LOG_ERROR, "Could not fork: %s", strerror(errno));
|
||||
cmd->count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child: build null-terminated argv
|
||||
const char **argv_null = (const char **)malloc((cmd->count + 1) * sizeof(const char *));
|
||||
memcpy(argv_null, cmd->items, cmd->count * sizeof(const char *));
|
||||
argv_null[cmd->count] = nullptr;
|
||||
execvp(argv_null[0], (char *const *)argv_null);
|
||||
build_log(LOG_ERROR, "Could not exec %s: %s", argv_null[0], strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
cmd->count = 0;
|
||||
|
||||
int wstatus;
|
||||
for (;;) {
|
||||
if (waitpid(pid, &wstatus, 0) < 0) {
|
||||
build_log(LOG_ERROR, "Could not wait on pid %d: %s", pid, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (WIFEXITED(wstatus)) {
|
||||
int code = WEXITSTATUS(wstatus);
|
||||
if (code != 0) {
|
||||
build_log(LOG_ERROR, "Command exited with code %d", code);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (WIFSIGNALED(wstatus)) {
|
||||
build_log(LOG_ERROR, "Command killed by signal %d", WTERMSIG(wstatus));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void cmd_free(Cmd *cmd) {
|
||||
free(cmd->items);
|
||||
cmd->items = nullptr;
|
||||
cmd->count = 0;
|
||||
cmd->capacity = 0;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Self-rebuild
|
||||
|
||||
#ifdef _WIN32
|
||||
# define REBUILD_CMD(binary, source) "cl.exe", "/nologo", "/std:c++20", temp_sprintf("/Fe:%s", binary), source
|
||||
#else
|
||||
# define REBUILD_CMD(binary, source) "c++", "-std=c++20", "-o", binary, source
|
||||
#endif
|
||||
|
||||
void go_rebuild_urself(int argc, char **argv, const char *source) {
|
||||
const char *binary = argv[0];
|
||||
|
||||
#ifdef _WIN32
|
||||
// Ensure .exe extension for Windows
|
||||
size_t len = strlen(binary);
|
||||
if (len < 4 || strcmp(binary + len - 4, ".exe") != 0) {
|
||||
binary = temp_sprintf("%s.exe", binary);
|
||||
}
|
||||
#endif
|
||||
|
||||
int rebuild = needs_rebuild1(binary, source);
|
||||
if (rebuild < 0) exit(1);
|
||||
if (rebuild == 0) return;
|
||||
|
||||
const char *old_binary = temp_sprintf("%s.old", binary);
|
||||
if (!rename_file(binary, old_binary)) exit(1);
|
||||
|
||||
Cmd cmd = {};
|
||||
cmd_append(&cmd, REBUILD_CMD(binary, source));
|
||||
if (!cmd_run(&cmd)) {
|
||||
rename_file(old_binary, binary);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Re-execute with the new binary
|
||||
cmd_append(&cmd, binary);
|
||||
for (int i = 1; i < argc; i++) {
|
||||
cmd__append(&cmd, 1, argv[i]);
|
||||
}
|
||||
if (!cmd_run(&cmd)) exit(1);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#endif // BUILD_IMPLEMENTATION
|
||||
@@ -2,22 +2,22 @@
|
||||
|
||||
#include "base/base_core.h"
|
||||
|
||||
struct AudioEngine;
|
||||
typedef struct AudioEngine AudioEngine;
|
||||
|
||||
struct AudioDeviceInfo {
|
||||
char name[128];
|
||||
S32 id; // index into engine's device list
|
||||
};
|
||||
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);
|
||||
void audio_refresh_devices(AudioEngine *engine);
|
||||
S32 audio_get_device_count(AudioEngine *engine);
|
||||
AudioDeviceInfo *audio_get_device(AudioEngine *engine, S32 index);
|
||||
AudioEngine *audio_create(void *hwnd);
|
||||
void audio_destroy(AudioEngine *engine);
|
||||
void audio_refresh_devices(AudioEngine *engine);
|
||||
S32 audio_get_device_count(AudioEngine *engine);
|
||||
AudioDeviceInfo*audio_get_device(AudioEngine *engine, S32 index);
|
||||
|
||||
B32 audio_open_device(AudioEngine *engine, S32 index);
|
||||
void audio_close_device(AudioEngine *engine);
|
||||
B32 audio_open_device(AudioEngine *engine, S32 index);
|
||||
void audio_close_device(AudioEngine *engine);
|
||||
|
||||
void audio_play_test_tone(AudioEngine *engine);
|
||||
B32 audio_is_test_tone_playing(AudioEngine *engine);
|
||||
void audio_update(AudioEngine *engine, F32 dt);
|
||||
void audio_play_test_tone(AudioEngine *engine);
|
||||
B32 audio_is_test_tone_playing(AudioEngine *engine);
|
||||
void audio_update(AudioEngine *engine, F32 dt);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "audio/audio.h"
|
||||
#include <math.h>
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
#include <math.h>
|
||||
|
||||
#define AUDIO_MAX_DEVICES 32
|
||||
#define AUDIO_MAX_CHANNELS 32
|
||||
@@ -13,24 +13,24 @@
|
||||
////////////////////////////////
|
||||
// ASIO type definitions (minimal set for a host)
|
||||
|
||||
typedef long ASIOError;
|
||||
typedef long ASIOBool;
|
||||
typedef long ASIOError;
|
||||
typedef long ASIOBool;
|
||||
typedef long long int ASIOSamples;
|
||||
typedef long long int ASIOTimeStamp;
|
||||
|
||||
enum {
|
||||
ASE_OK = 0,
|
||||
ASE_SUCCESS = 0x3f4847a0,
|
||||
ASE_NotPresent = -1000,
|
||||
ASE_HWMalfunction = -999,
|
||||
ASE_OK = 0,
|
||||
ASE_SUCCESS = 0x3f4847a0,
|
||||
ASE_NotPresent = -1000,
|
||||
ASE_HWMalfunction = -999,
|
||||
ASE_InvalidParameter = -998,
|
||||
ASE_InvalidMode = -997,
|
||||
ASE_SPNotAdvancing = -996,
|
||||
ASE_NoClock = -995,
|
||||
ASE_NoMemory = -994,
|
||||
ASE_InvalidMode = -997,
|
||||
ASE_SPNotAdvancing = -996,
|
||||
ASE_NoClock = -995,
|
||||
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 {
|
||||
long index;
|
||||
long channel;
|
||||
long group;
|
||||
typedef struct ASIOClockSource {
|
||||
long index;
|
||||
long channel;
|
||||
long group;
|
||||
ASIOBool isCurrentSource;
|
||||
char name[32];
|
||||
};
|
||||
char name[32];
|
||||
} ASIOClockSource;
|
||||
|
||||
struct ASIOChannelInfo {
|
||||
long channel;
|
||||
ASIOBool isInput;
|
||||
ASIOBool isActive;
|
||||
long channelGroup;
|
||||
typedef struct ASIOChannelInfo {
|
||||
long channel;
|
||||
ASIOBool isInput;
|
||||
ASIOBool isActive;
|
||||
long channelGroup;
|
||||
ASIOSampleType type;
|
||||
char name[32];
|
||||
};
|
||||
char name[32];
|
||||
} ASIOChannelInfo;
|
||||
|
||||
struct ASIOBufferInfo {
|
||||
typedef struct ASIOBufferInfo {
|
||||
ASIOBool isInput;
|
||||
long channelNum;
|
||||
void *buffers[2]; // F64 buffer
|
||||
};
|
||||
} ASIOBufferInfo;
|
||||
|
||||
struct ASIOTimeCode {
|
||||
F64 speed;
|
||||
ASIOSamples timeCodeSamples;
|
||||
unsigned long flags;
|
||||
char future[64];
|
||||
};
|
||||
typedef struct ASIOTimeCode {
|
||||
F64 speed;
|
||||
ASIOSamples timeCodeSamples;
|
||||
unsigned long flags;
|
||||
char future[64];
|
||||
} ASIOTimeCode;
|
||||
|
||||
struct AsioTimeInfo {
|
||||
F64 speed;
|
||||
ASIOTimeStamp systemTime;
|
||||
ASIOSamples samplePosition;
|
||||
F64 sampleRate;
|
||||
unsigned long flags;
|
||||
char reserved[12];
|
||||
};
|
||||
typedef struct AsioTimeInfo {
|
||||
F64 speed;
|
||||
ASIOTimeStamp systemTime;
|
||||
ASIOSamples samplePosition;
|
||||
F64 sampleRate;
|
||||
unsigned long flags;
|
||||
char reserved[12];
|
||||
} AsioTimeInfo;
|
||||
|
||||
struct ASIOTime {
|
||||
long reserved[4];
|
||||
AsioTimeInfo timeInfo;
|
||||
ASIOTimeCode timeCode;
|
||||
};
|
||||
typedef struct ASIOTime {
|
||||
long reserved[4];
|
||||
AsioTimeInfo timeInfo;
|
||||
ASIOTimeCode timeCode;
|
||||
} ASIOTime;
|
||||
|
||||
struct ASIOCallbacks {
|
||||
void (*bufferSwitch)(long doubleBufferIndex, ASIOBool directProcess);
|
||||
void (*sampleRateDidChange)(F64 sRate);
|
||||
long (*asioMessage)(long selector, long value, void *message, F64 *opt);
|
||||
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 {
|
||||
char name[128];
|
||||
typedef struct AsioDriverInfo {
|
||||
char name[128];
|
||||
CLSID clsid;
|
||||
};
|
||||
} AsioDriverInfo;
|
||||
|
||||
struct AudioEngine {
|
||||
void *hwnd; // HWND for ASIO init
|
||||
@@ -162,31 +170,31 @@ struct AudioEngine {
|
||||
// Device enumeration
|
||||
AudioDeviceInfo devices[AUDIO_MAX_DEVICES];
|
||||
AsioDriverInfo drivers[AUDIO_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
S32 device_count;
|
||||
|
||||
// Active driver
|
||||
IASIO *driver;
|
||||
S32 active_device_index; // -1 = none
|
||||
IASIO *driver;
|
||||
S32 active_device_index; // -1 = none
|
||||
|
||||
// Buffer state
|
||||
ASIOBufferInfo buffer_infos[AUDIO_MAX_CHANNELS];
|
||||
long num_output_channels;
|
||||
long num_input_channels;
|
||||
long buffer_size;
|
||||
F64 sample_rate;
|
||||
ASIOSampleType output_sample_type;
|
||||
ASIOCallbacks callbacks;
|
||||
ASIOBufferInfo buffer_infos[AUDIO_MAX_CHANNELS];
|
||||
long num_output_channels;
|
||||
long num_input_channels;
|
||||
long buffer_size;
|
||||
F64 sample_rate;
|
||||
ASIOSampleType output_sample_type;
|
||||
ASIOCallbacks callbacks;
|
||||
|
||||
// Test tone state (accessed from callback thread)
|
||||
volatile LONG test_tone_active;
|
||||
volatile LONG test_tone_samples_remaining;
|
||||
F64 test_tone_phase; // written only from callback thread
|
||||
volatile LONG test_tone_active;
|
||||
volatile LONG test_tone_samples_remaining;
|
||||
F64 test_tone_phase; // written only from callback thread
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// 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
|
||||
@@ -204,9 +212,9 @@ static void write_sample(void *dest, ASIOSampleType type, F64 value) {
|
||||
case ASIOSTInt24LSB: {
|
||||
S32 s = (S32)(value * 8388607.0);
|
||||
U8 *d = (U8 *)dest;
|
||||
d[0] = (U8)(s & 0xFF);
|
||||
d[1] = (U8)((s >> 8) & 0xFF);
|
||||
d[2] = (U8)((s >> 16) & 0xFF);
|
||||
d[0] = (U8)(s & 0xFF);
|
||||
d[1] = (U8)((s >> 8) & 0xFF);
|
||||
d[2] = (U8)((s >> 16) & 0xFF);
|
||||
} break;
|
||||
case ASIOSTInt32LSB: {
|
||||
S32 s = (S32)(value * 2147483647.0);
|
||||
@@ -229,12 +237,12 @@ static void write_sample(void *dest, ASIOSampleType type, F64 value) {
|
||||
|
||||
static int sample_type_size(ASIOSampleType type) {
|
||||
switch (type) {
|
||||
case ASIOSTInt16LSB: return 2;
|
||||
case ASIOSTInt24LSB: return 3;
|
||||
case ASIOSTInt32LSB: return 4;
|
||||
case ASIOSTInt16LSB: return 2;
|
||||
case ASIOSTInt24LSB: return 3;
|
||||
case ASIOSTInt32LSB: return 4;
|
||||
case ASIOSTFloat32LSB: return 4;
|
||||
case ASIOSTFloat64LSB: return 8;
|
||||
default: return 4;
|
||||
default: return 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,9 +255,9 @@ static void asio_buffer_switch(long doubleBufferIndex, ASIOBool directProcess) {
|
||||
AudioEngine *engine = g_audio_engine;
|
||||
if (!engine) return;
|
||||
|
||||
long buf_size = engine->buffer_size;
|
||||
ASIOSampleType type = engine->output_sample_type;
|
||||
S32 bytes_per_sample = sample_type_size(type);
|
||||
long buf_size = engine->buffer_size;
|
||||
ASIOSampleType type = engine->output_sample_type;
|
||||
S32 bytes_per_sample = sample_type_size(type);
|
||||
|
||||
LONG tone_active = InterlockedCompareExchange(&engine->test_tone_active, 0, 0);
|
||||
|
||||
@@ -266,9 +274,9 @@ static void asio_buffer_switch(long doubleBufferIndex, ASIOBool directProcess) {
|
||||
}
|
||||
|
||||
if (tone_active) {
|
||||
F64 phase = engine->test_tone_phase;
|
||||
F64 phase_inc = 2.0 * AUDIO_PI * AUDIO_TEST_TONE_HZ / engine->sample_rate;
|
||||
LONG remaining = InterlockedCompareExchange(&engine->test_tone_samples_remaining, 0, 0);
|
||||
F64 phase = engine->test_tone_phase;
|
||||
F64 phase_inc = 2.0 * AUDIO_PI * AUDIO_TEST_TONE_HZ / engine->sample_rate;
|
||||
LONG remaining = InterlockedCompareExchange(&engine->test_tone_samples_remaining, 0, 0);
|
||||
long samples_to_gen = (remaining < buf_size) ? (long)remaining : buf_size;
|
||||
|
||||
for (long s = 0; s < buf_size; s++) {
|
||||
@@ -346,12 +354,12 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\ASIO", 0, KEY_READ, &asio_key) != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
char subkey_name[256];
|
||||
char subkey_name[256];
|
||||
DWORD subkey_name_len;
|
||||
|
||||
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,17 +367,17 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
continue;
|
||||
|
||||
// Read CLSID string
|
||||
char clsid_str[64] = {};
|
||||
DWORD clsid_len = sizeof(clsid_str);
|
||||
DWORD type = 0;
|
||||
if (RegQueryValueExA(driver_key, "CLSID", nullptr, &type, (LPBYTE)clsid_str, &clsid_len) != ERROR_SUCCESS ||
|
||||
char clsid_str[64] = {0};
|
||||
DWORD clsid_len = sizeof(clsid_str);
|
||||
DWORD type = 0;
|
||||
if (RegQueryValueExA(driver_key, "CLSID", NULL, &type, (LPBYTE)clsid_str, &clsid_len) != ERROR_SUCCESS ||
|
||||
type != REG_SZ) {
|
||||
RegCloseKey(driver_key);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse CLSID string to GUID
|
||||
CLSID clsid;
|
||||
CLSID clsid;
|
||||
wchar_t clsid_wide[64];
|
||||
MultiByteToWideChar(CP_ACP, 0, clsid_str, -1, clsid_wide, 64);
|
||||
if (CLSIDFromString(clsid_wide, &clsid) != S_OK) {
|
||||
@@ -379,7 +387,7 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
|
||||
S32 idx = engine->device_count++;
|
||||
strncpy_s(engine->devices[idx].name, sizeof(engine->devices[idx].name), subkey_name, _TRUNCATE);
|
||||
engine->devices[idx].id = idx;
|
||||
engine->devices[idx].id = idx;
|
||||
engine->drivers[idx].clsid = clsid;
|
||||
strncpy_s(engine->drivers[idx].name, sizeof(engine->drivers[idx].name), subkey_name, _TRUNCATE);
|
||||
|
||||
@@ -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));
|
||||
engine->hwnd = hwnd;
|
||||
AudioEngine *engine = (AudioEngine *)calloc(1, sizeof(AudioEngine));
|
||||
engine->hwnd = hwnd;
|
||||
engine->active_device_index = -1;
|
||||
g_audio_engine = engine;
|
||||
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,
|
||||
(void **)&driver);
|
||||
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 = {};
|
||||
chan_info.channel = 0;
|
||||
chan_info.isInput = 0;
|
||||
if (driver->getChannelInfo(&chan_info) != ASE_OK) {
|
||||
driver->Release();
|
||||
ASIOChannelInfo chan_info = {0};
|
||||
chan_info.channel = 0;
|
||||
chan_info.isInput = 0;
|
||||
if (driver->lpVtbl->getChannelInfo(driver, &chan_info) != ASE_OK) {
|
||||
driver->lpVtbl->Release(driver);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -488,47 +495,47 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
// Setup buffer infos for output channels only
|
||||
memset(engine->buffer_infos, 0, sizeof(engine->buffer_infos));
|
||||
for (long ch = 0; ch < num_out; ch++) {
|
||||
engine->buffer_infos[ch].isInput = 0;
|
||||
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
|
||||
engine->callbacks.bufferSwitch = asio_buffer_switch;
|
||||
engine->callbacks.sampleRateDidChange = asio_sample_rate_changed;
|
||||
engine->callbacks.asioMessage = asio_message;
|
||||
engine->callbacks.bufferSwitch = asio_buffer_switch;
|
||||
engine->callbacks.sampleRateDidChange = asio_sample_rate_changed;
|
||||
engine->callbacks.asioMessage = asio_message;
|
||||
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;
|
||||
}
|
||||
|
||||
// Store state
|
||||
engine->driver = driver;
|
||||
engine->active_device_index = index;
|
||||
engine->num_output_channels = num_out;
|
||||
engine->num_input_channels = num_in;
|
||||
engine->buffer_size = preferred_size;
|
||||
engine->sample_rate = sample_rate;
|
||||
engine->output_sample_type = chan_info.type;
|
||||
engine->test_tone_active = 0;
|
||||
engine->driver = driver;
|
||||
engine->active_device_index = index;
|
||||
engine->num_output_channels = num_out;
|
||||
engine->num_input_channels = num_in;
|
||||
engine->buffer_size = preferred_size;
|
||||
engine->sample_rate = sample_rate;
|
||||
engine->output_sample_type = chan_info.type;
|
||||
engine->test_tone_active = 0;
|
||||
engine->test_tone_samples_remaining = 0;
|
||||
engine->test_tone_phase = 0.0;
|
||||
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,19 +546,19 @@ 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;
|
||||
engine->test_tone_phase = 0.0;
|
||||
}
|
||||
|
||||
void audio_play_test_tone(AudioEngine *engine) {
|
||||
if (!engine->driver) return;
|
||||
|
||||
engine->test_tone_phase = 0.0;
|
||||
LONG total_samples = (LONG)(engine->sample_rate * AUDIO_TEST_TONE_SEC);
|
||||
LONG total_samples = (LONG)(engine->sample_rate * AUDIO_TEST_TONE_SEC);
|
||||
InterlockedExchange(&engine->test_tone_samples_remaining, total_samples);
|
||||
InterlockedExchange(&engine->test_tone_active, 1);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "audio/audio.h"
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
@@ -13,42 +13,40 @@
|
||||
#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];
|
||||
CoreAudioDeviceInfo ca_devices[AUDIO_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
AudioDeviceInfo devices[AUDIO_MAX_DEVICES];
|
||||
CoreAudioDeviceInfo ca_devices[AUDIO_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
|
||||
AUGraph graph;
|
||||
AudioUnit output_unit;
|
||||
S32 active_device_index;
|
||||
F64 sample_rate;
|
||||
S32 num_channels;
|
||||
AUGraph graph;
|
||||
AudioUnit output_unit;
|
||||
S32 active_device_index;
|
||||
F64 sample_rate;
|
||||
S32 num_channels;
|
||||
|
||||
// Test tone state (accessed from audio render thread)
|
||||
_Atomic S32 test_tone_active;
|
||||
_Atomic S32 test_tone_samples_remaining;
|
||||
F64 test_tone_phase;
|
||||
_Atomic S32 test_tone_active;
|
||||
_Atomic S32 test_tone_samples_remaining;
|
||||
F64 test_tone_phase;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// Audio render callback
|
||||
|
||||
static AudioEngine *g_audio_engine = nullptr;
|
||||
static AudioEngine *g_audio_engine = NULL;
|
||||
|
||||
static OSStatus audio_render_callback(void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) {
|
||||
(void)inRefCon;
|
||||
(void)ioActionFlags;
|
||||
(void)inTimeStamp;
|
||||
(void)inBusNumber;
|
||||
static OSStatus audio_render_callback(void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData)
|
||||
{
|
||||
(void)inRefCon; (void)ioActionFlags; (void)inTimeStamp; (void)inBusNumber;
|
||||
|
||||
AudioEngine *engine = g_audio_engine;
|
||||
if (!engine) {
|
||||
@@ -65,15 +63,15 @@ static OSStatus audio_render_callback(void *inRefCon,
|
||||
return noErr;
|
||||
}
|
||||
|
||||
F64 phase = engine->test_tone_phase;
|
||||
F64 phase_inc = 2.0 * AUDIO_PI * AUDIO_TEST_TONE_HZ / engine->sample_rate;
|
||||
S32 remaining = atomic_load(&engine->test_tone_samples_remaining);
|
||||
F64 phase = engine->test_tone_phase;
|
||||
F64 phase_inc = 2.0 * AUDIO_PI * AUDIO_TEST_TONE_HZ / engine->sample_rate;
|
||||
S32 remaining = atomic_load(&engine->test_tone_samples_remaining);
|
||||
S32 samples_to_gen = (remaining < (S32)inNumberFrames) ? remaining : (S32)inNumberFrames;
|
||||
|
||||
// CoreAudio with kAudioFormatFlagIsNonInterleaved: each buffer = one channel
|
||||
for (UInt32 buf = 0; buf < ioData->mNumberBuffers; buf++) {
|
||||
F32 *out = (F32 *)ioData->mBuffers[buf].mData;
|
||||
F64 p = phase;
|
||||
F64 p = phase;
|
||||
for (UInt32 s = 0; s < inNumberFrames; s++) {
|
||||
if ((S32)s < samples_to_gen) {
|
||||
out[s] = (F32)(sin(p) * 0.5); // -6dB
|
||||
@@ -87,8 +85,7 @@ static OSStatus audio_render_callback(void *inRefCon,
|
||||
|
||||
// Advance phase using first channel's traversal
|
||||
phase += phase_inc * samples_to_gen;
|
||||
while (phase >= 2.0 * AUDIO_PI)
|
||||
phase -= 2.0 * AUDIO_PI;
|
||||
while (phase >= 2.0 * AUDIO_PI) phase -= 2.0 * AUDIO_PI;
|
||||
engine->test_tone_phase = phase;
|
||||
|
||||
S32 new_remaining = atomic_fetch_sub(&engine->test_tone_samples_remaining, samples_to_gen);
|
||||
@@ -113,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;
|
||||
}
|
||||
@@ -134,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;
|
||||
}
|
||||
@@ -157,16 +154,16 @@ static void enumerate_output_devices(AudioEngine *engine) {
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
|
||||
CFStringRef name_ref = nullptr;
|
||||
UInt32 name_size = sizeof(name_ref);
|
||||
if (AudioObjectGetPropertyData(device_ids[i], &name_prop, 0, nullptr, &name_size, &name_ref) != noErr)
|
||||
CFStringRef name_ref = NULL;
|
||||
UInt32 name_size = sizeof(name_ref);
|
||||
if (AudioObjectGetPropertyData(device_ids[i], &name_prop, 0, NULL, &name_size, &name_ref) != noErr)
|
||||
continue;
|
||||
|
||||
S32 idx = engine->device_count++;
|
||||
CFStringGetCString(name_ref, engine->devices[idx].name, sizeof(engine->devices[idx].name),
|
||||
kCFStringEncodingUTF8);
|
||||
CFRelease(name_ref);
|
||||
engine->devices[idx].id = idx;
|
||||
engine->devices[idx].id = idx;
|
||||
engine->ca_devices[idx].device_id = device_ids[i];
|
||||
}
|
||||
|
||||
@@ -179,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);
|
||||
@@ -192,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) {
|
||||
@@ -206,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];
|
||||
}
|
||||
|
||||
@@ -221,35 +217,35 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
if (NewAUGraph(&engine->graph) != noErr) return false;
|
||||
|
||||
// Add HAL output node
|
||||
AudioComponentDescription output_desc = {};
|
||||
output_desc.componentType = kAudioUnitType_Output;
|
||||
output_desc.componentSubType = kAudioUnitSubType_HALOutput;
|
||||
output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
AudioComponentDescription output_desc = {0};
|
||||
output_desc.componentType = kAudioUnitType_Output;
|
||||
output_desc.componentSubType = kAudioUnitSubType_HALOutput;
|
||||
output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Set device
|
||||
if (AudioUnitSetProperty(engine->output_unit, kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0, &device_id, sizeof(device_id)) != noErr) {
|
||||
kAudioUnitScope_Global, 0, &device_id, sizeof(device_id)) != noErr) {
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
engine->graph = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -260,52 +256,52 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
Float64 sample_rate = 44100.0;
|
||||
UInt32 rate_size = sizeof(sample_rate);
|
||||
AudioObjectGetPropertyData(device_id, &rate_prop, 0, nullptr, &rate_size, &sample_rate);
|
||||
UInt32 rate_size = sizeof(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 = {};
|
||||
fmt.mSampleRate = sample_rate;
|
||||
fmt.mFormatID = kAudioFormatLinearPCM;
|
||||
fmt.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsPacked;
|
||||
fmt.mBitsPerChannel = 32;
|
||||
fmt.mChannelsPerFrame = 2;
|
||||
fmt.mFramesPerPacket = 1;
|
||||
fmt.mBytesPerFrame = 4;
|
||||
fmt.mBytesPerPacket = 4;
|
||||
engine->num_channels = 2;
|
||||
AudioStreamBasicDescription fmt = {0};
|
||||
fmt.mSampleRate = sample_rate;
|
||||
fmt.mFormatID = kAudioFormatLinearPCM;
|
||||
fmt.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsPacked;
|
||||
fmt.mBitsPerChannel = 32;
|
||||
fmt.mChannelsPerFrame = 2;
|
||||
fmt.mFramesPerPacket = 1;
|
||||
fmt.mBytesPerFrame = 4;
|
||||
fmt.mBytesPerPacket = 4;
|
||||
engine->num_channels = 2;
|
||||
|
||||
AudioUnitSetProperty(engine->output_unit, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &fmt, sizeof(fmt));
|
||||
kAudioUnitScope_Input, 0, &fmt, sizeof(fmt));
|
||||
|
||||
// Set render callback
|
||||
AURenderCallbackStruct cb = {};
|
||||
cb.inputProc = audio_render_callback;
|
||||
cb.inputProcRefCon = engine;
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
engine->active_device_index = index;
|
||||
engine->test_tone_phase = 0.0;
|
||||
engine->test_tone_phase = 0.0;
|
||||
atomic_store(&engine->test_tone_active, 0);
|
||||
atomic_store(&engine->test_tone_samples_remaining, 0);
|
||||
|
||||
@@ -321,17 +317,17 @@ 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;
|
||||
engine->test_tone_phase = 0.0;
|
||||
}
|
||||
|
||||
void audio_play_test_tone(AudioEngine *engine) {
|
||||
if (!engine->graph) return;
|
||||
|
||||
engine->test_tone_phase = 0.0;
|
||||
S32 total_samples = (S32)(engine->sample_rate * AUDIO_TEST_TONE_SEC);
|
||||
S32 total_samples = (S32)(engine->sample_rate * AUDIO_TEST_TONE_SEC);
|
||||
atomic_store(&engine->test_tone_samples_remaining, total_samples);
|
||||
atomic_store(&engine->test_tone_active, 1);
|
||||
}
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
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;
|
||||
arena->cap = cap;
|
||||
arena->base = mem + sizeof(Arena);
|
||||
arena->pos = 0;
|
||||
arena->cap = cap;
|
||||
return arena;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
U8 *base;
|
||||
U64 pos;
|
||||
U64 cap;
|
||||
};
|
||||
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
|
||||
|
||||
@@ -2,22 +2,23 @@
|
||||
// base_core.h - Fundamental types, macros, and linked list helpers
|
||||
// Inspired by raddebugger's base_core.h
|
||||
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
////////////////////////////////
|
||||
// Codebase keywords
|
||||
|
||||
#ifndef __APPLE__
|
||||
#define internal static
|
||||
#define global static
|
||||
#define internal static
|
||||
#define global static
|
||||
#endif
|
||||
#define local_persist static
|
||||
|
||||
#define trvke true
|
||||
#define trvke 1
|
||||
|
||||
////////////////////////////////
|
||||
// Base types
|
||||
@@ -60,26 +61,25 @@ typedef double F64;
|
||||
#define Max(A, B) (((A) > (B)) ? (A) : (B))
|
||||
#define ClampTop(A, X) Min(A, X)
|
||||
#define ClampBot(X, B) Max(X, B)
|
||||
#define Clamp(A, X, B) (((X) < (A)) ? (A) : ((X) > (B)) ? (B) \
|
||||
: (X))
|
||||
#define Clamp(A, X, B) (((X) < (A)) ? (A) : ((X) > (B)) ? (B) : (X))
|
||||
|
||||
////////////////////////////////
|
||||
// Alignment / Sizing
|
||||
|
||||
#define AlignPow2(x, b) (((x) + (b)-1) & (~((b)-1)))
|
||||
#define AlignDownPow2(x, b) ((x) & (~((b)-1)))
|
||||
#define ArrayCount(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define AlignPow2(x, b) (((x) + (b) - 1) & (~((b) - 1)))
|
||||
#define AlignDownPow2(x, b) ((x) & (~((b) - 1)))
|
||||
#define ArrayCount(a) (sizeof(a) / sizeof((a)[0]))
|
||||
|
||||
////////////////////////////////
|
||||
// Memory macros
|
||||
|
||||
#define MemoryCopy(dst, src, size) memmove((dst), (src), (size))
|
||||
#define MemorySet(dst, byte, size) memset((dst), (byte), (size))
|
||||
#define MemoryCompare(a, b, size) memcmp((a), (b), (size))
|
||||
#define MemoryZero(s, z) memset((s), 0, (z))
|
||||
#define MemoryZeroStruct(s) MemoryZero((s), sizeof(*(s)))
|
||||
#define MemoryZeroArray(a) MemoryZero((a), sizeof(a))
|
||||
#define MemoryMatch(a, b, z) (MemoryCompare((a), (b), (z)) == 0)
|
||||
#define MemoryCopy(dst, src, size) memmove((dst), (src), (size))
|
||||
#define MemorySet(dst, byte, size) memset((dst), (byte), (size))
|
||||
#define MemoryCompare(a, b, size) memcmp((a), (b), (size))
|
||||
#define MemoryZero(s, z) memset((s), 0, (z))
|
||||
#define MemoryZeroStruct(s) MemoryZero((s), sizeof(*(s)))
|
||||
#define MemoryZeroArray(a) MemoryZero((a), sizeof(a))
|
||||
#define MemoryMatch(a, b, z) (MemoryCompare((a), (b), (z)) == 0)
|
||||
|
||||
////////////////////////////////
|
||||
// Pointer / integer casts
|
||||
@@ -110,33 +110,25 @@ typedef double F64;
|
||||
#define Glue_(A, B) A##B
|
||||
#define Glue(A, B) Glue_(A, B)
|
||||
|
||||
#define Swap(T, a, b) \
|
||||
do { \
|
||||
T t__ = a; \
|
||||
a = b; \
|
||||
b = t__; \
|
||||
} while (0)
|
||||
#define Swap(T, a, b) do { T t__ = a; a = b; b = t__; } while (0)
|
||||
|
||||
////////////////////////////////
|
||||
// Assert
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define Trap() __debugbreak()
|
||||
# define Trap() __debugbreak()
|
||||
#elif defined(__clang__) || defined(__GNUC__)
|
||||
#define Trap() __builtin_trap()
|
||||
# define Trap() __builtin_trap()
|
||||
#else
|
||||
#define Trap() (*(volatile int *)0 = 0)
|
||||
# define Trap() (*(volatile int *)0 = 0)
|
||||
#endif
|
||||
|
||||
#define AssertAlways(x) \
|
||||
do { \
|
||||
if (!(x)) { Trap(); } \
|
||||
} while (0)
|
||||
#define AssertAlways(x) do { if (!(x)) { Trap(); } } while (0)
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define Assert(x) AssertAlways(x)
|
||||
# define Assert(x) AssertAlways(x)
|
||||
#else
|
||||
#define Assert(x) (void)(x)
|
||||
# define Assert(x) (void)(x)
|
||||
#endif
|
||||
|
||||
#define InvalidPath Assert(!"Invalid Path!")
|
||||
@@ -150,17 +142,22 @@ typedef double F64;
|
||||
#define SetNil(nil, p) ((p) = nil)
|
||||
|
||||
// Doubly-linked-list (with nil support)
|
||||
#define DLLInsert_NPZ(nil, f, l, p, n, next, prev) \
|
||||
(CheckNil(nil, f) ? ((f) = (l) = (n), SetNil(nil, (n)->next), SetNil(nil, (n)->prev)) : CheckNil(nil, p) ? ((n)->next = (f), (f)->prev = (n), (f) = (n), SetNil(nil, (n)->prev)) \
|
||||
: ((p) == (l)) ? ((l)->next = (n), (n)->prev = (l), (l) = (n), SetNil(nil, (n)->next)) \
|
||||
: (((!CheckNil(nil, p) && CheckNil(nil, (p)->next)) ? (0) : ((p)->next->prev = (n))), ((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p))))
|
||||
#define DLLInsert_NPZ(nil, f, l, p, n, next, prev) \
|
||||
(CheckNil(nil, f) ? \
|
||||
((f) = (l) = (n), SetNil(nil, (n)->next), SetNil(nil, (n)->prev)) : \
|
||||
CheckNil(nil, p) ? \
|
||||
((n)->next = (f), (f)->prev = (n), (f) = (n), SetNil(nil, (n)->prev)) : \
|
||||
((p) == (l)) ? \
|
||||
((l)->next = (n), (n)->prev = (l), (l) = (n), SetNil(nil, (n)->next)) : \
|
||||
(((!CheckNil(nil, p) && CheckNil(nil, (p)->next)) ? (0) : ((p)->next->prev = (n))), \
|
||||
((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p))))
|
||||
|
||||
#define DLLPushBack_NPZ(nil, f, l, n, next, prev) DLLInsert_NPZ(nil, f, l, l, n, next, prev)
|
||||
#define DLLPushFront_NPZ(nil, f, l, n, next, prev) DLLInsert_NPZ(nil, l, f, f, n, prev, next)
|
||||
|
||||
#define DLLRemove_NPZ(nil, f, l, n, next, prev) \
|
||||
(((n) == (f) ? (f) = (n)->next : (0)), \
|
||||
((n) == (l) ? (l) = (l)->prev : (0)), \
|
||||
#define DLLRemove_NPZ(nil, f, l, n, next, prev) \
|
||||
(((n) == (f) ? (f) = (n)->next : (0)), \
|
||||
((n) == (l) ? (l) = (l)->prev : (0)), \
|
||||
(CheckNil(nil, (n)->prev) ? (0) : ((n)->prev->next = (n)->next)), \
|
||||
(CheckNil(nil, (n)->next) ? (0) : ((n)->next->prev = (n)->prev)))
|
||||
|
||||
@@ -171,7 +168,9 @@ typedef double F64;
|
||||
|
||||
// Singly-linked queue (doubly-headed)
|
||||
#define SLLQueuePush_NZ(nil, f, l, n, next) \
|
||||
(CheckNil(nil, f) ? ((f) = (l) = (n), SetNil(nil, (n)->next)) : ((l)->next = (n), (l) = (n), SetNil(nil, (n)->next)))
|
||||
(CheckNil(nil, f) ? \
|
||||
((f) = (l) = (n), SetNil(nil, (n)->next)) : \
|
||||
((l)->next = (n), (l) = (n), SetNil(nil, (n)->next)))
|
||||
|
||||
#define SLLQueuePush(f, l, n) SLLQueuePush_NZ(0, f, l, n, next)
|
||||
#define SLLQueuePushFront(f, l, n) (((n)->next = (f)), ((f) = (n)))
|
||||
|
||||
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"
|
||||
@@ -2,7 +2,7 @@
|
||||
// base_inc.h - Umbrella include for the base layer
|
||||
// Include this one header to get all base types.
|
||||
|
||||
#include "base/base_arena.h"
|
||||
#include "base/base_core.h"
|
||||
#include "base/base_arena.h"
|
||||
#include "base/base_math.h"
|
||||
#include "base/base_strings.h"
|
||||
|
||||
@@ -7,110 +7,95 @@
|
||||
////////////////////////////////
|
||||
// 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; }
|
||||
static inline F32 v2f32_axis(Vec2F32 v, Axis2 a) { return a == Axis2_X ? v.x : v.y; }
|
||||
static inline void v2f32_set_axis(Vec2F32 *v, Axis2 a, F32 val) {
|
||||
if (a == Axis2_X) v->x = val;
|
||||
else v->y = val;
|
||||
if (a == Axis2_X) v->x = val; else v->y = 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};
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Rng2F32 operations
|
||||
|
||||
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 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 (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) },
|
||||
{ Min(a.p1.x, b.p1.x), Min(a.p1.y, b.p1.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)}};
|
||||
}
|
||||
|
||||
// Axis-indexed range dimension
|
||||
@@ -122,4 +107,4 @@ static inline F32 rng2f32_dim_axis(Rng2F32 r, Axis2 a) {
|
||||
// F32 helpers
|
||||
|
||||
static inline F32 lerp_1f32(F32 a, F32 b, F32 t) { return a + (b - a) * t; }
|
||||
static inline F32 abs_f32(F32 x) { return x < 0 ? -x : x; }
|
||||
static inline F32 abs_f32(F32 x) { return x < 0 ? -x : x; }
|
||||
|
||||
@@ -5,27 +5,29 @@ 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) {
|
||||
Str8Node *node = push_array(arena, Str8Node, 1);
|
||||
node->string = s;
|
||||
node->string = s;
|
||||
SLLQueuePush(list->first, list->last, node);
|
||||
list->count++;
|
||||
list->total_size += s.size;
|
||||
@@ -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)
|
||||
|
||||
2092
src/main.c
Normal file
2092
src/main.c
Normal file
File diff suppressed because it is too large
Load Diff
1974
src/main.cpp
1974
src/main.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
#include "platform/platform.h"
|
||||
|
||||
enum MenuCmd {
|
||||
typedef enum MenuCmd {
|
||||
MENU_NONE = 0,
|
||||
MENU_FILE_NEW,
|
||||
MENU_FILE_OPEN,
|
||||
@@ -15,29 +15,29 @@ enum MenuCmd {
|
||||
MENU_VIEW_DEMO,
|
||||
MENU_VIEW_MIDI_DEVICES,
|
||||
MENU_PREFERENCES_EDIT,
|
||||
};
|
||||
} MenuCmd;
|
||||
|
||||
static void setup_menus(PlatformWindow *window) {
|
||||
PlatformMenuItem file_items[] = {
|
||||
{ "New", MENU_FILE_NEW },
|
||||
{ "Open...", MENU_FILE_OPEN },
|
||||
{ "Save", MENU_FILE_SAVE },
|
||||
{ "New", MENU_FILE_NEW },
|
||||
{ "Open...", MENU_FILE_OPEN },
|
||||
{ "Save", MENU_FILE_SAVE },
|
||||
{ "Save As...", MENU_FILE_SAVE_AS },
|
||||
{ nullptr, 0 },
|
||||
{ "Exit", MENU_FILE_EXIT },
|
||||
{ NULL, 0 },
|
||||
{ "Exit", MENU_FILE_EXIT },
|
||||
};
|
||||
|
||||
PlatformMenuItem import_items[] = {
|
||||
{ "Audio...", MENU_IMPORT_AUDIO },
|
||||
{ "MIDI...", MENU_IMPORT_MIDI },
|
||||
{ "Audio...", MENU_IMPORT_AUDIO },
|
||||
{ "MIDI...", MENU_IMPORT_MIDI },
|
||||
};
|
||||
|
||||
PlatformMenuItem view_items[] = {
|
||||
{ "Browser", MENU_VIEW_BROWSER },
|
||||
{ "Browser", MENU_VIEW_BROWSER },
|
||||
{ "Properties", MENU_VIEW_PROPERTIES },
|
||||
{ "Log", MENU_VIEW_LOG },
|
||||
{ nullptr, 0 },
|
||||
{ "Demo", MENU_VIEW_DEMO },
|
||||
{ "Log", MENU_VIEW_LOG },
|
||||
{ NULL, 0 },
|
||||
{ "Demo", MENU_VIEW_DEMO },
|
||||
{ "MIDI Devices", MENU_VIEW_MIDI_DEVICES },
|
||||
};
|
||||
|
||||
@@ -46,10 +46,10 @@ static void setup_menus(PlatformWindow *window) {
|
||||
};
|
||||
|
||||
PlatformMenu menus[] = {
|
||||
{ "File", file_items, sizeof(file_items) / sizeof(file_items[0]) },
|
||||
{ "Import", import_items, sizeof(import_items) / sizeof(import_items[0]) },
|
||||
{ "View", view_items, sizeof(view_items) / sizeof(view_items[0]) },
|
||||
{ "Preferences", prefs_items, sizeof(prefs_items) / sizeof(prefs_items[0]) },
|
||||
{ "File", file_items, sizeof(file_items) / sizeof(file_items[0]) },
|
||||
{ "Import", import_items, sizeof(import_items) / sizeof(import_items[0]) },
|
||||
{ "View", view_items, sizeof(view_items) / sizeof(view_items[0]) },
|
||||
{ "Preferences", prefs_items, sizeof(prefs_items) / sizeof(prefs_items[0]) },
|
||||
};
|
||||
|
||||
platform_set_menu(window, menus, sizeof(menus) / sizeof(menus[0]));
|
||||
@@ -2,30 +2,30 @@
|
||||
|
||||
#include "base/base_core.h"
|
||||
|
||||
struct MidiEngine;
|
||||
typedef struct MidiEngine MidiEngine;
|
||||
|
||||
struct MidiDeviceInfo {
|
||||
char name[64];
|
||||
S32 id;
|
||||
B32 is_input;
|
||||
B32 active; // true when note(s) currently held
|
||||
B32 releasing; // true during release flash
|
||||
S32 velocity; // last note-on velocity (0-127)
|
||||
S32 note; // last MIDI note number (0-127)
|
||||
};
|
||||
typedef struct MidiDeviceInfo {
|
||||
char name[64];
|
||||
S32 id;
|
||||
B32 is_input;
|
||||
B32 active; // true when note(s) currently held
|
||||
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);
|
||||
S32 midi_get_device_count(MidiEngine *engine);
|
||||
MidiDeviceInfo *midi_get_device(MidiEngine *engine, S32 index);
|
||||
|
||||
void midi_open_all_inputs(MidiEngine *engine);
|
||||
void midi_close_all_inputs(MidiEngine *engine);
|
||||
void midi_update(MidiEngine *engine, F32 dt);
|
||||
B32 midi_is_input_active(MidiEngine *engine, S32 device_index);
|
||||
void midi_open_all_inputs(MidiEngine *engine);
|
||||
void midi_close_all_inputs(MidiEngine *engine);
|
||||
void midi_update(MidiEngine *engine, F32 dt);
|
||||
B32 midi_is_input_active(MidiEngine *engine, S32 device_index);
|
||||
|
||||
// Per-note state: returns true if note (0-127) is currently held on any input device
|
||||
B32 midi_is_note_held(MidiEngine *engine, S32 note);
|
||||
B32 midi_is_note_held(MidiEngine *engine, S32 note);
|
||||
// Returns the last note-on velocity (0-127) for a given note, or 0 if not held
|
||||
S32 midi_get_note_velocity(MidiEngine *engine, S32 note);
|
||||
S32 midi_get_note_velocity(MidiEngine *engine, S32 note);
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
#include "midi/midi.h"
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <CoreMIDI/CoreMIDI.h>
|
||||
#include <stdatomic.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MIDI_MAX_DEVICES 64
|
||||
#define MIDI_MAX_DEVICES 64
|
||||
#define MIDI_RELEASE_FLASH_DURATION 0.15f
|
||||
|
||||
struct MidiEngine {
|
||||
MidiDeviceInfo devices[MIDI_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
MidiDeviceInfo devices[MIDI_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
|
||||
MIDIClientRef client;
|
||||
MIDIPortRef input_port;
|
||||
MIDIClientRef client;
|
||||
MIDIPortRef input_port;
|
||||
|
||||
// Map: source endpoint index -> our device array index
|
||||
S32 source_to_device[MIDI_MAX_DEVICES];
|
||||
S32 source_to_device[MIDI_MAX_DEVICES];
|
||||
|
||||
// Set atomically from callback thread
|
||||
_Atomic S32 pending_note_on_vel[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 pending_note_num[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 pending_note_off[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 held_note_count[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 pending_note_on_vel[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 pending_note_num[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 pending_note_off[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 held_note_count[MIDI_MAX_DEVICES];
|
||||
|
||||
// Per-note state (across all devices), set atomically from callback
|
||||
_Atomic S32 note_states[128]; // held count
|
||||
_Atomic S32 note_velocities[128]; // last note-on velocity
|
||||
_Atomic S32 note_states[128]; // held count
|
||||
_Atomic S32 note_velocities[128]; // last note-on velocity
|
||||
|
||||
// Main thread only
|
||||
S32 display_velocity[MIDI_MAX_DEVICES];
|
||||
S32 display_note[MIDI_MAX_DEVICES];
|
||||
F32 release_timers[MIDI_MAX_DEVICES];
|
||||
S32 display_velocity[MIDI_MAX_DEVICES];
|
||||
S32 display_note[MIDI_MAX_DEVICES];
|
||||
F32 release_timers[MIDI_MAX_DEVICES];
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
@@ -39,26 +40,23 @@ struct MidiEngine {
|
||||
static void midi_read_callback(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) {
|
||||
(void)readProcRefCon;
|
||||
|
||||
MidiEngine *engine = (MidiEngine *)readProcRefCon;
|
||||
S32 device_idx = (S32)(intptr_t)srcConnRefCon;
|
||||
MidiEngine *engine = (MidiEngine *)readProcRefCon;
|
||||
S32 device_idx = (S32)(intptr_t)srcConnRefCon;
|
||||
if (!engine || device_idx < 0 || device_idx >= MIDI_MAX_DEVICES) return;
|
||||
|
||||
const MIDIPacket *packet = &pktlist->packet[0];
|
||||
for (UInt32 i = 0; i < pktlist->numPackets; i++) {
|
||||
// Parse MIDI bytes
|
||||
for (UInt16 j = 0; j < packet->length;) {
|
||||
for (UInt16 j = 0; j < packet->length; ) {
|
||||
U8 status = packet->data[j];
|
||||
|
||||
// Skip non-status bytes (running status not handled for simplicity)
|
||||
if (status < 0x80) {
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
if (status < 0x80) { j++; continue; }
|
||||
|
||||
U8 kind = status & 0xF0;
|
||||
|
||||
if (kind == 0x90 && j + 2 < packet->length) {
|
||||
U8 note = packet->data[j + 1];
|
||||
U8 note = packet->data[j + 1];
|
||||
U8 velocity = packet->data[j + 2];
|
||||
j += 3;
|
||||
|
||||
@@ -95,8 +93,7 @@ static void midi_read_callback(const MIDIPacketList *pktlist, void *readProcRefC
|
||||
} else if (kind == 0xF0) {
|
||||
// System messages — skip to end or next status byte
|
||||
j++;
|
||||
while (j < packet->length && packet->data[j] < 0x80)
|
||||
j++;
|
||||
while (j < packet->length && packet->data[j] < 0x80) j++;
|
||||
} else {
|
||||
j += 3; // Default: 3-byte message
|
||||
}
|
||||
@@ -117,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];
|
||||
@@ -130,9 +127,9 @@ static void enumerate_midi_devices(MidiEngine *engine) {
|
||||
snprintf(dev->name, sizeof(dev->name), "MIDI Source %d", (S32)i);
|
||||
}
|
||||
|
||||
dev->id = engine->device_count;
|
||||
dev->id = engine->device_count;
|
||||
dev->is_input = true;
|
||||
dev->active = false;
|
||||
dev->active = false;
|
||||
|
||||
engine->source_to_device[i] = engine->device_count;
|
||||
engine->device_count++;
|
||||
@@ -143,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];
|
||||
@@ -156,9 +153,9 @@ static void enumerate_midi_devices(MidiEngine *engine) {
|
||||
snprintf(dev->name, sizeof(dev->name), "MIDI Dest %d", (S32)i);
|
||||
}
|
||||
|
||||
dev->id = engine->device_count;
|
||||
dev->id = engine->device_count;
|
||||
dev->is_input = false;
|
||||
dev->active = false;
|
||||
dev->active = false;
|
||||
|
||||
engine->device_count++;
|
||||
}
|
||||
@@ -167,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);
|
||||
@@ -182,14 +178,14 @@ 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) {
|
||||
ItemCount num_sources = MIDIGetNumberOfSources();
|
||||
for (ItemCount i = 0; i < num_sources && (S32)i < MIDI_MAX_DEVICES; i++) {
|
||||
MIDIEndpointRef endpoint = MIDIGetSource(i);
|
||||
S32 dev_idx = engine->source_to_device[i];
|
||||
S32 dev_idx = engine->source_to_device[i];
|
||||
MIDIPortConnectSource(engine->input_port, endpoint, (void *)(intptr_t)dev_idx);
|
||||
}
|
||||
}
|
||||
@@ -207,8 +203,8 @@ void midi_close_all_inputs(MidiEngine *engine) {
|
||||
atomic_store(&engine->pending_note_off[i], 0);
|
||||
atomic_store(&engine->held_note_count[i], 0);
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->display_note[i] = 0;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
}
|
||||
for (S32 i = 0; i < 128; i++) {
|
||||
atomic_store(&engine->note_states[i], 0);
|
||||
@@ -220,36 +216,36 @@ void midi_update(MidiEngine *engine, F32 dt) {
|
||||
for (S32 i = 0; i < engine->device_count; i++) {
|
||||
if (!engine->devices[i].is_input) continue;
|
||||
|
||||
S32 vel = atomic_exchange(&engine->pending_note_on_vel[i], 0);
|
||||
S32 vel = atomic_exchange(&engine->pending_note_on_vel[i], 0);
|
||||
S32 note_p1 = atomic_exchange(&engine->pending_note_num[i], 0);
|
||||
if (vel > 0) engine->display_velocity[i] = vel;
|
||||
if (note_p1 > 0) engine->display_note[i] = note_p1 - 1;
|
||||
|
||||
S32 off = atomic_exchange(&engine->pending_note_off[i], 0);
|
||||
S32 off = atomic_exchange(&engine->pending_note_off[i], 0);
|
||||
S32 held = atomic_load(&engine->held_note_count[i]);
|
||||
|
||||
if (held > 0) {
|
||||
engine->devices[i].active = true;
|
||||
engine->devices[i].active = true;
|
||||
engine->devices[i].releasing = false;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
} else if (off || (engine->devices[i].active && held <= 0)) {
|
||||
engine->devices[i].active = false;
|
||||
engine->devices[i].active = false;
|
||||
engine->devices[i].releasing = true;
|
||||
engine->release_timers[i] = MIDI_RELEASE_FLASH_DURATION;
|
||||
engine->release_timers[i] = MIDI_RELEASE_FLASH_DURATION;
|
||||
}
|
||||
|
||||
if (engine->release_timers[i] > 0.0f) {
|
||||
engine->release_timers[i] -= dt;
|
||||
if (engine->release_timers[i] <= 0.0f) {
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->devices[i].releasing = false;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
engine->devices[i].velocity = engine->display_velocity[i];
|
||||
engine->devices[i].note = engine->display_note[i];
|
||||
engine->devices[i].note = engine->display_note[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,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];
|
||||
}
|
||||
@@ -1,40 +1,41 @@
|
||||
#include "midi/midi.h"
|
||||
#include <windows.h>
|
||||
#include <mmeapi.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MIDI_MAX_DEVICES 64
|
||||
#define MIDI_MAX_DEVICES 64
|
||||
#define MIDI_RELEASE_FLASH_DURATION 0.15f
|
||||
|
||||
struct MidiEngine {
|
||||
MidiDeviceInfo devices[MIDI_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
S32 device_count;
|
||||
|
||||
HMIDIIN input_handles[MIDI_MAX_DEVICES];
|
||||
HMIDIIN input_handles[MIDI_MAX_DEVICES];
|
||||
|
||||
// Set atomically from callback thread
|
||||
volatile LONG pending_note_on_vel[MIDI_MAX_DEVICES]; // last note-on velocity (0 = consumed)
|
||||
volatile LONG pending_note_num[MIDI_MAX_DEVICES]; // last note-on note number + 1 (0 = consumed)
|
||||
volatile LONG pending_note_off[MIDI_MAX_DEVICES]; // note-off received flag
|
||||
volatile LONG held_note_count[MIDI_MAX_DEVICES]; // number of notes currently held
|
||||
volatile LONG pending_note_on_vel[MIDI_MAX_DEVICES]; // last note-on velocity (0 = consumed)
|
||||
volatile LONG pending_note_num[MIDI_MAX_DEVICES]; // last note-on note number + 1 (0 = consumed)
|
||||
volatile LONG pending_note_off[MIDI_MAX_DEVICES]; // note-off received flag
|
||||
volatile LONG held_note_count[MIDI_MAX_DEVICES]; // number of notes currently held
|
||||
|
||||
// Per-note state (across all devices), set atomically from callback
|
||||
volatile LONG note_states[128]; // held count
|
||||
volatile LONG note_velocities[128]; // last note-on velocity
|
||||
volatile LONG note_states[128]; // held count
|
||||
volatile LONG note_velocities[128]; // last note-on velocity
|
||||
|
||||
// Main thread only
|
||||
S32 display_velocity[MIDI_MAX_DEVICES];
|
||||
S32 display_note[MIDI_MAX_DEVICES];
|
||||
F32 release_timers[MIDI_MAX_DEVICES];
|
||||
S32 display_velocity[MIDI_MAX_DEVICES];
|
||||
S32 display_note[MIDI_MAX_DEVICES];
|
||||
F32 release_timers[MIDI_MAX_DEVICES];
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// 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) {
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
(void)hMidiIn;
|
||||
(void)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,10 +91,10 @@ void midi_open_all_inputs(MidiEngine *engine) {
|
||||
if (!dev->is_input) continue;
|
||||
if (engine->input_handles[i]) continue; // already open
|
||||
|
||||
HMIDIIN handle = nullptr;
|
||||
MMRESULT res = midiInOpen(&handle, (UINT)dev->id,
|
||||
(DWORD_PTR)midi_in_callback,
|
||||
(DWORD_PTR)i, CALLBACK_FUNCTION);
|
||||
HMIDIIN handle = NULL;
|
||||
MMRESULT res = midiInOpen(&handle, (UINT)dev->id,
|
||||
(DWORD_PTR)midi_in_callback,
|
||||
(DWORD_PTR)i, CALLBACK_FUNCTION);
|
||||
if (res == MMSYSERR_NOERROR) {
|
||||
engine->input_handles[i] = handle;
|
||||
midiInStart(handle);
|
||||
@@ -107,18 +107,18 @@ 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;
|
||||
engine->pending_note_off[i] = 0;
|
||||
engine->held_note_count[i] = 0;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->pending_note_num[i] = 0;
|
||||
engine->pending_note_off[i] = 0;
|
||||
engine->held_note_count[i] = 0;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
}
|
||||
for (S32 i = 0; i < 128; i++) {
|
||||
engine->note_states[i] = 0;
|
||||
engine->note_states[i] = 0;
|
||||
engine->note_velocities[i] = 0;
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ void midi_update(MidiEngine *engine, F32 dt) {
|
||||
if (!engine->devices[i].is_input) continue;
|
||||
|
||||
// Consume pending note-on velocity and note number
|
||||
LONG vel = InterlockedExchange(&engine->pending_note_on_vel[i], 0);
|
||||
LONG vel = InterlockedExchange(&engine->pending_note_on_vel[i], 0);
|
||||
LONG note_p1 = InterlockedExchange(&engine->pending_note_num[i], 0);
|
||||
if (vel > 0) {
|
||||
engine->display_velocity[i] = (S32)vel;
|
||||
@@ -144,29 +144,29 @@ void midi_update(MidiEngine *engine, F32 dt) {
|
||||
LONG held = engine->held_note_count[i];
|
||||
|
||||
if (held > 0) {
|
||||
engine->devices[i].active = true;
|
||||
engine->devices[i].active = true;
|
||||
engine->devices[i].releasing = false;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
} else if (off || (engine->devices[i].active && held <= 0)) {
|
||||
// All notes just released — start release flash
|
||||
engine->devices[i].active = false;
|
||||
engine->devices[i].active = false;
|
||||
engine->devices[i].releasing = true;
|
||||
engine->release_timers[i] = MIDI_RELEASE_FLASH_DURATION;
|
||||
engine->release_timers[i] = MIDI_RELEASE_FLASH_DURATION;
|
||||
}
|
||||
|
||||
// Decay release flash timer
|
||||
if (engine->release_timers[i] > 0.0f) {
|
||||
engine->release_timers[i] -= dt;
|
||||
if (engine->release_timers[i] <= 0.0f) {
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->devices[i].releasing = false;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
engine->devices[i].velocity = engine->display_velocity[i];
|
||||
engine->devices[i].note = engine->display_note[i];
|
||||
engine->devices[i].note = engine->display_note[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,25 +193,25 @@ 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);
|
||||
dev->id = (S32)i;
|
||||
dev->id = (S32)i;
|
||||
dev->is_input = true;
|
||||
dev->active = false;
|
||||
dev->active = false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
dev->id = (S32)i;
|
||||
dev->id = (S32)i;
|
||||
dev->is_input = false;
|
||||
dev->active = false;
|
||||
dev->active = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -27,63 +27,72 @@ enum {
|
||||
PKEY_V = 0x56,
|
||||
PKEY_X = 0x58,
|
||||
PKEY_0 = 0x30,
|
||||
PKEY_EQUAL = 0xBB, // '='/'+' (VK_OEM_PLUS)
|
||||
PKEY_MINUS = 0xBD, // '-'/'_' (VK_OEM_MINUS)
|
||||
PKEY_EQUAL = 0xBB, // '='/'+' (VK_OEM_PLUS)
|
||||
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;
|
||||
S32 char_count;
|
||||
|
||||
// Key-down events (virtual key codes)
|
||||
U8 keys[PLATFORM_MAX_KEYS_PER_FRAME];
|
||||
S32 key_count;
|
||||
S32 key_count;
|
||||
|
||||
// Modifier state at time of last key event
|
||||
B32 ctrl_held;
|
||||
B32 shift_held;
|
||||
B32 ctrl_held;
|
||||
B32 shift_held;
|
||||
|
||||
// Mouse state (polled per frame)
|
||||
Vec2F32 mouse_pos;
|
||||
Vec2F32 scroll_delta;
|
||||
B32 mouse_down;
|
||||
B32 was_mouse_down;
|
||||
};
|
||||
Vec2F32 mouse_pos;
|
||||
Vec2F32 scroll_delta;
|
||||
B32 mouse_down;
|
||||
B32 was_mouse_down;
|
||||
} PlatformInput;
|
||||
|
||||
struct PlatformWindow;
|
||||
typedef struct PlatformWindow PlatformWindow;
|
||||
|
||||
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
|
||||
};
|
||||
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
|
||||
S32 id; // command ID (ignored for separators)
|
||||
};
|
||||
typedef struct PlatformMenuItem {
|
||||
const char *label; // NULL = separator
|
||||
S32 id; // command ID (ignored for separators)
|
||||
} PlatformMenuItem;
|
||||
|
||||
struct PlatformMenu {
|
||||
const char *label;
|
||||
PlatformMenuItem *items;
|
||||
S32 item_count;
|
||||
};
|
||||
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
|
||||
@@ -92,13 +101,13 @@ typedef void (*PlatformFrameCallback)(void *user_data);
|
||||
|
||||
PlatformWindow *platform_create_window(PlatformWindowDesc *desc);
|
||||
void platform_destroy_window(PlatformWindow *window);
|
||||
B32 platform_poll_events(PlatformWindow *window);
|
||||
B32 platform_poll_events(PlatformWindow *window);
|
||||
void platform_get_size(PlatformWindow *window, S32 *w, S32 *h);
|
||||
void *platform_get_native_handle(PlatformWindow *window);
|
||||
|
||||
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data);
|
||||
void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_count);
|
||||
S32 platform_poll_menu_command(PlatformWindow *window);
|
||||
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data);
|
||||
void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_count);
|
||||
S32 platform_poll_menu_command(PlatformWindow *window);
|
||||
|
||||
// Returns accumulated input since last call (keyboard events + polled mouse state), then clears the buffer.
|
||||
PlatformInput platform_get_input(PlatformWindow *window);
|
||||
@@ -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
|
||||
};
|
||||
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,66 +1,55 @@
|
||||
#include "platform/platform.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// macOS virtual key codes (avoids Carbon.h include)
|
||||
enum {
|
||||
kVK_ANSI_A = 0x00,
|
||||
kVK_ANSI_C = 0x08,
|
||||
kVK_ANSI_V = 0x09,
|
||||
kVK_ANSI_X = 0x07,
|
||||
kVK_Return = 0x24,
|
||||
kVK_Tab = 0x30,
|
||||
kVK_Delete = 0x33,
|
||||
kVK_Escape = 0x35,
|
||||
kVK_ForwardDelete = 0x75,
|
||||
kVK_LeftArrow = 0x7B,
|
||||
kVK_RightArrow = 0x7C,
|
||||
kVK_DownArrow = 0x7D,
|
||||
kVK_UpArrow = 0x7E,
|
||||
kVK_Home = 0x73,
|
||||
kVK_End = 0x77,
|
||||
kVK_Command = 0x37,
|
||||
kVK_Shift = 0x38,
|
||||
kVK_RightShift = 0x3C,
|
||||
kVK_RightCommand = 0x36,
|
||||
kVK_ANSI_Equal = 0x18,
|
||||
kVK_ANSI_Minus = 0x1B,
|
||||
kVK_ANSI_0 = 0x1D,
|
||||
kVK_ANSI_A = 0x00, kVK_ANSI_C = 0x08, kVK_ANSI_V = 0x09,
|
||||
kVK_ANSI_X = 0x07,
|
||||
kVK_Return = 0x24, kVK_Tab = 0x30, kVK_Delete = 0x33,
|
||||
kVK_Escape = 0x35, kVK_ForwardDelete = 0x75,
|
||||
kVK_LeftArrow = 0x7B, kVK_RightArrow = 0x7C,
|
||||
kVK_DownArrow = 0x7D, kVK_UpArrow = 0x7E,
|
||||
kVK_Home = 0x73, kVK_End = 0x77,
|
||||
kVK_Command = 0x37, kVK_Shift = 0x38,
|
||||
kVK_RightShift = 0x3C, kVK_RightCommand = 0x36,
|
||||
kVK_ANSI_Equal = 0x18, kVK_ANSI_Minus = 0x1B,
|
||||
kVK_ANSI_0 = 0x1D,
|
||||
kVK_ANSI_KeypadEnter = 0x4C,
|
||||
};
|
||||
|
||||
static U8 macos_keycode_to_pkey(U16 keycode) {
|
||||
switch (keycode) {
|
||||
case kVK_ANSI_A: return PKEY_A;
|
||||
case kVK_ANSI_C: return PKEY_C;
|
||||
case kVK_ANSI_V: return PKEY_V;
|
||||
case kVK_ANSI_X: return PKEY_X;
|
||||
case kVK_Return: return PKEY_RETURN;
|
||||
case kVK_ANSI_A: return PKEY_A;
|
||||
case kVK_ANSI_C: return PKEY_C;
|
||||
case kVK_ANSI_V: return PKEY_V;
|
||||
case kVK_ANSI_X: return PKEY_X;
|
||||
case kVK_Return: return PKEY_RETURN;
|
||||
case kVK_ANSI_KeypadEnter: return PKEY_RETURN;
|
||||
case kVK_Tab: return PKEY_TAB;
|
||||
case kVK_Delete: return PKEY_BACKSPACE;
|
||||
case kVK_ForwardDelete: return PKEY_DELETE;
|
||||
case kVK_Escape: return PKEY_ESCAPE;
|
||||
case kVK_LeftArrow: return PKEY_LEFT;
|
||||
case kVK_RightArrow: return PKEY_RIGHT;
|
||||
case kVK_UpArrow: return PKEY_UP;
|
||||
case kVK_DownArrow: return PKEY_DOWN;
|
||||
case kVK_Home: return PKEY_HOME;
|
||||
case kVK_End: return PKEY_END;
|
||||
case kVK_ANSI_Equal: return PKEY_EQUAL;
|
||||
case kVK_ANSI_Minus: return PKEY_MINUS;
|
||||
case kVK_ANSI_0: return PKEY_0;
|
||||
default: return 0;
|
||||
case kVK_Tab: return PKEY_TAB;
|
||||
case kVK_Delete: return PKEY_BACKSPACE;
|
||||
case kVK_ForwardDelete:return PKEY_DELETE;
|
||||
case kVK_Escape: return PKEY_ESCAPE;
|
||||
case kVK_LeftArrow: return PKEY_LEFT;
|
||||
case kVK_RightArrow: return PKEY_RIGHT;
|
||||
case kVK_UpArrow: return PKEY_UP;
|
||||
case kVK_DownArrow: return PKEY_DOWN;
|
||||
case kVK_Home: return PKEY_HOME;
|
||||
case kVK_End: return PKEY_END;
|
||||
case kVK_ANSI_Equal: return PKEY_EQUAL;
|
||||
case kVK_ANSI_Minus: return PKEY_MINUS;
|
||||
case kVK_ANSI_0: return PKEY_0;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// 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
|
||||
@@ -69,23 +58,18 @@ static PlatformWindow *g_main_window = nullptr;
|
||||
@end
|
||||
|
||||
@implementation ASmplAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
|
||||
(void)notification;
|
||||
}
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
||||
(void)sender;
|
||||
return YES;
|
||||
}
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification { (void)notification; }
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { (void)sender; return YES; }
|
||||
@end
|
||||
|
||||
@interface ASmplWindowDelegate : NSObject <NSWindowDelegate> {
|
||||
@public
|
||||
@public
|
||||
PlatformWindow *_platformWindow;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ASmplView : NSView <NSTextInputClient> {
|
||||
@public
|
||||
@public
|
||||
PlatformWindow *_platformWindow;
|
||||
}
|
||||
@end
|
||||
@@ -93,21 +77,21 @@ static PlatformWindow *g_main_window = nullptr;
|
||||
////////////////////////////////
|
||||
// PlatformWindow struct
|
||||
|
||||
struct PlatformWindow {
|
||||
typedef struct PlatformWindow {
|
||||
NSWindow *ns_window;
|
||||
ASmplView *view;
|
||||
ASmplWindowDelegate *delegate;
|
||||
B32 should_close;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 pending_menu_cmd;
|
||||
B32 should_close;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 pending_menu_cmd;
|
||||
PlatformFrameCallback frame_callback;
|
||||
void *frame_callback_user_data;
|
||||
PlatformInput input;
|
||||
B32 prev_mouse_down;
|
||||
B32 mouse_down_state;
|
||||
F32 backing_scale;
|
||||
};
|
||||
} PlatformWindow;
|
||||
|
||||
////////////////////////////////
|
||||
// C callback helpers (called from ObjC via PlatformWindow pointer)
|
||||
@@ -117,19 +101,16 @@ static void platform_macos_insert_text_pw(PlatformWindow *pw, const char *utf8)
|
||||
PlatformInput *ev = &pw->input;
|
||||
while (*utf8 && ev->char_count < PLATFORM_MAX_CHARS_PER_FRAME) {
|
||||
U8 c = (U8)*utf8;
|
||||
if (c < 32) {
|
||||
utf8++;
|
||||
continue;
|
||||
}
|
||||
if (c < 32) { utf8++; continue; }
|
||||
// Handle ASCII printable range (single-byte UTF-8)
|
||||
if (c < 0x80) {
|
||||
ev->chars[ev->char_count++] = (U16)c;
|
||||
utf8++;
|
||||
} else {
|
||||
// Skip multi-byte UTF-8 sequences for now (UI only handles ASCII)
|
||||
if (c < 0xE0) utf8 += 2;
|
||||
if (c < 0xE0) utf8 += 2;
|
||||
else if (c < 0xF0) utf8 += 3;
|
||||
else utf8 += 4;
|
||||
else utf8 += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,11 +143,11 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
- (void)windowDidResize:(NSNotification *)notification {
|
||||
(void)notification;
|
||||
if (!_platformWindow) return;
|
||||
PlatformWindow *pw = _platformWindow;
|
||||
NSRect frame = [pw->view bounds];
|
||||
F32 scale = pw->backing_scale;
|
||||
pw->width = (S32)(frame.size.width * scale);
|
||||
pw->height = (S32)(frame.size.height * scale);
|
||||
PlatformWindow *pw = _platformWindow;
|
||||
NSRect frame = [pw->view bounds];
|
||||
F32 scale = pw->backing_scale;
|
||||
pw->width = (S32)(frame.size.width * scale);
|
||||
pw->height = (S32)(frame.size.height * scale);
|
||||
if (pw->frame_callback)
|
||||
pw->frame_callback(pw->frame_callback_user_data);
|
||||
}
|
||||
@@ -174,45 +155,25 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
|
||||
@implementation ASmplView
|
||||
|
||||
- (BOOL)acceptsFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)canBecomeKeyView {
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)acceptsFirstResponder { return YES; }
|
||||
- (BOOL)canBecomeKeyView { return YES; }
|
||||
|
||||
// Needed for NSTextInputClient
|
||||
- (BOOL)hasMarkedText {
|
||||
return NO;
|
||||
}
|
||||
- (NSRange)markedRange {
|
||||
return NSMakeRange(NSNotFound, 0);
|
||||
}
|
||||
- (NSRange)selectedRange {
|
||||
return NSMakeRange(NSNotFound, 0);
|
||||
}
|
||||
- (BOOL)hasMarkedText { return NO; }
|
||||
- (NSRange)markedRange { return NSMakeRange(NSNotFound, 0); }
|
||||
- (NSRange)selectedRange { return NSMakeRange(NSNotFound, 0); }
|
||||
- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
|
||||
(void)string;
|
||||
(void)selectedRange;
|
||||
(void)replacementRange;
|
||||
}
|
||||
- (void)unmarkText {
|
||||
}
|
||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||
return @[];
|
||||
(void)string; (void)selectedRange; (void)replacementRange;
|
||||
}
|
||||
- (void)unmarkText {}
|
||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText { return @[]; }
|
||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
|
||||
(void)range;
|
||||
(void)actualRange;
|
||||
(void)range; (void)actualRange;
|
||||
return nil;
|
||||
}
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)point {
|
||||
(void)point;
|
||||
return NSNotFound;
|
||||
}
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)point { (void)point; return NSNotFound; }
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
|
||||
(void)range;
|
||||
(void)actualRange;
|
||||
(void)range; (void)actualRange;
|
||||
return NSZeroRect;
|
||||
}
|
||||
|
||||
@@ -231,7 +192,7 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
platform_macos_key_down_pw(_platformWindow, [event keyCode], [event modifierFlags]);
|
||||
|
||||
// Feed into text input system for character generation
|
||||
[self interpretKeyEvents:@[ event ]];
|
||||
[self interpretKeyEvents:@[event]];
|
||||
}
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)event {
|
||||
@@ -249,12 +210,8 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
if (_platformWindow) _platformWindow->mouse_down_state = 0;
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
(void)event;
|
||||
}
|
||||
- (void)mouseDragged:(NSEvent *)event {
|
||||
(void)event;
|
||||
}
|
||||
- (void)mouseMoved:(NSEvent *)event { (void)event; }
|
||||
- (void)mouseDragged:(NSEvent *)event { (void)event; }
|
||||
|
||||
- (void)scrollWheel:(NSEvent *)event {
|
||||
if (!_platformWindow) return;
|
||||
@@ -264,10 +221,7 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
_platformWindow->input.scroll_delta.y += dy;
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstMouse:(NSEvent *)event {
|
||||
(void)event;
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)acceptsFirstMouse:(NSEvent *)event { (void)event; return YES; }
|
||||
|
||||
@end
|
||||
|
||||
@@ -281,7 +235,7 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
@implementation ASmplMenuTarget
|
||||
- (void)menuAction:(id)sender {
|
||||
if (!g_main_window) return;
|
||||
NSMenuItem *item = (NSMenuItem *)sender;
|
||||
NSMenuItem *item = (NSMenuItem *)sender;
|
||||
g_main_window->pending_menu_cmd = (S32)[item tag];
|
||||
}
|
||||
@end
|
||||
@@ -317,9 +271,9 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
|
||||
NSWindow *ns_window = [[NSWindow alloc]
|
||||
initWithContentRect:content_rect
|
||||
styleMask:style_mask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
styleMask:style_mask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
|
||||
[ns_window setTitle:[NSString stringWithUTF8String:desc->title]];
|
||||
[ns_window center];
|
||||
@@ -334,18 +288,17 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
ASmplWindowDelegate *delegate = [[ASmplWindowDelegate alloc] init];
|
||||
[ns_window setDelegate:delegate];
|
||||
|
||||
PlatformWindow *window = new PlatformWindow();
|
||||
memset(window, 0, sizeof(*window));
|
||||
window->ns_window = ns_window;
|
||||
window->view = view;
|
||||
window->delegate = delegate;
|
||||
window->should_close = false;
|
||||
PlatformWindow *window = (PlatformWindow *)calloc(1, sizeof(PlatformWindow));
|
||||
window->ns_window = ns_window;
|
||||
window->view = view;
|
||||
window->delegate = delegate;
|
||||
window->should_close = false;
|
||||
window->backing_scale = (F32)[ns_window backingScaleFactor];
|
||||
window->width = (S32)(desc->width * window->backing_scale);
|
||||
window->height = (S32)(desc->height * window->backing_scale);
|
||||
window->width = (S32)(desc->width * window->backing_scale);
|
||||
window->height = (S32)(desc->height * window->backing_scale);
|
||||
|
||||
// Wire up per-window pointers
|
||||
view->_platformWindow = window;
|
||||
view->_platformWindow = window;
|
||||
delegate->_platformWindow = window;
|
||||
|
||||
// Track main window for menu commands
|
||||
@@ -375,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) {
|
||||
@@ -384,8 +337,8 @@ B32 platform_poll_events(PlatformWindow *window) {
|
||||
// When the app is not active (e.g. during Cmd+Tab away), block until
|
||||
// an event arrives instead of spinning. This keeps CPU near zero while
|
||||
// backgrounded and lets macOS handle the app-switch animation smoothly.
|
||||
BOOL is_active = [NSApp isActive];
|
||||
NSDate *deadline = is_active ? nil : [NSDate distantFuture];
|
||||
BOOL is_active = [NSApp isActive];
|
||||
NSDate *deadline = is_active ? nil : [NSDate distantFuture];
|
||||
|
||||
NSEvent *event;
|
||||
while ((event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
@@ -412,7 +365,7 @@ void *platform_get_native_handle(PlatformWindow *window) {
|
||||
}
|
||||
|
||||
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data) {
|
||||
window->frame_callback = cb;
|
||||
window->frame_callback = cb;
|
||||
window->frame_callback_user_data = user_data;
|
||||
}
|
||||
|
||||
@@ -426,7 +379,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
|
||||
// App menu (required on macOS)
|
||||
NSMenuItem *app_menu_item = [[NSMenuItem alloc] init];
|
||||
NSMenu *app_menu = [[NSMenu alloc] init];
|
||||
NSMenu *app_menu = [[NSMenu alloc] init];
|
||||
[app_menu addItemWithTitle:@"Quit autosample"
|
||||
action:@selector(terminate:)
|
||||
keyEquivalent:@"q"];
|
||||
@@ -435,8 +388,8 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
|
||||
for (S32 i = 0; i < menu_count; i++) {
|
||||
NSMenuItem *top_item = [[NSMenuItem alloc] init];
|
||||
NSMenu *submenu = [[NSMenu alloc] initWithTitle:
|
||||
[NSString stringWithUTF8String:menus[i].label]];
|
||||
NSMenu *submenu = [[NSMenu alloc] initWithTitle:
|
||||
[NSString stringWithUTF8String:menus[i].label]];
|
||||
|
||||
for (S32 j = 0; j < menus[i].item_count; j++) {
|
||||
PlatformMenuItem *item = &menus[i].items[j];
|
||||
@@ -445,7 +398,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
} else {
|
||||
NSMenuItem *ns_item = [[NSMenuItem alloc]
|
||||
initWithTitle:[NSString stringWithUTF8String:item->label]
|
||||
action:@selector(menuAction:)
|
||||
action:@selector(menuAction:)
|
||||
keyEquivalent:@""];
|
||||
[ns_item setTag:item->id];
|
||||
[ns_item setTarget:g_menu_target];
|
||||
@@ -461,7 +414,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
}
|
||||
|
||||
S32 platform_poll_menu_command(PlatformWindow *window) {
|
||||
S32 cmd = window->pending_menu_cmd;
|
||||
S32 cmd = window->pending_menu_cmd;
|
||||
window->pending_menu_cmd = 0;
|
||||
return cmd;
|
||||
}
|
||||
@@ -471,24 +424,24 @@ PlatformInput platform_get_input(PlatformWindow *window) {
|
||||
|
||||
// Poll mouse position (Cocoa uses bottom-left origin, flip Y)
|
||||
NSPoint mouse_in_window = [window->ns_window mouseLocationOutsideOfEventStream];
|
||||
NSRect view_bounds = [window->view bounds];
|
||||
F32 scale = window->backing_scale;
|
||||
result.mouse_pos = v2f32(
|
||||
NSRect view_bounds = [window->view bounds];
|
||||
F32 scale = window->backing_scale;
|
||||
result.mouse_pos = v2f32(
|
||||
(F32)mouse_in_window.x * scale,
|
||||
(F32)(view_bounds.size.height - mouse_in_window.y) * scale);
|
||||
|
||||
// Mouse button state
|
||||
result.was_mouse_down = window->prev_mouse_down;
|
||||
result.mouse_down = window->mouse_down_state;
|
||||
result.was_mouse_down = window->prev_mouse_down;
|
||||
result.mouse_down = window->mouse_down_state;
|
||||
window->prev_mouse_down = result.mouse_down;
|
||||
|
||||
// Poll current modifier state (so shift/ctrl are accurate even without key events)
|
||||
NSEventModifierFlags mods = [NSEvent modifierFlags];
|
||||
result.ctrl_held = (mods & NSEventModifierFlagCommand) != 0;
|
||||
result.shift_held = (mods & NSEventModifierFlagShift) != 0;
|
||||
result.ctrl_held = (mods & NSEventModifierFlagCommand) != 0;
|
||||
result.shift_held = (mods & NSEventModifierFlagShift) != 0;
|
||||
|
||||
// Clear accumulated events for next frame
|
||||
window->input = {};
|
||||
memset(&window->input, 0, sizeof(window->input));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -509,8 +462,8 @@ F32 platform_get_dpi_scale(PlatformWindow *window) {
|
||||
void platform_set_cursor(PlatformCursor cursor) {
|
||||
switch (cursor) {
|
||||
case PLATFORM_CURSOR_SIZE_WE: [[NSCursor resizeLeftRightCursor] set]; break;
|
||||
case PLATFORM_CURSOR_SIZE_NS: [[NSCursor resizeUpDownCursor] set]; break;
|
||||
default: [[NSCursor arrowCursor] set]; break;
|
||||
case PLATFORM_CURSOR_SIZE_NS: [[NSCursor resizeUpDownCursor] set]; break;
|
||||
default: [[NSCursor arrowCursor] set]; break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,8 +478,8 @@ const char *platform_clipboard_get() {
|
||||
static char buf[4096];
|
||||
buf[0] = '\0';
|
||||
|
||||
NSPasteboard *pb = [NSPasteboard generalPasteboard];
|
||||
NSString *str = [pb stringForType:NSPasteboardTypeString];
|
||||
NSPasteboard *pb = [NSPasteboard generalPasteboard];
|
||||
NSString *str = [pb stringForType:NSPasteboardTypeString];
|
||||
if (str) {
|
||||
const char *utf8 = [str UTF8String];
|
||||
if (utf8) {
|
||||
@@ -537,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,
|
||||
@@ -3,25 +3,27 @@
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <malloc.h>
|
||||
#include <windows.h>
|
||||
#include <malloc.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct PlatformWindow {
|
||||
HWND hwnd;
|
||||
B32 should_close;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 pending_menu_cmd;
|
||||
typedef struct PlatformWindow {
|
||||
HWND hwnd;
|
||||
B32 should_close;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 pending_menu_cmd;
|
||||
PlatformFrameCallback frame_callback;
|
||||
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 B32 g_wndclass_registered = false;
|
||||
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) {
|
||||
PlatformWindow *pw = (PlatformWindow *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
||||
@@ -69,16 +71,16 @@ 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,
|
||||
suggested->right - suggested->left, suggested->bottom - suggested->top,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
SetWindowPos(hwnd, NULL, suggested->left, suggested->top,
|
||||
suggested->right - suggested->left, suggested->bottom - suggested->top,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
return 0;
|
||||
case WM_CLOSE:
|
||||
@@ -101,25 +103,25 @@ 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;
|
||||
}
|
||||
|
||||
UINT dpi = GetDpiForSystem();
|
||||
int screen_w = GetSystemMetrics(SM_CXSCREEN);
|
||||
int screen_h = GetSystemMetrics(SM_CYSCREEN);
|
||||
int x = (screen_w - desc->width) / 2;
|
||||
int y = (screen_h - desc->height) / 2;
|
||||
UINT dpi = GetDpiForSystem();
|
||||
int screen_w = GetSystemMetrics(SM_CXSCREEN);
|
||||
int screen_h = GetSystemMetrics(SM_CYSCREEN);
|
||||
int x = (screen_w - desc->width) / 2;
|
||||
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,8 +135,8 @@ 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);
|
||||
wchar_t *wtitle = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||
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);
|
||||
|
||||
HWND hwnd = CreateWindowExW(
|
||||
@@ -143,18 +145,19 @@ 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();
|
||||
window->hwnd = hwnd;
|
||||
window->should_close = false;
|
||||
window->width = desc->width;
|
||||
window->height = desc->height;
|
||||
PlatformWindow *window = (PlatformWindow *)calloc(1, sizeof(PlatformWindow));
|
||||
window->hwnd = hwnd;
|
||||
window->should_close = false;
|
||||
window->width = desc->width;
|
||||
window->height = desc->height;
|
||||
|
||||
// Store PlatformWindow* on the HWND so WndProc can find it
|
||||
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)window);
|
||||
@@ -178,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) {
|
||||
@@ -205,7 +208,7 @@ void *platform_get_native_handle(PlatformWindow *window) {
|
||||
}
|
||||
|
||||
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data) {
|
||||
window->frame_callback = cb;
|
||||
window->frame_callback = cb;
|
||||
window->frame_callback_user_data = user_data;
|
||||
}
|
||||
|
||||
@@ -218,18 +221,18 @@ 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);
|
||||
wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||
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);
|
||||
_freea(wlabel);
|
||||
}
|
||||
}
|
||||
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, nullptr, 0);
|
||||
wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||
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);
|
||||
_freea(wlabel);
|
||||
@@ -239,7 +242,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
}
|
||||
|
||||
S32 platform_poll_menu_command(PlatformWindow *window) {
|
||||
S32 cmd = window->pending_menu_cmd;
|
||||
S32 cmd = window->pending_menu_cmd;
|
||||
window->pending_menu_cmd = 0;
|
||||
return cmd;
|
||||
}
|
||||
@@ -254,12 +257,12 @@ PlatformInput platform_get_input(PlatformWindow *window) {
|
||||
result.mouse_pos = v2f32((F32)cursor.x, (F32)cursor.y);
|
||||
|
||||
// Poll mouse button
|
||||
result.was_mouse_down = window->prev_mouse_down;
|
||||
result.mouse_down = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
|
||||
result.was_mouse_down = window->prev_mouse_down;
|
||||
result.mouse_down = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
|
||||
window->prev_mouse_down = result.mouse_down;
|
||||
|
||||
// Clear accumulated events for next frame
|
||||
window->input = {};
|
||||
memset(&window->input, 0, sizeof(window->input));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -280,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,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));
|
||||
@@ -303,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);
|
||||
@@ -317,53 +320,53 @@ 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,
|
||||
const char *message, PlatformMsgBoxType type) {
|
||||
UINT mb_type;
|
||||
switch (type) {
|
||||
case PLATFORM_MSGBOX_OK: mb_type = MB_OK; break;
|
||||
case PLATFORM_MSGBOX_OK: mb_type = MB_OK; break;
|
||||
case PLATFORM_MSGBOX_OK_CANCEL: mb_type = MB_OKCANCEL; break;
|
||||
case PLATFORM_MSGBOX_YES_NO: mb_type = MB_YESNO; break;
|
||||
default: mb_type = MB_OK; break;
|
||||
case PLATFORM_MSGBOX_YES_NO: mb_type = MB_YESNO; break;
|
||||
default: mb_type = MB_OK; break;
|
||||
}
|
||||
|
||||
// Convert UTF-8 to wide strings
|
||||
int title_wlen = MultiByteToWideChar(CP_UTF8, 0, title, -1, nullptr, 0);
|
||||
wchar_t *wtitle = (wchar_t *)_malloca(title_wlen * sizeof(wchar_t));
|
||||
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);
|
||||
wchar_t *wmsg = (wchar_t *)_malloca(msg_wlen * sizeof(wchar_t));
|
||||
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;
|
||||
int result = MessageBoxW(hwnd, wmsg, wtitle, mb_type);
|
||||
HWND hwnd = parent ? parent->hwnd : NULL;
|
||||
int result = MessageBoxW(hwnd, wmsg, wtitle, mb_type);
|
||||
|
||||
_freea(wmsg);
|
||||
_freea(wtitle);
|
||||
|
||||
switch (result) {
|
||||
case IDOK: return 0;
|
||||
case IDYES: return 0;
|
||||
case IDOK: return 0;
|
||||
case IDYES: return 0;
|
||||
case IDCANCEL: return 1;
|
||||
case IDNO: return 1;
|
||||
default: return -1;
|
||||
case IDNO: return 1;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
#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;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 frame_count;
|
||||
void *window_handle;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 frame_count;
|
||||
} RendererDesc;
|
||||
|
||||
Renderer *renderer_create(RendererDesc *desc);
|
||||
@@ -24,13 +21,13 @@ Renderer *renderer_create(RendererDesc *desc);
|
||||
// windows.
|
||||
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_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
|
||||
void renderer_set_clear_color(Renderer *renderer, F32 r, F32 g, F32 b);
|
||||
void renderer_destroy(Renderer *renderer);
|
||||
B32 renderer_begin_frame(Renderer *renderer);
|
||||
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
|
||||
void renderer_set_clear_color(Renderer *renderer, F32 r, F32 g, F32 b);
|
||||
|
||||
// Text measurement callback compatible with UI_MeasureTextFn
|
||||
// Measures text of given length (NOT necessarily null-terminated) at font_size pixels.
|
||||
@@ -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];
|
||||
1823
src/renderer/renderer_vulkan.c
Normal file
1823
src/renderer/renderer_vulkan.c
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
239
src/ui/ui_core.c
Normal file
239
src/ui/ui_core.c
Normal file
@@ -0,0 +1,239 @@
|
||||
// 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"
|
||||
|
||||
////////////////////////////////
|
||||
// UI scale global
|
||||
|
||||
F32 g_ui_scale = 1.0f;
|
||||
|
||||
////////////////////////////////
|
||||
// Theme global
|
||||
|
||||
UI_Theme g_theme = {0};
|
||||
S32 g_theme_id = 0;
|
||||
|
||||
void ui_set_theme(S32 theme_id) {
|
||||
g_theme_id = 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.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.corner_radius = 4.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Accent palette
|
||||
|
||||
S32 g_accent_id = 0;
|
||||
|
||||
void ui_set_accent(S32 accent_id) {
|
||||
g_accent_id = accent_id;
|
||||
B32 dark = (g_theme_id == 0);
|
||||
|
||||
// Each palette: accent, accent_hover, tab_top, tab_bottom, button_text, tab_text
|
||||
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[] = {
|
||||
// 0: Blue
|
||||
{ {87,138,176,255}, {102,153,191,255}, {70,120,160,255}, {30,55,80,255}, {224,224,224,255}, {240,240,240,255} },
|
||||
// 1: Turquoise
|
||||
{ {60,160,155,255}, {75,175,170,255}, {50,140,135,255}, {25,70,68,255}, {224,224,224,255}, {240,240,240,255} },
|
||||
// 2: Orange
|
||||
{ {200,130,50,255}, {215,145,65,255}, {190,120,40,255}, {100,60,20,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
// 3: Purple
|
||||
{ {130,100,180,255}, {145,115,195,255}, {120,90,170,255}, {60,45,85,255}, {224,224,224,255}, {240,240,240,255} },
|
||||
// 4: Pink
|
||||
{ {185,95,140,255}, {200,110,155,255}, {175,85,130,255}, {88,42,65,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
// 5: Red
|
||||
{ {190,75,75,255}, {205,90,90,255}, {180,65,65,255}, {90,32,32,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
// 6: Green
|
||||
{ {80,155,80,255}, {95,170,95,255}, {70,140,70,255}, {35,70,35,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
};
|
||||
|
||||
// Light-mode palettes (slightly more saturated for contrast on light bg)
|
||||
static const AccentColors light_palettes[] = {
|
||||
// 0: Blue
|
||||
{ {50,110,170,255}, {65,125,185,255}, {70,130,180,255}, {50,100,150,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 1: Turquoise
|
||||
{ {30,140,135,255}, {45,155,150,255}, {40,150,145,255}, {25,115,110,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 2: Orange
|
||||
{ {190,115,25,255}, {205,130,40,255}, {195,120,30,255}, {155,90,15,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 3: Purple
|
||||
{ {110,75,170,255}, {125,90,185,255}, {115,80,175,255}, {85,55,140,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 4: Pink
|
||||
{ {175,70,125,255}, {190,85,140,255}, {180,75,130,255}, {140,50,100,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 5: Red
|
||||
{ {185,55,55,255}, {200,70,70,255}, {190,60,60,255}, {150,40,40,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 6: Green
|
||||
{ {55,140,55,255}, {70,155,70,255}, {60,145,60,255}, {40,110,40,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
};
|
||||
|
||||
S32 idx = 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;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Clay error handler
|
||||
|
||||
static void clay_error_handler(Clay_ErrorData error) {
|
||||
char buf[512];
|
||||
S32 len = error.errorText.length < 511 ? error.errorText.length : 511;
|
||||
memcpy(buf, error.errorText.chars, len);
|
||||
buf[len] = '\0';
|
||||
fprintf(stderr, "[Clay Error] %s\n", buf);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Clay text measurement bridge
|
||||
// Clay calls this; we forward to the app's UI_MeasureTextFn
|
||||
|
||||
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};
|
||||
}
|
||||
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};
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Lifecycle
|
||||
|
||||
UI_Context *ui_create(F32 viewport_w, F32 viewport_h) {
|
||||
UI_Context *ctx = (UI_Context *)calloc(1, sizeof(UI_Context));
|
||||
|
||||
U32 min_memory = Clay_MinMemorySize();
|
||||
ctx->clay_memory = malloc(min_memory);
|
||||
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(min_memory, ctx->clay_memory);
|
||||
|
||||
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},
|
||||
err_handler);
|
||||
|
||||
Clay_SetMeasureTextFunction(clay_measure_text, ctx);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void ui_destroy(UI_Context *ctx) {
|
||||
if (!ctx) return;
|
||||
free(ctx->clay_memory);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
void ui_begin_frame(UI_Context *ctx, F32 viewport_w, F32 viewport_h,
|
||||
Vec2F32 mouse_pos, B32 mouse_down,
|
||||
Vec2F32 scroll_delta, F32 dt)
|
||||
{
|
||||
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_BeginLayout();
|
||||
}
|
||||
|
||||
Clay_RenderCommandArray ui_end_frame(UI_Context *ctx) {
|
||||
(void)ctx;
|
||||
return Clay_EndLayout();
|
||||
}
|
||||
|
||||
void ui_set_measure_text_fn(UI_Context *ctx, UI_MeasureTextFn fn, void *user_data) {
|
||||
ctx->measure_text_fn = fn;
|
||||
ctx->measure_text_user_data = user_data;
|
||||
}
|
||||
|
||||
Vec2F32 ui_measure_text(const char *text, S32 length, F32 font_size) {
|
||||
if (!g_measure_ctx || !g_measure_ctx->measure_text_fn || length == 0)
|
||||
return v2f32(0, font_size);
|
||||
return g_measure_ctx->measure_text_fn(text, length, font_size, g_measure_ctx->measure_text_user_data);
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
// ui_core.cpp - 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>
|
||||
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#include "clay.h"
|
||||
|
||||
#include "ui/ui_core.h"
|
||||
|
||||
////////////////////////////////
|
||||
// UI scale global
|
||||
|
||||
F32 g_ui_scale = 1.0f;
|
||||
|
||||
////////////////////////////////
|
||||
// Theme global
|
||||
|
||||
UI_Theme g_theme = {};
|
||||
S32 g_theme_id = 0;
|
||||
|
||||
void ui_set_theme(S32 theme_id) {
|
||||
g_theme_id = 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.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.corner_radius = 4.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Accent palette
|
||||
|
||||
S32 g_accent_id = 0;
|
||||
|
||||
void ui_set_accent(S32 accent_id) {
|
||||
g_accent_id = accent_id;
|
||||
B32 dark = (g_theme_id == 0);
|
||||
|
||||
// Each palette: accent, accent_hover, tab_top, tab_bottom, button_text, tab_text
|
||||
struct AccentColors {
|
||||
Clay_Color accent, accent_hover, tab_top, tab_bottom, button_text, tab_text;
|
||||
};
|
||||
|
||||
// Dark-mode palettes
|
||||
static const AccentColors dark_palettes[] = {
|
||||
// 0: Blue
|
||||
{ { 87, 138, 176, 255 }, { 102, 153, 191, 255 }, { 70, 120, 160, 255 }, { 30, 55, 80, 255 }, { 224, 224, 224, 255 }, { 240, 240, 240, 255 } },
|
||||
// 1: Turquoise
|
||||
{ { 60, 160, 155, 255 }, { 75, 175, 170, 255 }, { 50, 140, 135, 255 }, { 25, 70, 68, 255 }, { 224, 224, 224, 255 }, { 240, 240, 240, 255 } },
|
||||
// 2: Orange
|
||||
{ { 200, 130, 50, 255 }, { 215, 145, 65, 255 }, { 190, 120, 40, 255 }, { 100, 60, 20, 255 }, { 240, 240, 240, 255 }, { 240, 240, 240, 255 } },
|
||||
// 3: Purple
|
||||
{ { 130, 100, 180, 255 }, { 145, 115, 195, 255 }, { 120, 90, 170, 255 }, { 60, 45, 85, 255 }, { 224, 224, 224, 255 }, { 240, 240, 240, 255 } },
|
||||
// 4: Pink
|
||||
{ { 185, 95, 140, 255 }, { 200, 110, 155, 255 }, { 175, 85, 130, 255 }, { 88, 42, 65, 255 }, { 240, 240, 240, 255 }, { 240, 240, 240, 255 } },
|
||||
// 5: Red
|
||||
{ { 190, 75, 75, 255 }, { 205, 90, 90, 255 }, { 180, 65, 65, 255 }, { 90, 32, 32, 255 }, { 240, 240, 240, 255 }, { 240, 240, 240, 255 } },
|
||||
// 6: Green
|
||||
{ { 80, 155, 80, 255 }, { 95, 170, 95, 255 }, { 70, 140, 70, 255 }, { 35, 70, 35, 255 }, { 240, 240, 240, 255 }, { 240, 240, 240, 255 } },
|
||||
};
|
||||
|
||||
// Light-mode palettes (slightly more saturated for contrast on light bg)
|
||||
static const AccentColors light_palettes[] = {
|
||||
// 0: Blue
|
||||
{ { 50, 110, 170, 255 }, { 65, 125, 185, 255 }, { 70, 130, 180, 255 }, { 50, 100, 150, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 1: Turquoise
|
||||
{ { 30, 140, 135, 255 }, { 45, 155, 150, 255 }, { 40, 150, 145, 255 }, { 25, 115, 110, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 2: Orange
|
||||
{ { 190, 115, 25, 255 }, { 205, 130, 40, 255 }, { 195, 120, 30, 255 }, { 155, 90, 15, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 3: Purple
|
||||
{ { 110, 75, 170, 255 }, { 125, 90, 185, 255 }, { 115, 80, 175, 255 }, { 85, 55, 140, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 4: Pink
|
||||
{ { 175, 70, 125, 255 }, { 190, 85, 140, 255 }, { 180, 75, 130, 255 }, { 140, 50, 100, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 5: Red
|
||||
{ { 185, 55, 55, 255 }, { 200, 70, 70, 255 }, { 190, 60, 60, 255 }, { 150, 40, 40, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 6: Green
|
||||
{ { 55, 140, 55, 255 }, { 70, 155, 70, 255 }, { 60, 145, 60, 255 }, { 40, 110, 40, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
};
|
||||
|
||||
S32 idx = 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;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Clay error handler
|
||||
|
||||
static void clay_error_handler(Clay_ErrorData error) {
|
||||
char buf[512];
|
||||
S32 len = error.errorText.length < 511 ? error.errorText.length : 511;
|
||||
memcpy(buf, error.errorText.chars, len);
|
||||
buf[len] = '\0';
|
||||
fprintf(stderr, "[Clay Error] %s\n", buf);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Clay text measurement bridge
|
||||
// Clay calls this; we forward to the app's UI_MeasureTextFn
|
||||
|
||||
static UI_Context *g_measure_ctx = nullptr;
|
||||
|
||||
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 };
|
||||
}
|
||||
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 };
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Lifecycle
|
||||
|
||||
UI_Context *ui_create(F32 viewport_w, F32 viewport_h) {
|
||||
UI_Context *ctx = (UI_Context *)calloc(1, sizeof(UI_Context));
|
||||
|
||||
U32 min_memory = Clay_MinMemorySize();
|
||||
ctx->clay_memory = malloc(min_memory);
|
||||
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(min_memory, ctx->clay_memory);
|
||||
|
||||
Clay_ErrorHandler err_handler = {};
|
||||
err_handler.errorHandlerFunction = clay_error_handler;
|
||||
err_handler.userData = ctx;
|
||||
|
||||
ctx->clay_ctx = Clay_Initialize(clay_arena,
|
||||
Clay_Dimensions{ viewport_w, viewport_h },
|
||||
err_handler);
|
||||
|
||||
Clay_SetMeasureTextFunction(clay_measure_text, ctx);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void ui_destroy(UI_Context *ctx) {
|
||||
if (!ctx) return;
|
||||
free(ctx->clay_memory);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
void ui_begin_frame(UI_Context *ctx, F32 viewport_w, F32 viewport_h,
|
||||
Vec2F32 mouse_pos, B32 mouse_down,
|
||||
Vec2F32 scroll_delta, F32 dt) {
|
||||
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_BeginLayout();
|
||||
}
|
||||
|
||||
Clay_RenderCommandArray ui_end_frame(UI_Context *ctx) {
|
||||
(void)ctx;
|
||||
return Clay_EndLayout();
|
||||
}
|
||||
|
||||
void ui_set_measure_text_fn(UI_Context *ctx, UI_MeasureTextFn fn, void *user_data) {
|
||||
ctx->measure_text_fn = fn;
|
||||
ctx->measure_text_user_data = user_data;
|
||||
}
|
||||
|
||||
Vec2F32 ui_measure_text(const char *text, S32 length, F32 font_size) {
|
||||
if (!g_measure_ctx || !g_measure_ctx->measure_text_fn || length == 0)
|
||||
return v2f32(0, font_size);
|
||||
return g_measure_ctx->measure_text_fn(text, length, font_size, g_measure_ctx->measure_text_user_data);
|
||||
}
|
||||
@@ -14,14 +14,14 @@ typedef Vec2F32 (*UI_MeasureTextFn)(const char *text, S32 length, F32 font_size,
|
||||
////////////////////////////////
|
||||
// UI Context
|
||||
|
||||
struct UI_Context {
|
||||
Clay_Context *clay_ctx;
|
||||
void *clay_memory;
|
||||
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;
|
||||
@@ -65,28 +65,28 @@ struct UI_Theme {
|
||||
Clay_Color title_bar;
|
||||
Clay_Color scrollbar_bg;
|
||||
Clay_Color scrollbar_grab;
|
||||
Clay_Color shadow; // Semi-transparent black for drop shadows
|
||||
Clay_Color shadow; // Semi-transparent black for drop shadows
|
||||
|
||||
// Tab bar colors
|
||||
Clay_Color tab_active_top;
|
||||
Clay_Color tab_active_bottom;
|
||||
Clay_Color tab_inactive;
|
||||
Clay_Color tab_inactive_hover;
|
||||
Clay_Color tab_text; // Always light — readable on colored tab gradient
|
||||
Clay_Color tab_text; // Always light — readable on colored tab gradient
|
||||
|
||||
// Corner radius (unscaled pixels, applied via uis())
|
||||
F32 corner_radius;
|
||||
};
|
||||
} UI_Theme;
|
||||
|
||||
extern UI_Theme g_theme;
|
||||
extern S32 g_theme_id;
|
||||
extern S32 g_theme_id;
|
||||
|
||||
// Set theme by index: 0 = Dark, 1 = Light
|
||||
void ui_set_theme(S32 theme_id);
|
||||
|
||||
// Accent color palette: 0=Blue, 1=Turquoise, 2=Orange, 3=Purple, 4=Pink, 5=Red, 6=Green
|
||||
extern S32 g_accent_id;
|
||||
void ui_set_accent(S32 accent_id);
|
||||
void ui_set_accent(S32 accent_id);
|
||||
|
||||
////////////////////////////////
|
||||
// UI scale (Cmd+/Cmd- zoom)
|
||||
@@ -105,62 +105,62 @@ static inline U16 uifs(F32 x) { return (U16)(x * g_ui_scale + 0.5f); }
|
||||
////////////////////////////////
|
||||
// Tab styling
|
||||
|
||||
#define TAB_ACTIVE_TOP g_theme.tab_active_top
|
||||
#define TAB_ACTIVE_BOTTOM g_theme.tab_active_bottom
|
||||
#define TAB_INACTIVE_BG g_theme.tab_inactive
|
||||
#define TAB_INACTIVE_HOVER g_theme.tab_inactive_hover
|
||||
#define TAB_HEIGHT uis(26)
|
||||
#define TAB_CORNER_RADIUS CORNER_RADIUS
|
||||
#define TAB_PADDING_H uip(10)
|
||||
#define TAB_ACTIVE_TOP g_theme.tab_active_top
|
||||
#define TAB_ACTIVE_BOTTOM g_theme.tab_active_bottom
|
||||
#define TAB_INACTIVE_BG g_theme.tab_inactive
|
||||
#define TAB_INACTIVE_HOVER g_theme.tab_inactive_hover
|
||||
#define TAB_HEIGHT uis(26)
|
||||
#define TAB_CORNER_RADIUS CORNER_RADIUS
|
||||
#define TAB_PADDING_H uip(10)
|
||||
|
||||
////////////////////////////////
|
||||
// Custom render types (for gradient rects via CLAY_RENDER_COMMAND_TYPE_CUSTOM)
|
||||
|
||||
enum CustomRenderType {
|
||||
CUSTOM_RENDER_VGRADIENT = 1,
|
||||
CUSTOM_RENDER_ICON = 2,
|
||||
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;
|
||||
};
|
||||
Clay_Color top_color;
|
||||
Clay_Color bottom_color;
|
||||
} CustomGradientData;
|
||||
|
||||
struct CustomIconData {
|
||||
CustomRenderType type; // CUSTOM_RENDER_ICON
|
||||
typedef struct CustomIconData {
|
||||
CustomRenderType type; // CUSTOM_RENDER_ICON
|
||||
S32 icon_id;
|
||||
Clay_Color color;
|
||||
};
|
||||
} CustomIconData;
|
||||
|
||||
struct CustomRotatedIconData {
|
||||
CustomRenderType type; // CUSTOM_RENDER_ROTATED_ICON
|
||||
typedef struct CustomRotatedIconData {
|
||||
CustomRenderType type; // CUSTOM_RENDER_ROTATED_ICON
|
||||
S32 icon_id;
|
||||
Clay_Color color;
|
||||
F32 angle_rad;
|
||||
};
|
||||
} CustomRotatedIconData;
|
||||
|
||||
////////////////////////////////
|
||||
// Font sizes
|
||||
|
||||
#define FONT_SIZE_NORMAL uifs(15)
|
||||
#define FONT_SIZE_SMALL uifs(12)
|
||||
#define FONT_SIZE_TAB uifs(13)
|
||||
#define FONT_SIZE_NORMAL uifs(15)
|
||||
#define FONT_SIZE_SMALL uifs(12)
|
||||
#define FONT_SIZE_TAB uifs(13)
|
||||
|
||||
////////////////////////////////
|
||||
// Widget sizing
|
||||
|
||||
#define WIDGET_BUTTON_HEIGHT uis(30)
|
||||
#define WIDGET_CHECKBOX_HEIGHT uis(28)
|
||||
#define WIDGET_CHECKBOX_SIZE uis(18)
|
||||
#define WIDGET_RADIO_OUTER uis(16)
|
||||
#define WIDGET_RADIO_INNER uis(8)
|
||||
#define WIDGET_INPUT_HEIGHT uis(30)
|
||||
#define WIDGET_DROPDOWN_HEIGHT uis(30)
|
||||
#define WIDGET_DROPDOWN_ITEM_H uis(28)
|
||||
#define WIDGET_KNOB_SIZE uis(48)
|
||||
#define WIDGET_KNOB_LABEL_GAP uip(4)
|
||||
#define WIDGET_BUTTON_HEIGHT uis(30)
|
||||
#define WIDGET_CHECKBOX_HEIGHT uis(28)
|
||||
#define WIDGET_CHECKBOX_SIZE uis(18)
|
||||
#define WIDGET_RADIO_OUTER uis(16)
|
||||
#define WIDGET_RADIO_INNER uis(8)
|
||||
#define WIDGET_INPUT_HEIGHT uis(30)
|
||||
#define WIDGET_DROPDOWN_HEIGHT uis(30)
|
||||
#define WIDGET_DROPDOWN_ITEM_H uis(28)
|
||||
#define WIDGET_KNOB_SIZE uis(48)
|
||||
#define WIDGET_KNOB_LABEL_GAP uip(4)
|
||||
|
||||
#define WIDGET_SLIDER_H_WIDTH uis(160)
|
||||
#define WIDGET_SLIDER_H_TRACK_H uis(6)
|
||||
@@ -181,11 +181,11 @@ struct CustomRotatedIconData {
|
||||
////////////////////////////////
|
||||
// Corner radius (from theme)
|
||||
|
||||
#define CORNER_RADIUS uis(g_theme.corner_radius)
|
||||
#define CORNER_RADIUS uis(g_theme.corner_radius)
|
||||
|
||||
////////////////////////////////
|
||||
// Panel sizing
|
||||
|
||||
#define PANEL_BROWSER_WIDTH uis(200)
|
||||
#define PANEL_RIGHT_COL_WIDTH uis(250)
|
||||
#define PANEL_LOG_HEIGHT uis(180)
|
||||
#define PANEL_BROWSER_WIDTH uis(200)
|
||||
#define PANEL_RIGHT_COL_WIDTH uis(250)
|
||||
#define PANEL_LOG_HEIGHT uis(180)
|
||||
|
||||
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 {
|
||||
F32 u0, v0, u1, v1; // UV coordinates in icon atlas
|
||||
F32 w, h; // pixel dimensions at rasterized size
|
||||
};
|
||||
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];
|
||||
|
||||
|
||||
136
src/ui/ui_piano.c
Normal file
136
src/ui/ui_piano.c
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "ui/ui_piano.h"
|
||||
|
||||
#define PIANO_BLACK_W 11.0f
|
||||
#define PIANO_BLACK_H_PCT 0.6f
|
||||
|
||||
B32 piano_is_black_key(S32 note) {
|
||||
S32 n = note % 12;
|
||||
return n == 1 || n == 3 || n == 6 || n == 8 || n == 10;
|
||||
}
|
||||
|
||||
// Velocity-based color: blue (vel 0) → green (mid) → red (vel 127)
|
||||
Clay_Color piano_velocity_color(S32 velocity) {
|
||||
F32 t = (F32)velocity / 127.0f;
|
||||
F32 r, g, b;
|
||||
if (t < 0.5f) {
|
||||
F32 s = t * 2.0f;
|
||||
r = 40.0f + s * (76.0f - 40.0f);
|
||||
g = 120.0f + s * (175.0f - 120.0f);
|
||||
b = 220.0f + s * (80.0f - 220.0f);
|
||||
} else {
|
||||
F32 s = (t - 0.5f) * 2.0f;
|
||||
r = 76.0f + s * (220.0f - 76.0f);
|
||||
g = 175.0f + s * (50.0f - 175.0f);
|
||||
b = 80.0f + s * (40.0f - 80.0f);
|
||||
}
|
||||
return (Clay_Color){r, g, b, 255};
|
||||
}
|
||||
|
||||
void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h) {
|
||||
Clay_ElementId piano_id = CLAY_ID("PianoContainer");
|
||||
CLAY(piano_id,
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT,
|
||||
}
|
||||
) {
|
||||
// Compute black key size proportional to white keys
|
||||
F32 black_key_h = avail_h * PIANO_BLACK_H_PCT;
|
||||
if (black_key_h < uis(20)) black_key_h = uis(20);
|
||||
|
||||
F32 white_key_w = avail_w / 52.0f;
|
||||
F32 black_key_w = white_key_w * 0.6f;
|
||||
if (black_key_w < uis(8)) black_key_w = uis(8);
|
||||
|
||||
// White keys (grow to fill width and height)
|
||||
for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) {
|
||||
if (piano_is_black_key(note)) continue;
|
||||
|
||||
B32 midi_held = midi_is_note_held(midi, note);
|
||||
B32 mouse_held = state->mouse_note == note;
|
||||
Clay_Color bg;
|
||||
if (midi_held) {
|
||||
bg = piano_velocity_color(midi_get_note_velocity(midi, note));
|
||||
} else if (mouse_held) {
|
||||
bg = g_theme.accent;
|
||||
} else {
|
||||
bg = (Clay_Color){240, 240, 240, 255};
|
||||
}
|
||||
|
||||
CLAY(CLAY_IDI("PKey", note),
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
},
|
||||
.backgroundColor = bg,
|
||||
.border = { .color = {190, 190, 190, 255}, .width = { .right = 1 } },
|
||||
) {}
|
||||
}
|
||||
|
||||
// Black keys (floating, attached to left white key)
|
||||
for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) {
|
||||
if (!piano_is_black_key(note)) continue;
|
||||
|
||||
Clay_ElementId parent_wkey = CLAY_IDI("PKey", note - 1);
|
||||
B32 midi_held = midi_is_note_held(midi, note);
|
||||
B32 mouse_held = state->mouse_note == note;
|
||||
Clay_Color bg;
|
||||
if (midi_held) {
|
||||
bg = piano_velocity_color(midi_get_note_velocity(midi, note));
|
||||
} else if (mouse_held) {
|
||||
bg = g_theme.accent;
|
||||
} else {
|
||||
bg = (Clay_Color){25, 25, 30, 255};
|
||||
}
|
||||
|
||||
CLAY(CLAY_IDI("PKey", note),
|
||||
.layout = {
|
||||
.sizing = {
|
||||
.width = CLAY_SIZING_FIXED(black_key_w),
|
||||
.height = CLAY_SIZING_FIXED(black_key_h),
|
||||
},
|
||||
},
|
||||
.backgroundColor = bg,
|
||||
.cornerRadius = { .topLeft = 0, .topRight = 0, .bottomLeft = uis(2), .bottomRight = uis(2) },
|
||||
.floating = {
|
||||
.parentId = parent_wkey.id,
|
||||
.zIndex = 100,
|
||||
.attachPoints = {
|
||||
.element = CLAY_ATTACH_POINT_CENTER_TOP,
|
||||
.parent = CLAY_ATTACH_POINT_RIGHT_TOP,
|
||||
},
|
||||
.attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID,
|
||||
},
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ui_piano_update_input(UI_PianoState *state) {
|
||||
PlatformInput input = g_wstate.input;
|
||||
|
||||
if (!input.mouse_down) {
|
||||
state->mouse_note = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find hovered piano key — check black keys first (they're on top)
|
||||
S32 hovered_note = -1;
|
||||
for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) {
|
||||
if (piano_is_black_key(note) && Clay_PointerOver(CLAY_IDI("PKey", note))) {
|
||||
hovered_note = note;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hovered_note == -1) {
|
||||
for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) {
|
||||
if (!piano_is_black_key(note) && Clay_PointerOver(CLAY_IDI("PKey", note))) {
|
||||
hovered_note = note;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hovered_note != -1) {
|
||||
state->mouse_note = hovered_note;
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
#include "ui/ui_piano.h"
|
||||
|
||||
#define PIANO_BLACK_W 11.0f
|
||||
#define PIANO_BLACK_H_PCT 0.6f
|
||||
|
||||
B32 piano_is_black_key(S32 note) {
|
||||
S32 n = note % 12;
|
||||
return n == 1 || n == 3 || n == 6 || n == 8 || n == 10;
|
||||
}
|
||||
|
||||
// Velocity-based color: blue (vel 0) → green (mid) → red (vel 127)
|
||||
Clay_Color piano_velocity_color(S32 velocity) {
|
||||
F32 t = (F32)velocity / 127.0f;
|
||||
F32 r, g, b;
|
||||
if (t < 0.5f) {
|
||||
F32 s = t * 2.0f;
|
||||
r = 40.0f + s * (76.0f - 40.0f);
|
||||
g = 120.0f + s * (175.0f - 120.0f);
|
||||
b = 220.0f + s * (80.0f - 220.0f);
|
||||
} else {
|
||||
F32 s = (t - 0.5f) * 2.0f;
|
||||
r = 76.0f + s * (220.0f - 76.0f);
|
||||
g = 175.0f + s * (50.0f - 175.0f);
|
||||
b = 80.0f + s * (40.0f - 80.0f);
|
||||
}
|
||||
return Clay_Color{ r, g, b, 255 };
|
||||
}
|
||||
|
||||
void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h) {
|
||||
Clay_ElementId piano_id = CLAY_ID("PianoContainer");
|
||||
CLAY(piano_id,
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT,
|
||||
}) {
|
||||
// Compute black key size proportional to white keys
|
||||
F32 black_key_h = avail_h * PIANO_BLACK_H_PCT;
|
||||
if (black_key_h < uis(20)) black_key_h = uis(20);
|
||||
|
||||
F32 white_key_w = avail_w / 52.0f;
|
||||
F32 black_key_w = white_key_w * 0.6f;
|
||||
if (black_key_w < uis(8)) black_key_w = uis(8);
|
||||
|
||||
// White keys (grow to fill width and height)
|
||||
for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) {
|
||||
if (piano_is_black_key(note)) continue;
|
||||
|
||||
B32 midi_held = midi_is_note_held(midi, note);
|
||||
B32 mouse_held = state->mouse_note == note;
|
||||
Clay_Color bg;
|
||||
if (midi_held) {
|
||||
bg = piano_velocity_color(midi_get_note_velocity(midi, note));
|
||||
} else if (mouse_held) {
|
||||
bg = g_theme.accent;
|
||||
} else {
|
||||
bg = Clay_Color{ 240, 240, 240, 255 };
|
||||
}
|
||||
|
||||
CLAY(CLAY_IDI("PKey", note),
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
},
|
||||
.backgroundColor = bg, .border = { .color = { 190, 190, 190, 255 }, .width = { .right = 1 } }, ) {}
|
||||
}
|
||||
|
||||
// Black keys (floating, attached to left white key)
|
||||
for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) {
|
||||
if (!piano_is_black_key(note)) continue;
|
||||
|
||||
Clay_ElementId parent_wkey = CLAY_IDI("PKey", note - 1);
|
||||
B32 midi_held = midi_is_note_held(midi, note);
|
||||
B32 mouse_held = state->mouse_note == note;
|
||||
Clay_Color bg;
|
||||
if (midi_held) {
|
||||
bg = piano_velocity_color(midi_get_note_velocity(midi, note));
|
||||
} else if (mouse_held) {
|
||||
bg = g_theme.accent;
|
||||
} else {
|
||||
bg = Clay_Color{ 25, 25, 30, 255 };
|
||||
}
|
||||
|
||||
CLAY(CLAY_IDI("PKey", note),
|
||||
.layout = {
|
||||
.sizing = {
|
||||
.width = CLAY_SIZING_FIXED(black_key_w),
|
||||
.height = CLAY_SIZING_FIXED(black_key_h),
|
||||
},
|
||||
},
|
||||
.backgroundColor = bg, .cornerRadius = { .topLeft = 0, .topRight = 0, .bottomLeft = uis(2), .bottomRight = uis(2) }, .floating = {
|
||||
.parentId = parent_wkey.id,
|
||||
.zIndex = 100,
|
||||
.attachPoints = {
|
||||
.element = CLAY_ATTACH_POINT_CENTER_TOP,
|
||||
.parent = CLAY_ATTACH_POINT_RIGHT_TOP,
|
||||
},
|
||||
.attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID,
|
||||
}, ) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ui_piano_update_input(UI_PianoState *state) {
|
||||
PlatformInput input = g_wstate.input;
|
||||
|
||||
if (!input.mouse_down) {
|
||||
state->mouse_note = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find hovered piano key — check black keys first (they're on top)
|
||||
S32 hovered_note = -1;
|
||||
for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) {
|
||||
if (piano_is_black_key(note) && Clay_PointerOver(CLAY_IDI("PKey", note))) {
|
||||
hovered_note = note;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hovered_note == -1) {
|
||||
for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) {
|
||||
if (!piano_is_black_key(note) && Clay_PointerOver(CLAY_IDI("PKey", note))) {
|
||||
hovered_note = note;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hovered_note != -1) {
|
||||
state->mouse_note = hovered_note;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
#include "midi/midi.h"
|
||||
#include "ui/ui_core.h"
|
||||
#include "midi/midi.h"
|
||||
|
||||
#define PIANO_FIRST_NOTE 21 // A0
|
||||
#define PIANO_LAST_NOTE 108 // C8
|
||||
#define PIANO_FIRST_NOTE 21 // A0
|
||||
#define PIANO_LAST_NOTE 108 // C8
|
||||
|
||||
struct UI_PianoState {
|
||||
S32 mouse_note; // MIDI note held by mouse click (-1 = none)
|
||||
};
|
||||
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,56 +21,53 @@ 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 (!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 = {};
|
||||
desc.title = title;
|
||||
desc.width = width;
|
||||
desc.height = height;
|
||||
desc.style = style;
|
||||
desc.parent = parent_window;
|
||||
desc.independent = independent;
|
||||
popup->platform_window = platform_create_window(&desc);
|
||||
if (!popup->platform_window) return nullptr;
|
||||
PlatformWindowDesc desc = {0};
|
||||
desc.title = title;
|
||||
desc.width = width;
|
||||
desc.height = height;
|
||||
desc.style = style;
|
||||
desc.parent = parent_window;
|
||||
desc.independent = independent;
|
||||
popup->platform_window = platform_create_window(&desc);
|
||||
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);
|
||||
popup->renderer = renderer_create_shared(parent_renderer, &rdesc);
|
||||
if (!popup->renderer) {
|
||||
platform_destroy_window(popup->platform_window);
|
||||
return nullptr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create UI context
|
||||
popup->ui_ctx = ui_create((F32)pw, (F32)ph);
|
||||
ui_set_measure_text_fn(popup->ui_ctx, renderer_measure_text, popup->renderer);
|
||||
|
||||
popup->alive = 1;
|
||||
popup->open_flag = open_flag;
|
||||
popup->content_fn = content_fn;
|
||||
popup->alive = 1;
|
||||
popup->open_flag = open_flag;
|
||||
popup->content_fn = content_fn;
|
||||
popup->content_user_data = user_data;
|
||||
popup->width = width;
|
||||
popup->height = height;
|
||||
popup->last_w = pw;
|
||||
popup->last_h = ph;
|
||||
popup->title = title;
|
||||
popup->wstate = {};
|
||||
popup->width = width;
|
||||
popup->height = height;
|
||||
popup->last_w = pw;
|
||||
popup->last_h = ph;
|
||||
popup->title = title;
|
||||
memset(&popup->wstate, 0, sizeof(popup->wstate));
|
||||
|
||||
platform_set_frame_callback(popup->platform_window, popup_frame_callback, popup);
|
||||
|
||||
@@ -84,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);
|
||||
@@ -114,7 +111,7 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
if (input.ctrl_held) {
|
||||
if (input.keys[k] == PKEY_EQUAL) g_ui_scale *= 1.1f;
|
||||
if (input.keys[k] == PKEY_MINUS) g_ui_scale /= 1.1f;
|
||||
if (input.keys[k] == PKEY_0) g_ui_scale = 1.0f;
|
||||
if (input.keys[k] == PKEY_0) g_ui_scale = 1.0f;
|
||||
}
|
||||
}
|
||||
g_ui_scale = Clamp(0.5f, g_ui_scale, 3.0f);
|
||||
@@ -124,7 +121,7 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
|
||||
// Swap widget state
|
||||
UI_WidgetState saved_wstate = g_wstate;
|
||||
g_wstate = popup->wstate;
|
||||
g_wstate = popup->wstate;
|
||||
|
||||
ui_widgets_begin_frame(input);
|
||||
|
||||
@@ -134,13 +131,14 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
|
||||
// Background fill
|
||||
CLAY(CLAY_ID("PopupBg"),
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
.padding = { uip(12), uip(12), uip(10), uip(10) },
|
||||
.childGap = uip(8),
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
},
|
||||
.backgroundColor = g_theme.bg_medium, ) {
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
.padding = { uip(12), uip(12), uip(10), uip(10) },
|
||||
.childGap = uip(8),
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
},
|
||||
.backgroundColor = g_theme.bg_medium,
|
||||
) {
|
||||
if (popup->content_fn) {
|
||||
popup->content_fn(popup->content_user_data);
|
||||
}
|
||||
@@ -150,18 +148,18 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
|
||||
// Save widget state back
|
||||
popup->wstate = g_wstate;
|
||||
g_wstate = saved_wstate;
|
||||
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]);
|
||||
@@ -1,31 +1,31 @@
|
||||
#pragma once
|
||||
#include "platform/platform.h"
|
||||
#include "renderer/renderer.h"
|
||||
#include "ui/ui_core.h"
|
||||
#include "ui/ui_widgets.h"
|
||||
#include "platform/platform.h"
|
||||
#include "renderer/renderer.h"
|
||||
|
||||
#define MAX_POPUP_WINDOWS 4
|
||||
|
||||
struct PopupWindow {
|
||||
B32 alive;
|
||||
B32 *open_flag; // e.g. &app->show_settings_window
|
||||
PlatformWindow *platform_window;
|
||||
Renderer *renderer;
|
||||
UI_Context *ui_ctx;
|
||||
UI_WidgetState wstate;
|
||||
typedef struct PopupWindow {
|
||||
B32 alive;
|
||||
B32 *open_flag; // e.g. &app->show_settings_window
|
||||
PlatformWindow *platform_window;
|
||||
Renderer *renderer;
|
||||
UI_Context *ui_ctx;
|
||||
UI_WidgetState wstate;
|
||||
UI_WindowContentFn content_fn;
|
||||
void *content_user_data;
|
||||
S32 width, height;
|
||||
S32 last_w, last_h;
|
||||
const char *title;
|
||||
};
|
||||
void *content_user_data;
|
||||
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);
|
||||
|
||||
2054
src/ui/ui_widgets.c
Normal file
2054
src/ui/ui_widgets.c
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,9 @@
|
||||
// The caller owns all data — the widget layer only stores transient UI state
|
||||
// like which text field is focused or which dropdown is open.
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "ui/ui_core.h"
|
||||
#include "ui/ui_icons.h"
|
||||
#include "platform/platform.h"
|
||||
|
||||
////////////////////////////////
|
||||
// Widget state (global, managed by widget layer)
|
||||
@@ -16,30 +16,30 @@
|
||||
#define UI_WIDGET_MAX_DROPDOWN_ITEMS 32
|
||||
#define UI_WIDGET_MAX_TEXT_INPUTS 16
|
||||
|
||||
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)
|
||||
F32 value_at_start; // Value when drag started
|
||||
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
|
||||
};
|
||||
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)
|
||||
F32 value_at_start; // Value when drag started
|
||||
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
|
||||
F32 cursor_blink; // Blink timer (seconds)
|
||||
U32 focused_id; // Clay element ID hash of the focused text input (0 = none)
|
||||
S32 cursor_pos; // Cursor position in focused text input
|
||||
F32 cursor_blink; // Blink timer (seconds)
|
||||
|
||||
// Text selection (sel_start == sel_end means no selection)
|
||||
S32 sel_start; // Selection anchor (where selection began)
|
||||
S32 sel_end; // Selection extent (moves with cursor)
|
||||
S32 sel_start; // Selection anchor (where selection began)
|
||||
S32 sel_end; // Selection extent (moves with cursor)
|
||||
|
||||
// Tab cycling: registered text input IDs in order of declaration
|
||||
U32 text_input_ids[UI_WIDGET_MAX_TEXT_INPUTS];
|
||||
S32 text_input_count;
|
||||
B32 tab_pressed; // True on the frame Tab was pressed
|
||||
U32 text_input_ids[UI_WIDGET_MAX_TEXT_INPUTS];
|
||||
S32 text_input_count;
|
||||
B32 tab_pressed; // True on the frame Tab was pressed
|
||||
|
||||
// Dropdown
|
||||
U32 open_dropdown_id; // Clay element ID hash of the open dropdown (0 = none)
|
||||
@@ -54,12 +54,12 @@ struct UI_WidgetState {
|
||||
UI_KnobDragState knob_drag;
|
||||
|
||||
// Knob text edit state
|
||||
U32 knob_edit_id; // Hash of knob in text edit mode (0 = none)
|
||||
char knob_edit_buf[32]; // Text buffer for numeric entry
|
||||
S32 knob_edit_cursor; // Cursor position in edit buffer
|
||||
S32 knob_edit_sel_start; // Selection anchor
|
||||
S32 knob_edit_sel_end; // Selection extent
|
||||
};
|
||||
U32 knob_edit_id; // Hash of knob in text edit mode (0 = none)
|
||||
char knob_edit_buf[32]; // Text buffer for numeric entry
|
||||
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
3156
vendor/nob/nob.h
vendored
Normal file
3156
vendor/nob/nob.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user