Compare commits

...

3 Commits

Author SHA1 Message Date
8695f69282 Reapply "Death to C++"
This reverts commit 13f856cfbc.
2026-03-12 16:30:04 -04:00
d5d2f6db8e Revert "Format all"
This reverts commit c7bb89fd6d.
2026-03-12 16:30:04 -04:00
48f2c51d92 Revert "replace nob with new custom noblike"
This reverts commit 06e1212483.
2026-03-12 16:30:04 -04:00
92 changed files with 15953 additions and 47029 deletions

View File

@@ -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

View File

@@ -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
View File

@@ -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"
}
]
}

View File

@@ -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
View 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
View File

@@ -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
View File

@@ -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, &quoted);
BOOL ok = CreateProcessA(nullptr, quoted.items, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi);
sb_free(&quoted);
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

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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
View File

@@ -0,0 +1,3 @@
// base_inc.c - Unity build for the base layer
#include "base/base_arena.c"
#include "base/base_strings.c"

View File

@@ -1,3 +0,0 @@
// base_inc.cpp - Unity build for the base layer
#include "base/base_arena.cpp"
#include "base/base_strings.cpp"

View File

@@ -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"

View File

@@ -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; }

View File

@@ -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;

View File

@@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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]));

View File

@@ -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);

View File

@@ -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];
}

View File

@@ -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];
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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];

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
View 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);
}

View File

@@ -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);
}

View File

@@ -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
View 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;
}

View File

@@ -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;
}

View File

@@ -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
View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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]);

View File

@@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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
View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 */

View File

@@ -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 */

File diff suppressed because it is too large Load Diff

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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, &current_x, &current_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, &current_x, &current_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;
}

View File

@@ -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

View File

@@ -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(&params);
ft_outline_destroy(outline);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff